openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.0.0-beta.13

Branches

Tags

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

Clone

HTTPS

Download ZIP

examples/Chat/Example04_FunctionCallingStreaming.cs

207lines · modecode

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