|
| 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