Skip to content

Commit 4a529e6

Browse files
authored
Add REPL run_demo_loop helper (#811)
1 parent 204bec1 commit 4a529e6

File tree

7 files changed

+145
-0
lines changed

7 files changed

+145
-0
lines changed

docs/ja/repl.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
search:
3+
exclude: true
4+
---
5+
# REPL ユーティリティ
6+
7+
`run_demo_loop` を使うと、ターミナルから手軽にエージェントを試せます。
8+
9+
```python
10+
import asyncio
11+
from agents import Agent, run_demo_loop
12+
13+
async def main() -> None:
14+
agent = Agent(name="Assistant", instructions="あなたは親切なアシスタントです")
15+
await run_demo_loop(agent)
16+
17+
if __name__ == "__main__":
18+
asyncio.run(main())
19+
```
20+
21+
`run_demo_loop` は入力を繰り返し受け取り、会話履歴を保持したままエージェントを実行します。既定ではストリーミング出力を表示します。
22+
`quit` または `exit` と入力するか `Ctrl-D` を押すと終了します。

docs/ref/repl.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# `repl`
2+
3+
::: agents.repl
4+
options:
5+
members:
6+
- run_demo_loop

docs/repl.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# REPL utility
2+
3+
The SDK provides `run_demo_loop` for quick interactive testing.
4+
5+
```python
6+
import asyncio
7+
from agents import Agent, run_demo_loop
8+
9+
async def main() -> None:
10+
agent = Agent(name="Assistant", instructions="You are a helpful assistant.")
11+
await run_demo_loop(agent)
12+
13+
if __name__ == "__main__":
14+
asyncio.run(main())
15+
```
16+
17+
`run_demo_loop` prompts for user input in a loop, keeping the conversation
18+
history between turns. By default it streams model output as it is produced.
19+
Type `quit` or `exit` (or press `Ctrl-D`) to leave the loop.

mkdocs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ plugins:
5959
- running_agents.md
6060
- results.md
6161
- streaming.md
62+
- repl.md
6263
- tools.md
6364
- mcp.md
6465
- handoffs.md
@@ -80,6 +81,7 @@ plugins:
8081
- ref/index.md
8182
- ref/agent.md
8283
- ref/run.md
84+
- ref/repl.md
8385
- ref/tool.md
8486
- ref/result.md
8587
- ref/stream_events.md
@@ -139,6 +141,7 @@ plugins:
139141
- running_agents.md
140142
- results.md
141143
- streaming.md
144+
- repl.md
142145
- tools.md
143146
- mcp.md
144147
- handoffs.md

src/agents/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from .models.openai_chatcompletions import OpenAIChatCompletionsModel
4646
from .models.openai_provider import OpenAIProvider
4747
from .models.openai_responses import OpenAIResponsesModel
48+
from .repl import run_demo_loop
4849
from .result import RunResult, RunResultStreaming
4950
from .run import RunConfig, Runner
5051
from .run_context import RunContextWrapper, TContext
@@ -160,6 +161,7 @@ def enable_verbose_stdout_logging():
160161
"ToolsToFinalOutputFunction",
161162
"ToolsToFinalOutputResult",
162163
"Runner",
164+
"run_demo_loop",
163165
"Model",
164166
"ModelProvider",
165167
"ModelTracing",

src/agents/repl.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
from openai.types.responses.response_text_delta_event import ResponseTextDeltaEvent
6+
7+
from .agent import Agent
8+
from .items import ItemHelpers, TResponseInputItem
9+
from .result import RunResultBase
10+
from .run import Runner
11+
from .stream_events import AgentUpdatedStreamEvent, RawResponsesStreamEvent, RunItemStreamEvent
12+
13+
14+
async def run_demo_loop(agent: Agent[Any], *, stream: bool = True) -> None:
15+
"""Run a simple REPL loop with the given agent.
16+
17+
This utility allows quick manual testing and debugging of an agent from the
18+
command line. Conversation state is preserved across turns. Enter ``exit``
19+
or ``quit`` to stop the loop.
20+
21+
Args:
22+
agent: The starting agent to run.
23+
stream: Whether to stream the agent output.
24+
"""
25+
26+
current_agent = agent
27+
input_items: list[TResponseInputItem] = []
28+
while True:
29+
try:
30+
user_input = input(" > ")
31+
except (EOFError, KeyboardInterrupt):
32+
print()
33+
break
34+
if user_input.strip().lower() in {"exit", "quit"}:
35+
break
36+
if not user_input:
37+
continue
38+
39+
input_items.append({"role": "user", "content": user_input})
40+
41+
result: RunResultBase
42+
if stream:
43+
result = Runner.run_streamed(current_agent, input=input_items)
44+
async for event in result.stream_events():
45+
if isinstance(event, RawResponsesStreamEvent):
46+
if isinstance(event.data, ResponseTextDeltaEvent):
47+
print(event.data.delta, end="", flush=True)
48+
elif isinstance(event, RunItemStreamEvent):
49+
if event.item.type == "tool_call_item":
50+
print("\n[tool called]", flush=True)
51+
elif event.item.type == "tool_call_output_item":
52+
print(f"\n[tool output: {event.item.output}]", flush=True)
53+
elif event.item.type == "message_output_item":
54+
message = ItemHelpers.text_message_output(event.item)
55+
print(message, end="", flush=True)
56+
elif isinstance(event, AgentUpdatedStreamEvent):
57+
print(f"\n[Agent updated: {event.new_agent.name}]", flush=True)
58+
print()
59+
else:
60+
result = await Runner.run(current_agent, input_items)
61+
if result.final_output is not None:
62+
print(result.final_output)
63+
64+
current_agent = result.last_agent
65+
input_items = result.to_input_list()

tests/test_repl.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
3+
from agents import Agent, run_demo_loop
4+
5+
from .fake_model import FakeModel
6+
from .test_responses import get_text_input_item, get_text_message
7+
8+
9+
@pytest.mark.asyncio
10+
async def test_run_demo_loop_conversation(monkeypatch, capsys):
11+
model = FakeModel()
12+
model.add_multiple_turn_outputs([[get_text_message("hello")], [get_text_message("good")]])
13+
14+
agent = Agent(name="test", model=model)
15+
16+
inputs = iter(["Hi", "How are you?", "quit"])
17+
monkeypatch.setattr("builtins.input", lambda _=" > ": next(inputs))
18+
19+
await run_demo_loop(agent, stream=False)
20+
21+
output = capsys.readouterr().out
22+
assert "hello" in output
23+
assert "good" in output
24+
assert model.last_turn_args["input"] == [
25+
get_text_input_item("Hi"),
26+
get_text_message("hello").model_dump(exclude_unset=True),
27+
get_text_input_item("How are you?"),
28+
]

0 commit comments

Comments
 (0)