Skip to content

Commit f098560

Browse files
Merge pull request #4717 from RobiladK/patch-1
Update function-calling.md
2 parents 6aaebcd + 3e3bcf6 commit f098560

File tree

1 file changed

+169
-111
lines changed

1 file changed

+169
-111
lines changed

articles/ai-services/agents/how-to/tools/function-calling.md

Lines changed: 169 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -28,140 +28,198 @@ Azure AI Agents supports function calling, which allows you to describe the stru
2828

2929
::: zone pivot="csharp"
3030

31-
## Define a function for your agent to call
32-
33-
Start by defining a function for your agent to call. When you create a function for an agent to call, you describe its structure of it with any required parameters in a docstring.
34-
35-
```csharp
36-
// Example of a function that defines no parameters
37-
string GetUserFavoriteCity() => "Seattle, WA";
38-
FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
39-
// Example of a function with a single required parameter
40-
string GetCityNickname(string location) => location switch
41-
{
42-
"Seattle, WA" => "The Emerald City",
43-
_ => throw new NotImplementedException(),
44-
};
45-
FunctionToolDefinition getCityNicknameTool = new(
46-
name: "getCityNickname",
47-
description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
48-
parameters: BinaryData.FromObjectAsJson(
49-
new
50-
{
51-
Type = "object",
52-
Properties = new
31+
## Using the .NET SDK
32+
33+
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.
34+
35+
1. First, set up the configuration and create a `PersistentAgentsClient`. This client will be used for all interactions with the Azure AI Agents Service. This step also includes all necessary `using` directives.
36+
37+
```csharp
38+
using Azure;
39+
using Azure.AI.Agents.Persistent;
40+
using Azure.Identity;
41+
using Microsoft.Extensions.Configuration;
42+
using System.Text.Json;
43+
44+
IConfigurationRoot configuration = new ConfigurationBuilder()
45+
.SetBasePath(AppContext.BaseDirectory)
46+
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
47+
.Build();
48+
49+
var projectEndpoint = configuration["ProjectEndpoint"];
50+
var modelDeploymentName = configuration["ModelDeploymentName"];
51+
PersistentAgentsClient client = new(projectEndpoint, new DefaultAzureCredential());
52+
```
53+
54+
2. Next, define the local functions that the agent can call. For each function, create a `FunctionToolDefinition` that describes its name, purpose, and parameters to the agent. These functions and definitions are used by both synchronous and asynchronous agent operations.
55+
56+
```csharp
57+
string GetUserFavoriteCity() => "Seattle, WA";
58+
FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
59+
60+
string GetCityNickname(string location) => location switch
61+
{
62+
"Seattle, WA" => "The Emerald City",
63+
_ => throw new NotImplementedException(),
64+
};
65+
FunctionToolDefinition getCityNicknameTool = new(
66+
name: "getCityNickname",
67+
description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
68+
parameters: BinaryData.FromObjectAsJson(
69+
new
5370
{
54-
Location = new
71+
Type = "object",
72+
Properties = new
5573
{
56-
Type = "string",
57-
Description = "The city and state, e.g. San Francisco, CA",
74+
Location = new
75+
{
76+
Type = "string",
77+
Description = "The city and state, e.g. San Francisco, CA",
78+
},
5879
},
80+
Required = new[] { "location" },
5981
},
60-
Required = new[] { "location" },
61-
},
62-
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
63-
```
64-
65-
<!--See the [C# file on GitHub](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/agents/user_functions.py) for an additional function definition examples. -->
82+
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
83+
84+
string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
85+
{
86+
"Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
87+
_ => throw new NotImplementedException()
88+
};
89+
FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
90+
name: "getCurrentWeatherAtLocation",
91+
description: "Gets the current weather at a provided location.",
92+
parameters: BinaryData.FromObjectAsJson(
93+
new
94+
{
95+
Type = "object",
96+
Properties = new
97+
{
98+
Location = new
99+
{
100+
Type = "string",
101+
Description = "The city and state, e.g. San Francisco, CA",
102+
},
103+
Unit = new
104+
{
105+
Type = "string",
106+
Enum = new[] { "c", "f" },
107+
},
108+
},
109+
Required = new[] { "location" },
110+
},
111+
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
112+
```
66113

67-
In the following sample, we create a helper function to get and parse the resolved tools' outputs, and return it.
114+
3. Create a helper function, `GetResolvedToolOutput`. This function takes a `RequiredToolCall` (when the agent determines a local function should be executed) and invokes the appropriate C# function defined in the previous step. It then wraps the result in a `ToolOutput` object for the agent.
68115

69-
```csharp
70-
ToolOutput GetResolvedToolOutput(RequiredToolCall toolCall)
71-
{
72-
if (toolCall is RequiredFunctionToolCall functionToolCall)
116+
```csharp
117+
ToolOutput GetResolvedToolOutput(RequiredToolCall toolCall)
73118
{
74-
if (functionToolCall.Name == getUserFavoriteCityTool.Name)
119+
if (toolCall is RequiredFunctionToolCall functionToolCall)
120+
{
121+
if (functionToolCall.Name == getUserFavoriteCityTool.Name)
122+
{
123+
return new ToolOutput(toolCall, GetUserFavoriteCity());
124+
}
125+
using JsonDocument argumentsJson = JsonDocument.Parse(functionToolCall.Arguments);
126+
if (functionToolCall.Name == getCityNicknameTool.Name)
127+
{
128+
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
129+
return new ToolOutput(toolCall, GetCityNickname(locationArgument));
130+
}
131+
if (functionToolCall.Name == getCurrentWeatherAtLocationTool.Name)
132+
{
133+
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
134+
if (argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unitElement))
75135
{
76-
return new ToolOutput(toolCall, GetUserFavoriteCity());
136+
string unitArgument = unitElement.GetString();
137+
return new ToolOutput(toolCall, GetWeatherAtLocation(locationArgument, unitArgument));
77138
}
78-
using JsonDocument argumentsJson = JsonDocument.Parse(functionToolCall.Arguments);
79-
if (functionToolCall.Name == getCityNicknameTool.Name)
80-
{
81-
string locationArgument = argumentsJson.RootElement.GetProperty("location").GetString();
82-
return new ToolOutput(toolCall, GetCityNickname(locationArgument));
139+
return new ToolOutput(toolCall, GetWeatherAtLocation(locationArgument));
140+
}
83141
}
142+
return null;
84143
}
85-
return null;
86-
}
87-
```
144+
```
88145

