Skip to content

Commit

Permalink
.Net Agents - Include code and output generated by code-interpreter i…
Browse files Browse the repository at this point in the history
…n 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 😄
  • Loading branch information
crickman authored Oct 2, 2024
1 parent 3e7ff12 commit 329d572
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 15 deletions.
70 changes: 55 additions & 15 deletions dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,24 @@ namespace Agents;
/// </summary>
public class OpenAIAssistant_Streaming(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ParrotName = "Parrot";
private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";

[Fact]
public async Task UseStreamingAssistantAgentAsync()
{
const string AgentName = "Parrot";
const string AgentInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";

// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = ParrotInstructions,
Name = ParrotName,
Metadata = AssistantSampleMetadata,
});
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = AgentInstructions,
Name = AgentName,
EnableCodeInterpreter = true,
Metadata = AssistantSampleMetadata,
});

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

// Define the agent
OpenAIAssistantAgent agent =
Expand All @@ -53,8 +55,8 @@ await OpenAIAssistantAgent.CreateAsync(
clientProvider: this.GetClientProvider(),
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = MenuInstructions,
Name = "Host",
Instructions = AgentInstructions,
Name = AgentName,
Metadata = AssistantSampleMetadata,
});

Expand All @@ -73,6 +75,36 @@ await OpenAIAssistantAgent.CreateAsync(
await DisplayChatHistoryAsync(agent, threadId);
}

[Fact]
public async Task UseStreamingAssistantWithCodeInterpreterAsync()
{
const string AgentName = "MathGuy";
const string AgentInstructions = "Solve math problems with code.";

// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
clientProvider: this.GetClientProvider(),
definition: new OpenAIAssistantDefinition(this.Model)
{
Instructions = AgentInstructions,
Name = AgentName,
EnableCodeInterpreter = true,
Metadata = AssistantSampleMetadata,
});

// Create a thread for the agent conversation.
string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata });

// Respond to user input
await InvokeAgentAsync(agent, threadId, "Is 191 a prime number?");
await InvokeAgentAsync(agent, threadId, "Determine the values in the Fibonacci sequence that that are less then the value of 101");

// Output the entire chat history
await DisplayChatHistoryAsync(agent, threadId);
}

// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string threadId, string input)
{
Expand All @@ -83,13 +115,21 @@ private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string threadId,
ChatHistory history = [];

bool isFirst = false;
bool isCode = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(threadId, messages: history))
{
if (string.IsNullOrEmpty(response.Content))
{
continue;
}

// Differentiate between assistant and tool messages
if (isCode != (response.Metadata?.ContainsKey(OpenAIAssistantAgent.CodeInterpreterMetadataKey) ?? false))
{
isFirst = false;
isCode = !isCode;
}

if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
Expand Down
39 changes: 39 additions & 0 deletions dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,14 @@ public static async IAsyncEnumerable<StreamingChatMessageContent> InvokeStreamin
break;
}
}
else if (update is RunStepDetailsUpdate detailsUpdate)
{
StreamingChatMessageContent? toolContent = GenerateStreamingCodeInterpreterContent(agent.GetName(), detailsUpdate);
if (toolContent != null)
{
yield return toolContent;
}
}
else if (update is RunStepUpdate stepUpdate)
{
switch (stepUpdate.UpdateKind)
Expand All @@ -416,6 +424,8 @@ public static async IAsyncEnumerable<StreamingChatMessageContent> InvokeStreamin
case StreamingUpdateReason.RunStepCompleted:
currentStep = null;
break;
default:
break;
}
}
}
Expand Down Expand Up @@ -571,6 +581,35 @@ private static StreamingChatMessageContent GenerateStreamingMessageContent(strin
return content;
}

private static StreamingChatMessageContent? GenerateStreamingCodeInterpreterContent(string? assistantName, RunStepDetailsUpdate update)
{
StreamingChatMessageContent content =
new(AuthorRole.Assistant, content: null)
{
AuthorName = assistantName,
};

// Process text content
if (update.CodeInterpreterInput != null)
{
content.Items.Add(new StreamingTextContent(update.CodeInterpreterInput));
content.Metadata = new Dictionary<string, object?> { { OpenAIAssistantAgent.CodeInterpreterMetadataKey, true } };
}

if ((update.CodeInterpreterOutputs?.Count ?? 0) > 0)
{
foreach (var output in update.CodeInterpreterOutputs!)
{
if (output.ImageFileId != null)
{
content.Items.Add(new StreamingFileReferenceContent(output.ImageFileId));
}
}
}

return content.Items.Count > 0 ? content : null;
}

private static AnnotationContent GenerateAnnotationContent(TextAnnotation annotation)
{
string? fileId = null;
Expand Down

0 comments on commit 329d572

Please sign in to comment.