Skip to content

Commit e62dc1e

Browse files
committed
docs: Added FunctionCallingStreaming example.
1 parent 821dd9d commit e62dc1e

File tree

6 files changed

+320
-187
lines changed

6 files changed

+320
-187
lines changed

docs/samples/Chat.FunctionCalling.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ do
1515
requiresAction = false;
1616
CreateChatCompletionResponse chatCompletion = await api.Chat.CreateChatCompletionAsync(
1717
messages,
18-
model: CreateChatCompletionRequestModel.Gpt4o,
18+
model: CreateChatCompletionRequestModel.Gpt4o20240806,
1919
tools: tools);
2020

2121
switch (chatCompletion.Choices[0].FinishReason)
@@ -66,7 +66,6 @@ foreach (ChatCompletionRequestMessage requestMessage in messages)
6666
Console.WriteLine($"[SYSTEM]:");
6767
Console.WriteLine($"{systemMessage.Content.Value1}");
6868
Console.WriteLine();
69-
break;
7069
}
7170
else if (requestMessage.User is { } userMessage)
7271
{
@@ -80,11 +79,9 @@ foreach (ChatCompletionRequestMessage requestMessage in messages)
8079
Console.WriteLine($"{assistantMessage.Content?.Value1}");
8180
Console.WriteLine();
8281
}
83-
else if (requestMessage.Tool is { } toolMessage)
82+
else if (requestMessage.Tool is not null)
8483
{
8584
// Do not print any tool messages; let the assistant summarize the tool results instead.
86-
break;
87-
8885
}
8986
}
9087
```
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
```csharp
2+
using var api = GetAuthenticatedClient();
3+
4+
List<ChatCompletionRequestMessage> messages = [
5+
"What's the weather like today?",
6+
];
7+
8+
var service = new FunctionCallingService();
9+
IList<ChatCompletionTool> tools = service.AsTools();
10+
11+
bool requiresAction;
12+
13+
do
14+
{
15+
requiresAction = false;
16+
Dictionary<int, string> indexToToolCallId = [];
17+
Dictionary<int, string> indexToFunctionName = [];
18+
Dictionary<int, StringBuilder> indexToFunctionArguments = [];
19+
StringBuilder contentBuilder = new();
20+
IAsyncEnumerable<CreateChatCompletionStreamResponse> chatUpdates
21+
= api.Chat.CreateChatCompletionAsStreamAsync(
22+
messages,
23+
model: CreateChatCompletionRequestModel.Gpt4o20240806,
24+
tools: tools);
25+
26+
await foreach (CreateChatCompletionStreamResponse chatUpdate in chatUpdates)
27+
{
28+
// Accumulate the text content as new updates arrive.
29+
if (!string.IsNullOrEmpty(chatUpdate.Choices[0].Delta.Content))
30+
{
31+
contentBuilder.Append(chatUpdate.Choices[0].Delta.Content);
32+
}
33+
34+
// Build the tool calls as new updates arrive.
35+
foreach (ChatCompletionMessageToolCallChunk toolCallUpdate in chatUpdate.Choices[0].Delta.ToolCalls ?? [])
36+
{
37+
// Keep track of which tool call ID belongs to this update index.
38+
if (toolCallUpdate.Id is not null)
39+
{
40+
indexToToolCallId[toolCallUpdate.Index] = toolCallUpdate.Id;
41+
}
42+
43+
// Keep track of which function name belongs to this update index.
44+
if (toolCallUpdate.Function?.Name is {} functionName)
45+
{
46+
indexToFunctionName[toolCallUpdate.Index] = functionName;
47+
}
48+
49+
// Keep track of which function arguments belong to this update index,
50+
// and accumulate the arguments string as new updates arrive.
51+
if (toolCallUpdate.Function?.Arguments is not null)
52+
{
53+
StringBuilder argumentsBuilder
54+
= indexToFunctionArguments.TryGetValue(toolCallUpdate.Index, out StringBuilder? existingBuilder)
55+
? existingBuilder
56+
: new StringBuilder();
57+
argumentsBuilder.Append(toolCallUpdate.Function?.Arguments);
58+
indexToFunctionArguments[toolCallUpdate.Index] = argumentsBuilder;
59+
}
60+
}
61+
62+
switch (chatUpdate.Choices[0].FinishReason)
63+
{
64+
case CreateChatCompletionStreamResponseChoiceFinishReason.Stop:
65+
{
66+
// Add the assistant message to the conversation history.
67+
messages.Add(contentBuilder.ToString().AsAssistantMessage());
68+
break;
69+
}
70+
71+
case CreateChatCompletionStreamResponseChoiceFinishReason.ToolCalls:
72+
{
73+
// First, collect the accumulated function arguments into complete tool calls to be processed
74+
List<ChatCompletionMessageToolCall> toolCalls = [];
75+
foreach ((int index, string toolCallId) in indexToToolCallId)
76+
{
77+
toolCalls.Add(new ChatCompletionMessageToolCall
78+
{
79+
Id = toolCallId,
80+
Function = new ChatCompletionMessageToolCallFunction
81+
{
82+
Name = indexToFunctionName[index],
83+
Arguments = indexToFunctionArguments[index].ToString(),
84+
},
85+
Type = ChatCompletionMessageToolCallType.Function,
86+
});
87+
}
88+
89+
// Next, add the assistant message with tool calls to the conversation history.
90+
var content = contentBuilder.Length > 0
91+
? new OneOf<string, IList<ChatCompletionRequestAssistantMessageContentPart>>(contentBuilder.ToString())
92+
: (OneOf<string, IList<ChatCompletionRequestAssistantMessageContentPart>>?)null;
93+
messages.Add(new ChatCompletionRequestAssistantMessage
94+
{
95+
Content = content,
96+
Role = ChatCompletionRequestAssistantMessageRole.Assistant,
97+
ToolCalls = toolCalls,
98+
});
99+
100+
// Then, add a new tool message for each tool call to be resolved.
101+
foreach (ChatCompletionMessageToolCall toolCall in toolCalls)
102+
{
103+
var json = await service.CallAsync(
104+
functionName: toolCall.Function.Name,
105+
argumentsAsJson: toolCall.Function.Arguments);
106+
messages.Add(json.AsToolMessage(toolCall.Id));
107+
}
108+
109+
requiresAction = true;
110+
break;
111+
}
112+
113+
case CreateChatCompletionStreamResponseChoiceFinishReason.Length:
114+
throw new NotImplementedException("Incomplete model output due to MaxTokens parameter or token limit exceeded.");
115+
116+
case CreateChatCompletionStreamResponseChoiceFinishReason.ContentFilter:
117+
throw new NotImplementedException("Omitted content due to a content filter flag.");
118+
119+
case CreateChatCompletionStreamResponseChoiceFinishReason.FunctionCall:
120+
throw new NotImplementedException("Deprecated in favor of tool calls.");
121+
122+
case null:
123+
break;
124+
}
125+
}
126+
} while (requiresAction);
127+
128+
foreach (ChatCompletionRequestMessage requestMessage in messages)
129+
{
130+
if (requestMessage.System is { } systemMessage)
131+
{
132+
Console.WriteLine($"[SYSTEM]:");
133+
Console.WriteLine($"{systemMessage.Content.Value1}");
134+
Console.WriteLine();
135+
}
136+
else if (requestMessage.User is { } userMessage)
137+
{
138+
Console.WriteLine($"[USER]:");
139+
Console.WriteLine($"{userMessage.Content.Value1}");
140+
Console.WriteLine();
141+
}
142+
else if (requestMessage.Assistant is { Content: not null } assistantMessage)
143+
{
144+
Console.WriteLine($"[ASSISTANT]:");
145+
Console.WriteLine($"{assistantMessage.Content?.Value1}");
146+
Console.WriteLine();
147+
}
148+
else if (requestMessage.Tool is not null)
149+
{
150+
// Do not print any tool messages; let the assistant summarize the tool results instead.
151+
}
152+
}
153+
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ nav:
44
# START EXAMPLES #
55
- Examples:
66
- Chat:
7+
- FunctionCallingStreaming: samples/Chat.FunctionCallingStreaming.md
78
- SimpleChat: samples/Chat.SimpleChat.md
89
- SimpleChatStreaming: samples/Chat.SimpleChatStreaming.md
910
- ChatWithVision: samples/Chat.ChatWithVision.md

