Skip to content

Commit a722452

Browse files
authored
Python: Added fixes and more examples for OpenAI and Azure chat client agents (#232)
* Added non-streaming and streaming examples * Updated resource management * Added examples with thread management * Added function tools examples * Small rename * Added code interpreter example * Updated example * Addressed PR feedback * Added more OpenAI and Azure chat completion examples * File renaming * Small fix in tests * Small revert * More renaming * Small fix
1 parent 14efb62 commit a722452

14 files changed

Lines changed: 694 additions & 89 deletions

‎python/packages/main/agent_framework/_agents.py‎

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,11 @@ async def run(
441441
kwargs: Additional keyword arguments for the agent.
442442
will only be passed to functions that are called.
443443
"""
444+
input_messages = self._normalize_messages(messages)
445+
444446
thread, thread_messages = await self._prepare_thread_and_messages(
445447
thread=thread,
446-
input_messages=messages,
448+
input_messages=input_messages,
447449
construct_thread=lambda: ChatClientAgentThread(),
448450
expected_type=ChatClientAgentThread,
449451
)
@@ -477,7 +479,7 @@ async def run(
477479

478480
# Only notify the thread of new messages if the chatResponse was successful
479481
# to avoid inconsistent messages state in the thread.
480-
await self._notify_thread_of_new_messages(thread, thread_messages)
482+
await self._notify_thread_of_new_messages(thread, input_messages)
481483
await self._notify_thread_of_new_messages(thread, response.messages)
482484

483485
return AgentRunResponse(
@@ -549,9 +551,11 @@ async def run_stream(
549551
will only be passed to functions that are called.
550552
551553
"""
554+
input_messages = self._normalize_messages(messages)
555+
552556
thread, thread_messages = await self._prepare_thread_and_messages(
553557
thread=thread,
554-
input_messages=messages,
558+
input_messages=input_messages,
555559
construct_thread=lambda: ChatClientAgentThread(),
556560
expected_type=ChatClientAgentThread,
557561
)
@@ -600,7 +604,7 @@ async def run_stream(
600604

601605
# Only notify the thread of new messages if the chatResponse was successful
602606
# to avoid inconsistent messages state in the thread.
603-
await self._notify_thread_of_new_messages(thread, thread_messages)
607+
await self._notify_thread_of_new_messages(thread, input_messages)
604608
await self._notify_thread_of_new_messages(thread, response.messages)
605609

606610
def get_new_thread(self) -> AgentThread:
@@ -639,15 +643,15 @@ async def _prepare_thread_and_messages(
639643
self,
640644
*,
641645
thread: AgentThread | None,
642-
input_messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None,
646+
input_messages: list[ChatMessage] | None = None,
643647
construct_thread: Callable[[], TThreadType],
644648
expected_type: type[TThreadType],
645649
) -> tuple[TThreadType, list[ChatMessage]]:
646650
"""Prepare thread and messages for agent execution.
647651
648652
Args:
649653
thread: The conversation thread, or None to create a new one.
650-
input_messages: Messages to process, can be string, ChatMessage, or sequence.
654+
input_messages: Messages to process.
651655
construct_thread: Factory function to create a new thread.
652656
expected_type: Expected thread type for validation.
653657
@@ -673,15 +677,24 @@ async def _prepare_thread_and_messages(
673677
if isinstance(thread, MessagesRetrievableThread):
674678
async for message in thread.get_messages():
675679
messages.append(message)
680+
676681
if input_messages is None:
677682
return thread, messages
678-
if isinstance(input_messages, str):
679-
messages.append(ChatMessage(role=ChatRole.USER, text=input_messages))
680-
return thread, messages
681-
if isinstance(input_messages, ChatMessage):
682-
messages.append(input_messages)
683-
return thread, messages
684-
messages.extend([
685-
ChatMessage(role=ChatRole.USER, text=msg) if isinstance(msg, str) else msg for msg in input_messages
686-
])
683+
684+
messages.extend(input_messages)
687685
return thread, messages
686+
687+
def _normalize_messages(
688+
self,
689+
messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None,
690+
) -> list[ChatMessage]:
691+
if messages is None:
692+
return []
693+
694+
if isinstance(messages, str):
695+
return [ChatMessage(role=ChatRole.USER, text=messages)]
696+
697+
if isinstance(messages, ChatMessage):
698+
return [messages]
699+
700+
return [ChatMessage(role=ChatRole.USER, text=msg) if isinstance(msg, str) else msg for msg in messages]

‎python/packages/main/tests/test_agents.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,12 @@ async def test_chat_client_agent_get_new_thread(chat_client: ChatClient) -> None
229229

230230
async def test_chat_client_agent_prepare_thread_and_messages(chat_client: ChatClient) -> None:
231231
agent = ChatClientAgent(chat_client=chat_client)
232-
message = ChatMessage(role=ChatRole.USER, contents=[TextContent("Hello")])
232+
message = ChatMessage(role=ChatRole.USER, text="Hello")
233233
thread = ChatClientAgentThread(messages=[message])
234234

235235
result_thread, result_messages = await agent._prepare_thread_and_messages( # type: ignore[reportPrivateUsage]
236236
thread=thread,
237-
input_messages="Test",
237+
input_messages=[ChatMessage(role=ChatRole.USER, text="Test")],
238238
construct_thread=lambda: ChatClientAgentThread(),
239239
expected_type=ChatClientAgentThread,
240240
)

‎python/samples/getting_started/agents/azure/azure_chat_client_agent.py‎

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
from random import randint
5+
from typing import Annotated
6+
7+
from agent_framework import ChatClientAgent
8+
from agent_framework.azure import AzureChatClient
9+
from pydantic import Field
10+
11+
12+
def get_weather(
13+
location: Annotated[str, Field(description="The location to get the weather for.")],
14+
) -> str:
15+
"""Get the weather for a given location."""
16+
conditions = ["sunny", "cloudy", "rainy", "stormy"]
17+
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
18+
19+
20+
async def non_streaming_example() -> None:
21+
"""Example of non-streaming response (get the complete result at once)."""
22+
print("=== Non-streaming Response Example ===")
23+
24+
# Create agent with Azure Chat Client
25+
agent = ChatClientAgent(
26+
chat_client=AzureChatClient(),
27+
instructions="You are a helpful weather agent.",
28+
tools=get_weather,
29+
)
30+
31+
query = "What's the weather like in Seattle?"
32+
print(f"User: {query}")
33+
result = await agent.run(query)
34+
print(f"Result: {result}\n")
35+
36+
37+
async def streaming_example() -> None:
38+
"""Example of streaming response (get results as they are generated)."""
39+
print("=== Streaming Response Example ===")
40+
41+
# Create agent with Azure Chat Client
42+
agent = ChatClientAgent(
43+
chat_client=AzureChatClient(),
44+
instructions="You are a helpful weather agent.",
45+
tools=get_weather,
46+
)
47+
48+
query = "What's the weather like in Portland?"
49+
print(f"User: {query}")
50+
print("Agent: ", end="", flush=True)
51+
async for chunk in agent.run_stream(query):
52+
if chunk.text:
53+
print(chunk.text, end="", flush=True)
54+
print("\n")
55+
56+
57+
async def main() -> None:
58+
print("=== Basic Azure Chat Client Agent Example ===")
59+
60+
await non_streaming_example()
61+
await streaming_example()
62+
63+
64+
if __name__ == "__main__":
65+
asyncio.run(main())
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
from datetime import datetime, timezone
5+
from random import randint
6+
from typing import Annotated
7+
8+
from agent_framework import ChatClientAgent
9+
from agent_framework.azure import AzureChatClient
10+
from pydantic import Field
11+
12+
13+
def get_weather(
14+
location: Annotated[str, Field(description="The location to get the weather for.")],
15+
) -> str:
16+
"""Get the weather for a given location."""
17+
conditions = ["sunny", "cloudy", "rainy", "stormy"]
18+
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
19+
20+
21+
def get_time() -> str:
22+
"""Get the current UTC time."""
23+
current_time = datetime.now(timezone.utc)
24+
return f"The current UTC time is {current_time.strftime('%Y-%m-%d %H:%M:%S')}."
25+
26+
27+
async def tools_on_agent_level() -> None:
28+
"""Example showing tools defined when creating the agent."""
29+
print("=== Tools Defined on Agent Level ===")
30+
31+
# Tools are provided when creating the agent
32+
# The agent can use these tools for any query during its lifetime
33+
agent = ChatClientAgent(
34+
chat_client=AzureChatClient(),
35+
instructions="You are a helpful assistant that can provide weather and time information.",
36+
tools=[get_weather, get_time], # Tools defined at agent creation
37+
)
38+
39+
# First query - agent can use weather tool
40+
query1 = "What's the weather like in New York?"
41+
print(f"User: {query1}")
42+
result1 = await agent.run(query1)
43+
print(f"Agent: {result1}\n")
44+
45+
# Second query - agent can use time tool
46+
query2 = "What's the current UTC time?"
47+
print(f"User: {query2}")
48+
result2 = await agent.run(query2)
49+
print(f"Agent: {result2}\n")
50+
51+
# Third query - agent can use both tools if needed
52+
query3 = "What's the weather in London and what's the current UTC time?"
53+
print(f"User: {query3}")
54+
result3 = await agent.run(query3)
55+
print(f"Agent: {result3}\n")
56+
57+
58+
async def tools_on_run_level() -> None:
59+
"""Example showing tools passed to the run method."""
60+
print("=== Tools Passed to Run Method ===")
61+
62+
# Agent created without tools
63+
agent = ChatClientAgent(
64+
chat_client=AzureChatClient(),
65+
instructions="You are a helpful assistant.",
66+
# No tools defined here
67+
)
68+
69+
# First query with weather tool
70+
query1 = "What's the weather like in Seattle?"
71+
print(f"User: {query1}")
72+
result1 = await agent.run(query1, tools=[get_weather]) # Tool passed to run method
73+
print(f"Agent: {result1}\n")
74+
75+
# Second query with time tool
76+
query2 = "What's the current UTC time?"
77+
print(f"User: {query2}")
78+
result2 = await agent.run(query2, tools=[get_time]) # Different tool for this query
79+
print(f"Agent: {result2}\n")
80+
81+
# Third query with multiple tools
82+
query3 = "What's the weather in Chicago and what's the current UTC time?"
83+
print(f"User: {query3}")
84+
result3 = await agent.run(query3, tools=[get_weather, get_time]) # Multiple tools
85+
print(f"Agent: {result3}\n")
86+
87+
88+
async def mixed_tools_example() -> None:
89+
"""Example showing both agent-level tools and run-method tools."""
90+
print("=== Mixed Tools Example (Agent + Run Method) ===")
91+
92+
# Agent created with some base tools
93+
agent = ChatClientAgent(
94+
chat_client=AzureChatClient(),
95+
instructions="You are a comprehensive assistant that can help with various information requests.",
96+
tools=[get_weather], # Base tool available for all queries
97+
)
98+
99+
# Query using both agent tool and additional run-method tools
100+
query = "What's the weather in Denver and what's the current UTC time?"
101+
print(f"User: {query}")
102+
103+
# Agent has access to get_weather (from creation) + additional tools from run method
104+
result = await agent.run(
105+
query,
106+
tools=[get_time], # Additional tools for this specific query
107+
)
108+
print(f"Agent: {result}\n")
109+
110+
111+
async def main() -> None:
112+
print("=== Azure Chat Client Agent with Function Tools Examples ===\n")
113+
114+
await tools_on_agent_level()
115+
await tools_on_run_level()
116+
await mixed_tools_example()
117+
118+
119+
if __name__ == "__main__":
120+
asyncio.run(main())

0 commit comments

Comments
 (0)