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_FunctionCallingStreaming.cs

202lines · 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 ResultCollection<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 string content = contentBuilder.Length > 0 ? contentBuilder.ToString() : null;
105 messages.Add(new AssistantChatMessage(toolCalls, content));
106
107 // Then, add a new tool message for each tool call to be resolved.
108 foreach (ChatToolCall toolCall in toolCalls)
109 {
110 switch (toolCall.FunctionName)
111 {
112 case nameof(GetCurrentLocation):
113 {
114 string toolResult = GetCurrentLocation();
115 messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
116 break;
117 }
118
119 case nameof(GetCurrentWeather):
120 {
121 // The arguments that the model wants to use to call the function are specified as a
122 // stringified JSON object based on the schema defined in the tool definition. Note that
123 // the model may hallucinate arguments too. Consequently, it is important to do the
124 // appropriate parsing and validation before calling the function.
125 using JsonDocument argumentsJson = JsonDocument.Parse(toolCall.FunctionArguments);
126 bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
127 bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);
128
129 if (!hasLocation)
130 {
131 throw new ArgumentNullException(nameof(location), "The location argument is required.");
132 }
133
134 string toolResult = hasUnit
135 ? GetCurrentWeather(location.GetString(), unit.GetString())
136 : GetCurrentWeather(location.GetString());
137 messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
138 break;
139 }
140
141 default:
142 {
143 // Handle other unexpected calls.
144 throw new NotImplementedException();
145 }
146 }
147 }
148
149 requiresAction = true;
150 break;
151 }
152
153 case ChatFinishReason.Length:
154 throw new NotImplementedException("Incomplete model output due to MaxTokens parameter or token limit exceeded.");
155
156 case ChatFinishReason.ContentFilter:
157 throw new NotImplementedException("Omitted content due to a content filter flag.");
158
159 case ChatFinishReason.FunctionCall:
160 throw new NotImplementedException("Deprecated in favor of tool calls.");
161
162 case null:
163 break;
164 }
165 }
166 } while (requiresAction);
167 #endregion
168
169 #region
170 foreach (ChatMessage requestMessage in messages)
171 {
172 switch (requestMessage)
173 {
174 case SystemChatMessage systemMessage:
175 Console.WriteLine($"[SYSTEM]:");
176 Console.WriteLine($"{systemMessage.Content[0].Text}");
177 Console.WriteLine();
178 break;
179
180 case UserChatMessage userMessage:
181 Console.WriteLine($"[USER]:");
182 Console.WriteLine($"{userMessage.Content[0].Text}");
183 Console.WriteLine();
184 break;
185
186 case AssistantChatMessage assistantMessage when assistantMessage.Content.Count > 0:
187 Console.WriteLine($"[ASSISTANT]:");
188 Console.WriteLine($"{assistantMessage.Content[0].Text}");
189 Console.WriteLine();
190 break;
191
192 case ToolChatMessage:
193 // Do not print any tool messages; let the assistant summarize the tool results instead.
194 break;
195
196 default:
197 break;
198 }
199 }
200 #endregion
201 }
202}
203