openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.9.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

examples/Assistants/Example02_FunctionCallingAsync.cs

195lines · modecode

1using NUnit.Framework;
2using OpenAI.Assistants;
3using System;
4using System.ClientModel;
5using System.Collections.Generic;
6using System.Text.Json;
7using System.Threading.Tasks;
8
9namespace OpenAI.Examples;
10
11// This example uses experimental APIs which are subject to change. To use experimental APIs,
12// please acknowledge their experimental status by suppressing the corresponding warning.
13#pragma warning disable OPENAI001
14
15public partial class AssistantExamples
16{
17 [Test]
18 public async Task Example02_FunctionCallingAsync()
19 {
20 #region
21 string GetCurrentLocation()
22 {
23 // Call a location API here.
24 return "San Francisco";
25 }
26
27 const string GetCurrentLocationFunctionName = "get_current_location";
28
29 FunctionToolDefinition getLocationTool = new(GetCurrentLocationFunctionName)
30 {
31 Description = "Get the user's current location"
32 };
33
34 string GetCurrentWeather(string location, string unit = "celsius")
35 {
36 // Call a weather API here.
37 return $"31 {unit}";
38 }
39
40 const string GetCurrentWeatherFunctionName = "get_current_weather";
41
42 FunctionToolDefinition getWeatherTool = new(GetCurrentWeatherFunctionName)
43 {
44 Description = "Get the current weather in a given location",
45 Parameters = BinaryData.FromString("""
46 {
47 "type": "object",
48 "properties": {
49 "location": {
50 "type": "string",
51 "description": "The city and state, e.g. Boston, MA"
52 },
53 "unit": {
54 "type": "string",
55 "enum": [ "celsius", "fahrenheit" ],
56 "description": "The temperature unit to use. Infer this from the specified location."
57 }
58 },
59 "required": [ "location" ]
60 }
61 """),
62 };
63 #endregion
64
65 // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning.
66 AssistantClient client = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
67
68 #region
69 // Create an assistant that can call the function tools.
70 AssistantCreationOptions assistantOptions = new()
71 {
72 Name = "Example: Function Calling",
73 Instructions =
74 "Don't make assumptions about what values to plug into functions."
75 + " Ask for clarification if a user request is ambiguous.",
76 Tools = { getLocationTool, getWeatherTool },
77 };
78
79 Assistant assistant = await client.CreateAssistantAsync("gpt-4-turbo", assistantOptions);
80 #endregion
81
82 #region
83 // Create a thread with an initial user message and run it.
84 ThreadCreationOptions threadOptions = new()
85 {
86 InitialMessages = { "What's the weather like today?" }
87 };
88
89 ThreadRun run = await client.CreateThreadAndRunAsync(assistant.Id, threadOptions);
90 #endregion
91
92 #region
93 // Poll the run until it is no longer queued or in progress.
94 while (!run.Status.IsTerminal)
95 {
96 await Task.Delay(TimeSpan.FromSeconds(1));
97 run = await client.GetRunAsync(run.ThreadId, run.Id);
98
99 // If the run requires action, resolve them.
100 if (run.Status == RunStatus.RequiresAction)
101 {
102 List<ToolOutput> toolOutputs = [];
103
104 foreach (RequiredAction action in run.RequiredActions)
105 {
106 switch (action.FunctionName)
107 {
108 case GetCurrentLocationFunctionName:
109 {
110 string toolResult = GetCurrentLocation();
111 toolOutputs.Add(new ToolOutput(action.ToolCallId, toolResult));
112 break;
113 }
114
115 case GetCurrentWeatherFunctionName:
116 {
117 // The arguments that the model wants to use to call the function are specified as a
118 // stringified JSON object based on the schema defined in the tool definition. Note that
119 // the model may hallucinate arguments too. Consequently, it is important to do the
120 // appropriate parsing and validation before calling the function.
121 using JsonDocument argumentsJson = JsonDocument.Parse(action.FunctionArguments);
122 bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
123 bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);
124
125 if (!hasLocation)
126 {
127 throw new ArgumentNullException(nameof(location), "The location argument is required.");
128 }
129
130 string toolResult = hasUnit
131 ? GetCurrentWeather(location.GetString(), unit.GetString())
132 : GetCurrentWeather(location.GetString());
133 toolOutputs.Add(new ToolOutput(action.ToolCallId, toolResult));
134 break;
135 }
136
137 default:
138 {
139 // Handle other or unexpected calls.
140 throw new NotImplementedException();
141 }
142 }
143 }
144
145 // Submit the tool outputs to the assistant, which returns the run to the queued state.
146 run = await client.SubmitToolOutputsToRunAsync(run.ThreadId, run.Id, toolOutputs);
147 }
148 }
149 #endregion
150
151 #region
152 // With the run complete, list the messages and display their content
153 if (run.Status == RunStatus.Completed)
154 {
155 AsyncCollectionResult<ThreadMessage> messages
156 = client.GetMessagesAsync(run.ThreadId, new MessageCollectionOptions() { Order = MessageCollectionOrder.Ascending });
157
158 await foreach (ThreadMessage message in messages)
159 {
160 Console.WriteLine($"[{message.Role.ToString().ToUpper()}]: ");
161 foreach (MessageContent contentItem in message.Content)
162 {
163 Console.WriteLine($"{contentItem.Text}");
164
165 if (contentItem.ImageFileId is not null)
166 {
167 Console.WriteLine($" <Image File ID> {contentItem.ImageFileId}");
168 }
169
170 // Include annotations, if any.
171 if (contentItem.TextAnnotations.Count > 0)
172 {
173 Console.WriteLine();
174 foreach (TextAnnotation annotation in contentItem.TextAnnotations)
175 {
176 Console.WriteLine($"* File ID used by file_search: {annotation.InputFileId}");
177 Console.WriteLine($"* File ID created by code_interpreter: {annotation.OutputFileId}");
178 Console.WriteLine($"* Text to replace: {annotation.TextToReplace}");
179 Console.WriteLine($"* Message content index range: {annotation.StartIndex}-{annotation.EndIndex}");
180 }
181 }
182
183 }
184 Console.WriteLine();
185 }
186 }
187 else
188 {
189 throw new NotImplementedException(run.Status.ToString());
190 }
191 #endregion
192 }
193}
194
195#pragma warning restore OPENAI001