chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import asyncio
|
||||
import random
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
AgentHookContext,
|
||||
AgentHooks,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
Tool,
|
||||
function_tool,
|
||||
)
|
||||
from examples.auto_mode import input_with_fallback, is_auto_mode
|
||||
|
||||
|
||||
class CustomAgentHooks(AgentHooks):
|
||||
def __init__(self, display_name: str):
|
||||
self.event_counter = 0
|
||||
self.display_name = display_name
|
||||
|
||||
async def on_start(self, context: AgentHookContext, agent: Agent) -> None:
|
||||
self.event_counter += 1
|
||||
# Access the turn_input from the context to see what input the agent received
|
||||
print(
|
||||
f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started with turn_input: {context.turn_input}"
|
||||
)
|
||||
|
||||
async def on_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None:
|
||||
self.event_counter += 1
|
||||
print(
|
||||
f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} ended with output {output}"
|
||||
)
|
||||
|
||||
async def on_handoff(self, context: RunContextWrapper, agent: Agent, source: Agent) -> None:
|
||||
self.event_counter += 1
|
||||
print(
|
||||
f"### ({self.display_name}) {self.event_counter}: Agent {source.name} handed off to {agent.name}"
|
||||
)
|
||||
|
||||
# Note: The on_tool_start and on_tool_end hooks apply only to local tools.
|
||||
# They do not include hosted tools that run on the OpenAI server side,
|
||||
# such as WebSearchTool, FileSearchTool, CodeInterpreterTool, HostedMCPTool,
|
||||
# or other built-in hosted tools.
|
||||
async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None:
|
||||
self.event_counter += 1
|
||||
print(
|
||||
f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started tool {tool.name}"
|
||||
)
|
||||
|
||||
async def on_tool_end(
|
||||
self, context: RunContextWrapper, agent: Agent, tool: Tool, result: object
|
||||
) -> None:
|
||||
self.event_counter += 1
|
||||
print(
|
||||
f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} ended tool {tool.name} with result {result}"
|
||||
)
|
||||
|
||||
|
||||
###
|
||||
|
||||
|
||||
@function_tool
|
||||
def random_number(max: int) -> int:
|
||||
"""
|
||||
Generate a random number from 0 to max (inclusive).
|
||||
"""
|
||||
if is_auto_mode():
|
||||
if max <= 0:
|
||||
print("[debug] auto mode returning deterministic value 0")
|
||||
return 0
|
||||
value = min(max, 37)
|
||||
if value % 2 == 0:
|
||||
value = value - 1 if value > 1 else 1
|
||||
print(f"[debug] auto mode returning deterministic odd number {value}")
|
||||
return value
|
||||
return random.randint(0, max)
|
||||
|
||||
|
||||
@function_tool
|
||||
def multiply_by_two(x: int) -> int:
|
||||
"""Simple multiplication by two."""
|
||||
return x * 2
|
||||
|
||||
|
||||
class FinalResult(BaseModel):
|
||||
number: int
|
||||
|
||||
|
||||
multiply_agent = Agent(
|
||||
name="Multiply Agent",
|
||||
instructions="Multiply the number by 2 and then return the final result.",
|
||||
tools=[multiply_by_two],
|
||||
output_type=FinalResult,
|
||||
hooks=CustomAgentHooks(display_name="Multiply Agent"),
|
||||
)
|
||||
|
||||
start_agent = Agent(
|
||||
name="Start Agent",
|
||||
instructions="Generate a random number. If it's even, stop. If it's odd, hand off to the multiply agent.",
|
||||
tools=[random_number],
|
||||
output_type=FinalResult,
|
||||
handoffs=[multiply_agent],
|
||||
hooks=CustomAgentHooks(display_name="Start Agent"),
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
user_input = input_with_fallback("Enter a max number: ", "50")
|
||||
try:
|
||||
max_number = int(user_input)
|
||||
await Runner.run(
|
||||
start_agent,
|
||||
input=f"Generate a random number between 0 and {max_number}.",
|
||||
)
|
||||
except ValueError:
|
||||
print("Please enter a valid integer.")
|
||||
return
|
||||
|
||||
print("Done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
"""
|
||||
$ python examples/basic/agent_lifecycle_example.py
|
||||
|
||||
Enter a max number: 250
|
||||
### (Start Agent) 1: Agent Start Agent started
|
||||
### (Start Agent) 2: Agent Start Agent started tool random_number
|
||||
### (Start Agent) 3: Agent Start Agent ended tool random_number with result 37
|
||||
### (Start Agent) 4: Agent Start Agent handed off to Multiply Agent
|
||||
### (Multiply Agent) 1: Agent Multiply Agent started
|
||||
### (Multiply Agent) 2: Agent Multiply Agent started tool multiply_by_two
|
||||
### (Multiply Agent) 3: Agent Multiply Agent ended tool multiply_by_two with result 74
|
||||
### (Multiply Agent) 4: Agent Multiply Agent ended with output number=74
|
||||
Done!
|
||||
"""
|
||||
@@ -0,0 +1,70 @@
|
||||
import asyncio
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from agents import Agent, RunContextWrapper, Runner
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomContext:
|
||||
style: Literal["haiku", "pirate", "robot"]
|
||||
|
||||
|
||||
def custom_instructions(
|
||||
run_context: RunContextWrapper[CustomContext], agent: Agent[CustomContext]
|
||||
) -> str:
|
||||
context = run_context.context
|
||||
if context.style == "haiku":
|
||||
return "Only respond in haikus."
|
||||
elif context.style == "pirate":
|
||||
return "Respond as a pirate."
|
||||
else:
|
||||
return "Respond as a robot and say 'beep boop' a lot."
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Chat agent",
|
||||
instructions=custom_instructions,
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
context = CustomContext(style=random.choice(["haiku", "pirate", "robot"]))
|
||||
print(f"Using style: {context.style}\n")
|
||||
|
||||
user_message = "Tell me a joke."
|
||||
print(f"User: {user_message}")
|
||||
result = await Runner.run(agent, user_message, context=context)
|
||||
|
||||
print(f"Assistant: {result.final_output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
"""
|
||||
$ python examples/basic/dynamic_system_prompt.py
|
||||
|
||||
Using style: haiku
|
||||
|
||||
User: Tell me a joke.
|
||||
Assistant: Why don't eggs tell jokes?
|
||||
They might crack each other's shells,
|
||||
leaving yolk on face.
|
||||
|
||||
$ python examples/basic/dynamic_system_prompt.py
|
||||
Using style: robot
|
||||
|
||||
User: Tell me a joke.
|
||||
Assistant: Beep boop! Why was the robot so bad at soccer? Beep boop... because it kept kicking up a debug! Beep boop!
|
||||
|
||||
$ python examples/basic/dynamic_system_prompt.py
|
||||
Using style: pirate
|
||||
|
||||
User: Tell me a joke.
|
||||
Assistant: Why did the pirate go to school?
|
||||
|
||||
To improve his arrr-ticulation! Har har har! 🏴☠️
|
||||
"""
|
||||
@@ -0,0 +1,20 @@
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You only respond in haikus.",
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Tell me about recursion in programming.")
|
||||
print(result.final_output)
|
||||
# Function calls itself,
|
||||
# Looping in smaller pieces,
|
||||
# Endless by design.
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,30 @@
|
||||
import asyncio
|
||||
|
||||
from openai.types.shared import Reasoning
|
||||
|
||||
from agents import Agent, ModelSettings, Runner
|
||||
|
||||
# If you have a certain reason to use Chat Completions, you can configure the model this way,
|
||||
# and then you can pass the chat_completions_model to the Agent constructor.
|
||||
# from openai import AsyncOpenAI
|
||||
# client = AsyncOpenAI()
|
||||
# from agents import OpenAIChatCompletionsModel
|
||||
# chat_completions_model = OpenAIChatCompletionsModel(model="gpt-5.6-sol", openai_client=client)
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Knowledgable GPT-5 Assistant",
|
||||
instructions="You're a knowledgable assistant. You always provide an interesting answer.",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
reasoning=Reasoning(effort="low"), # "none", "low", "medium", "high", "xhigh"
|
||||
verbosity="low", # "low", "medium", "high"
|
||||
),
|
||||
)
|
||||
result = await Runner.run(agent, "Tell me something about recursion in programming.")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,39 @@
|
||||
import asyncio
|
||||
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
from agents import Agent, OpenAIChatCompletionsModel, Runner, set_tracing_disabled
|
||||
|
||||
set_tracing_disabled(True)
|
||||
|
||||
# import logging
|
||||
# logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
# This is an example of how to use gpt-oss with Ollama.
|
||||
# Refer to https://cookbook.openai.com/articles/gpt-oss/run-locally-ollama for more details.
|
||||
# If you prefer using LM Studio, refer to https://cookbook.openai.com/articles/gpt-oss/run-locally-lmstudio
|
||||
gpt_oss_model = OpenAIChatCompletionsModel(
|
||||
model="gpt-oss:20b",
|
||||
openai_client=AsyncOpenAI(
|
||||
base_url="http://localhost:11434/v1",
|
||||
api_key="ollama",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
# Note that using a custom outputType for an agent may not work well with gpt-oss models.
|
||||
# Consider going with the default "text" outputType.
|
||||
# See also: https://github.com/openai/openai-agents-python/issues/1414
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You're a helpful assistant. You provide a concise answer to the user's question.",
|
||||
model=gpt_oss_model,
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Tell me about recursion in programming.")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "8a77ee2e-22f2-409c-837d-b994978b0aa2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"A function calls self, \n",
|
||||
"Unraveling layers deep, \n",
|
||||
"Base case ends the quest. \n",
|
||||
"\n",
|
||||
"Infinite loops lurk, \n",
|
||||
"Mind the base condition well, \n",
|
||||
"Or it will not work. \n",
|
||||
"\n",
|
||||
"Trees and lists unfold, \n",
|
||||
"Elegant solutions bloom, \n",
|
||||
"Recursion's art told.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from agents import Agent, Runner\n",
|
||||
"\n",
|
||||
"agent = Agent(name=\"Assistant\", instructions=\"You are a helpful assistant\")\n",
|
||||
"\n",
|
||||
"# Intended for Jupyter notebooks where there's an existing event loop\n",
|
||||
"result = await Runner.run(agent, \"Write a haiku about recursion in programming.\") # type: ignore[top-level-await] # noqa: F704\n",
|
||||
"print(result.final_output)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner, ToolOutputImage, ToolOutputImageDict, function_tool
|
||||
|
||||
return_typed_dict = True
|
||||
|
||||
URL = "https://images.unsplash.com/photo-1505761671935-60b3a7427bad?auto=format&fit=crop&w=400&q=80"
|
||||
|
||||
|
||||
@function_tool
|
||||
def fetch_random_image() -> ToolOutputImage | ToolOutputImageDict:
|
||||
"""Fetch a random image."""
|
||||
|
||||
print("Image tool called")
|
||||
if return_typed_dict:
|
||||
return {"type": "image", "image_url": URL, "detail": "auto"}
|
||||
|
||||
return ToolOutputImage(image_url=URL, detail="auto")
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
tools=[fetch_random_image],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
input="Fetch an image using the random_image tool, then describe it",
|
||||
)
|
||||
print(result.final_output)
|
||||
"""This image features the famous clock tower, commonly known as Big Ben, ..."""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,189 @@
|
||||
import asyncio
|
||||
import random
|
||||
from typing import Any, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
AgentHookContext,
|
||||
AgentHooks,
|
||||
RunContextWrapper,
|
||||
RunHooks,
|
||||
Runner,
|
||||
Tool,
|
||||
Usage,
|
||||
function_tool,
|
||||
)
|
||||
from agents.items import ModelResponse, TResponseInputItem
|
||||
from agents.tool_context import ToolContext
|
||||
from examples.auto_mode import input_with_fallback
|
||||
|
||||
|
||||
class LoggingHooks(AgentHooks[Any]):
|
||||
async def on_start(
|
||||
self,
|
||||
context: AgentHookContext[Any],
|
||||
agent: Agent[Any],
|
||||
) -> None:
|
||||
# Access the turn_input from the context to see what input the agent received
|
||||
print(f"#### {agent.name} is starting with turn_input: {context.turn_input}")
|
||||
|
||||
async def on_end(
|
||||
self,
|
||||
context: RunContextWrapper[Any],
|
||||
agent: Agent[Any],
|
||||
output: Any,
|
||||
) -> None:
|
||||
print(f"#### {agent.name} produced output: {output}.")
|
||||
|
||||
|
||||
class ExampleHooks(RunHooks):
|
||||
def __init__(self):
|
||||
self.event_counter = 0
|
||||
|
||||
def _usage_to_str(self, usage: Usage) -> str:
|
||||
return f"{usage.requests} requests, {usage.input_tokens} input tokens, {usage.output_tokens} output tokens, {usage.total_tokens} total tokens"
|
||||
|
||||
async def on_agent_start(self, context: AgentHookContext, agent: Agent) -> None:
|
||||
self.event_counter += 1
|
||||
# Access the turn_input from the context to see what input the agent received
|
||||
print(
|
||||
f"### {self.event_counter}: Agent {agent.name} started. turn_input: {context.turn_input}. Usage: {self._usage_to_str(context.usage)}"
|
||||
)
|
||||
|
||||
async def on_llm_start(
|
||||
self,
|
||||
context: RunContextWrapper,
|
||||
agent: Agent,
|
||||
system_prompt: str | None,
|
||||
input_items: list[TResponseInputItem],
|
||||
) -> None:
|
||||
self.event_counter += 1
|
||||
print(f"### {self.event_counter}: LLM started. Usage: {self._usage_to_str(context.usage)}")
|
||||
|
||||
async def on_llm_end(
|
||||
self, context: RunContextWrapper, agent: Agent, response: ModelResponse
|
||||
) -> None:
|
||||
self.event_counter += 1
|
||||
print(f"### {self.event_counter}: LLM ended. Usage: {self._usage_to_str(context.usage)}")
|
||||
|
||||
async def on_agent_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None:
|
||||
self.event_counter += 1
|
||||
print(
|
||||
f"### {self.event_counter}: Agent {agent.name} ended with output {output}. Usage: {self._usage_to_str(context.usage)}"
|
||||
)
|
||||
|
||||
# Note: The on_tool_start and on_tool_end hooks apply only to local tools.
|
||||
# They do not include hosted tools that run on the OpenAI server side,
|
||||
# such as WebSearchTool, FileSearchTool, CodeInterpreterTool, HostedMCPTool,
|
||||
# or other built-in hosted tools.
|
||||
async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None:
|
||||
self.event_counter += 1
|
||||
# While this type cast is not ideal,
|
||||
# we don't plan to change the context arg type in the near future for backwards compatibility.
|
||||
tool_context = cast(ToolContext[Any], context)
|
||||
print(
|
||||
f"### {self.event_counter}: Tool {tool.name} started. name={tool_context.tool_name}, call_id={tool_context.tool_call_id}, args={tool_context.tool_arguments}. Usage: {self._usage_to_str(tool_context.usage)}"
|
||||
)
|
||||
|
||||
async def on_tool_end(
|
||||
self, context: RunContextWrapper, agent: Agent, tool: Tool, result: object
|
||||
) -> None:
|
||||
self.event_counter += 1
|
||||
# While this type cast is not ideal,
|
||||
# we don't plan to change the context arg type in the near future for backwards compatibility.
|
||||
tool_context = cast(ToolContext[Any], context)
|
||||
print(
|
||||
f"### {self.event_counter}: Tool {tool.name} finished. result={result}, name={tool_context.tool_name}, call_id={tool_context.tool_call_id}, args={tool_context.tool_arguments}. Usage: {self._usage_to_str(tool_context.usage)}"
|
||||
)
|
||||
|
||||
async def on_handoff(
|
||||
self, context: RunContextWrapper, from_agent: Agent, to_agent: Agent
|
||||
) -> None:
|
||||
self.event_counter += 1
|
||||
print(
|
||||
f"### {self.event_counter}: Handoff from {from_agent.name} to {to_agent.name}. Usage: {self._usage_to_str(context.usage)}"
|
||||
)
|
||||
|
||||
|
||||
hooks = ExampleHooks()
|
||||
|
||||
###
|
||||
|
||||
|
||||
@function_tool
|
||||
def random_number(max: int) -> int:
|
||||
"""Generate a random number from 0 to max (inclusive)."""
|
||||
return random.randint(0, max)
|
||||
|
||||
|
||||
@function_tool
|
||||
def multiply_by_two(x: int) -> int:
|
||||
"""Return x times two."""
|
||||
return x * 2
|
||||
|
||||
|
||||
class FinalResult(BaseModel):
|
||||
number: int
|
||||
|
||||
|
||||
multiply_agent = Agent(
|
||||
name="Multiply Agent",
|
||||
instructions="Multiply the number by 2 and then return the final result.",
|
||||
tools=[multiply_by_two],
|
||||
output_type=FinalResult,
|
||||
hooks=LoggingHooks(),
|
||||
)
|
||||
|
||||
start_agent = Agent(
|
||||
name="Start Agent",
|
||||
instructions="Generate a random number. If it's even, stop. If it's odd, hand off to the multiplier agent.",
|
||||
tools=[random_number],
|
||||
output_type=FinalResult,
|
||||
handoffs=[multiply_agent],
|
||||
hooks=LoggingHooks(),
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
user_input = input_with_fallback("Enter a max number: ", "50")
|
||||
try:
|
||||
max_number = int(user_input)
|
||||
await Runner.run(
|
||||
start_agent,
|
||||
hooks=hooks,
|
||||
input=f"Generate a random number between 0 and {max_number}.",
|
||||
)
|
||||
except ValueError:
|
||||
print("Please enter a valid integer.")
|
||||
return
|
||||
|
||||
print("Done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
"""
|
||||
$ python examples/basic/lifecycle_example.py
|
||||
|
||||
Enter a max number: 250
|
||||
### 1: Agent Start Agent started. Usage: 0 requests, 0 input tokens, 0 output tokens, 0 total tokens
|
||||
### 2: LLM started. Usage: 0 requests, 0 input tokens, 0 output tokens, 0 total tokens
|
||||
### 3: LLM ended. Usage: 1 requests, 143 input tokens, 15 output tokens, 158 total tokens
|
||||
### 4: Tool random_number started. name=random_number, call_id=call_IujmDZYiM800H0hy7v17VTS0, args={"max":250}. Usage: 1 requests, 143 input tokens, 15 output tokens, 158 total tokens
|
||||
### 5: Tool random_number finished. result=107, name=random_number, call_id=call_IujmDZYiM800H0hy7v17VTS0, args={"max":250}. Usage: 1 requests, 143 input tokens, 15 output tokens, 158 total tokens
|
||||
### 6: LLM started. Usage: 1 requests, 143 input tokens, 15 output tokens, 158 total tokens
|
||||
### 7: LLM ended. Usage: 2 requests, 310 input tokens, 29 output tokens, 339 total tokens
|
||||
### 8: Handoff from Start Agent to Multiply Agent. Usage: 2 requests, 310 input tokens, 29 output tokens, 339 total tokens
|
||||
### 9: Agent Multiply Agent started. Usage: 2 requests, 310 input tokens, 29 output tokens, 339 total tokens
|
||||
### 10: LLM started. Usage: 2 requests, 310 input tokens, 29 output tokens, 339 total tokens
|
||||
### 11: LLM ended. Usage: 3 requests, 472 input tokens, 45 output tokens, 517 total tokens
|
||||
### 12: Tool multiply_by_two started. name=multiply_by_two, call_id=call_KhHvTfsgaosZsfi741QvzgYw, args={"x":107}. Usage: 3 requests, 472 input tokens, 45 output tokens, 517 total tokens
|
||||
### 13: Tool multiply_by_two finished. result=214, name=multiply_by_two, call_id=call_KhHvTfsgaosZsfi741QvzgYw, args={"x":107}. Usage: 3 requests, 472 input tokens, 45 output tokens, 517 total tokens
|
||||
### 14: LLM started. Usage: 3 requests, 472 input tokens, 45 output tokens, 517 total tokens
|
||||
### 15: LLM ended. Usage: 4 requests, 660 input tokens, 56 output tokens, 716 total tokens
|
||||
### 16: Agent Multiply Agent ended with output number=214. Usage: 4 requests, 660 input tokens, 56 output tokens, 716 total tokens
|
||||
Done!
|
||||
|
||||
"""
|
||||
@@ -0,0 +1,45 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import os
|
||||
|
||||
from agents import Agent, Runner
|
||||
|
||||
FILEPATH = os.path.join(os.path.dirname(__file__), "media/partial_o3-and-o4-mini-system-card.pdf")
|
||||
|
||||
|
||||
def file_to_base64(file_path: str) -> str:
|
||||
with open(file_path, "rb") as f:
|
||||
return base64.b64encode(f.read()).decode("utf-8")
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
b64_file = file_to_base64(FILEPATH)
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_file",
|
||||
"file_data": f"data:application/pdf;base64,{b64_file}",
|
||||
"filename": "partial_o3-and-o4-mini-system-card.pdf",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is the first sentence of the introduction?",
|
||||
},
|
||||
],
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,48 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import os
|
||||
|
||||
from agents import Agent, Runner
|
||||
|
||||
FILEPATH = os.path.join(os.path.dirname(__file__), "media/image_bison.jpg")
|
||||
|
||||
|
||||
def image_to_base64(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
return encoded_string
|
||||
|
||||
|
||||
async def main():
|
||||
# Print base64-encoded image
|
||||
b64_image = image_to_base64(FILEPATH)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_image",
|
||||
"detail": "auto",
|
||||
"image_url": f"data:image/jpeg;base64,{b64_image}",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What do you see in this image?",
|
||||
},
|
||||
],
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
Binary file not shown.
@@ -0,0 +1,84 @@
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from agents import Agent, AgentOutputSchema, AgentOutputSchemaBase, ModelBehaviorError, Runner
|
||||
|
||||
"""This example demonstrates how to use an output type that is not in strict mode. Strict mode
|
||||
allows us to guarantee valid JSON output, but some schemas are not strict-compatible.
|
||||
|
||||
In this example, we define an output type that is not strict-compatible, and then we run the
|
||||
agent with strict_json_schema=False.
|
||||
|
||||
We also demonstrate a custom output type.
|
||||
|
||||
To understand which schemas are strict-compatible, see:
|
||||
https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class OutputType:
|
||||
jokes: dict[int, str]
|
||||
"""A list of jokes, indexed by joke number."""
|
||||
|
||||
|
||||
class CustomOutputSchema(AgentOutputSchemaBase):
|
||||
"""A demonstration of a custom output schema."""
|
||||
|
||||
def is_plain_text(self) -> bool:
|
||||
return False
|
||||
|
||||
def name(self) -> str:
|
||||
return "CustomOutputSchema"
|
||||
|
||||
def json_schema(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {"jokes": {"type": "object", "properties": {"joke": {"type": "string"}}}},
|
||||
}
|
||||
|
||||
def is_strict_json_schema(self) -> bool:
|
||||
return False
|
||||
|
||||
def validate_json(self, json_str: str) -> Any:
|
||||
json_obj = json.loads(json_str)
|
||||
# Just for demonstration, we'll return a list.
|
||||
return list(json_obj["jokes"].values())
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
output_type=OutputType,
|
||||
)
|
||||
|
||||
input = "Tell me 3 short jokes."
|
||||
|
||||
# First, let's try with a strict output type. This should raise an exception.
|
||||
try:
|
||||
result = await Runner.run(agent, input)
|
||||
raise AssertionError("Should have raised an exception")
|
||||
except Exception as e:
|
||||
print(f"Error (expected): {e}")
|
||||
|
||||
# Now let's try again with a non-strict output type. This should work.
|
||||
# In some cases, it will raise an error - the schema isn't strict, so the model may
|
||||
# produce an invalid JSON object.
|
||||
agent.output_type = AgentOutputSchema(OutputType, strict_json_schema=False)
|
||||
try:
|
||||
result = await Runner.run(agent, input)
|
||||
print(result.final_output)
|
||||
except ModelBehaviorError as e:
|
||||
print(f"Non-strict output validation failed (expected possibility): {e}")
|
||||
|
||||
# Finally, let's try a custom output type.
|
||||
agent.output_type = CustomOutputSchema()
|
||||
result = await Runner.run(agent, input)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,74 @@
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner
|
||||
from examples.auto_mode import input_with_fallback, is_auto_mode
|
||||
|
||||
"""This demonstrates usage of the `previous_response_id` parameter to continue a conversation.
|
||||
The second run passes the previous response ID to the model, which allows it to continue the
|
||||
conversation without re-sending the previous messages.
|
||||
|
||||
Notes:
|
||||
1. This only applies to the OpenAI Responses API. Other models will ignore this parameter.
|
||||
2. Responses are only stored for 30 days as of this writing, so in production you should
|
||||
store the response ID along with an expiration date; if the response is no longer valid,
|
||||
you'll need to re-send the previous conversation history.
|
||||
"""
|
||||
|
||||
|
||||
async def main():
|
||||
print("=== Non-streaming Example ===")
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. be VERY concise.",
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "What is the largest country in South America?")
|
||||
print(result.final_output)
|
||||
# Brazil
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What is the capital of that country?",
|
||||
previous_response_id=result.last_response_id,
|
||||
)
|
||||
print(result.final_output)
|
||||
# Brasilia
|
||||
|
||||
|
||||
async def main_stream():
|
||||
print("=== Streaming Example ===")
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. be VERY concise.",
|
||||
)
|
||||
|
||||
result = Runner.run_streamed(agent, "What is the largest country in South America?")
|
||||
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event" and event.data.type == "response.output_text.delta":
|
||||
print(event.data.delta, end="", flush=True)
|
||||
|
||||
print()
|
||||
|
||||
result = Runner.run_streamed(
|
||||
agent,
|
||||
"What is the capital of that country?",
|
||||
previous_response_id=result.last_response_id,
|
||||
)
|
||||
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event" and event.data.type == "response.output_text.delta":
|
||||
print(event.data.delta, end="", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if is_auto_mode():
|
||||
asyncio.run(main())
|
||||
print()
|
||||
asyncio.run(main_stream())
|
||||
else:
|
||||
is_stream = input_with_fallback("Run in stream mode? (y/n): ", "n")
|
||||
if is_stream == "y":
|
||||
asyncio.run(main_stream())
|
||||
else:
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,79 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from agents import Agent, GenerateDynamicPromptData, Runner
|
||||
|
||||
"""
|
||||
NOTE: This example will not work out of the box, because the default prompt ID will not be available
|
||||
in your project.
|
||||
|
||||
To use it, please:
|
||||
1. Go to https://platform.openai.com/playground/prompts
|
||||
2. Create a new prompt variable, `poem_style`.
|
||||
3. Create a system prompt with the content:
|
||||
```
|
||||
Write a poem in {{poem_style}}
|
||||
```
|
||||
4. Run the example with the `--prompt-id` flag.
|
||||
"""
|
||||
|
||||
DEFAULT_PROMPT_ID = "pmpt_6965a984c7ac8194a8f4e79b00f838840118c1e58beb3332"
|
||||
|
||||
|
||||
class DynamicContext:
|
||||
def __init__(self, prompt_id: str):
|
||||
self.prompt_id = prompt_id
|
||||
self.poem_style = random.choice(["limerick", "haiku", "ballad"])
|
||||
print(f"[debug] DynamicContext initialized with poem_style: {self.poem_style}")
|
||||
|
||||
|
||||
async def _get_dynamic_prompt(data: GenerateDynamicPromptData):
|
||||
ctx: DynamicContext = data.context.context
|
||||
return {
|
||||
"id": ctx.prompt_id,
|
||||
"version": "1",
|
||||
"variables": {
|
||||
"poem_style": ctx.poem_style,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def dynamic_prompt(prompt_id: str):
|
||||
context = DynamicContext(prompt_id)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
prompt=_get_dynamic_prompt,
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Tell me about recursion in programming.", context=context)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def static_prompt(prompt_id: str):
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
prompt={
|
||||
"id": prompt_id,
|
||||
"version": "1",
|
||||
"variables": {
|
||||
"poem_style": "limerick",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Tell me about recursion in programming.")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dynamic", action="store_true")
|
||||
parser.add_argument("--prompt-id", type=str, default=DEFAULT_PROMPT_ID)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dynamic:
|
||||
asyncio.run(dynamic_prompt(args.prompt_id))
|
||||
else:
|
||||
asyncio.run(static_prompt(args.prompt_id))
|
||||
@@ -0,0 +1,31 @@
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner
|
||||
|
||||
URL = "https://images.unsplash.com/photo-1505761671935-60b3a7427bad?auto=format&fit=crop&w=400&q=80"
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "input_image", "detail": "auto", "image_url": URL}],
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What do you see in this image?",
|
||||
},
|
||||
],
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,31 @@
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner
|
||||
|
||||
URL = "https://www.berkshirehathaway.com/letters/2024ltr.pdf"
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "input_file", "file_url": URL}],
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can you summarize the letter?",
|
||||
},
|
||||
],
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,112 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
ModelRetrySettings,
|
||||
ModelSettings,
|
||||
RetryDecision,
|
||||
RunConfig,
|
||||
Runner,
|
||||
retry_policies,
|
||||
)
|
||||
|
||||
|
||||
def format_error(error: object) -> str:
|
||||
if not isinstance(error, BaseException):
|
||||
return "Unknown error"
|
||||
return str(error) or error.__class__.__name__
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
apply_policies = retry_policies.any(
|
||||
# On OpenAI-backed models, provider_suggested() follows provider retry advice,
|
||||
# including fallback retryable statuses when x-should-retry is absent
|
||||
# (for example 408/409/429/5xx).
|
||||
retry_policies.provider_suggested(),
|
||||
retry_policies.retry_after(),
|
||||
retry_policies.network_error(),
|
||||
retry_policies.http_status([408, 409, 429, 500, 502, 503, 504]),
|
||||
)
|
||||
|
||||
async def policy(context) -> bool | RetryDecision:
|
||||
raw_decision = apply_policies(context)
|
||||
decision: bool | RetryDecision
|
||||
if inspect.isawaitable(raw_decision):
|
||||
decision = await raw_decision
|
||||
else:
|
||||
decision = raw_decision
|
||||
if isinstance(decision, RetryDecision):
|
||||
if not decision.retry:
|
||||
print(
|
||||
f"[retry] stop after attempt {context.attempt}/{context.max_retries + 1}: "
|
||||
f"{format_error(context.error)}"
|
||||
)
|
||||
return False
|
||||
|
||||
print(
|
||||
" | ".join(
|
||||
part
|
||||
for part in [
|
||||
f"[retry] retry attempt {context.attempt}/{context.max_retries + 1}",
|
||||
(
|
||||
f"waiting {decision.delay:.2f}s"
|
||||
if decision.delay is not None
|
||||
else "using default backoff"
|
||||
),
|
||||
f"reason: {decision.reason}" if decision.reason else None,
|
||||
f"error: {format_error(context.error)}",
|
||||
]
|
||||
if part is not None
|
||||
)
|
||||
)
|
||||
return decision
|
||||
|
||||
if not decision:
|
||||
print(
|
||||
f"[retry] stop after attempt {context.attempt}/{context.max_retries + 1}: "
|
||||
f"{format_error(context.error)}"
|
||||
)
|
||||
return decision
|
||||
|
||||
retry = ModelRetrySettings(
|
||||
max_retries=4,
|
||||
backoff={
|
||||
"initial_delay": 0.5,
|
||||
"max_delay": 5.0,
|
||||
"multiplier": 2.0,
|
||||
"jitter": True,
|
||||
},
|
||||
policy=policy,
|
||||
)
|
||||
|
||||
# RunConfig-level model_settings are shared defaults for the run.
|
||||
# If an Agent also defines model_settings, the Agent wins for overlapping
|
||||
# keys, while nested objects like retry/backoff are merged.
|
||||
run_config = RunConfig(model_settings=ModelSettings(retry=retry))
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a concise assistant. Answer in 3 short bullet points at most.",
|
||||
# This Agent repeats the same retry config for clarity. In real code you
|
||||
# can keep shared defaults in RunConfig and only put per-agent overrides
|
||||
# here when you need different retry behavior.
|
||||
model_settings=ModelSettings(retry=retry),
|
||||
)
|
||||
|
||||
print(
|
||||
"Retry support is configured. You will only see [retry] logs if a transient failure happens."
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Explain exponential backoff for API retries in plain English.",
|
||||
run_config=run_config,
|
||||
)
|
||||
|
||||
print("\nFinal output:\n")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,114 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
ModelRetrySettings,
|
||||
ModelSettings,
|
||||
RetryDecision,
|
||||
RunConfig,
|
||||
Runner,
|
||||
retry_policies,
|
||||
)
|
||||
|
||||
|
||||
def format_error(error: object) -> str:
|
||||
if not isinstance(error, BaseException):
|
||||
return "Unknown error"
|
||||
return str(error) or error.__class__.__name__
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
apply_policies = retry_policies.any(
|
||||
# On OpenAI-backed models, provider_suggested() follows provider retry advice,
|
||||
# including fallback retryable statuses when x-should-retry is absent
|
||||
# (for example 408/409/429/5xx).
|
||||
retry_policies.provider_suggested(),
|
||||
retry_policies.retry_after(),
|
||||
retry_policies.network_error(),
|
||||
retry_policies.http_status([408, 409, 429, 500, 502, 503, 504]),
|
||||
)
|
||||
|
||||
async def policy(context) -> bool | RetryDecision:
|
||||
raw_decision = apply_policies(context)
|
||||
decision: bool | RetryDecision
|
||||
if inspect.isawaitable(raw_decision):
|
||||
decision = await raw_decision
|
||||
else:
|
||||
decision = raw_decision
|
||||
if isinstance(decision, RetryDecision):
|
||||
if not decision.retry:
|
||||
print(
|
||||
f"[retry] stop after attempt {context.attempt}/{context.max_retries + 1}: "
|
||||
f"{format_error(context.error)}"
|
||||
)
|
||||
return False
|
||||
|
||||
print(
|
||||
" | ".join(
|
||||
part
|
||||
for part in [
|
||||
f"[retry] retry attempt {context.attempt}/{context.max_retries + 1}",
|
||||
(
|
||||
f"waiting {decision.delay:.2f}s"
|
||||
if decision.delay is not None
|
||||
else "using default backoff"
|
||||
),
|
||||
f"reason: {decision.reason}" if decision.reason else None,
|
||||
f"error: {format_error(context.error)}",
|
||||
]
|
||||
if part is not None
|
||||
)
|
||||
)
|
||||
return decision
|
||||
|
||||
if not decision:
|
||||
print(
|
||||
f"[retry] stop after attempt {context.attempt}/{context.max_retries + 1}: "
|
||||
f"{format_error(context.error)}"
|
||||
)
|
||||
return decision
|
||||
|
||||
retry = ModelRetrySettings(
|
||||
max_retries=4,
|
||||
backoff={
|
||||
"initial_delay": 0.5,
|
||||
"max_delay": 5.0,
|
||||
"multiplier": 2.0,
|
||||
"jitter": True,
|
||||
},
|
||||
policy=policy,
|
||||
)
|
||||
|
||||
# RunConfig-level model_settings are shared defaults for the run.
|
||||
# If an Agent also defines model_settings, the Agent wins for overlapping
|
||||
# keys, while nested objects like retry/backoff are merged.
|
||||
run_config = RunConfig(model_settings=ModelSettings(retry=retry))
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a concise assistant. Answer in 3 short bullet points at most.",
|
||||
# Prefix with litellm/ to route this request through the LiteLLM adapter.
|
||||
model="litellm/openai/gpt-4o-mini",
|
||||
# This Agent repeats the same retry config for clarity. In real code you
|
||||
# can keep shared defaults in RunConfig and only put per-agent overrides
|
||||
# here when you need different retry behavior.
|
||||
model_settings=ModelSettings(retry=retry),
|
||||
)
|
||||
|
||||
print(
|
||||
"Retry support is configured. You will only see [retry] logs if a transient failure happens."
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Explain exponential backoff for API retries in plain English.",
|
||||
run_config=run_config,
|
||||
)
|
||||
|
||||
print("\nFinal output:\n")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,87 @@
|
||||
import asyncio
|
||||
from typing import Annotated, Any
|
||||
|
||||
from openai.types.responses import ResponseFunctionCallArgumentsDeltaEvent
|
||||
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool
|
||||
def write_file(filename: Annotated[str, "Name of the file"], content: str) -> str:
|
||||
"""Write content to a file."""
|
||||
return f"File {filename} written successfully"
|
||||
|
||||
|
||||
@function_tool
|
||||
def create_config(
|
||||
project_name: Annotated[str, "Project name"],
|
||||
version: Annotated[str, "Project version"],
|
||||
dependencies: Annotated[list[str] | None, "Dependencies (list of packages)"],
|
||||
) -> str:
|
||||
"""Generate a project configuration file."""
|
||||
return f"Config for {project_name} v{version} created"
|
||||
|
||||
|
||||
async def main():
|
||||
"""
|
||||
Demonstrates real-time streaming of function call arguments.
|
||||
|
||||
Function arguments are streamed incrementally as they are generated,
|
||||
providing immediate feedback during parameter generation.
|
||||
"""
|
||||
agent = Agent(
|
||||
name="CodeGenerator",
|
||||
instructions="You are a helpful coding assistant. Use the provided tools to create files and configurations.",
|
||||
tools=[write_file, create_config],
|
||||
)
|
||||
|
||||
print("🚀 Function Call Arguments Streaming Demo")
|
||||
|
||||
result = Runner.run_streamed(
|
||||
agent,
|
||||
input="Create a Python web project called 'my-app' with FastAPI. Version 1.0.0, dependencies: fastapi, uvicorn",
|
||||
)
|
||||
|
||||
# Track function calls for detailed output
|
||||
function_calls: dict[Any, dict[str, Any]] = {} # call_id -> {name, arguments}
|
||||
current_active_call_id = None
|
||||
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event":
|
||||
# Function call started
|
||||
if event.data.type == "response.output_item.added":
|
||||
if getattr(event.data.item, "type", None) == "function_call":
|
||||
function_name = getattr(event.data.item, "name", "unknown")
|
||||
call_id = getattr(event.data.item, "call_id", "unknown")
|
||||
|
||||
function_calls[call_id] = {"name": function_name, "arguments": ""}
|
||||
current_active_call_id = call_id
|
||||
print(f"\n📞 Function call streaming started: {function_name}()")
|
||||
print("📝 Arguments building...")
|
||||
|
||||
# Real-time argument streaming
|
||||
elif isinstance(event.data, ResponseFunctionCallArgumentsDeltaEvent):
|
||||
if current_active_call_id and current_active_call_id in function_calls:
|
||||
function_calls[current_active_call_id]["arguments"] += event.data.delta
|
||||
print(event.data.delta, end="", flush=True)
|
||||
|
||||
# Function call completed
|
||||
elif event.data.type == "response.output_item.done":
|
||||
if hasattr(event.data.item, "call_id"):
|
||||
call_id = getattr(event.data.item, "call_id", "unknown")
|
||||
if call_id in function_calls:
|
||||
function_info = function_calls[call_id]
|
||||
print(f"\n✅ Function call streaming completed: {function_info['name']}")
|
||||
print()
|
||||
if current_active_call_id == call_id:
|
||||
current_active_call_id = None
|
||||
|
||||
print("Summary of all function calls:")
|
||||
for call_id, info in function_calls.items():
|
||||
print(f" - #{call_id}: {info['name']}({info['arguments']})")
|
||||
|
||||
print(f"\nResult: {result.final_output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,66 @@
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from agents import Agent, ItemHelpers, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool
|
||||
def how_many_jokes() -> int:
|
||||
"""Return a random integer of jokes to tell between 1 and 10 (inclusive)."""
|
||||
return random.randint(1, 10)
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Joker",
|
||||
instructions="First call the `how_many_jokes` tool, then tell that many jokes.",
|
||||
tools=[how_many_jokes],
|
||||
)
|
||||
|
||||
result = Runner.run_streamed(
|
||||
agent,
|
||||
input="Hello",
|
||||
)
|
||||
print("=== Run starting ===")
|
||||
async for event in result.stream_events():
|
||||
# We'll ignore the raw responses event deltas
|
||||
if event.type == "raw_response_event":
|
||||
continue
|
||||
elif event.type == "agent_updated_stream_event":
|
||||
print(f"Agent updated: {event.new_agent.name}")
|
||||
continue
|
||||
elif event.type == "run_item_stream_event":
|
||||
if event.item.type == "tool_call_item":
|
||||
print(f"-- Tool was called: {getattr(event.item.raw_item, 'name', 'Unknown Tool')}")
|
||||
elif event.item.type == "tool_call_output_item":
|
||||
print(f"-- Tool output: {event.item.output}")
|
||||
elif event.item.type == "message_output_item":
|
||||
print(f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}")
|
||||
else:
|
||||
pass # Ignore other event types
|
||||
|
||||
print("=== Run complete ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
# === Run starting ===
|
||||
# Agent updated: Joker
|
||||
# -- Tool was called: how_many_jokes
|
||||
# -- Tool output: 4
|
||||
# -- Message output:
|
||||
# Sure, here are four jokes for you:
|
||||
|
||||
# 1. **Why don't skeletons fight each other?**
|
||||
# They don't have the guts!
|
||||
|
||||
# 2. **What do you call fake spaghetti?**
|
||||
# An impasta!
|
||||
|
||||
# 3. **Why did the scarecrow win an award?**
|
||||
# Because he was outstanding in his field!
|
||||
|
||||
# 4. **Why did the bicycle fall over?**
|
||||
# Because it was two-tired!
|
||||
# === Run complete ===
|
||||
@@ -0,0 +1,21 @@
|
||||
import asyncio
|
||||
|
||||
from openai.types.responses import ResponseTextDeltaEvent
|
||||
|
||||
from agents import Agent, Runner
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Joker",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
|
||||
print(event.data.delta, end="", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,236 @@
|
||||
"""Responses websocket streaming example with function tools, agent-as-tool, and approval.
|
||||
|
||||
This example shows a user-facing websocket workflow using
|
||||
`responses_websocket_session(...)`:
|
||||
- Streaming output (including reasoning summary deltas when available)
|
||||
- Regular function tools
|
||||
- An `Agent.as_tool(...)` specialist agent
|
||||
- HITL approval for a sensitive tool call
|
||||
- A follow-up turn using `previous_response_id` on the same trace
|
||||
|
||||
Required environment variable:
|
||||
- `OPENAI_API_KEY`
|
||||
|
||||
Optional environment variables:
|
||||
- `OPENAI_MODEL` (defaults to `gpt-5.6-sol`)
|
||||
- `OPENAI_BASE_URL`
|
||||
- `OPENAI_WEBSOCKET_BASE_URL`
|
||||
- `EXAMPLES_INTERACTIVE_MODE=auto` (auto-approve HITL prompts for scripted runs)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from openai.types.shared import Reasoning
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
ModelSettings,
|
||||
ResponsesWebSocketSession,
|
||||
function_tool,
|
||||
responses_websocket_session,
|
||||
trace,
|
||||
)
|
||||
from examples.auto_mode import confirm_with_fallback
|
||||
|
||||
|
||||
@function_tool
|
||||
def lookup_order(order_id: str) -> dict[str, Any]:
|
||||
"""Return deterministic order data for the demo."""
|
||||
orders = {
|
||||
"ORD-1001": {
|
||||
"order_id": "ORD-1001",
|
||||
"status": "delivered",
|
||||
"delivered_days_ago": 3,
|
||||
"amount": 49.99,
|
||||
"currency": "USD",
|
||||
"item": "Wireless Mouse",
|
||||
},
|
||||
"ORD-2002": {
|
||||
"order_id": "ORD-2002",
|
||||
"status": "delivered",
|
||||
"delivered_days_ago": 12,
|
||||
"amount": 129.0,
|
||||
"currency": "USD",
|
||||
"item": "Keyboard",
|
||||
},
|
||||
}
|
||||
return orders.get(
|
||||
order_id,
|
||||
{
|
||||
"order_id": order_id,
|
||||
"status": "unknown",
|
||||
"delivered_days_ago": 999,
|
||||
"amount": 0.0,
|
||||
"currency": "USD",
|
||||
"item": "unknown",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@function_tool(needs_approval=True)
|
||||
def submit_refund(order_id: str, amount: float, reason: str) -> dict[str, Any]:
|
||||
"""Create a refund request. This tool requires approval."""
|
||||
ticket = "RF-1001" if order_id == "ORD-1001" else f"RF-{order_id[-4:]}"
|
||||
return {
|
||||
"refund_ticket": ticket,
|
||||
"order_id": order_id,
|
||||
"amount": amount,
|
||||
"reason": reason,
|
||||
"status": "approved_pending_processing",
|
||||
}
|
||||
|
||||
|
||||
def ask_approval(question: str) -> bool:
|
||||
"""Prompt for approval (or auto-approve in examples auto mode)."""
|
||||
return confirm_with_fallback(f"[approval] {question} [y/N]: ", default=True)
|
||||
|
||||
|
||||
async def run_streamed_turn(
|
||||
ws: ResponsesWebSocketSession,
|
||||
agent: Agent[Any],
|
||||
prompt: str,
|
||||
*,
|
||||
previous_response_id: str | None = None,
|
||||
) -> tuple[str, str]:
|
||||
"""Run one streamed turn and handle HITL approvals if needed."""
|
||||
print(f"\nUser: {prompt}\n")
|
||||
|
||||
result = ws.run_streamed(
|
||||
agent,
|
||||
prompt,
|
||||
previous_response_id=previous_response_id,
|
||||
)
|
||||
printed_reasoning = False
|
||||
printed_output = False
|
||||
|
||||
while True:
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event":
|
||||
raw = event.data
|
||||
if raw.type == "response.reasoning_summary_text.delta":
|
||||
if not printed_reasoning:
|
||||
print("Reasoning:")
|
||||
printed_reasoning = True
|
||||
print(raw.delta, end="", flush=True)
|
||||
elif raw.type == "response.output_text.delta":
|
||||
if printed_reasoning and not printed_output:
|
||||
print("\n")
|
||||
if not printed_output:
|
||||
print("Assistant:")
|
||||
printed_output = True
|
||||
print(raw.delta, end="", flush=True)
|
||||
continue
|
||||
|
||||
if event.type != "run_item_stream_event":
|
||||
continue
|
||||
|
||||
item = event.item
|
||||
if item.type == "tool_call_item":
|
||||
tool_name = getattr(item.raw_item, "name", "unknown")
|
||||
tool_args = getattr(item.raw_item, "arguments", "")
|
||||
print(f"\n[tool call] {tool_name}({tool_args})")
|
||||
elif item.type == "tool_call_output_item":
|
||||
print(f"[tool result] {item.output}")
|
||||
|
||||
if printed_reasoning or printed_output:
|
||||
print("\n")
|
||||
|
||||
if not result.interruptions:
|
||||
break
|
||||
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
question = f"Approve {interruption.name} with args {interruption.arguments}?"
|
||||
if ask_approval(question):
|
||||
state.approve(interruption)
|
||||
else:
|
||||
state.reject(interruption)
|
||||
|
||||
result = ws.run_streamed(agent, state)
|
||||
|
||||
if result.last_response_id is None:
|
||||
raise RuntimeError("The streamed run completed without a response_id.")
|
||||
|
||||
final_output = str(result.final_output)
|
||||
print(f"response_id: {result.last_response_id}")
|
||||
print(f"final_output: {final_output}\n")
|
||||
return result.last_response_id, final_output
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
model_name = os.getenv("OPENAI_MODEL", "gpt-5.6-sol")
|
||||
policy_agent = Agent(
|
||||
name="RefundPolicySpecialist",
|
||||
instructions=(
|
||||
"You are a refund policy specialist. The policy is simple: orders delivered "
|
||||
"within 7 days are eligible for a full refund, and older delivered orders "
|
||||
"are not. Return a short answer with eligibility and a one-line reason."
|
||||
),
|
||||
model=model_name,
|
||||
model_settings=ModelSettings(max_tokens=120),
|
||||
)
|
||||
|
||||
support_agent = Agent(
|
||||
name="SupportAgent",
|
||||
instructions=(
|
||||
"You are a support agent. For refund requests, do this in order: "
|
||||
"1) call lookup_order, 2) call refund_policy_specialist, 3) if the user "
|
||||
"asked to proceed and the order is eligible, call submit_refund. "
|
||||
"When asked for only the refund ticket, return only the ticket token "
|
||||
"(for example RF-1001)."
|
||||
),
|
||||
tools=[
|
||||
lookup_order,
|
||||
policy_agent.as_tool(
|
||||
tool_name="refund_policy_specialist",
|
||||
tool_description="Check refund eligibility and explain the policy decision.",
|
||||
),
|
||||
submit_refund,
|
||||
],
|
||||
model=model_name,
|
||||
model_settings=ModelSettings(
|
||||
max_tokens=200,
|
||||
reasoning=Reasoning(effort="medium", summary="detailed"),
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
# You can skip this helper and call Runner.run_streamed(...) directly.
|
||||
# It will still work, but each run will create/connect again unless you manually
|
||||
# reuse the same RunConfig/provider. This helper makes that reuse easy across turns
|
||||
# (and nested agent-as-tool runs) so the websocket connection can stay warm.
|
||||
async with responses_websocket_session() as ws:
|
||||
with trace("Responses WS support example") as current_trace:
|
||||
print(f"Using model={model_name}")
|
||||
print(f"trace_id={current_trace.trace_id}")
|
||||
|
||||
first_response_id, _ = await run_streamed_turn(
|
||||
ws,
|
||||
support_agent,
|
||||
(
|
||||
"Customer wants a refund for order ORD-1001 because the mouse arrived "
|
||||
"damaged. Please check the order, ask the refund policy specialist, and "
|
||||
"if it is eligible submit the refund. Reply with only the refund ticket."
|
||||
),
|
||||
)
|
||||
|
||||
await run_streamed_turn(
|
||||
ws,
|
||||
support_agent,
|
||||
"What refund ticket did you just create? Reply with only the ticket.",
|
||||
previous_response_id=first_response_id,
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
if "closed before any response events" in str(exc):
|
||||
print(
|
||||
"\nWebsocket mode closed before sending events. This usually means the "
|
||||
"feature is not enabled for this account/model yet."
|
||||
)
|
||||
return
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,181 @@
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
Runner,
|
||||
ToolGuardrailFunctionOutput,
|
||||
ToolInputGuardrailData,
|
||||
ToolOutputGuardrailData,
|
||||
ToolOutputGuardrailTripwireTriggered,
|
||||
function_tool,
|
||||
tool_input_guardrail,
|
||||
tool_output_guardrail,
|
||||
)
|
||||
|
||||
|
||||
@function_tool
|
||||
def send_email(to: str, subject: str, body: str) -> str:
|
||||
"""Send an email to the specified recipient."""
|
||||
return f"Email sent to {to} with subject '{subject}'"
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_user_data(user_id: str) -> dict[str, str]:
|
||||
"""Get user data by ID."""
|
||||
# Simulate returning sensitive data
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"ssn": "123-45-6789", # Sensitive data that should be blocked!
|
||||
"phone": "555-1234",
|
||||
}
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_contact_info(user_id: str) -> dict[str, str]:
|
||||
"""Get contact info by ID."""
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"name": "Jane Smith",
|
||||
"email": "jane@example.com",
|
||||
"phone": "555-1234",
|
||||
}
|
||||
|
||||
|
||||
@tool_input_guardrail
|
||||
def reject_sensitive_words(data: ToolInputGuardrailData) -> ToolGuardrailFunctionOutput:
|
||||
"""Reject tool calls that contain sensitive words in arguments."""
|
||||
try:
|
||||
args = json.loads(data.context.tool_arguments) if data.context.tool_arguments else {}
|
||||
except json.JSONDecodeError:
|
||||
return ToolGuardrailFunctionOutput(output_info="Invalid JSON arguments")
|
||||
|
||||
# Check for suspicious content
|
||||
sensitive_words = [
|
||||
"password",
|
||||
"hack",
|
||||
"exploit",
|
||||
"malware",
|
||||
"ACME",
|
||||
]
|
||||
for key, value in args.items():
|
||||
value_str = str(value).lower()
|
||||
for word in sensitive_words:
|
||||
if word.lower() in value_str:
|
||||
# Reject tool call and inform the model the function was not called
|
||||
return ToolGuardrailFunctionOutput.reject_content(
|
||||
message=f"🚨 Tool call blocked: contains '{word}'",
|
||||
output_info={"blocked_word": word, "argument": key},
|
||||
)
|
||||
|
||||
return ToolGuardrailFunctionOutput(output_info="Input validated")
|
||||
|
||||
|
||||
@tool_output_guardrail
|
||||
def block_sensitive_output(data: ToolOutputGuardrailData) -> ToolGuardrailFunctionOutput:
|
||||
"""Block tool outputs that contain sensitive data."""
|
||||
output_str = str(data.output).lower()
|
||||
|
||||
# Check for sensitive data patterns
|
||||
if "ssn" in output_str or "123-45-6789" in output_str:
|
||||
# Use raise_exception to halt execution completely for sensitive data
|
||||
return ToolGuardrailFunctionOutput.raise_exception(
|
||||
output_info={"blocked_pattern": "SSN", "tool": data.context.tool_name},
|
||||
)
|
||||
|
||||
return ToolGuardrailFunctionOutput(output_info="Output validated")
|
||||
|
||||
|
||||
@tool_output_guardrail
|
||||
def reject_phone_numbers(data: ToolOutputGuardrailData) -> ToolGuardrailFunctionOutput:
|
||||
"""Reject function output containing phone numbers."""
|
||||
output_str = str(data.output)
|
||||
if "555-1234" in output_str:
|
||||
return ToolGuardrailFunctionOutput.reject_content(
|
||||
message="User data not retrieved as it contains a phone number which is restricted.",
|
||||
output_info={"redacted": "phone_number"},
|
||||
)
|
||||
return ToolGuardrailFunctionOutput(output_info="Phone number check passed")
|
||||
|
||||
|
||||
# Apply guardrails to tools
|
||||
send_email.tool_input_guardrails = [reject_sensitive_words]
|
||||
get_user_data.tool_output_guardrails = [block_sensitive_output]
|
||||
get_contact_info.tool_output_guardrails = [reject_phone_numbers]
|
||||
|
||||
agent = Agent(
|
||||
name="Secure Assistant",
|
||||
instructions=(
|
||||
"You are a helpful assistant with access to email and user data tools. "
|
||||
"When the user provides all required arguments for a requested tool, call it instead of "
|
||||
"asking a follow-up question."
|
||||
),
|
||||
tools=[send_email, get_user_data, get_contact_info],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
print("=== Tool Guardrails Example ===\n")
|
||||
|
||||
try:
|
||||
# Example 1: Normal operation - should work fine
|
||||
print("1. Normal email sending:")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Send an email to john@example.com with subject 'Welcome' and body "
|
||||
"'Welcome to our service.'",
|
||||
)
|
||||
print(f"✅ Successful tool execution: {result.final_output}\n")
|
||||
|
||||
# Example 2: Input guardrail triggers - function tool call is rejected but execution continues
|
||||
print("2. Attempting to send email with suspicious content:")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Send an email to john@example.com with subject 'Introduction' and body "
|
||||
"'Introducing ACME corp.'",
|
||||
)
|
||||
print(f"❌ Guardrail rejected function tool call: {result.final_output}\n")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}\n")
|
||||
|
||||
try:
|
||||
# Example 3: Output guardrail triggers - should raise exception for sensitive data
|
||||
print("3. Attempting to get user data (contains SSN). Execution blocked:")
|
||||
result = await Runner.run(agent, "Get the data for user ID user123")
|
||||
print(f"✅ Successful tool execution: {result.final_output}\n")
|
||||
except ToolOutputGuardrailTripwireTriggered as e:
|
||||
print("🚨 Output guardrail triggered: Execution halted for sensitive data")
|
||||
print(f"Details: {e.output.output_info}\n")
|
||||
|
||||
try:
|
||||
# Example 4: Output guardrail triggers - reject returning function tool output but continue execution
|
||||
print("4. Rejecting function tool output containing phone numbers:")
|
||||
result = await Runner.run(agent, "Get contact info for user456")
|
||||
print(f"❌ Guardrail rejected function tool output: {result.final_output}\n")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
"""
|
||||
Example output:
|
||||
|
||||
=== Tool Guardrails Example ===
|
||||
|
||||
1. Normal email sending:
|
||||
✅ Successful tool execution: I've sent a welcome email to john@example.com with an appropriate subject and greeting message.
|
||||
|
||||
2. Attempting to send email with suspicious content:
|
||||
❌ Guardrail rejected function tool call: I'm unable to send the email as mentioning ACME Corp. is restricted.
|
||||
|
||||
3. Attempting to get user data (contains SSN). Execution blocked:
|
||||
🚨 Output guardrail triggered: Execution halted for sensitive data
|
||||
Details: {'blocked_pattern': 'SSN', 'tool': 'get_user_data'}
|
||||
|
||||
4. Rejecting function tool output containing sensitive data:
|
||||
❌ Guardrail rejected function tool output: I'm unable to retrieve the contact info for user456 because it contains restricted information.
|
||||
"""
|
||||
@@ -0,0 +1,36 @@
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
class Weather(BaseModel):
|
||||
city: str = Field(description="The city name")
|
||||
temperature_range: str = Field(description="The temperature range in Celsius")
|
||||
conditions: str = Field(description="The weather conditions")
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: Annotated[str, "The city to get the weather for"]) -> Weather:
|
||||
"""Get the current weather information for a specified city."""
|
||||
print("[debug] get_weather called")
|
||||
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Hello world",
|
||||
instructions="You are a helpful agent.",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(agent, input="What's the weather in Tokyo?")
|
||||
print(result.final_output)
|
||||
# The weather in Tokyo is sunny.
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,47 @@
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, Runner, Usage, function_tool
|
||||
|
||||
|
||||
class Weather(BaseModel):
|
||||
city: str
|
||||
temperature_range: str
|
||||
conditions: str
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> Weather:
|
||||
"""Get the current weather information for a specified city."""
|
||||
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
|
||||
|
||||
|
||||
def print_usage(usage: Usage) -> None:
|
||||
print("\n=== Usage ===")
|
||||
print(f"Input tokens: {usage.input_tokens}")
|
||||
print(f"Output tokens: {usage.output_tokens}")
|
||||
print(f"Total tokens: {usage.total_tokens}")
|
||||
print(f"Requests: {usage.requests}")
|
||||
for i, request in enumerate(usage.request_usage_entries):
|
||||
print(f" {i + 1}: {request.input_tokens} input, {request.output_tokens} output")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
agent = Agent(
|
||||
name="Usage Demo",
|
||||
instructions="You are a concise assistant. Use tools if needed.",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
|
||||
print("\nFinal output:")
|
||||
print(result.final_output)
|
||||
|
||||
# Access usage from the run context
|
||||
print_usage(result.context_wrapper.usage)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user