Skip to content

Commit ecbe9f9

Browse files
Supporting OpenAI Responses
1 parent 9c1cfbc commit ecbe9f9

8 files changed

Lines changed: 2983 additions & 51 deletions

File tree

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,51 @@ uv pip install tinyagent-py[all]
111111

112112
## Developer Boilerplate & Quick Start
113113

114+
### OpenAI Responses API (optional)
115+
116+
TinyAgent supports OpenAI's Responses API alongside the default Chat Completions flow. To opt in without changing your code, set an environment variable:
117+
118+
```bash
119+
export TINYAGENT_LLM_API=responses
120+
```
121+
122+
Your existing TinyAgent code continues to work. Under the hood, TinyAgent translates your chat `messages`/`tools` to a Responses request and maps the Responses result back to the same structure it already uses (including `tool_calls` and usage accounting). To switch back, unset or set `TINYAGENT_LLM_API=chat`.
123+
124+
Example with explicit toggle:
125+
126+
```python
127+
import os
128+
import asyncio
129+
from tinyagent import TinyAgent
130+
131+
async def main():
132+
# Option A: via environment variable
133+
os.environ["TINYAGENT_LLM_API"] = "responses" # or "chat" (default)
134+
agent = await TinyAgent.create(
135+
model="gpt-5-mini",
136+
api_key=os.getenv("OPENAI_API_KEY"),
137+
# Option B: programmatic preference via model_kwargs
138+
model_kwargs={"llm_api": "responses"}, # or {"use_responses_api": True}
139+
)
140+
print(await agent.run("List three safe git commands for a repo"))
141+
142+
asyncio.run(main())
143+
```
144+
145+
Notes:
146+
- The adapter preserves TinyAgent hooks, storage schema, and tool-calling behavior.
147+
- Streaming and semantic events can be added later without changing your code.
148+
- Optional tracing: set `RESPONSES_TRACE_FILE=./responses_trace.jsonl` to capture raw request/response JSON for debugging. Set `DEBUG_RESPONSES=1` to print pairing details.
149+
150+
Examples you can run:
151+
- `examples/openai_sdk_responses_multiturn.py` — baseline SDK multi-turn chaining
152+
- `examples/openai_sdk_responses_extended_tools.py` — SDK multi-turn with function calls
153+
- `examples/litellm_responses_extended_tools.py` — LiteLLM multi-turn with function calls
154+
- `examples/litellm_responses_three_tools.py` — LiteLLM three-tool demo
155+
- `examples/tinyagent_responses_three_tools.py` — TinyAgent three-tool demo (Responses)
156+
- `examples/seatbelt_verbose_tools.py` — TinyCodeAgent + seatbelt, verbose hook stream
157+
- `examples/seatbelt_responses_three_tools.py` — TinyCodeAgent + seatbelt three-tool demo
158+
114159
### 🚀 TinyAgent with New Tools
115160

