mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: OpenAI Responses Agent - threads, code interpreter, bug fixes and examples (#242)
* Added basic example with small fix * Added example with function tools * Added example with thread management * Small renaming * Added example with code interpreter
This commit is contained in:
committed by
GitHub
Unverified
parent
190035bb69
commit
c794892d79
@@ -13,6 +13,7 @@ else:
|
||||
|
||||
from openai import AsyncOpenAI, AsyncStream
|
||||
from openai.types.responses.response import Response as OpenAIResponse
|
||||
from openai.types.responses.response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall
|
||||
from openai.types.responses.response_completed_event import ResponseCompletedEvent
|
||||
from openai.types.responses.response_content_part_added_event import ResponseContentPartAddedEvent
|
||||
from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
|
||||
@@ -27,6 +28,7 @@ from openai.types.responses.response_usage import ResponseUsage
|
||||
from pydantic import BaseModel, SecretStr, ValidationError
|
||||
|
||||
from .._clients import ChatClientBase, use_tool_calling
|
||||
from .._tools import HostedCodeInterpreterTool
|
||||
from .._types import (
|
||||
AIContents,
|
||||
AITool,
|
||||
@@ -189,8 +191,9 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
timeout=timeout,
|
||||
)
|
||||
filtered_options.update(additional_properties or {})
|
||||
chat_options = ChatOptions(
|
||||
ai_model_id=model,
|
||||
return await super().get_response(
|
||||
messages=messages,
|
||||
model=model,
|
||||
max_tokens=max_tokens,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
@@ -198,13 +201,9 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
temperature=temperature,
|
||||
top_p=top_p,
|
||||
tool_choice=tool_choice,
|
||||
tools=tools, # type: ignore
|
||||
tools=tools,
|
||||
user=user,
|
||||
additional_properties=filtered_options,
|
||||
)
|
||||
return await super().get_response(
|
||||
messages=messages,
|
||||
chat_options=chat_options,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -282,8 +281,9 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
timeout=timeout,
|
||||
)
|
||||
filtered_options.update(additional_properties or {})
|
||||
chat_options = ChatOptions(
|
||||
ai_model_id=model,
|
||||
async for update in super().get_streaming_response(
|
||||
messages=messages,
|
||||
model=model,
|
||||
max_tokens=max_tokens,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
@@ -291,13 +291,9 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
temperature=temperature,
|
||||
top_p=top_p,
|
||||
tool_choice=tool_choice,
|
||||
tools=tools, # type: ignore
|
||||
tools=tools,
|
||||
user=user,
|
||||
additional_properties=filtered_options,
|
||||
)
|
||||
async for update in super().get_streaming_response(
|
||||
messages=messages,
|
||||
chat_options=chat_options,
|
||||
**kwargs,
|
||||
):
|
||||
yield update
|
||||
@@ -307,6 +303,8 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
for tool in tools:
|
||||
if isinstance(tool, AITool):
|
||||
# TODO(peterychang): Support AITools
|
||||
if isinstance(tool, HostedCodeInterpreterTool):
|
||||
response_tools.append({"type": "code_interpreter", "container": {"type": "auto"}})
|
||||
continue
|
||||
if "function" not in tool:
|
||||
response_tools.append(tool if isinstance(tool, dict) else dict(tool))
|
||||
@@ -337,7 +335,7 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
})
|
||||
response = await self._send_request(chat_options, messages=self._prepare_chat_history_for_request(messages))
|
||||
assert isinstance(response, OpenAIResponse) # nosec # noqa: S101
|
||||
return next(self._create_response_content(response, item) for item in response.output)
|
||||
return next(self._create_response_content(response, item, store=chat_options.store) for item in response.output)
|
||||
|
||||
async def _inner_get_streaming_response(
|
||||
self,
|
||||
@@ -357,12 +355,14 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
if not isinstance(response, AsyncStream):
|
||||
raise ServiceInvalidResponseError("Expected an AsyncStream[ResponseStreamEvent] response.")
|
||||
async for chunk in response:
|
||||
update = self._create_streaming_response_content(chunk) # type: ignore
|
||||
update = self._create_streaming_response_content(chunk, store=chat_options.store) # type: ignore
|
||||
if not update:
|
||||
continue
|
||||
yield update
|
||||
|
||||
def _create_response_content(self, response: OpenAIResponse, item: ResponseOutputItem) -> "ChatResponse":
|
||||
def _create_response_content(
|
||||
self, response: OpenAIResponse, item: ResponseOutputItem, store: bool | None
|
||||
) -> "ChatResponse":
|
||||
"""Create a chat message content object from a choice."""
|
||||
items: MutableSequence[ChatMessage] = []
|
||||
metadata: dict[str, Any] = response.metadata or {}
|
||||
@@ -377,8 +377,11 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
metadata.update(self._get_metadata_from_response(content))
|
||||
elif isinstance(content, ResponseOutputRefusal):
|
||||
items.append(ChatMessage(role=item.role, text=content.refusal))
|
||||
if isinstance(item, ResponseCodeInterpreterToolCall):
|
||||
items.append(ChatMessage(role=ChatRole.ASSISTANT, text=response.output_text))
|
||||
return ChatResponse(
|
||||
response_id=response.id,
|
||||
conversation_id=response.id if store is True else None,
|
||||
created_at=datetime.fromtimestamp(response.created_at).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
usage_details=self._usage_details_from_openai(response.usage) if response.usage else None,
|
||||
messages=items,
|
||||
@@ -388,12 +391,12 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
)
|
||||
|
||||
def _create_streaming_response_content(
|
||||
self,
|
||||
event: OpenAIResponseStreamEvent,
|
||||
self, event: OpenAIResponseStreamEvent, store: bool | None
|
||||
) -> ChatResponseUpdate | None:
|
||||
"""Create a streaming chat message content object from a choice."""
|
||||
metadata: dict[str, Any] = {}
|
||||
items: list[AIContents] = []
|
||||
conversation_id: str | None = None
|
||||
# TODO(peterychang): Add support for other content types
|
||||
if isinstance(event, ResponseContentPartAddedEvent):
|
||||
if isinstance(event.part, ResponseOutputText):
|
||||
@@ -405,6 +408,7 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
items.append(TextContent(text=event.delta))
|
||||
metadata.update(self._get_metadata_from_response(event))
|
||||
elif isinstance(event, ResponseCompletedEvent):
|
||||
conversation_id = event.response.id if store is True else None
|
||||
# Tool calls are available in the completed event
|
||||
if parsed_tool_calls := [tool for tool in self._get_tool_calls_from_response(event.response)]:
|
||||
items.extend(parsed_tool_calls)
|
||||
@@ -412,6 +416,7 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
return None
|
||||
return ChatResponseUpdate(
|
||||
contents=items,
|
||||
conversation_id=conversation_id,
|
||||
role=ChatRole.ASSISTANT,
|
||||
ai_model_id=self.ai_model_id,
|
||||
additional_properties=metadata,
|
||||
@@ -455,21 +460,22 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
match content:
|
||||
case FunctionResultContent():
|
||||
new_args: dict[str, Any] = {}
|
||||
new_args.update(self._openai_content_parser(content, tool_id_to_call_id))
|
||||
new_args.update(self._openai_content_parser(message.role, content, tool_id_to_call_id))
|
||||
all_messages.append(new_args)
|
||||
case FunctionCallContent():
|
||||
function_call = self._openai_content_parser(content, tool_id_to_call_id)
|
||||
function_call = self._openai_content_parser(message.role, content, tool_id_to_call_id)
|
||||
all_messages.append(function_call) # type: ignore
|
||||
case _:
|
||||
if "content" not in args:
|
||||
args["content"] = []
|
||||
args["content"].append(self._openai_content_parser(content, tool_id_to_call_id)) # type: ignore
|
||||
args["content"].append(self._openai_content_parser(message.role, content, tool_id_to_call_id)) # type: ignore
|
||||
if "content" in args or "tool_calls" in args:
|
||||
all_messages.append(args)
|
||||
return all_messages
|
||||
|
||||
def _openai_content_parser(
|
||||
self,
|
||||
role: ChatRole,
|
||||
content: AIContents,
|
||||
tool_id_to_call_id: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
@@ -492,19 +498,14 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
}
|
||||
case TextContent():
|
||||
return {
|
||||
"type": "input_text",
|
||||
"type": "output_text" if role == ChatRole.ASSISTANT else "input_text",
|
||||
"text": content.text,
|
||||
}
|
||||
# TODO(peterychang): We'll probably need to specialize the other content types as well
|
||||
case _:
|
||||
return content.model_dump(exclude_none=True)
|
||||
|
||||
def _prepare_chat_history_for_request(
|
||||
self,
|
||||
chat_messages: Sequence[ChatMessage],
|
||||
role_key: str = "role",
|
||||
content_key: str = "content",
|
||||
) -> list[dict[str, Any]]:
|
||||
def _prepare_chat_history_for_request(self, chat_messages: Sequence[ChatMessage]) -> list[dict[str, Any]]:
|
||||
"""Prepare the chat history for a request.
|
||||
|
||||
Allowing customization of the key names for role/author, and optionally overriding the role.
|
||||
@@ -517,8 +518,6 @@ class OpenAIResponsesClient(OpenAIConfigBase, ChatClientBase, OpenAIHandler):
|
||||
|
||||
Args:
|
||||
chat_messages: The chat history to prepare.
|
||||
role_key: The key name for the role/author.
|
||||
content_key: The key name for the content/message.
|
||||
|
||||
Returns:
|
||||
prepared_chat_history (Any): The prepared chat history for a request.
|
||||
|
||||
@@ -238,6 +238,11 @@ class OpenAIHandler(AFBaseModel, ABC):
|
||||
**options_dict,
|
||||
text_format=resp_format,
|
||||
)
|
||||
if "store" not in options_dict:
|
||||
options_dict["store"] = False
|
||||
if "conversation_id" in options_dict:
|
||||
options_dict["previous_response_id"] = options_dict["conversation_id"]
|
||||
options_dict.pop("conversation_id")
|
||||
return await self.client.responses.create(**options_dict) # type: ignore
|
||||
except BadRequestError as ex:
|
||||
if ex.code == "content_filter":
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ async def example_with_thread_persistence() -> None:
|
||||
print(f"Agent: {result1.text}")
|
||||
|
||||
# Second conversation using the same thread - maintains context
|
||||
query2 = "How about comparing it to London?"
|
||||
query2 = "How about London?"
|
||||
print(f"\nUser: {query2}")
|
||||
result2 = await agent.run(query2, thread=thread)
|
||||
print(f"Agent: {result2.text}")
|
||||
|
||||
@@ -33,7 +33,7 @@ def get_code_interpreter_chunk(chunk: AgentRunResponseUpdate) -> str | None:
|
||||
|
||||
async def main() -> None:
|
||||
"""Example showing how to use the HostedCodeInterpreterTool with Foundry."""
|
||||
print("=== Foundry Chat Client with Code Interpreter Example ===")
|
||||
print("=== Foundry Agent with Code Interpreter Example ===")
|
||||
|
||||
async with ChatClientAgent(
|
||||
chat_client=FoundryChatClient(),
|
||||
|
||||
@@ -60,7 +60,7 @@ async def example_with_thread_persistence() -> None:
|
||||
print(f"Agent: {result1.text}")
|
||||
|
||||
# Second conversation using the same thread - maintains context
|
||||
query2 = "How about comparing it to London?"
|
||||
query2 = "How about London?"
|
||||
print(f"\nUser: {query2}")
|
||||
result2 = await agent.run(query2, thread=thread)
|
||||
print(f"Agent: {result2.text}")
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ async def example_with_thread_persistence() -> None:
|
||||
print(f"Agent: {result1.text}")
|
||||
|
||||
# Second conversation using the same thread - maintains context
|
||||
query2 = "How about comparing it to London?"
|
||||
query2 = "How about London?"
|
||||
print(f"\nUser: {query2}")
|
||||
result2 = await agent.run(query2, thread=thread)
|
||||
print(f"Agent: {result2.text}")
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def non_streaming_example() -> None:
|
||||
"""Example of non-streaming response (get the complete result at once)."""
|
||||
print("=== Non-streaming Response Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
|
||||
query = "What's the weather like in Seattle?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Result: {result}\n")
|
||||
|
||||
|
||||
async def streaming_example() -> None:
|
||||
"""Example of streaming response (get results as they are generated)."""
|
||||
print("=== Streaming Response Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Basic OpenAI Responses Client Agent Example ===")
|
||||
|
||||
await non_streaming_example()
|
||||
await streaming_example()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatClientAgent, HostedCodeInterpreterTool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from openai.types.responses.response import Response as OpenAIResponse
|
||||
from openai.types.responses.response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Example showing how to use the HostedCodeInterpreterTool with OpenAI Responses."""
|
||||
print("=== OpenAI Responses Agent with Code Interpreter Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
)
|
||||
|
||||
query = "What is current datetime?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Result: {result}\n")
|
||||
|
||||
if (
|
||||
isinstance(result.raw_representation, OpenAIResponse)
|
||||
and len(result.raw_representation.output) > 0
|
||||
and isinstance(result.raw_representation.output[0], ResponseCodeInterpreterToolCall)
|
||||
):
|
||||
generated_code = result.raw_representation.output[0].code
|
||||
|
||||
print(f"Generated code:\n{generated_code}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
current_time = datetime.now(timezone.utc)
|
||||
return f"The current UTC time is {current_time.strftime('%Y-%m-%d %H:%M:%S')}."
|
||||
|
||||
|
||||
async def tools_on_agent_level() -> None:
|
||||
"""Example showing tools defined when creating the agent."""
|
||||
print("=== Tools Defined on Agent Level ===")
|
||||
|
||||
# Tools are provided when creating the agent
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
)
|
||||
|
||||
# First query - agent can use weather tool
|
||||
query1 = "What's the weather like in New York?"
|
||||
print(f"User: {query1}")
|
||||
result1 = await agent.run(query1)
|
||||
print(f"Agent: {result1}\n")
|
||||
|
||||
# Second query - agent can use time tool
|
||||
query2 = "What's the current UTC time?"
|
||||
print(f"User: {query2}")
|
||||
result2 = await agent.run(query2)
|
||||
print(f"Agent: {result2}\n")
|
||||
|
||||
# Third query - agent can use both tools if needed
|
||||
query3 = "What's the weather in London and what's the current UTC time?"
|
||||
print(f"User: {query3}")
|
||||
result3 = await agent.run(query3)
|
||||
print(f"Agent: {result3}\n")
|
||||
|
||||
|
||||
async def tools_on_run_level() -> None:
|
||||
"""Example showing tools passed to the run method."""
|
||||
print("=== Tools Passed to Run Method ===")
|
||||
|
||||
# Agent created without tools
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
)
|
||||
|
||||
# First query with weather tool
|
||||
query1 = "What's the weather like in Seattle?"
|
||||
print(f"User: {query1}")
|
||||
result1 = await agent.run(query1, tools=[get_weather]) # Tool passed to run method
|
||||
print(f"Agent: {result1}\n")
|
||||
|
||||
# Second query with time tool
|
||||
query2 = "What's the current UTC time?"
|
||||
print(f"User: {query2}")
|
||||
result2 = await agent.run(query2, tools=[get_time]) # Different tool for this query
|
||||
print(f"Agent: {result2}\n")
|
||||
|
||||
# Third query with multiple tools
|
||||
query3 = "What's the weather in Chicago and what's the current UTC time?"
|
||||
print(f"User: {query3}")
|
||||
result3 = await agent.run(query3, tools=[get_weather, get_time]) # Multiple tools
|
||||
print(f"Agent: {result3}\n")
|
||||
|
||||
|
||||
async def mixed_tools_example() -> None:
|
||||
"""Example showing both agent-level tools and run-method tools."""
|
||||
print("=== Mixed Tools Example (Agent + Run Method) ===")
|
||||
|
||||
# Agent created with some base tools
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
)
|
||||
|
||||
# Query using both agent tool and additional run-method tools
|
||||
query = "What's the weather in Denver and what's the current UTC time?"
|
||||
print(f"User: {query}")
|
||||
|
||||
# Agent has access to get_weather (from creation) + additional tools from run method
|
||||
result = await agent.run(
|
||||
query,
|
||||
tools=[get_time], # Additional tools for this specific query
|
||||
)
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== OpenAI Responses Client Agent with Function Tools Examples ===\n")
|
||||
|
||||
await tools_on_agent_level()
|
||||
await tools_on_run_level()
|
||||
await mixed_tools_example()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent, ChatClientAgentThread
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def example_with_automatic_thread_creation() -> None:
|
||||
"""Example showing automatic thread creation."""
|
||||
print("=== Automatic Thread Creation Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
|
||||
# First conversation - no thread provided, will be created automatically
|
||||
query1 = "What's the weather like in Seattle?"
|
||||
print(f"User: {query1}")
|
||||
result1 = await agent.run(query1)
|
||||
print(f"Agent: {result1.text}")
|
||||
|
||||
# Second conversation - still no thread provided, will create another new thread
|
||||
query2 = "What was the last city I asked about?"
|
||||
print(f"\nUser: {query2}")
|
||||
result2 = await agent.run(query2)
|
||||
print(f"Agent: {result2.text}")
|
||||
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
|
||||
|
||||
|
||||
async def example_with_thread_persistence_in_memory() -> None:
|
||||
"""
|
||||
Example showing thread persistence across multiple conversations.
|
||||
In this example, messages are stored in-memory.
|
||||
"""
|
||||
print("=== Thread Persistence Example (In-Memory) ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
|
||||
# Create a new thread that will be reused
|
||||
thread = agent.get_new_thread()
|
||||
assert isinstance(thread, ChatClientAgentThread)
|
||||
|
||||
# First conversation
|
||||
query1 = "What's the weather like in Tokyo?"
|
||||
print(f"User: {query1}")
|
||||
result1 = await agent.run(query1, thread=thread)
|
||||
print(f"Agent: {result1.text}")
|
||||
print(f"Thread contains {len(thread.chat_messages or [])} messages in-memory.")
|
||||
|
||||
# Second conversation using the same thread - maintains context
|
||||
query2 = "How about London?"
|
||||
print(f"\nUser: {query2}")
|
||||
result2 = await agent.run(query2, thread=thread)
|
||||
print(f"Agent: {result2.text}")
|
||||
print(f"Thread contains {len(thread.chat_messages or [])} messages in-memory.")
|
||||
|
||||
# Third conversation - agent should remember both previous cities
|
||||
query3 = "Which of the cities I asked about has better weather?"
|
||||
print(f"\nUser: {query3}")
|
||||
result3 = await agent.run(query3, thread=thread)
|
||||
print(f"Agent: {result3.text}")
|
||||
print(f"Thread contains {len(thread.chat_messages or [])} messages in-memory.")
|
||||
print("Note: The agent remembers context from previous messages in the same thread.\n")
|
||||
|
||||
|
||||
async def example_with_existing_thread_id() -> None:
|
||||
"""
|
||||
Example showing how to work with an existing thread ID from the service.
|
||||
In this example, messages are stored on the server using OpenAI conversation state.
|
||||
"""
|
||||
print("=== Existing Thread ID Example ===")
|
||||
|
||||
# First, create a conversation and capture the thread ID
|
||||
existing_thread_id = None
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
|
||||
# Start a conversation and get the thread ID
|
||||
thread = agent.get_new_thread()
|
||||
assert isinstance(thread, ChatClientAgentThread)
|
||||
|
||||
query1 = "What's the weather in Paris?"
|
||||
print(f"User: {query1}")
|
||||
# Enable OpenAI conversation state by setting `store` parameter to True
|
||||
result1 = await agent.run(query1, thread=thread, store=True)
|
||||
print(f"Agent: {result1.text}")
|
||||
print(f"Thread contains {len(thread.chat_messages or [])} messages in-memory.")
|
||||
|
||||
# The thread ID is set after the first response
|
||||
existing_thread_id = thread.id
|
||||
print(f"Thread ID: {existing_thread_id}")
|
||||
|
||||
if existing_thread_id:
|
||||
print("\n--- Continuing with the same thread ID in a new agent instance ---")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
|
||||
# Create a thread with the existing ID
|
||||
thread = ChatClientAgentThread(id=existing_thread_id)
|
||||
|
||||
query2 = "What was the last city I asked about?"
|
||||
print(f"User: {query2}")
|
||||
result2 = await agent.run(query2, thread=thread, store=True)
|
||||
print(f"Agent: {result2.text}")
|
||||
print(f"Thread contains {len(thread.chat_messages or [])} messages in-memory.")
|
||||
print("Note: The agent continues the conversation from the previous thread.\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== OpenAI Response Client Agent Thread Management Examples ===\n")
|
||||
|
||||
await example_with_automatic_thread_creation()
|
||||
await example_with_thread_persistence_in_memory()
|
||||
await example_with_existing_thread_id()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user