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 · modeblame

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