Skip to content

Commit a08f0fe

Browse files
authored
Create an example of connected agent tool with Azure function (#41840)
1 parent 417602b commit a08f0fe

File tree

2 files changed

+230
-5
lines changed

2 files changed

+230
-5
lines changed

sdk/ai/azure-ai-agents/README.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ To report an issue with the client library, or request additional features, plea
3737
- [Azure Function Call](#create-agent-with-azure-function-call)
3838
- [OpenAPI](#create-agent-with-openapi)
3939
- [Fabric data](#create-an-agent-with-fabric)
40+
- [Connected agents](#create-an-agent-using-another-agents)
4041
- [Deep Research](#create-agent-with-deep-research)
4142
- [Create thread](#create-thread) with
4243
- [Tool resource](#create-thread-with-tool-resource)
@@ -801,6 +802,178 @@ with project_client:
801802

802803
<!-- END SNIPPET -->
803804

805+
### Create an Agent using another agents
806+
807+
We can use `ConnectedAgentTool` to call specialized agents. In the example below we will create two agents, one is returning the Microsoft stock price and another returns weather. Note that the `ConnectedAgentTool` does not support local functions, we will use Azure function to return weather. The code of that function is given below; please see [Azure Function Call](#create-agent-with-azure-function-call) section for the instructions on how to deploy Azure Function.
808+
809+
```python
810+
import azure.functions as func
811+
import datetime
812+
import json
813+
import logging
814+
815+
app = func.FunctionApp()
816+
817+
@app.function_name(name="GetWeather")
818+
@app.queue_trigger(
819+
arg_name="arguments",
820+
queue_name="weather-input",
821+
connection="AzureWebJobsStorage")
822+
@app.queue_output(
823+
arg_name="outputQueue",
824+
queue_name="weather-output",
825+
connection="AzureWebJobsStorage")
826+
def foo(arguments: func.QueueMessage, outputQueue: func.Out[str]) -> None:
827+
"""
828+
The function, answering question about weather.
829+
830+
:param arguments: The arguments, containing json serialized request.
831+
:param outputQueue: The output queue to write messages to.
832+
"""
833+
834+
parsed_args = json.loads(arguments.get_body().decode('utf-8'))
835+
location = parsed_args.get("Location")
836+
try:
837+
response = {
838+
"Value": "60 degrees and cloudy" if location == "Seattle" else "10 degrees and sunny",
839+
"CorrelationId": parsed_args['CorrelationId']
840+
}
841+
outputQueue.set(json.dumps(response))
842+
logging.info(f'The function returns the following message: {json.dumps(response)}')
843+
except Exception as e:
844+
logging.error(f"Error processing message: {e}")
845+
raise
846+
```
847+
We will define two agents that has descriptions, explaining when they need to be called.
848+
849+
<!-- SNIPPET:sample_agents_multiple_connected_agents.create_two_toy_agents -->
850+
851+
```python
852+
connected_agent_name = "stock_price_bot"
853+
weather_agent_name = "weather_bot"
854+
855+
stock_price_agent = agents_client.create_agent(
856+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
857+
name=connected_agent_name,
858+
instructions=(
859+
"Your job is to get the stock price of a company. If asked for the Microsoft stock price, always return $350."
860+
),
861+
)
862+
863+
azure_function_tool = AzureFunctionTool(
864+
name="GetWeather",
865+
description="Get answers from the weather bot.",
866+
parameters={
867+
"type": "object",
868+
"properties": {
869+
"Location": {"type": "string", "description": "The location to get the weather for."},
870+
},
871+
},
872+
input_queue=AzureFunctionStorageQueue(
873+
queue_name="weather-input",
874+
storage_service_endpoint=storage_service_endpoint,
875+
),
876+
output_queue=AzureFunctionStorageQueue(
877+
queue_name="weather-output",
878+
storage_service_endpoint=storage_service_endpoint,
879+
),
880+
)
881+
882+
weather_agent = agents_client.create_agent(
883+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
884+
name=weather_agent_name,
885+
instructions=(
886+
"Your job is to get the weather for a given location. "
887+
"Use the provided function to get the weather in the given location."
888+
),
889+
tools=azure_function_tool.definitions,
890+
)
891+
892+
# Initialize Connected Agent tools with the agent id, name, and description
893+
connected_agent = ConnectedAgentTool(
894+
id=stock_price_agent.id, name=connected_agent_name, description="Gets the stock price of a company"
895+
)
896+
connected_weather_agent = ConnectedAgentTool(
897+
id=weather_agent.id, name=weather_agent_name, description="Gets the weather for a given location"
898+
)
899+
```
900+
901+
<!-- END SNIPPET -->
902+
903+
Add the `ConnectedAgentTool`-s to main agent.
904+
905+
<!-- SNIPPET:sample_agents_multiple_connected_agents.create_agent_with_connected_agent_tool -->
906+
907+
```python
908+
# Create agent with the Connected Agent tool and process assistant run
909+
agent = agents_client.create_agent(
910+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
911+
name="my-assistant",
912+
instructions="You are a helpful assistant, and use the connected agents to get stock prices and weather.",
913+
tools=[
914+
connected_agent.definitions[0],
915+
connected_weather_agent.definitions[0],
916+
],
917+
)
918+
```
919+
920+
<!-- END SNIPPET -->
921+
922+
Create thread and run.
923+
924+
<!-- SNIPPET:sample_agents_multiple_connected_agents.run_agent_with_connected_agent_tool -->
925+
926+
```python
927+
# Create thread for communication
928+
thread = agents_client.threads.create()
929+
print(f"Created thread, ID: {thread.id}")
930+
931+
# Create message to thread
932+
message = agents_client.messages.create(
933+
thread_id=thread.id,
934+
role=MessageRole.USER,
935+
content="What is the stock price of Microsoft and the weather in Seattle?",
936+
)
937+
print(f"Created message, ID: {message.id}")
938+
939+
# Create and process Agent run in thread with tools
940+
run = agents_client.runs.create_and_process(thread_id=thread.id, agent_id=agent.id)
941+
print(f"Run finished with status: {run.status}")
942+
```
943+
944+
<!-- END SNIPPET -->
945+
946+
To understand what calls were made by the main agent to the connected ones, we will list run steps.
947+
948+
<!-- SNIPPET:sample_agents_multiple_connected_agents.list_tool_calls -->
949+
950+
```python
951+
for run_step in agents_client.run_steps.list(thread_id=thread.id, run_id=run.id, order=ListSortOrder.ASCENDING):
952+
if isinstance(run_step.step_details, RunStepToolCallDetails):
953+
for tool_call in run_step.step_details.tool_calls:
954+
print(f"\tAgent: {tool_call._data['connected_agent']['name']} "
955+
f"query: {tool_call._data['connected_agent']['arguments']} ",
956+
f"output: {tool_call._data['connected_agent']['output']}")
957+
```
958+
959+
<!-- END SNIPPET -->
960+
961+
The messages contain references, marked by unicode opening and closing brackets, which cannot be printed by python `print` command. To fix this issue we will replace them by ASCII brackets.
962+
963+
<!-- SNIPPET:sample_agents_multiple_connected_agents.list_messages -->
964+
965+
```python
966+
# Fetch and log all messages
967+
messages = agents_client.messages.list(thread_id=thread.id, order=ListSortOrder.ASCENDING)
968+
for msg in messages:
969+
if msg.text_messages:
970+
last_text = msg.text_messages[-1]
971+
text = last_text.text.value.replace('\u3010', '[').replace('\u3011', ']')
972+
print(f"{msg.role}: {text}")
973+
```
974+
975+
<!-- END SNIPPET -->
976+
804977

805978
### Create Thread
806979

sdk/ai/azure-ai-agents/samples/agents_tools/sample_agents_multiple_connected_agents.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,35 @@
2121
page of your Azure AI Foundry portal.
2222
2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in
2323
the "Models + endpoints" tab in your Azure AI Foundry project.
24+
3) STORAGE_QUEUE_URI - the storage service queue endpoint, triggering Azure function.
25+
Please see Getting Started with Azure Functions page for more information on Azure Functions:
26+
https://learn.microsoft.com/azure/azure-functions/functions-get-started
2427
"""
2528

2629
import os
2730
from azure.ai.projects import AIProjectClient
28-
from azure.ai.agents.models import ConnectedAgentTool, MessageRole
31+
from azure.ai.agents.models import (
32+
AzureFunctionStorageQueue,
33+
AzureFunctionTool,
34+
ConnectedAgentTool,
35+
ListSortOrder,
36+
MessageRole,
37+
RunStepToolCallDetails,
38+
39+
)
2940
from azure.identity import DefaultAzureCredential
3041

3142

3243
project_client = AIProjectClient(
3344
endpoint=os.environ["PROJECT_ENDPOINT"],
3445
credential=DefaultAzureCredential(),
3546
)
47+
storage_service_endpoint = os.environ["STORAGE_QUEUE_URI"]
3648

3749
with project_client:
3850
agents_client = project_client.agents
3951

52+
# [START create_two_toy_agents]
4053
connected_agent_name = "stock_price_bot"
4154
weather_agent_name = "weather_bot"
4255

@@ -48,12 +61,33 @@
4861
),
4962
)
5063

64+
azure_function_tool = AzureFunctionTool(
65+
name="GetWeather",
66+
description="Get answers from the weather bot.",
67+
parameters={
68+
"type": "object",
69+
"properties": {
70+
"Location": {"type": "string", "description": "The location to get the weather for."},
71+
},
72+
},
73+
input_queue=AzureFunctionStorageQueue(
74+
queue_name="weather-input",
75+
storage_service_endpoint=storage_service_endpoint,
76+
),
77+
output_queue=AzureFunctionStorageQueue(
78+
queue_name="weather-output",
79+
storage_service_endpoint=storage_service_endpoint,
80+
),
81+
)
82+
5183
weather_agent = agents_client.create_agent(
5284
model=os.environ["MODEL_DEPLOYMENT_NAME"],
5385
name=weather_agent_name,
5486
instructions=(
55-
"Your job is to get the weather for a given location. If asked for the weather in Seattle, always return 60 degrees and cloudy."
87+
"Your job is to get the weather for a given location. "
88+
"Use the provided function to get the weather in the given location."
5689
),
90+
tools=azure_function_tool.definitions,
5791
)
5892

5993
# Initialize Connected Agent tools with the agent id, name, and description
@@ -63,7 +97,9 @@
6397
connected_weather_agent = ConnectedAgentTool(
6498
id=weather_agent.id, name=weather_agent_name, description="Gets the weather for a given location"
6599
)
100+
# [END create_two_toy_agents]
66101

102+
# [START create_agent_with_connected_agent_tool]
67103
# Create agent with the Connected Agent tool and process assistant run
68104
agent = agents_client.create_agent(
69105
model=os.environ["MODEL_DEPLOYMENT_NAME"],
@@ -78,6 +114,7 @@
78114

79115
print(f"Created agent, ID: {agent.id}")
80116

117+
# [START run_agent_with_connected_agent_tool]
81118
# Create thread for communication
82119
thread = agents_client.threads.create()
83120
print(f"Created thread, ID: {thread.id}")
@@ -93,6 +130,7 @@
93130
# Create and process Agent run in thread with tools
94131
run = agents_client.runs.create_and_process(thread_id=thread.id, agent_id=agent.id)
95132
print(f"Run finished with status: {run.status}")
133+
# [END run_agent_with_connected_agent_tool]
96134

97135
if run.status == "failed":
98136
print(f"Run failed: {run.last_error}")
@@ -108,10 +146,24 @@
108146
# Delete the connected Agent when done
109147
agents_client.delete_agent(weather_agent.id)
110148
print("Deleted weather agent")
111-
149+
150+
# [START list_tool_calls]
151+
for run_step in agents_client.run_steps.list(thread_id=thread.id, run_id=run.id, order=ListSortOrder.ASCENDING):
152+
if isinstance(run_step.step_details, RunStepToolCallDetails):
153+
for tool_call in run_step.step_details.tool_calls:
154+
print(f"\tAgent: {tool_call._data['connected_agent']['name']} "
155+
f"query: {tool_call._data['connected_agent']['arguments']} ",
156+
f"output: {tool_call._data['connected_agent']['output']}")
157+
# [END list_tool_calls]
158+
159+
# [START list_messages]
112160
# Fetch and log all messages
113-
messages = agents_client.messages.list(thread_id=thread.id)
161+
messages = agents_client.messages.list(thread_id=thread.id, order=ListSortOrder.ASCENDING)
114162
for msg in messages:
115163
if msg.text_messages:
116164
last_text = msg.text_messages[-1]
117-
print(f"{msg.role}: {last_text.text.value}")
165+
text = last_text.text.value.replace('\u3010', '[').replace('\u3011', ']')
166+
print(f"{msg.role}: {text}")
167+
# [END list_messages]
168+
169+
agents_client.threads.delete(thread.id)

0 commit comments

Comments
 (0)