openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.0.0-beta.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

examples/Chat/Example04_FunctionCallingStreamingAsync.cs

203lines · modecode

1using NUnit.Framework;
2using OpenAI.Chat;
3using System;
4using System.ClientModel;
5using System.Collections.Generic;
6using System.Text;
7using System.Text.Json;
8using System.Threading.Tasks;
9
10namespace OpenAI.Examples;
11
12public partial class ChatExamples
13{
14 // See Example03_FunctionCalling.cs for the tool and function definitions.
15
16 [Test]
17 public async Task Example04_FunctionCallingStreamingAsync()
18 {
19 ChatClient client = new("gpt-4-turbo", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
20
21 #region
22 List<ChatMessage> messages = [
23 new UserChatMessage("What's the weather like today?"),
24 ];
25
26 ChatCompletionOptions options = new()
27 {
28 Tools = { getCurrentLocationTool, getCurrentWeatherTool },
29 };
30 #endregion
31
32 #region
33 bool requiresAction;
34
35 do
36 {
37 requiresAction = false;
38 Dictionary<int, string> indexToToolCallId = [];
39 Dictionary<int, string> indexToFunctionName = [];
40 Dictionary<int, StringBuilder> indexToFunctionArguments = [];
41 StringBuilder contentBuilder = new();
42 AsyncResultCollection<StreamingChatCompletionUpdate> chatUpdates
43 = client.CompleteChatStreamingAsync(messages, options);
44
45 await foreach (StreamingChatCompletionUpdate chatUpdate in chatUpdates)
46 {
47 // Accumulate the text content as new updates arrive.
48 foreach (ChatMessageContentPart contentPart in chatUpdate.ContentUpdate)
49 {
50 contentBuilder.Append(contentPart.Text);
51 }
52
53 // Build the tool calls as new updates arrive.
54 foreach (StreamingChatToolCallUpdate toolCallUpdate in chatUpdate.ToolCallUpdates)
55 {
56 // Keep track of which tool call ID belongs to this update index.
57 if (toolCallUpdate.Id is not null)
58 {
59 indexToToolCallId[toolCallUpdate.Index] = toolCallUpdate.Id;
60 }
61
62 // Keep track of which function name belongs to this update index.
63 if (toolCallUpdate.FunctionName is not null)
64 {
65 indexToFunctionName[toolCallUpdate.Index] = toolCallUpdate.FunctionName;
66 }
67
68 // Keep track of which function arguments belong to this update index,
69 // and accumulate the arguments string as new updates arrive.
70 if (toolCallUpdate.FunctionArgumentsUpdate is not null)
71 {
72 StringBuilder argumentsBuilder
73 = indexToFunctionArguments.TryGetValue(toolCallUpdate.Index, out StringBuilder existingBuilder)
74 ? existingBuilder
75 : new StringBuilder();
76 argumentsBuilder.Append(toolCallUpdate.FunctionArgumentsUpdate);
77 indexToFunctionArguments[toolCallUpdate.Index] = argumentsBuilder;
78 }
79 }
80
81 switch (chatUpdate.FinishReason)
82 {
83 case ChatFinishReason.Stop:
84 {
85 // Add the assistant message to the conversation history.
86 messages.Add(new AssistantChatMessage(contentBuilder.ToString()));
87 break;
88 }
89
90 case ChatFinishReason.ToolCalls:
91 {
92 // First, collect the accumulated function arguments into complete tool calls to be processed
93 List<ChatToolCall> toolCalls = [];
94 foreach ((int index, string toolCallId) in indexToToolCallId)
95 {
96 ChatToolCall toolCall = ChatToolCall.CreateFunctionToolCall(
97 toolCallId,
98 indexToFunctionName[index],
99 indexToFunctionArguments[index].ToString());
100
101 toolCalls.Add(toolCall);
102 }
103
104 // Next, add the assistant message with tool calls to the conversation history.
105 string content = contentBuilder.Length > 0 ? contentBuilder.ToString() : null;
106 messages.Add(new AssistantChatMessage(toolCalls, content));
107
108 // Then, add a new tool message for each tool call to be resolved.
109 foreach (ChatToolCall toolCall in toolCalls)
110 {
111 switch (toolCall.FunctionName)
112 {
113 case nameof(GetCurrentLocation):
114 {
115 string toolResult = GetCurrentLocation();
116 messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
117 break;
118 }
119
120 case nameof(GetCurrentWeather):
121 {
122 // The arguments that the model wants to use to call the function are specified as a
123 // stringified JSON object based on the schema defined in the tool definition. Note that
124 // the model may hallucinate arguments too. Consequently, it is important to do the
125 // appropriate parsing and validation before calling the function.
126 using JsonDocument argumentsJson = JsonDocument.Parse(toolCall.FunctionArguments);
127 bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
128 bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);
129
130 if (!hasLocation)
131 {
132 throw new ArgumentNullException(nameof(location), "The location argument is required.");
133 }
134
135 string toolResult = hasUnit
136 ? GetCurrentWeather(location.GetString(), unit.GetString())
137 : GetCurrentWeather(location.GetString());
138 messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
139 break;
140 }
141
142 default:
143 {
144 // Handle other unexpected calls.
145 throw new NotImplementedException();
146 }
147 }
148 }
149
150 requiresAction = true;
151 break;
152 }
153
154 case ChatFinishReason.Length:
155 throw new NotImplementedException("Incomplete model output due to MaxTokens parameter or token limit exceeded.");
156
157 case ChatFinishReason.ContentFilter:
158 throw new NotImplementedException("Omitted content due to a content filter flag.");
159
160 case ChatFinishReason.FunctionCall:
161 throw new NotImplementedException("Deprecated in favor of tool calls.");
162
163 case null:
164 break;
165 }
166 }
167 } while (requiresAction);
168 #endregion
169
170 #region
171 foreach (ChatMessage requestMessage in messages)
172 {
173 switch (requestMessage)
174 {
175 case SystemChatMessage systemMessage:
176 Console.WriteLine($"[SYSTEM]:");
177 Console.WriteLine($"{systemMessage.Content[0].Text}");
178 Console.WriteLine();
179 break;
180
181 case UserChatMessage userMessage:
182 Console.WriteLine($"[USER]:");
183 Console.WriteLine($"{userMessage.Content[0].Text}");
184 Console.WriteLine();
185 break;
186
187 case AssistantChatMessage assistantMessage when assistantMessage.Content.Count > 0:
188 Console.WriteLine($"[ASSISTANT]:");
189 Console.WriteLine($"{assistantMessage.Content[0].Text}");
190 Console.WriteLine();
191 break;
192
193 case ToolChatMessage:
194 // Do not print any tool messages; let the assistant summarize the tool results instead.
195 break;
196
197 default:
198 break;
199 }
200 }
201 #endregion
202 }
203}
204