Skip to content

Commit 329d572

Browse files
authored
.Net Agents - Include code and output generated by code-interpreter in streaming output (#9068)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> As part of building sample for Learn site updated, discovered that generated code wasn't visible as streamed output. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Showing generated code maintains parity with non-streamed features. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent 3e7ff12 commit 329d572

File tree

2 files changed

+94
-15
lines changed

2 files changed

+94
-15
lines changed

dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,24 @@ namespace Agents;
1111
/// </summary>
1212
public class OpenAIAssistant_Streaming(ITestOutputHelper output) : BaseAgentsTest(output)
1313
{
14-
private const string ParrotName = "Parrot";
15-
private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
16-
1714
[Fact]
1815
public async Task UseStreamingAssistantAgentAsync()
1916
{
17+
const string AgentName = "Parrot";
18+
const string AgentInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
19+
2020
// Define the agent
2121
OpenAIAssistantAgent agent =
22-
await OpenAIAssistantAgent.CreateAsync(
23-
kernel: new(),
24-
clientProvider: this.GetClientProvider(),
25-
definition: new OpenAIAssistantDefinition(this.Model)
26-
{
27-
Instructions = ParrotInstructions,
28-
Name = ParrotName,
29-
Metadata = AssistantSampleMetadata,
30-
});
22+
await OpenAIAssistantAgent.CreateAsync(
23+
kernel: new(),
24+
clientProvider: this.GetClientProvider(),
25+
definition: new OpenAIAssistantDefinition(this.Model)
26+
{
27+
Instructions = AgentInstructions,
28+
Name = AgentName,
29+
EnableCodeInterpreter = true,
30+
Metadata = AssistantSampleMetadata,
31+
});
3132

3233
// Create a thread for the agent conversation.
3334
string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata });
@@ -44,7 +45,8 @@ await OpenAIAssistantAgent.CreateAsync(
4445
[Fact]
4546
public async Task UseStreamingAssistantAgentWithPluginAsync()
4647
{
47-
const string MenuInstructions = "Answer questions about the menu.";
48+
const string AgentName = "Host";
49+
const string AgentInstructions = "Answer questions about the menu.";
4850

4951
// Define the agent
5052
OpenAIAssistantAgent agent =
@@ -53,8 +55,8 @@ await OpenAIAssistantAgent.CreateAsync(
5355
clientProvider: this.GetClientProvider(),
5456
definition: new OpenAIAssistantDefinition(this.Model)
5557
{
56-
Instructions = MenuInstructions,
57-
Name = "Host",
58+
Instructions = AgentInstructions,
59+
Name = AgentName,
5860
Metadata = AssistantSampleMetadata,
5961
});
6062

@@ -73,6 +75,36 @@ await OpenAIAssistantAgent.CreateAsync(
7375
await DisplayChatHistoryAsync(agent, threadId);
7476
}
7577

78+
[Fact]
79+
public async Task UseStreamingAssistantWithCodeInterpreterAsync()
80+
{
81+
const string AgentName = "MathGuy";
82+
const string AgentInstructions = "Solve math problems with code.";
83+
84+
// Define the agent
85+
OpenAIAssistantAgent agent =
86+
await OpenAIAssistantAgent.CreateAsync(
87+
kernel: new(),
88+
clientProvider: this.GetClientProvider(),
89+
definition: new OpenAIAssistantDefinition(this.Model)
90+
{
91+
Instructions = AgentInstructions,
92+
Name = AgentName,
93+
EnableCodeInterpreter = true,
94+
Metadata = AssistantSampleMetadata,
95+
});
96+
97+
// Create a thread for the agent conversation.
98+
string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata });
99+
100+
// Respond to user input
101+
await InvokeAgentAsync(agent, threadId, "Is 191 a prime number?");
102+
await InvokeAgentAsync(agent, threadId, "Determine the values in the Fibonacci sequence that that are less then the value of 101");
103+
104+
// Output the entire chat history
105+
await DisplayChatHistoryAsync(agent, threadId);
106+
}
107+
76108
// Local function to invoke agent and display the conversation messages.
77109
private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string threadId, string input)
78110
{
@@ -83,13 +115,21 @@ private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string threadId,
83115
ChatHistory history = [];
84116

85117
bool isFirst = false;
118+
bool isCode = false;
86119
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(threadId, messages: history))
87120
{
88121
if (string.IsNullOrEmpty(response.Content))
89122
{
90123
continue;
91124
}
92125

126+
// Differentiate between assistant and tool messages
127+
if (isCode != (response.Metadata?.ContainsKey(OpenAIAssistantAgent.CodeInterpreterMetadataKey) ?? false))
128+
{
129+
isFirst = false;
130+
isCode = !isCode;
131+
}
132+
93133
if (!isFirst)
94134
{
95135
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");

dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,14 @@ public static async IAsyncEnumerable<StreamingChatMessageContent> InvokeStreamin
406406
break;
407407
}
408408
}
409+
else if (update is RunStepDetailsUpdate detailsUpdate)
410+
{
411+
StreamingChatMessageContent? toolContent = GenerateStreamingCodeInterpreterContent(agent.GetName(), detailsUpdate);
412+
if (toolContent != null)
413+
{
414+
yield return toolContent;
415+
}
416+
}
409417
else if (update is RunStepUpdate stepUpdate)
410418
{
411419
switch (stepUpdate.UpdateKind)
@@ -416,6 +424,8 @@ public static async IAsyncEnumerable<StreamingChatMessageContent> InvokeStreamin
416424
case StreamingUpdateReason.RunStepCompleted:
417425
currentStep = null;
418426
break;
427+
default:
428+
break;
419429
}
420430
}
421431
}
@@ -571,6 +581,35 @@ private static StreamingChatMessageContent GenerateStreamingMessageContent(strin
571581
return content;
572582
}
573583

584+
private static StreamingChatMessageContent? GenerateStreamingCodeInterpreterContent(string? assistantName, RunStepDetailsUpdate update)
585+
{
586+
StreamingChatMessageContent content =
587+
new(AuthorRole.Assistant, content: null)
588+
{
589+
AuthorName = assistantName,
590+
};
591+
592+
// Process text content
593+
if (update.CodeInterpreterInput != null)
594+
{
595+
content.Items.Add(new StreamingTextContent(update.CodeInterpreterInput));
596+
content.Metadata = new Dictionary<string, object?> { { OpenAIAssistantAgent.CodeInterpreterMetadataKey, true } };
597+
}
598+
599+
if ((update.CodeInterpreterOutputs?.Count ?? 0) > 0)
600+
{
601+
foreach (var output in update.CodeInterpreterOutputs!)
602+
{
603+
if (output.ImageFileId != null)
604+
{
605+
content.Items.Add(new StreamingFileReferenceContent(output.ImageFileId));
606+
}
607+
}
608+
}
609+
610+
return content.Items.Count > 0 ? content : null;
611+
}
612+
574613
private static AnnotationContent GenerateAnnotationContent(TextAnnotation annotation)
575614
{
576615
string? fileId = null;

0 commit comments

Comments
 (0)