89-
## Create a client and agent
146+
4. Now, create the agent. Provide the model deployment name (retrieved in step 1), a descriptive name for the agent, instructions for its behavior, and the list of `FunctionToolDefinition`s (defined in step 2) it can use.
90147

91-
```csharp
92-
// note: parallel function calling is only supported with newer models like gpt-4-1106-preview
93-
Response<Agent> agentResponse = await client.CreateAgentAsync(
94-
model: "gpt-4-1106-preview",
95-
name: "SDK Test Agent - Functions",
148+
```csharp
149+
PersistentAgent agent = client.Administration.CreateAgent(
150+
model: modelDeploymentName,
151+
name: "SDK Test Agent - Functions",
96152
instructions: "You are a weather bot. Use the provided functions to help answer questions. "
97153
+ "Customize your responses to the user's preferences as much as possible and use friendly "
98154
+ "nicknames for cities whenever possible.",
99-
tools: new List<ToolDefinition> { getUserFavoriteCityTool, getCityNicknameTool, getCurrentWeatherAtLocationTool }
100-
);
101-
Agent agent = agentResponse.Value;
102-
```
103-
104-
## Create a thread
105-
106-
```csharp
107-
Response<AgentThread> threadResponse = await client.CreateThreadAsync();
108-
AgentThread thread = threadResponse.Value;
109-
110-
Response<ThreadMessage> messageResponse = await client.CreateMessageAsync(
111-
thread.Id,
112-
MessageRole.User,
113-
"What's the weather like in my favorite city?");
114-
ThreadMessage message = messageResponse.Value;
115-
```
116-
117-
## Create a run and check the output
118-
119-
```csharp
120-
Response<ThreadRun> runResponse = await client.CreateRunAsync(thread, agent);
121-
122-
#region Snippet:FunctionsHandlePollingWithRequiredAction
123-
do
124-
{
125-
await Task.Delay(TimeSpan.FromMilliseconds(500));
126-
runResponse = await client.GetRunAsync(thread.Id, runResponse.Value.Id);
127-
128-
if (runResponse.Value.Status == RunStatus.RequiresAction
129-
&& runResponse.Value.RequiredAction is SubmitToolOutputsAction submitToolOutputsAction)
155+
tools: [getUserFavoriteCityTool, getCityNicknameTool, getCurrentWeatherAtLocationTool]);
156+
```
157+
158+
5. Create a new conversation thread and add an initial user message to it. The agent will respond to this message.
159+
160+
```csharp
161+
PersistentAgentThread thread = client.Threads.CreateThread();
162+
163+
client.Messages.CreateMessage(
164+
thread.Id,
165+
MessageRole.User,
166+
"What's the weather like in my favorite city?");
167+
```
168+
169+
6. Create a run for the agent on the thread and poll for its completion. If the run requires action (e.g., a function call), submit the tool outputs.
170+
171+
```csharp
172+
ThreadRun run = client.Runs.CreateRun(thread.Id, agent.Id);
173+
174+
do
130175
{
131-
List<ToolOutput> toolOutputs = new();
132-
foreach (RequiredToolCall toolCall in submitToolOutputsAction.ToolCalls)
176+
Thread.Sleep(TimeSpan.FromMilliseconds(500));
177+
run = client.Runs.GetRun(thread.Id, run.Id);
178+
179+
if (run.Status == RunStatus.RequiresAction
180+
&& run.RequiredAction is SubmitToolOutputsAction submitToolOutputsAction)
133181
{
134-
toolOutputs.Add(GetResolvedToolOutput(toolCall));
182+
List<ToolOutput> toolOutputs = [];
183+
foreach (RequiredToolCall toolCall in submitToolOutputsAction.ToolCalls)
184+
{
185+
toolOutputs.Add(GetResolvedToolOutput(toolCall));
186+
}
187+
run = client.Runs.SubmitToolOutputsToRun(run, toolOutputs);
135188
}
136-
runResponse = await client.SubmitToolOutputsToRunAsync(runResponse.Value, toolOutputs);
137189
}
138-
}
139-
while (runResponse.Value.Status == RunStatus.Queued
140-
|| runResponse.Value.Status == RunStatus.InProgress);
141-
#endregion
142-
143-
Response<PageableList<ThreadMessage>> afterRunMessagesResponse
144-
= await client.GetMessagesAsync(thread.Id);
145-
IReadOnlyList<ThreadMessage> messages = afterRunMessagesResponse.Value.Data;
146-
147-
// Note: messages iterate from newest to oldest, with the messages[0] being the most recent
148-
foreach (ThreadMessage threadMessage in messages)
149-
{
150-
Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: ");
151-
foreach (MessageContent contentItem in threadMessage.ContentItems)
190+
while (run.Status == RunStatus.Queued
191+
|| run.Status == RunStatus.InProgress
192+
|| run.Status == RunStatus.RequiresAction);
193+
```
194+
195+
7. After the run completes, retrieve and display the messages from the thread to see the conversation, including the agent's responses.
196+
197+
```csharp
198+
Pageable<ThreadMessage> messages = client.Messages.GetMessages(
199+
threadId: thread.Id,
200+
order: ListSortOrder.Ascending
201+
);
202+
203+
foreach (ThreadMessage threadMessage in messages)
152204
{
153-
if (contentItem is MessageTextContent textItem)
205+
foreach (MessageContent content in threadMessage.ContentItems)
154206
{
155-
Console.Write(textItem.Text);
156-
}
157-
else if (contentItem is MessageImageFileContent imageFileItem)
158-
{
159-
Console.Write($"<image from ID: {imageFileItem.FileId}");
207+
switch (content)
208+
{
209+
case MessageTextContent textItem:
210+
Console.WriteLine($"[{threadMessage.Role}]: {textItem.Text}");
211+
break;
212+
}
160213
}
161-
Console.WriteLine();
162214
}
163-
}
164-
```
215+
```
216+
217+
8. Finally, clean up the created resources by deleting the thread and the agent.
218+
219+
```csharp
220+
client.Threads.DeleteThread(threadId: thread.Id);
221+
client.Administration.DeleteAgent(agentId: agent.Id);
222+
```
165223

166224
::: zone-end
167225

@@ -510,4 +568,4 @@ curl $AZURE_AI_AGENTS_ENDPOINT/threads/thread_abc123/messages?api-version=2024-1
510568
-H "Authorization: Bearer $AZURE_AI_AGENTS_TOKEN"
511569
```
512570
513-
::: zone-end
571+
::: zone-end

0 commit comments

Comments
 (0)