src/tests/OpenAI.IntegrationTests/Examples/Examples.Chat.FunctionCalling.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task FunctionCalling()
8686
Console.WriteLine($"{assistantMessage.Content?.Value1}");
8787
Console.WriteLine();
8888
}
89-
else if (requestMessage.Tool is { } toolMessage)
89+
else if (requestMessage.Tool is not null)
9090
{
9191
// Do not print any tool messages; let the assistant summarize the tool results instead.
9292
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System.Text;
2+
3+
namespace OpenAI.IntegrationTests.Examples;
4+
5+
public partial class Examples
6+
{
7+
[Test]
8+
[Explicit]
9+
public async Task FunctionCallingStreaming()
10+
{
11+
using var api = GetAuthenticatedClient();
12+
13+
List<ChatCompletionRequestMessage> messages = [
14+
"What's the weather like today?",
15+
];
16+
17+
var service = new FunctionCallingService();
18+
IList<ChatCompletionTool> tools = service.AsTools();
19+
20+
bool requiresAction;
21+
22+
do
23+
{
24+
requiresAction = false;
25+
Dictionary<int, string> indexToToolCallId = [];
26+
Dictionary<int, string> indexToFunctionName = [];
27+
Dictionary<int, StringBuilder> indexToFunctionArguments = [];
28+
StringBuilder contentBuilder = new();
29+
IAsyncEnumerable<CreateChatCompletionStreamResponse> chatUpdates
30+
= api.Chat.CreateChatCompletionAsStreamAsync(
31+
messages,
32+
model: CreateChatCompletionRequestModel.Gpt4o20240806,
33+
tools: tools);
34+
35+
await foreach (CreateChatCompletionStreamResponse chatUpdate in chatUpdates)
36+
{
37+
// Accumulate the text content as new updates arrive.
38+
if (!string.IsNullOrEmpty(chatUpdate.Choices[0].Delta.Content))
39+
{
40+
contentBuilder.Append(chatUpdate.Choices[0].Delta.Content);
41+
}
42+
43+
// Build the tool calls as new updates arrive.
44+
foreach (ChatCompletionMessageToolCallChunk toolCallUpdate in chatUpdate.Choices[0].Delta.ToolCalls ?? [])
45+
{
46+
// Keep track of which tool call ID belongs to this update index.
47+
if (toolCallUpdate.Id is not null)
48+
{
49+
indexToToolCallId[toolCallUpdate.Index] = toolCallUpdate.Id;
50+
}
51+
52+
// Keep track of which function name belongs to this update index.
53+
if (toolCallUpdate.Function?.Name is {} functionName)
54+
{
55+
indexToFunctionName[toolCallUpdate.Index] = functionName;
56+
}
57+
58+
// Keep track of which function arguments belong to this update index,
59+
// and accumulate the arguments string as new updates arrive.
60+
if (toolCallUpdate.Function?.Arguments is not null)
61+
{
62+
StringBuilder argumentsBuilder
63+
= indexToFunctionArguments.TryGetValue(toolCallUpdate.Index, out StringBuilder? existingBuilder)
64+
? existingBuilder
65+
: new StringBuilder();
66+
argumentsBuilder.Append(toolCallUpdate.Function?.Arguments);
67+
indexToFunctionArguments[toolCallUpdate.Index] = argumentsBuilder;
68+
}
69+
}
70+
71+
switch (chatUpdate.Choices[0].FinishReason)
72+
{
73+
case CreateChatCompletionStreamResponseChoiceFinishReason.Stop:
74+
{
75+
// Add the assistant message to the conversation history.
76+
messages.Add(contentBuilder.ToString().AsAssistantMessage());
77+
break;
78+
}
79+
80+
case CreateChatCompletionStreamResponseChoiceFinishReason.ToolCalls:
81+
{
82+
// First, collect the accumulated function arguments into complete tool calls to be processed
83+
List<ChatCompletionMessageToolCall> toolCalls = [];
84+
foreach ((int index, string toolCallId) in indexToToolCallId)
85+
{
86+
toolCalls.Add(new ChatCompletionMessageToolCall
87+
{
88+
Id = toolCallId,
89+
Function = new ChatCompletionMessageToolCallFunction
90+
{
91+
Name = indexToFunctionName[index],
92+
Arguments = indexToFunctionArguments[index].ToString(),
93+
},
94+
Type = ChatCompletionMessageToolCallType.Function,
95+
});
96+
}
97+
98+
// Next, add the assistant message with tool calls to the conversation history.
99+
var content = contentBuilder.Length > 0
100+
? new OneOf<string, IList<ChatCompletionRequestAssistantMessageContentPart>>(contentBuilder.ToString())
101+
: (OneOf<string, IList<ChatCompletionRequestAssistantMessageContentPart>>?)null;
102+
messages.Add(new ChatCompletionRequestAssistantMessage
103+
{
104+
Content = content,
105+
Role = ChatCompletionRequestAssistantMessageRole.Assistant,
106+
ToolCalls = toolCalls,
107+
});
108+
109+
// Then, add a new tool message for each tool call to be resolved.
110+
foreach (ChatCompletionMessageToolCall toolCall in toolCalls)
111+
{
112+
var json = await service.CallAsync(
113+
functionName: toolCall.Function.Name,
114+
argumentsAsJson: toolCall.Function.Arguments);
115+
messages.Add(json.AsToolMessage(toolCall.Id));
116+
}
117+
118+
requiresAction = true;
119+
break;
120+
}
121+
122+
case CreateChatCompletionStreamResponseChoiceFinishReason.Length:
123+
throw new NotImplementedException("Incomplete model output due to MaxTokens parameter or token limit exceeded.");
124+
125+
case CreateChatCompletionStreamResponseChoiceFinishReason.ContentFilter:
126+
throw new NotImplementedException("Omitted content due to a content filter flag.");
127+
128+
case CreateChatCompletionStreamResponseChoiceFinishReason.FunctionCall:
129+
throw new NotImplementedException("Deprecated in favor of tool calls.");
130+
131+
case null:
132+
break;
133+
}
134+
}
135+
} while (requiresAction);
136+
137+
foreach (ChatCompletionRequestMessage requestMessage in messages)
138+
{
139+
if (requestMessage.System is { } systemMessage)
140+
{
141+
Console.WriteLine($"[SYSTEM]:");
142+
Console.WriteLine($"{systemMessage.Content.Value1}");
143+
Console.WriteLine();
144+
}
145+
else if (requestMessage.User is { } userMessage)
146+
{
147+
Console.WriteLine($"[USER]:");
148+
Console.WriteLine($"{userMessage.Content.Value1}");
149+
Console.WriteLine();
150+
}
151+
else if (requestMessage.Assistant is { Content: not null } assistantMessage)
152+
{
153+
Console.WriteLine($"[ASSISTANT]:");
154+
Console.WriteLine($"{assistantMessage.Content?.Value1}");
155+
Console.WriteLine();
156+
}
157+
else if (requestMessage.Tool is not null)
158+
{
159+
// Do not print any tool messages; let the assistant summarize the tool results instead.
160+
}
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)