Skip to content

Commit ceef6f4

Browse files
authored
Create LocalFunctionCallingWithStreaming.md
Cleaned up streaming code and made it like non-streaming sample.
1 parent f5c6b65 commit ceef6f4

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Sample using agents with functions and streaming in Azure.AI.Agents
2+
3+
In this example we are demonstrating how to use the local functions with the agents in streaming scenarios. The functions can be used to provide agent specific information in response to user question.
4+
5+
1. First we need to create agent client and read the environment variables that will be used in the next steps.
6+
```C# Snippet:AgentsFunctionsWithStreaming_CreateClient
7+
var projectEndpoint = new Uri(configuration["ProjectEndpoint"]);
8+
var modelDeploymentName = configuration["ModelDeploymentName"];
9+
PersistentAgentsClient client = new(projectEndpoint, new DefaultAzureCredential());
10+
```
11+
12+
2 Define three toy functions: `GetUserFavoriteCity`that always returns "Seattle, WA" and `GetCityNickname`, which will handle only "Seattle, WA" and will throw exception in response to other city names. The last function `GetWeatherAtLocation` returns weather at Seattle, WA. For each function we need to create `FunctionToolDefinition`, which defines function name, description and parameters.
13+
```C# Snippet:AgentsFunctionsWithStreaming_DefineFunctionTools
14+
// Example of a function that defines no parameters
15+
string GetUserFavoriteCity() => "Seattle, WA";
16+
FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
17+
// Example of a function with a single required parameter
18+
string GetCityNickname(string location) => location switch
19+
{
20+
"Seattle, WA" => "The Emerald City",
21+
_ => throw new NotImplementedException(),
22+
};
23+
FunctionToolDefinition getCityNicknameTool = new(
24+
name: "getCityNickname",
25+
description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
26+
parameters: BinaryData.FromObjectAsJson(
27+
new
28+
{
29+
Type = "object",
30+
Properties = new
31+
{
32+
Location = new
33+
{
34+
Type = "string",
35+
Description = "The city and state, e.g. San Francisco, CA",
36+
},
37+
},
38+
Required = new[] { "location" },
39+
},
40+
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
41+
// Example of a function with one required and one optional, enum parameter
42+
string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
43+
{
44+
"Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
45+
_ => throw new NotImplementedException()
46+
};
47+
FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
48+
name: "getCurrentWeatherAtLocation",
49+
description: "Gets the current weather at a provided location.",
50+
parameters: BinaryData.FromObjectAsJson(
51+
new
52+
{
53+
Type = "object",
54+
Properties = new
55+
{
56+
Location = new
57+
{
58+
Type = "string",
59+
Description = "The city and state, e.g. San Francisco, CA",
60+
},
61+
Unit = new
62+
{
63+
Type = "string",
64+
Enum = new[] { "c", "f" },
65+
},
66+
},
67+
Required = new[] { "location" },
68+
},
69+
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
70+
```
71+
72+
3. We will create the function `GetResolvedToolOutput`. It runs the abovementioned functions and wraps their outouts in `ToolOutput` object.
73+
```C# Snippet:AgentsFunctionsWithStreamingUpdateHandling
74+
ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
75+
{
76+
if (functionName == getUserFavoriteCityTool.Name)
77+
{
78+
return new ToolOutput(toolCallId, GetUserFavoriteCity());
79+
}
80+
using JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
81+
if (functionName == getCityNicknameTool.Name)
82+
{
83+
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
84+
return new ToolOutput(toolCallId, GetCityNickname(locationArgument));
85+
}
86+
if (functionName == getCurrentWeatherAtLocationTool.Name)
87+
{
88+
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
89+
if (argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unitElement))
90+
{
91+
string unitArgument = unitElement.GetString();
92+
return new ToolOutput(toolCallId, GetWeatherAtLocation(locationArgument, unitArgument));
93+
}
94+
return new ToolOutput(toolCallId, GetWeatherAtLocation(locationArgument));
95+
}
96+
return null;
97+
}
98+
```
99+
100+
4. Create agent with the `FunctionToolDefinitions` we have created in step 2.
101+
102+
Synchronous sample:
103+
```C# Snippet:AgentsFunctionsWithStreamingSync_CreateAgent
104+
PersistentAgent agent = client.CreateAgent(
105+
model: modelDeploymentName,
106+
name: "SDK Test Agent - Functions",
107+
instructions: "You are a weather bot. Use the provided functions to help answer questions. "
108+
+ "Customize your responses to the user's preferences as much as possible and use friendly "
109+
+ "nicknames for cities whenever possible.",
110+
tools: [getUserFavoriteCityTool, getCityNicknameTool, getCurrentWeatherAtLocationTool]
111+
);
112+
```
113+
114+
Asynchronous sample:
115+
```C# Snippet:AgentsFunctionsWithStreaming_CreateAgent
116+
PersistentAgent agent = await client.CreateAgentAsync(
117+
model: modelDeploymentName,
118+
name: "SDK Test Agent - Functions",
119+
instructions: "You are a weather bot. Use the provided functions to help answer questions. "
120+
+ "Customize your responses to the user's preferences as much as possible and use friendly "
121+
+ "nicknames for cities whenever possible.",
122+
tools: [ getUserFavoriteCityTool, getCityNicknameTool, getCurrentWeatherAtLocationTool ]
123+
);
124+
```
125+
126+
5. Create `Thread` with the message.
127+
128+
Synchronous sample:
129+
```C# Snippet:AgentsFunctionsWithStreamingSync_CreateThread
130+
PersistentAgentThread thread = client.CreateThread();
131+
132+
ThreadMessage message = client.CreateMessage(
133+
thread.Id,
134+
MessageRole.User,
135+
"What's the weather like in my favorite city?");
136+
```
137+
138+
Asynchronous sample:
139+
```C# Snippet:AgentsFunctionsWithStreaming_CreateThread
140+
PersistentAgentThread thread = await client.CreateThreadAsync();
141+
142+
ThreadMessage message = await client.CreateMessageAsync(
143+
thread.Id,
144+
MessageRole.User,
145+
"What's the weather like in my favorite city?");
146+
```
147+
148+
6. Create a stream and wait for the stream update of the `RequiredActionUpdate` type. This update will mark the point, when we need to submit tool outputs to the stream. We will submit outputs in the inner cycle. Please note that `RequiredActionUpdate` keeps only one required action, while our run may require multiple function calls, this case is handled in the inner cycle, so that we can add tool output to the existing array of outputs. After all required actions were submitted we clean up the array of required actions.
149+
150+
Synchronous sample:
151+
```C# Snippet:AgentsFunctionsWithStreamingSyncUpdateCycle
152+
List<ToolOutput> toolOutputs = [];
153+
ThreadRun streamRun = null;
154+
CollectionResult<StreamingUpdate> stream = client.CreateRunStreaming(thread.Id, agent.Id);
155+
do
156+
{
157+
toolOutputs.Clear();
158+
foreach (StreamingUpdate streamingUpdate in stream)
159+
{
160+
if (streamingUpdate is RequiredActionUpdate submitToolOutputsUpdate)
161+
{
162+
RequiredActionUpdate newActionUpdate = submitToolOutputsUpdate;
163+
toolOutputs.Add(
164+
GetResolvedToolOutput(
165+
newActionUpdate.FunctionName,
166+
newActionUpdate.ToolCallId,
167+
newActionUpdate.FunctionArguments
168+
));
169+
streamRun = submitToolOutputsUpdate.Value;
170+
}
171+
else if (streamingUpdate is MessageContentUpdate contentUpdate)
172+
{
173+
Console.Write($"{contentUpdate?.Text}");
174+
}
175+
}
176+
if (toolOutputs.Count > 0)
177+
{
178+
stream = client.SubmitToolOutputsToStream(streamRun, toolOutputs);
179+
}
180+
}
181+
while (toolOutputs.Count > 0);
182+
```
183+
184+
Asynchronous sample:
185+
```C# Snippet:AgentsFunctionsWithStreamingUpdateCycle
186+
List<ToolOutput> toolOutputs = [];
187+
ThreadRun streamRun = null;
188+
AsyncCollectionResult<StreamingUpdate> stream = client.CreateRunStreamingAsync(thread.Id, agent.Id);
189+
do
190+
{
191+
toolOutputs.Clear();
192+
await foreach (StreamingUpdate streamingUpdate in stream)
193+
{
194+
if (streamingUpdate is RequiredActionUpdate submitToolOutputsUpdate)
195+
{
196+
RequiredActionUpdate newActionUpdate = submitToolOutputsUpdate;
197+
toolOutputs.Add(
198+
GetResolvedToolOutput(
199+
newActionUpdate.FunctionName,
200+
newActionUpdate.ToolCallId,
201+
newActionUpdate.FunctionArguments
202+
));
203+
streamRun = submitToolOutputsUpdate.Value;
204+
}
205+
else if (streamingUpdate is MessageContentUpdate contentUpdate)
206+
{
207+
Console.Write($"{contentUpdate?.Text}");
208+
}
209+
}
210+
if (toolOutputs.Count > 0)
211+
{
212+
stream = client.SubmitToolOutputsToStreamAsync(streamRun, toolOutputs);
213+
}
214+
}
215+
while (toolOutputs.Count > 0);
216+
```
217+
218+
7. Finally, we delete all the resources, we have created in this sample.
219+
220+
Synchronous sample:
221+
```C# Snippet:AgentsFunctionsWithStreamingSync_Cleanup
222+
client.DeleteThread(thread.Id);
223+
client.DeleteAgent(agent.Id);
224+
```
225+
226+
Asynchronous sample:
227+
```C# Snippet:AgentsFunctionsWithStreaming_Cleanup
228+
await client.DeleteThreadAsync(thread.Id);
229+
await client.DeleteAgentAsync(agent.Id);
230+
```

0 commit comments

Comments
 (0)