Skip to content

Commit c99fd16

Browse files
authored
Create LocalFunctionCalling.md
Fully working local function calling.
1 parent cae9ece commit c99fd16

File tree

1 file changed

+270
-0
lines changed

1 file changed

+270
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Sample of Azure.AI.Agents with local function calling
2+
3+
In this example we are demonstrating how to use the local functions with the agents. The functions can be used to provide agent specific information in response to user question.
4+
5+
1. First get `ProjectEndpoint` and `ModelDeploymentName` from config and create a `PersistentAgentsClient`.
6+
```C# Snippet:AgentsFunctions_CreateClient
7+
var projectEndpoint = configuration["ProjectEndpoint"]);
8+
var modelDeploymentName = configuration["ModelDeploymentName"];
9+
PersistentAgentsClient client = new(new Uri(projectEndpoint) new DefaultAzureCredential());
10+
```
11+
12+
2 Define three simple local 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. The `FunctionToolDefinition` enables help the agent use the local functions.
13+
```C# Snippet:AgentsFunctionsDefineFunctionTools
14+
// Example of a local function that requires no parameters and returns a string.
15+
string GetUserFavoriteCity() => "Seattle, WA";
16+
// Example tool definition that will be shared with agent.
17+
FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
18+
// Example local function that has a single required paramter, location.
19+
string GetCityNickname(string location) => location switch
20+
{
21+
"Seattle, WA" => "The Emerald City",
22+
_ => throw new NotImplementedException(),
23+
};
24+
// Example tool definition that will be shared with agent which defines a required paramter named location.
25+
FunctionToolDefinition getCityNicknameTool = new(
26+
name: "getCityNickname",
27+
description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
28+
parameters: BinaryData.FromObjectAsJson(
29+
new
30+
{
31+
Type = "object",
32+
Properties = new
33+
{
34+
Location = new
35+
{
36+
Type = "string",
37+
Description = "The city and state, e.g. San Francisco, CA",
38+
},
39+
},
40+
Required = new[] { "location" },
41+
},
42+
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
43+
// Example local function with one required and one optional parameter
44+
string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
45+
{
46+
"Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
47+
_ => throw new NotImplementedException()
48+
};
49+
// Example tool definition that will be shared with agent which defines a required paramter named location and optional paramter named unit.
50+
FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
51+
name: "getCurrentWeatherAtLocation",
52+
description: "Gets the current weather at a provided location.",
53+
parameters: BinaryData.FromObjectAsJson(
54+
new
55+
{
56+
Type = "object",
57+
Properties = new
58+
{
59+
Location = new
60+
{
61+
Type = "string",
62+
Description = "The city and state, e.g. San Francisco, CA",
63+
},
64+
Unit = new
65+
{
66+
Type = "string",
67+
Enum = new[] { "c", "f" },
68+
},
69+
},
70+
Required = new[] { "location" },
71+
},
72+
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
73+
```
74+
75+
3. Create a function named `GetResolvedToolOutput` to faciliate an agent's request for data from a local function. This function attempts to find, and run the local function and supply necessary paramters. The results of the function are wrapped in a `ToolOutput`. object.
76+
```C# Snippet:AgentsFunctionsHandleFunctionCalls
77+
ToolOutput GetResolvedToolOutput(RequiredToolCall toolCall)
78+
{
79+
if (toolCall is RequiredFunctionToolCall functionToolCall)
80+
{
81+
if (functionToolCall.Name == getUserFavoriteCityTool.Name)
82+
{
83+
return new ToolOutput(toolCall, GetUserFavoriteCity());
84+
}
85+
using JsonDocument argumentsJson = JsonDocument.Parse(functionToolCall.Arguments);
86+
if (functionToolCall.Name == getCityNicknameTool.Name)
87+
{
88+
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
89+
return new ToolOutput(toolCall, GetCityNickname(locationArgument));
90+
}
91+
if (functionToolCall.Name == getCurrentWeatherAtLocationTool.Name)
92+
{
93+
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
94+
if (argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unitElement))
95+
{
96+
string unitArgument = unitElement.GetString();
97+
return new ToolOutput(toolCall, GetWeatherAtLocation(locationArgument, unitArgument));
98+
}
99+
return new ToolOutput(toolCall, GetWeatherAtLocation(locationArgument));
100+
}
101+
}
102+
return null;
103+
}
104+
```
105+
106+
4. Create an agent with the `FunctionToolDefinitions` from step 2.
107+
108+
Synchronous sample:
109+
```C# Snippet:AgentsFunctionsSyncCreateAgentWithFunctionTools
110+
// note: parallel function calling is only supported with newer models like gpt-4-1106-preview
111+
PersistentAgent agent = client.CreateAgent(
112+
model: modelDeploymentName,
113+
name: "SDK Test Agent - Functions",
114+
instructions: "You are a weather bot. Use the provided functions to help answer questions. "
115+
+ "Customize your responses to the user's preferences as much as possible and use friendly "
116+
+ "nicknames for cities whenever possible.",
117+
tools: [getUserFavoriteCityTool, getCityNicknameTool, getCurrentWeatherAtLocationTool]
118+
);
119+
```
120+
121+
Asynchronous sample:
122+
```C# Snippet:AgentsFunctionsCreateAgentWithFunctionTools
123+
// note: parallel function calling is only supported with newer models like gpt-4-1106-preview
124+
PersistentAgent agent = await client.CreateAgentAsync(
125+
model: modelDeploymentName,
126+
name: "SDK Test Agent - Functions",
127+
instructions: "You are a weather bot. Use the provided functions to help answer questions. "
128+
+ "Customize your responses to the user's preferences as much as possible and use friendly "
129+
+ "nicknames for cities whenever possible.",
130+
tools: [getUserFavoriteCityTool, getCityNicknameTool, getCurrentWeatherAtLocationTool ]
131+
);
132+
```
133+
134+
5. This step has three parts that will start the process to answer a user's question.
135+
136+
* First create a `PersistentAgentThread` establishing a session with an agent.
137+
* Then add a `ThreadMessage` which is a question for the agent to answer using `CreateMessage` from `PersistentAgentsClient`
138+
* Next, create a `ThreadRun` which starts the answering of a user's question by the agent.
139+
140+
Synchronous sample:
141+
```C# Snippet:AgentsFunctionsSync_CreateRun
142+
PersistentAgentThread thread = client.CreateThread();
143+
144+
client.CreateMessage(
145+
thread.Id,
146+
MessageRole.User,
147+
"What's the weather like in my favorite city?");
148+
149+
ThreadRun run = client.CreateRun(thread, agent);
150+
```
151+
152+
Asynchronous sample:
153+
```C# Snippet:AgentsFunctions_CreateRun
154+
PersistentAgentThread thread = await client.CreateThreadAsync();
155+
156+
await client.CreateMessageAsync(
157+
thread.Id,
158+
MessageRole.User,
159+
"What's the weather like in my favorite city?");
160+
161+
ThreadRun run = await client.CreateRunAsync(thread, agent);
162+
```
163+
164+
6. Next, monitor `ThreadRun` by polling and examining the `ThreadRun.Status`. When `ThreadRun.Status` is `RunStatus.RequiresAction` pass each `RequiredToolCall` from the agent to `GetResolvedToolOutput` in order to run the local functions.
165+
166+
Synchronous sample:
167+
```C# Snippet:AgentsFunctionsSyncHandlePollingWithRequiredAction
168+
do
169+
{
170+
Thread.Sleep(TimeSpan.FromMilliseconds(500));
171+
run = client.GetRun(thread.Id, run.Id);
172+
173+
if (run.Status == RunStatus.RequiresAction
174+
&& run.RequiredAction is SubmitToolOutputsAction submitToolOutputsAction)
175+
{
176+
List<ToolOutput> toolOutputs = [];
177+
foreach (RequiredToolCall toolCall in submitToolOutputsAction.ToolCalls)
178+
{
179+
toolOutputs.Add(GetResolvedToolOutput(toolCall));
180+
}
181+
run = client.SubmitToolOutputsToRun(run, toolOutputs);
182+
}
183+
}
184+
while (run.Status == RunStatus.Queued
185+
|| run.Status == RunStatus.InProgress
186+
|| run.Status == RunStatus.RequiresAction);
187+
```
188+
189+
Asynchronous sample:
190+
```C# Snippet:AgentsFunctionsHandlePollingWithRequiredAction
191+
do
192+
{
193+
await Task.Delay(TimeSpan.FromMilliseconds(500));
194+
run = await client.GetRunAsync(thread.Id, run.Id);
195+
196+
if (run.Status == RunStatus.RequiresAction
197+
&& run.RequiredAction is SubmitToolOutputsAction submitToolOutputsAction)
198+
{
199+
List<ToolOutput> toolOutputs = [];
200+
foreach (RequiredToolCall toolCall in submitToolOutputsAction.ToolCalls)
201+
{
202+
toolOutputs.Add(GetResolvedToolOutput(toolCall));
203+
}
204+
run = await client.SubmitToolOutputsToRunAsync(run, toolOutputs);
205+
}
206+
}
207+
while (run.Status == RunStatus.Queued
208+
|| run.Status == RunStatus.InProgress
209+
|| run.Status == RunStatus.RequiresAction);
210+
```
211+
212+
7. Now, that the `ThreadRun` has completed it is time to display the messages in chronological order. Using the `PersistentAgentsClient.GetMessages` to get `PageableList<ThreadMessage>` which contain the user's question and the answer by the agent.
213+
214+
Sample output by agent:
215+
user: What's the weather like in my favorite city?
216+
assistant: The weather in "The Emerald City" is currently a pleasant 70°F. Perfect for a stroll around town!
217+
218+
Synchronous sample:
219+
```C# Snippet:AgentsFunctionsSync_ListMessages
220+
PageableList<ThreadMessage> messages = client.GetMessages(
221+
threadId: thread.Id,
222+
order: ListSortOrder.Ascending
223+
);
224+
225+
foreach (ThreadMessage threadMessage in messages)
226+
{
227+
foreach (MessageContent contentItem in threadMessage.ContentItems)
228+
{
229+
if (contentItem is MessageTextContent textItem)
230+
{
231+
Console.Write($"{threadMessage.Role}: {textItem.Text}");
232+
}
233+
Console.WriteLine();
234+
}
235+
}
236+
```
237+
238+
Asynchronous sample:
239+
```C# Snippet:AgentsFunctions_ListMessages
240+
PageableList<ThreadMessage> messages = await client.GetMessagesAsync(
241+
threadId: thread.Id,
242+
order: ListSortOrder.Ascending
243+
);
244+
245+
foreach (ThreadMessage threadMessage in messages)
246+
{
247+
foreach (MessageContent contentItem in threadMessage.ContentItems)
248+
{
249+
if (contentItem is MessageTextContent textItem)
250+
{
251+
Console.Write($"{threadMessage.Role}: {textItem.Text}");
252+
}
253+
Console.WriteLine();
254+
}
255+
}
256+
```
257+
258+
8. Finally, delete the resources created in this sample.
259+
260+
Synchronous sample:
261+
```C# Snippet:AgentsFunctionsSync_Cleanup
262+
client.DeleteThread(thread.Id);
263+
client.DeleteAgent(agent.Id);
264+
```
265+
266+
Asynchronous sample:
267+
```C# Snippet:AgentsFunctions_Cleanup
268+
await client.DeleteThreadAsync(thread.Id);
269+
await client.DeleteAgentAsync(agent.Id);
270+
```

0 commit comments

Comments
 (0)