116161
```python
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
TinyAgent + OpenAI Responses API: 3 tools end-to-end.
3+
4+
This mirrors the LiteLLM three-tools example, but runs through TinyAgent’s
5+
agent loop, hooks, and the Responses adapter.
6+
7+
Tools:
8+
- word_count(text) -> int
9+
- reverse_text(text) -> str
10+
- vowel_count(text) -> int
11+
12+
Run:
13+
export OPENAI_API_KEY=...
14+
export TINYAGENT_LLM_API=responses
15+
python examples/tinyagent_responses_three_tools.py
16+
"""
17+
18+
import asyncio
19+
import os
20+
import sys
21+
from pathlib import Path
22+
23+
24+
def _init_path():
25+
try:
26+
from tinyagent import TinyAgent # noqa: F401
27+
except ModuleNotFoundError:
28+
repo_root = Path(__file__).resolve().parents[1]
29+
sys.path.insert(0, str(repo_root))
30+
31+
32+
_init_path()
33+
34+
from tinyagent import TinyAgent, tool # noqa: E402
35+
36+
37+
@tool(name="word_count", description="Return the number of words in text.")
38+
def word_count(text: str) -> int:
39+
return len([t for t in text.split() if t.strip()])
40+
41+
42+
@tool(name="reverse_text", description="Reverse a string.")
43+
def reverse_text(text: str) -> str:
44+
return text[::-1]
45+
46+
47+
@tool(name="vowel_count", description="Count vowels (a,e,i,o,u) in a string.")
48+
def vowel_count(text: str) -> int:
49+
return sum(1 for ch in text.lower() if ch in "aeiou")
50+
51+
52+
def make_verbose_callback():
53+
def _short(s, n=200):
54+
s = str(s)
55+
return s if len(s) <= n else s[:n] + "..."
56+
57+
async def cb(event_name: str, agent: TinyAgent, *args, **kwargs):
58+
if event_name == "agent_start":
59+
print(f"[agent_start] user_input={_short(kwargs.get('user_input'))}")
60+
elif event_name == "llm_start":
61+
k = args[0] if args else kwargs
62+
msgs = (k or {}).get("messages", [])
63+
tools = (k or {}).get("tools", [])
64+
print(f"[llm_start] messages={len(msgs)} tools={len(tools)}")
65+
elif event_name == "message_add":
66+
m = kwargs.get("message", {})
67+
role = m.get("role")
68+
content = m.get("content")
69+
print(f"[message_add] role={role} content={_short(content)}")
70+
if m.get("tool_calls"):
71+
print(f" tool_calls={_short(m.get('tool_calls'))}")
72+
elif event_name == "tool_start":
73+
tc = kwargs.get("tool_call")
74+
name = getattr(tc.function, 'name', None) if tc else None
75+
args_str = getattr(tc.function, 'arguments', None) if tc else None
76+
print(f"[tool_start] name={name} args={_short(args_str)}")
77+
elif event_name == "tool_end":
78+
tc = kwargs.get("tool_call")
79+
res = kwargs.get("result")
80+
name = getattr(tc.function, 'name', None) if tc else None
81+
print(f"[tool_end] name={name} result={_short(res)}")
82+
elif event_name == "llm_end":
83+
rid = getattr(agent, "_responses_prev_id", None)
84+
print(f"[llm_end] last_response_id={rid}")
85+
elif event_name == "agent_end":
86+
print(f"[agent_end] result={_short(kwargs.get('result'))}")
87+
else:
88+
pass
89+
90+
return cb
91+
92+
93+
async def main():
94+
if not os.getenv("OPENAI_API_KEY"):
95+
print("OPENAI_API_KEY not set", file=sys.stderr)
96+
sys.exit(1)
97+
98+
# Set a default trace file for Responses requests/responses
99+
if not os.getenv("RESPONSES_TRACE_FILE"):
100+
default_trace = str(Path.cwd() / "responses_trace.jsonl")
101+
os.environ["RESPONSES_TRACE_FILE"] = default_trace
102+
print(f"[trace] RESPONSES_TRACE_FILE set to {default_trace}")
103+
104+
# Create TinyAgent in Responses mode (set via env)
105+
agent = await TinyAgent.create(model="gpt-5-mini", api_key=os.getenv("OPENAI_API_KEY"), parallel_tool_calls=False)
106+
agent.add_tools([word_count, reverse_text, vowel_count])
107+
agent.add_callback(make_verbose_callback())
108+
109+
input_text = "Refactor often, test always."
110+
prompt = (
111+
"You MUST call all three tools on the same input text.\n"
112+
f"Input text: '{input_text}'.\n"
113+
"Steps:\n"
114+
"1) Call word_count(text) and wait for tool output.\n"
115+
"2) Then call reverse_text(text) and wait for output.\n"
116+
"3) Then call vowel_count(text) and wait for output.\n"
117+
"Finally, call final_answer summarizing the results in one concise sentence."
118+
)
119+
120+
result = await agent.run(prompt, max_turns=12)
121+
print("\n=== Final ===")
122+
print(result)
123+
await agent.close()
124+
125+
126+
if __name__ == "__main__":
127+
asyncio.run(main())
128+

0 commit comments

Comments
 (0)