openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
joseharriaga/ReorganizeExamples

Branches

Tags

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

Clone

HTTPS

Download ZIP

examples/Chat/Example04_FunctionCallingStreamingAsync.cs

171lines · 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 [
24 new UserChatMessage("What's the weather like today?"),
25 ];
26
27 ChatCompletionOptions options = new()
28 {
29 Tools = { getCurrentLocationTool, getCurrentWeatherTool },
30 };
31 #endregion
32
33 #region
34 bool requiresAction;
35
36 do
37 {
38 requiresAction = false;
39 StringBuilder contentBuilder = new();
40 StreamingChatToolCallsBuilder toolCallsBuilder = new();
41
42 AsyncCollectionResult<StreamingChatCompletionUpdate> completionUpdates = client.CompleteChatStreamingAsync(messages, options);
43
44 await foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)
45 {
46 // Accumulate the text content as new updates arrive.
47 foreach (ChatMessageContentPart contentPart in completionUpdate.ContentUpdate)
48 {
49 contentBuilder.Append(contentPart.Text);
50 }
51
52 // Build the tool calls as new updates arrive.
53 foreach (StreamingChatToolCallUpdate toolCallUpdate in completionUpdate.ToolCallUpdates)
54 {
55 toolCallsBuilder.Append(toolCallUpdate);
56 }
57
58 switch (completionUpdate.FinishReason)
59 {
60 case ChatFinishReason.Stop:
61 {
62 // Add the assistant message to the conversation history.
63 messages.Add(new AssistantChatMessage(contentBuilder.ToString()));
64 break;
65 }
66
67 case ChatFinishReason.ToolCalls:
68 {
69 // First, collect the accumulated function arguments into complete tool calls to be processed
70 IReadOnlyList<ChatToolCall> toolCalls = toolCallsBuilder.Build();
71
72 // Next, add the assistant message with tool calls to the conversation history.
73 AssistantChatMessage assistantMessage = new(toolCalls);
74
75 if (contentBuilder.Length > 0)
76 {
77 assistantMessage.Content.Add(ChatMessageContentPart.CreateTextPart(contentBuilder.ToString()));
78 }
79
80 messages.Add(assistantMessage);
81
82 // Then, add a new tool message for each tool call to be resolved.
83 foreach (ChatToolCall toolCall in toolCalls)
84 {
85 switch (toolCall.FunctionName)
86 {
87 case nameof(GetCurrentLocation):
88 {
89 string toolResult = GetCurrentLocation();
90 messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
91 break;
92 }
93
94 case nameof(GetCurrentWeather):
95 {
96 // The arguments that the model wants to use to call the function are specified as a
97 // stringified JSON object based on the schema defined in the tool definition. Note that
98 // the model may hallucinate arguments too. Consequently, it is important to do the
99 // appropriate parsing and validation before calling the function.
100 using JsonDocument argumentsJson = JsonDocument.Parse(toolCall.FunctionArguments);
101 bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
102 bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);
103
104 if (!hasLocation)
105 {
106 throw new ArgumentNullException(nameof(location), "The location argument is required.");
107 }
108
109 string toolResult = hasUnit
110 ? GetCurrentWeather(location.GetString(), unit.GetString())
111 : GetCurrentWeather(location.GetString());
112 messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
113 break;
114 }
115
116 default:
117 {
118 // Handle other unexpected calls.
119 throw new NotImplementedException();
120 }
121 }
122 }
123
124 requiresAction = true;
125 break;
126 }
127
128 case ChatFinishReason.Length:
129 throw new NotImplementedException("Incomplete model output due to MaxTokens parameter or token limit exceeded.");
130
131 case ChatFinishReason.ContentFilter:
132 throw new NotImplementedException("Omitted content due to a content filter flag.");
133
134 case ChatFinishReason.FunctionCall:
135 throw new NotImplementedException("Deprecated in favor of tool calls.");
136
137 case null:
138 break;
139 }
140 }
141 } while (requiresAction);
142 #endregion
143
144 #region
145 foreach (ChatMessage message in messages)
146 {
147 switch (message)
148 {
149 case UserChatMessage userMessage:
150 Console.WriteLine($"[USER]:");
151 Console.WriteLine($"{userMessage.Content[0].Text}");
152 Console.WriteLine();
153 break;
154
155 case AssistantChatMessage assistantMessage when assistantMessage.Content.Count > 0:
156 Console.WriteLine($"[ASSISTANT]:");
157 Console.WriteLine($"{assistantMessage.Content[0].Text}");
158 Console.WriteLine();
159 break;
160
161 case ToolChatMessage:
162 // Do not print any tool messages; let the assistant summarize the tool results instead.
163 break;
164
165 default:
166 break;
167 }
168 }
169 #endregion
170 }
171}
172