diff --git a/python/packages/main/agent_framework/_agents.py b/python/packages/main/agent_framework/_agents.py index e84905a357..30c98fc1e5 100644 --- a/python/packages/main/agent_framework/_agents.py +++ b/python/packages/main/agent_framework/_agents.py @@ -441,9 +441,11 @@ class ChatClientAgent(AgentBase): kwargs: Additional keyword arguments for the agent. will only be passed to functions that are called. """ + input_messages = self._normalize_messages(messages) + thread, thread_messages = await self._prepare_thread_and_messages( thread=thread, - input_messages=messages, + input_messages=input_messages, construct_thread=lambda: ChatClientAgentThread(), expected_type=ChatClientAgentThread, ) @@ -477,7 +479,7 @@ class ChatClientAgent(AgentBase): # Only notify the thread of new messages if the chatResponse was successful # to avoid inconsistent messages state in the thread. - await self._notify_thread_of_new_messages(thread, thread_messages) + await self._notify_thread_of_new_messages(thread, input_messages) await self._notify_thread_of_new_messages(thread, response.messages) return AgentRunResponse( @@ -549,9 +551,11 @@ class ChatClientAgent(AgentBase): will only be passed to functions that are called. """ + input_messages = self._normalize_messages(messages) + thread, thread_messages = await self._prepare_thread_and_messages( thread=thread, - input_messages=messages, + input_messages=input_messages, construct_thread=lambda: ChatClientAgentThread(), expected_type=ChatClientAgentThread, ) @@ -600,7 +604,7 @@ class ChatClientAgent(AgentBase): # Only notify the thread of new messages if the chatResponse was successful # to avoid inconsistent messages state in the thread. - await self._notify_thread_of_new_messages(thread, thread_messages) + await self._notify_thread_of_new_messages(thread, input_messages) await self._notify_thread_of_new_messages(thread, response.messages) def get_new_thread(self) -> AgentThread: @@ -639,7 +643,7 @@ class ChatClientAgent(AgentBase): self, *, thread: AgentThread | None, - input_messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None, + input_messages: list[ChatMessage] | None = None, construct_thread: Callable[[], TThreadType], expected_type: type[TThreadType], ) -> tuple[TThreadType, list[ChatMessage]]: @@ -647,7 +651,7 @@ class ChatClientAgent(AgentBase): Args: thread: The conversation thread, or None to create a new one. - input_messages: Messages to process, can be string, ChatMessage, or sequence. + input_messages: Messages to process. construct_thread: Factory function to create a new thread. expected_type: Expected thread type for validation. @@ -673,15 +677,24 @@ class ChatClientAgent(AgentBase): if isinstance(thread, MessagesRetrievableThread): async for message in thread.get_messages(): messages.append(message) + if input_messages is None: return thread, messages - if isinstance(input_messages, str): - messages.append(ChatMessage(role=ChatRole.USER, text=input_messages)) - return thread, messages - if isinstance(input_messages, ChatMessage): - messages.append(input_messages) - return thread, messages - messages.extend([ - ChatMessage(role=ChatRole.USER, text=msg) if isinstance(msg, str) else msg for msg in input_messages - ]) + + messages.extend(input_messages) return thread, messages + + def _normalize_messages( + self, + messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None, + ) -> list[ChatMessage]: + if messages is None: + return [] + + if isinstance(messages, str): + return [ChatMessage(role=ChatRole.USER, text=messages)] + + if isinstance(messages, ChatMessage): + return [messages] + + return [ChatMessage(role=ChatRole.USER, text=msg) if isinstance(msg, str) else msg for msg in messages] diff --git a/python/packages/main/tests/test_agents.py b/python/packages/main/tests/test_agents.py index b6023b668f..8bcb75a35a 100644 --- a/python/packages/main/tests/test_agents.py +++ b/python/packages/main/tests/test_agents.py @@ -229,12 +229,12 @@ async def test_chat_client_agent_get_new_thread(chat_client: ChatClient) -> None async def test_chat_client_agent_prepare_thread_and_messages(chat_client: ChatClient) -> None: agent = ChatClientAgent(chat_client=chat_client) - message = ChatMessage(role=ChatRole.USER, contents=[TextContent("Hello")]) + message = ChatMessage(role=ChatRole.USER, text="Hello") thread = ChatClientAgentThread(messages=[message]) result_thread, result_messages = await agent._prepare_thread_and_messages( # type: ignore[reportPrivateUsage] thread=thread, - input_messages="Test", + input_messages=[ChatMessage(role=ChatRole.USER, text="Test")], construct_thread=lambda: ChatClientAgentThread(), expected_type=ChatClientAgentThread, ) diff --git a/python/samples/getting_started/agents/azure/azure_chat_client_agent.py b/python/samples/getting_started/agents/azure/azure_chat_client_agent.py deleted file mode 100644 index b35689beb7..0000000000 --- a/python/samples/getting_started/agents/azure/azure_chat_client_agent.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -import asyncio -from random import randint -from typing import Annotated - -from agent_framework import ChatClientAgent -from agent_framework.azure import AzureChatClient -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 main() -> None: - instructions = "You are a helpful assistant, you can help the user with weather information." - agent = ChatClientAgent(AzureChatClient(), instructions=instructions, tools=get_weather) - print(str(await agent.run("What's the weather in Amsterdam?"))) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_basic.py b/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_basic.py new file mode 100644 index 0000000000..21a8a25b27 --- /dev/null +++ b/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_basic.py @@ -0,0 +1,65 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +from random import randint +from typing import Annotated + +from agent_framework import ChatClientAgent +from agent_framework.azure import AzureChatClient +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 ===") + + # Create agent with Azure Chat Client + agent = ChatClientAgent( + chat_client=AzureChatClient(), + 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 ===") + + # Create agent with Azure Chat Client + agent = ChatClientAgent( + chat_client=AzureChatClient(), + 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 Azure Chat Client Agent Example ===") + + await non_streaming_example() + await streaming_example() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_with_function_tools.py b/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_with_function_tools.py new file mode 100644 index 0000000000..7050c75f4e --- /dev/null +++ b/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_with_function_tools.py @@ -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.azure import AzureChatClient +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=AzureChatClient(), + 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=AzureChatClient(), + 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=AzureChatClient(), + 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("=== Azure Chat 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()) diff --git a/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_with_thread.py b/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_with_thread.py new file mode 100644 index 0000000000..3f85881486 --- /dev/null +++ b/python/samples/getting_started/agents/azure_chat_client/azure_chat_client_with_thread.py @@ -0,0 +1,139 @@ +# 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.azure import AzureChatClient +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 (service-managed thread).""" + print("=== Automatic Thread Creation Example ===") + + agent = ChatClientAgent( + chat_client=AzureChatClient(), + 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() -> None: + """Example showing thread persistence across multiple conversations.""" + print("=== Thread Persistence Example ===") + print("Using the same thread across multiple conversations to maintain context.\n") + + agent = ChatClientAgent( + chat_client=AzureChatClient(), + instructions="You are a helpful weather agent.", + tools=get_weather, + ) + + # Create a new thread that will be reused + thread = agent.get_new_thread() + + # 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}") + + # Second conversation using the same thread - maintains context + query2 = "How about comparing it to London?" + print(f"\nUser: {query2}") + result2 = await agent.run(query2, thread=thread) + print(f"Agent: {result2.text}") + + # 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("Note: The agent remembers context from previous messages in the same thread.\n") + + +async def example_with_existing_thread_messages() -> None: + """Example showing how to work with existing thread messages for Azure.""" + print("=== Existing Thread Messages Example ===") + + agent = ChatClientAgent( + chat_client=AzureChatClient(), + instructions="You are a helpful weather agent.", + tools=get_weather, + ) + + # Start a conversation and build up message history + thread = agent.get_new_thread() + assert isinstance(thread, ChatClientAgentThread) # Ensure we have the right type + + query1 = "What's the weather in Paris?" + print(f"User: {query1}") + result1 = await agent.run(query1, thread=thread) + print(f"Agent: {result1.text}") + + # The thread now contains the conversation history in memory + message_count = len(thread.chat_messages or []) + print(f"Thread contains {message_count} messages") + + print("\n--- Continuing with the same thread in a new agent instance ---") + + # Create a new agent instance but use the existing thread with its message history + new_agent = ChatClientAgent( + chat_client=AzureChatClient(), + instructions="You are a helpful weather agent.", + tools=get_weather, + ) + + # Use the same thread object which contains the conversation history + query2 = "What was the last city I asked about?" + print(f"User: {query2}") + result2 = await new_agent.run(query2, thread=thread) + print(f"Agent: {result2.text}") + print("Note: The agent continues the conversation using the local message history.\n") + + print("\n--- Alternative: Creating a new thread from existing messages ---") + + # You can also create a new thread from existing messages + existing_messages = thread.chat_messages or [] + new_thread = ChatClientAgentThread(messages=existing_messages) + + query3 = "How does the Paris weather compare to London?" + print(f"User: {query3}") + result3 = await new_agent.run(query3, thread=new_thread) + print(f"Agent: {result3.text}") + print("Note: This creates a new thread with the same conversation history.\n") + + +async def main() -> None: + print("=== Azure Chat Client Agent Thread Management Examples ===\n") + + await example_with_automatic_thread_creation() + await example_with_thread_persistence() + await example_with_existing_thread_messages() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/getting_started/agents/foundry/foundry_basic.py b/python/samples/getting_started/agents/foundry/foundry_basic.py index 5df4b8e089..bfcbed40ba 100644 --- a/python/samples/getting_started/agents/foundry/foundry_basic.py +++ b/python/samples/getting_started/agents/foundry/foundry_basic.py @@ -47,7 +47,7 @@ async def streaming_example() -> None: ) as agent: query = "What's the weather like in Portland?" print(f"User: {query}") - print("Assistant: ", end="", flush=True) + print("Agent: ", end="", flush=True) async for chunk in agent.run_stream(query): if chunk.text: print(chunk.text, end="", flush=True) @@ -55,7 +55,7 @@ async def streaming_example() -> None: async def main() -> None: - print("=== Basic Foundry Chat Client Example ===") + print("=== Basic Foundry Chat Client Agent Example ===") await non_streaming_example() await streaming_example() diff --git a/python/samples/getting_started/agents/foundry/foundry_with_code_interpreter.py b/python/samples/getting_started/agents/foundry/foundry_with_code_interpreter.py index 493990b45d..1c4fedb775 100644 --- a/python/samples/getting_started/agents/foundry/foundry_with_code_interpreter.py +++ b/python/samples/getting_started/agents/foundry/foundry_with_code_interpreter.py @@ -42,7 +42,7 @@ async def main() -> None: ) as agent: query = "What is current datetime?" print(f"User: {query}") - print("Assistant: ", end="", flush=True) + print("Agent: ", end="", flush=True) generated_code = "" async for chunk in agent.run_stream(query): if chunk.text: diff --git a/python/samples/getting_started/agents/foundry/foundry_with_function_tools.py b/python/samples/getting_started/agents/foundry/foundry_with_function_tools.py index 9583750d17..5d3fc42c2e 100644 --- a/python/samples/getting_started/agents/foundry/foundry_with_function_tools.py +++ b/python/samples/getting_started/agents/foundry/foundry_with_function_tools.py @@ -39,19 +39,19 @@ async def tools_on_agent_level() -> None: query1 = "What's the weather like in New York?" print(f"User: {query1}") result1 = await agent.run(query1) - print(f"Assistant: {result1}\n") + 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"Assistant: {result2}\n") + 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"Assistant: {result3}\n") + print(f"Agent: {result3}\n") async def tools_on_run_level() -> None: @@ -68,19 +68,19 @@ async def tools_on_run_level() -> None: 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"Assistant: {result1}\n") + 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"Assistant: {result2}\n") + 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"Assistant: {result3}\n") + print(f"Agent: {result3}\n") async def mixed_tools_example() -> None: @@ -102,7 +102,7 @@ async def mixed_tools_example() -> None: query, tools=[get_time], # Additional tools for this specific query ) - print(f"Assistant: {result}\n") + print(f"Agent: {result}\n") async def main() -> None: diff --git a/python/samples/getting_started/agents/foundry/foundry_with_thread.py b/python/samples/getting_started/agents/foundry/foundry_with_thread.py index 8bd0e591f1..a34a32a420 100644 --- a/python/samples/getting_started/agents/foundry/foundry_with_thread.py +++ b/python/samples/getting_started/agents/foundry/foundry_with_thread.py @@ -30,13 +30,13 @@ async def example_with_automatic_thread_creation() -> None: query1 = "What's the weather like in Seattle?" print(f"User: {query1}") result1 = await agent.run(query1) - print(f"Assistant: {result1.text}") + 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"Assistant: {result2.text}") + print(f"Agent: {result2.text}") print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n") @@ -47,7 +47,7 @@ async def example_with_thread_persistence() -> None: async with ChatClientAgent( chat_client=FoundryChatClient(), - instructions="You are a helpful weather agent. Remember previous cities asked about.", + instructions="You are a helpful weather agent.", tools=get_weather, ) as agent: # Create a new thread that will be reused @@ -57,19 +57,19 @@ async def example_with_thread_persistence() -> None: query1 = "What's the weather like in Tokyo?" print(f"User: {query1}") result1 = await agent.run(query1, thread=thread) - print(f"Assistant: {result1.text}") + print(f"Agent: {result1.text}") # Second conversation using the same thread - maintains context query2 = "How about comparing it to London?" print(f"\nUser: {query2}") result2 = await agent.run(query2, thread=thread) - print(f"Assistant: {result2.text}") + print(f"Agent: {result2.text}") # 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"Assistant: {result3.text}") + print(f"Agent: {result3.text}") print("Note: The agent remembers context from previous messages in the same thread.\n") @@ -91,7 +91,7 @@ async def example_with_existing_thread_id() -> None: query1 = "What's the weather in Paris?" print(f"User: {query1}") result1 = await agent.run(query1, thread=thread) - print(f"Assistant: {result1.text}") + print(f"Agent: {result1.text}") # The thread ID is set after the first response existing_thread_id = thread.id @@ -112,7 +112,7 @@ async def example_with_existing_thread_id() -> None: query2 = "What was the last city I asked about?" print(f"User: {query2}") result2 = await agent.run(query2, thread=thread) - print(f"Assistant: {result2.text}") + print(f"Agent: {result2.text}") print("Note: The agent continues the conversation from the previous thread.\n") diff --git a/python/samples/getting_started/agents/openai/openai_chat_client_agent.py b/python/samples/getting_started/agents/openai/openai_chat_client_agent.py deleted file mode 100644 index d5c7512d7c..0000000000 --- a/python/samples/getting_started/agents/openai/openai_chat_client_agent.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 OpenAIChatClient -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 main() -> None: - instructions = "You are a helpful assistant, you can help the user with weather information." - agent = ChatClientAgent(OpenAIChatClient(), instructions=instructions, tools=get_weather) - print(str(await agent.run("What's the weather in Amsterdam?"))) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_basic.py b/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_basic.py new file mode 100644 index 0000000000..01425caba2 --- /dev/null +++ b/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_basic.py @@ -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 OpenAIChatClient +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=OpenAIChatClient(), + 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=OpenAIChatClient(), + 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 Chat Client Agent Example ===") + + await non_streaming_example() + await streaming_example() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_with_function_tools.py b/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_with_function_tools.py new file mode 100644 index 0000000000..068f505c8e --- /dev/null +++ b/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_with_function_tools.py @@ -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 OpenAIChatClient +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=OpenAIChatClient(), + 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=OpenAIChatClient(), + 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=OpenAIChatClient(), + 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 Chat 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()) diff --git a/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_with_thread.py b/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_with_thread.py new file mode 100644 index 0000000000..92bd7e113c --- /dev/null +++ b/python/samples/getting_started/agents/openai_chat_client/openai_chat_client_with_thread.py @@ -0,0 +1,139 @@ +# 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 OpenAIChatClient +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 (service-managed thread).""" + print("=== Automatic Thread Creation Example ===") + + agent = ChatClientAgent( + chat_client=OpenAIChatClient(), + 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() -> None: + """Example showing thread persistence across multiple conversations.""" + print("=== Thread Persistence Example ===") + print("Using the same thread across multiple conversations to maintain context.\n") + + agent = ChatClientAgent( + chat_client=OpenAIChatClient(), + instructions="You are a helpful weather agent.", + tools=get_weather, + ) + + # Create a new thread that will be reused + thread = agent.get_new_thread() + + # 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}") + + # Second conversation using the same thread - maintains context + query2 = "How about comparing it to London?" + print(f"\nUser: {query2}") + result2 = await agent.run(query2, thread=thread) + print(f"Agent: {result2.text}") + + # 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("Note: The agent remembers context from previous messages in the same thread.\n") + + +async def example_with_existing_thread_messages() -> None: + """Example showing how to work with existing thread messages for OpenAI.""" + print("=== Existing Thread Messages Example ===") + + agent = ChatClientAgent( + chat_client=OpenAIChatClient(), + instructions="You are a helpful weather agent.", + tools=get_weather, + ) + + # Start a conversation and build up message history + thread = agent.get_new_thread() + assert isinstance(thread, ChatClientAgentThread) # Ensure we have the right type + + query1 = "What's the weather in Paris?" + print(f"User: {query1}") + result1 = await agent.run(query1, thread=thread) + print(f"Agent: {result1.text}") + + # The thread now contains the conversation history in memory + message_count = len(thread.chat_messages or []) + print(f"Thread contains {message_count} messages") + + print("\n--- Continuing with the same thread in a new agent instance ---") + + # Create a new agent instance but use the existing thread with its message history + new_agent = ChatClientAgent( + chat_client=OpenAIChatClient(), + instructions="You are a helpful weather agent.", + tools=get_weather, + ) + + # Use the same thread object which contains the conversation history + query2 = "What was the last city I asked about?" + print(f"User: {query2}") + result2 = await new_agent.run(query2, thread=thread) + print(f"Agent: {result2.text}") + print("Note: The agent continues the conversation using the local message history.\n") + + print("\n--- Alternative: Creating a new thread from existing messages ---") + + # You can also create a new thread from existing messages + existing_messages = thread.chat_messages or [] + new_thread = ChatClientAgentThread(messages=existing_messages) + + query3 = "How does the Paris weather compare to London?" + print(f"User: {query3}") + result3 = await new_agent.run(query3, thread=new_thread) + print(f"Agent: {result3.text}") + print("Note: This creates a new thread with the same conversation history.\n") + + +async def main() -> None: + print("=== OpenAI Chat Client Agent Thread Management Examples ===\n") + + await example_with_automatic_thread_creation() + await example_with_thread_persistence() + await example_with_existing_thread_messages() + + +if __name__ == "__main__": + asyncio.run(main())