mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.Net: Code interpreter tool abstraction and implementation examples (#110)
* Added code interpreter abstraction updates for OpenAI Assistants * Updated Persistent Agents implementation based on latest changes in SDK * Added code interpreter abstraction updates for Azure AI Persistent Agents * Small note for OpenAI responses code interpreter * Small update * Fixes after merge * Addressed PR feedback * Small update * Small fix * Fix after merge
This commit is contained in:
committed by
GitHub
Unverified
parent
95738b0ac2
commit
fbb0fdfe0d
@@ -8,8 +8,11 @@ using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Shared.Samples;
|
||||
using OpenAI;
|
||||
using OpenAI.Assistants;
|
||||
using OpenAI.Responses;
|
||||
|
||||
#pragma warning disable OPENAI001
|
||||
|
||||
namespace GettingStarted;
|
||||
|
||||
public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
@@ -19,8 +22,9 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
/// </summary>
|
||||
public enum ChatClientProviders
|
||||
{
|
||||
OpenAI,
|
||||
AzureOpenAI,
|
||||
OpenAIChatCompletion,
|
||||
OpenAIAssistant,
|
||||
OpenAIResponses,
|
||||
OpenAIResponses_InMemoryMessageThread,
|
||||
OpenAIResponses_ConversationIdThread,
|
||||
@@ -30,7 +34,8 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
protected Task<IChatClient> GetChatClientAsync(ChatClientProviders provider, ChatClientAgentOptions options, CancellationToken cancellationToken = default)
|
||||
=> provider switch
|
||||
{
|
||||
ChatClientProviders.OpenAI => GetOpenAIChatClientAsync(),
|
||||
ChatClientProviders.OpenAIChatCompletion => GetOpenAIChatClientAsync(),
|
||||
ChatClientProviders.OpenAIAssistant => GetOpenAIAssistantChatClientAsync(options, cancellationToken),
|
||||
ChatClientProviders.AzureOpenAI => GetAzureOpenAIChatClientAsync(),
|
||||
ChatClientProviders.AzureAIAgentsPersistent => GetAzureAIAgentPersistentClientAsync(options, cancellationToken),
|
||||
ChatClientProviders.OpenAIResponses or
|
||||
@@ -48,6 +53,10 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
_ => null
|
||||
};
|
||||
|
||||
protected OpenAIClient OpenAIClient => new(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
protected PersistentAgentsClient AzureAIPersistentAgentsClient => new(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
|
||||
|
||||
/// <summary>
|
||||
/// For providers that store the agent and the thread on the server side, this will clean and delete
|
||||
/// any sample agent and thread that was created during this execution.
|
||||
@@ -74,7 +83,7 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
|
||||
private Task<IChatClient> GetOpenAIChatClientAsync()
|
||||
=> Task.FromResult(
|
||||
new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
|
||||
OpenAIClient
|
||||
.GetChatClient(TestConfiguration.OpenAI.ChatModelId)
|
||||
.AsIChatClient());
|
||||
|
||||
@@ -89,17 +98,14 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
|
||||
private Task<IChatClient> GetOpenAIResponsesClientAsync()
|
||||
=> Task.FromResult(
|
||||
new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
|
||||
OpenAIClient
|
||||
.GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId)
|
||||
.AsIChatClient());
|
||||
|
||||
private async Task<IChatClient> GetAzureAIAgentPersistentClientAsync(ChatClientAgentOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get a client to create server side agents with.
|
||||
var persistentAgentsClient = new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
|
||||
|
||||
// Create a server side agent to work with.
|
||||
var persistentAgentResponse = await persistentAgentsClient.Administration.CreateAgentAsync(
|
||||
var persistentAgentResponse = await AzureAIPersistentAgentsClient.Administration.CreateAgentAsync(
|
||||
model: TestConfiguration.AzureAI.DeploymentName,
|
||||
name: options.Name,
|
||||
instructions: options.Instructions,
|
||||
@@ -108,7 +114,23 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
|
||||
var persistentAgent = persistentAgentResponse.Value;
|
||||
|
||||
// Get the chat client to use for the agent.
|
||||
return persistentAgentsClient.AsIChatClient(persistentAgent.Id);
|
||||
return AzureAIPersistentAgentsClient.AsIChatClient(persistentAgent.Id);
|
||||
}
|
||||
|
||||
private async Task<IChatClient> GetOpenAIAssistantChatClientAsync(ChatClientAgentOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var assistantClient = OpenAIClient.GetAssistantClient();
|
||||
|
||||
Assistant assistant = await assistantClient.CreateAssistantAsync(
|
||||
TestConfiguration.OpenAI.ChatModelId,
|
||||
new()
|
||||
{
|
||||
Name = options.Name,
|
||||
Instructions = options.Instructions
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
return assistantClient.AsIChatClient(assistant.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -34,4 +34,10 @@
|
||||
<Using Include="GettingStarted" />
|
||||
<Using Include="Microsoft.Shared.SampleUtilities" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Tools\Files\groceries.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -20,8 +20,8 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A
|
||||
/// a unique interaction with no conversation history between them.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.OpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses)]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
public async Task RunWithoutThread(ChatClientProviders provider)
|
||||
@@ -61,7 +61,6 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A
|
||||
/// Demonstrate the usage of <see cref="ChatClientAgent"/> where a conversation history is maintained.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.OpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
|
||||
@@ -110,7 +109,6 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A
|
||||
/// where a conversation is maintained by the <see cref="AgentThread"/>.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.OpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Steps;
|
||||
public sealed class Step02_ChatClientAgent_UsingTools(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.OpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
public async Task RunningWithTools(ChatClientProviders provider)
|
||||
{
|
||||
// Creating a Menu Tools to be used by the agent.
|
||||
@@ -57,8 +57,8 @@ public sealed class Step02_ChatClientAgent_UsingTools(ITestOutputHelper output)
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.OpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
public async Task StreamingRunWithTools(ChatClientProviders provider)
|
||||
{
|
||||
// Creating a Menu Tools to be used by the agent.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace GettingStarted.Tools.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Proposal for abstraction updates based on the common code interpreter tool properties.
|
||||
/// Based on the decision, the <see cref="HostedCodeInterpreterTool"/> abstraction can be updated in M.E.AI or specific SDK if some properties are not common.
|
||||
/// </summary>
|
||||
public class NewHostedCodeInterpreterTool : HostedCodeInterpreterTool
|
||||
{
|
||||
public IList<string>? FileIds { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using GettingStarted.Tools.Abstractions;
|
||||
using GettingStarted.Tools.Extensions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using OpenAI;
|
||||
using OpenAI.Files;
|
||||
|
||||
#pragma warning disable OPENAI001
|
||||
|
||||
namespace GettingStarted.Tools;
|
||||
|
||||
public sealed class CodeInterpreterTools(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.OpenAIAssistant)]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
public async Task RunningWithFileReferenceAsync(ChatClientProviders provider)
|
||||
{
|
||||
var fileId = await UploadTestFileAsync(provider);
|
||||
|
||||
var chatOptions = new ChatOptions()
|
||||
{
|
||||
Tools = [new NewHostedCodeInterpreterTool { FileIds = [fileId] }]
|
||||
};
|
||||
|
||||
var agentOptions = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "HelpfulAssistant",
|
||||
Instructions = "You are a helpful assistant.",
|
||||
// Transformation is required until the abstraction will be added to either SDK provider or M.E.AI and
|
||||
// implementations will handle new properties/classes.
|
||||
ChatOptions = TransformChatOptions(chatOptions, provider)
|
||||
};
|
||||
|
||||
using var chatClient = await base.GetChatClientAsync(provider, agentOptions);
|
||||
|
||||
ChatClientAgent agent = new(chatClient, agentOptions);
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
// Prompt which allows to verify that the data was processed from file correctly and current datetime is returned.
|
||||
const string Prompt = "Calculate the total number of items, identify the most frequently puchased item and return the result with today's datetime.";
|
||||
|
||||
var assistantOutput = new StringBuilder();
|
||||
var codeInterpreterOutput = new StringBuilder();
|
||||
|
||||
await foreach (var update in agent.RunStreamingAsync(Prompt, thread))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(update.Text))
|
||||
{
|
||||
assistantOutput.Append(update.Text);
|
||||
}
|
||||
else if (update.RawRepresentation is not null)
|
||||
{
|
||||
ProcessRawRepresentationOutput(update.RawRepresentation, codeInterpreterOutput, provider);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Assistant Output:");
|
||||
Console.WriteLine(assistantOutput.ToString());
|
||||
|
||||
Console.WriteLine("Code interpreter Output:");
|
||||
Console.WriteLine(codeInterpreterOutput.ToString());
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
/// <summary>
|
||||
/// This method creates a raw representation of tools from newly proposed abstractions, so underlying SDKs can work with it.
|
||||
/// Once the tool abstraction is added to either SDK provider or M.E.AI, this method can be removed.
|
||||
/// The logic under each provider case should go to related SDK.
|
||||
/// </summary>
|
||||
private static ChatOptions TransformChatOptions(ChatOptions chatOptions, ChatClientProviders provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
ChatClientProviders.OpenAIAssistant => chatOptions.ToOpenAIAssistantChatOptions(),
|
||||
ChatClientProviders.AzureAIAgentsPersistent => chatOptions.ToAzureAIPersistentAgentChatOptions(),
|
||||
_ => chatOptions
|
||||
};
|
||||
}
|
||||
|
||||
private Task<string> UploadTestFileAsync(ChatClientProviders provider)
|
||||
{
|
||||
var filePath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "Tools", "Files", "groceries.txt"));
|
||||
return UploadFileAsync(filePath, provider);
|
||||
}
|
||||
|
||||
private async Task<string> UploadFileAsync(string filePath, ChatClientProviders provider)
|
||||
{
|
||||
switch (provider)
|
||||
{
|
||||
case ChatClientProviders.OpenAIAssistant:
|
||||
var fileClient = GetOpenAIFileClient();
|
||||
OpenAIFile openAIFileInfo = await fileClient.UploadFileAsync(filePath, FileUploadPurpose.Assistants);
|
||||
|
||||
return openAIFileInfo.Id;
|
||||
case ChatClientProviders.AzureAIAgentsPersistent:
|
||||
PersistentAgentFileInfo persistentAgentFileInfo = await AzureAIPersistentAgentsClient.Files.UploadFileAsync(filePath, PersistentAgentFilePurpose.Agents);
|
||||
|
||||
return persistentAgentFileInfo.Id;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Client provider {provider} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessRawRepresentationOutput(object rawRepresentation, StringBuilder builder, ChatClientProviders provider)
|
||||
{
|
||||
switch (provider)
|
||||
{
|
||||
case ChatClientProviders.OpenAIAssistant:
|
||||
if (rawRepresentation is OpenAI.Assistants.RunStepDetailsUpdate openAIStepDetailsUpdate)
|
||||
{
|
||||
builder.Append(openAIStepDetailsUpdate.CodeInterpreterInput);
|
||||
builder.Append(string.Join(string.Empty, openAIStepDetailsUpdate.CodeInterpreterOutputs.SelectMany(l => l.Logs)));
|
||||
}
|
||||
|
||||
break;
|
||||
case ChatClientProviders.AzureAIAgentsPersistent:
|
||||
if (rawRepresentation is Azure.AI.Agents.Persistent.RunStepDetailsUpdate persistentAgentStepDetailsUpdate)
|
||||
{
|
||||
builder.Append(persistentAgentStepDetailsUpdate.CodeInterpreterInput);
|
||||
builder.Append(string.Join(string.Empty, persistentAgentStepDetailsUpdate
|
||||
.CodeInterpreterOutputs
|
||||
.OfType<RunStepDeltaCodeInterpreterLogOutput>().SelectMany(l => l.Logs)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private OpenAIFileClient GetOpenAIFileClient() => OpenAIClient.GetOpenAIFileClient();
|
||||
|
||||
#endregion
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using GettingStarted.Tools.Abstractions;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace GettingStarted.Tools.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ChatOptions"/> conversion for Azure AI Persistent Agent.
|
||||
/// When abstraction is in place, this logic should go to Azure AI Persistent Agents SDK.
|
||||
/// </summary>
|
||||
internal static class AzureAIPersistentAgentChatOptionsExtensions
|
||||
{
|
||||
public static ChatOptions ToAzureAIPersistentAgentChatOptions(this ChatOptions chatOptions)
|
||||
{
|
||||
var fileIds = new List<string>();
|
||||
|
||||
foreach (var tool in chatOptions.Tools!)
|
||||
{
|
||||
if (tool is NewHostedCodeInterpreterTool codeInterpreterTool &&
|
||||
codeInterpreterTool.FileIds is { Count: > 0 })
|
||||
{
|
||||
fileIds.AddRange(codeInterpreterTool.FileIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileIds.Count > 0)
|
||||
{
|
||||
var toolResources = new Azure.AI.Agents.Persistent.ToolResources()
|
||||
{
|
||||
CodeInterpreter = new Azure.AI.Agents.Persistent.CodeInterpreterToolResource()
|
||||
};
|
||||
|
||||
foreach (var fileId in fileIds)
|
||||
{
|
||||
toolResources.CodeInterpreter.FileIds.Add(fileId);
|
||||
}
|
||||
|
||||
var threadAndRunOptions = new ThreadAndRunOptions { ToolResources = toolResources };
|
||||
|
||||
chatOptions.RawRepresentationFactory = (_) => threadAndRunOptions;
|
||||
}
|
||||
|
||||
return chatOptions;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using GettingStarted.Tools.Abstractions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Assistants;
|
||||
|
||||
#pragma warning disable OPENAI001
|
||||
|
||||
namespace GettingStarted.Tools.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ChatOptions"/> conversion for OpenAI Assistants.
|
||||
/// When abstraction is in place, this logic should go to OpenAI Assistants SDK.
|
||||
/// </summary>
|
||||
internal static class OpenAIAssistantChatOptionsExtensions
|
||||
{
|
||||
public static ChatOptions ToOpenAIAssistantChatOptions(this ChatOptions chatOptions)
|
||||
{
|
||||
// File references can be added on message attachment level only and not on code interpreter tool definition level.
|
||||
// Message attachment content should be non-empty.
|
||||
var threadInitializationMessage = new ThreadInitializationMessage(MessageRole.User, [MessageContent.FromText("attachments")]);
|
||||
var toolDefinitions = new List<ToolDefinition>();
|
||||
|
||||
foreach (var tool in chatOptions.Tools!)
|
||||
{
|
||||
if (tool is NewHostedCodeInterpreterTool codeInterpreterTool)
|
||||
{
|
||||
var codeInterpreterToolDefinition = new CodeInterpreterToolDefinition();
|
||||
toolDefinitions.Add(codeInterpreterToolDefinition);
|
||||
|
||||
if (codeInterpreterTool.FileIds is { Count: > 0 })
|
||||
{
|
||||
foreach (var fileId in codeInterpreterTool.FileIds)
|
||||
{
|
||||
threadInitializationMessage.Attachments.Add(new(fileId, [codeInterpreterToolDefinition]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var runCreationOptions = new RunCreationOptions();
|
||||
|
||||
runCreationOptions.AdditionalMessages.Add(threadInitializationMessage);
|
||||
|
||||
chatOptions.RawRepresentationFactory = (_) => runCreationOptions;
|
||||
|
||||
return chatOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
Item Quantity
|
||||
apple 3
|
||||
banana 2
|
||||
orange 5
|
||||
apple 1
|
||||
banana 3
|
||||
@@ -30,10 +30,13 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
private readonly string _agentId;
|
||||
|
||||
/// <summary>The thread ID to use if none is supplied in <see cref="ChatOptions.ConversationId"/>.</summary>
|
||||
private readonly string? _threadId;
|
||||
private readonly string? _defaultThreadId;
|
||||
|
||||
/// <summary>List of tools associated with the agent.</summary>
|
||||
private IReadOnlyList<ToolDefinition>? _agentTools;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="PersistentAgentsChatClient"/> class for the specified <see cref="PersistentAgentsClient"/>.</summary>
|
||||
public PersistentAgentsChatClient(PersistentAgentsClient client, string agentId, string? threadId)
|
||||
public PersistentAgentsChatClient(PersistentAgentsClient client, string agentId, string? defaultThreadId = null)
|
||||
{
|
||||
if (client is null)
|
||||
{
|
||||
@@ -47,7 +50,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
|
||||
this._client = client;
|
||||
this._agentId = agentId;
|
||||
this._threadId = threadId;
|
||||
this._defaultThreadId = defaultThreadId;
|
||||
|
||||
this._metadata = new(ProviderName);
|
||||
}
|
||||
@@ -76,10 +79,11 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
}
|
||||
|
||||
// Extract necessary state from messages and options.
|
||||
(ThreadAndRunOptions runOptions, List<FunctionResultContent>? toolResults) = this.CreateRunOptions(messages, options);
|
||||
(ThreadAndRunOptions runOptions, List<FunctionResultContent>? toolResults) =
|
||||
await this.CreateRunOptionsAsync(messages, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Get the thread ID.
|
||||
string? threadId = options?.ConversationId ?? this._threadId;
|
||||
string? threadId = options?.ConversationId ?? this._defaultThreadId;
|
||||
if (threadId is null && toolResults is not null)
|
||||
{
|
||||
throw new ArgumentException("No thread ID was provided, but chat messages includes tool results.", nameof(messages));
|
||||
@@ -89,7 +93,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
ThreadRun? threadRun = null;
|
||||
if (threadId is not null)
|
||||
{
|
||||
await foreach (var run in this._client.Runs.GetRunsAsync(threadId, limit: 1, ListSortOrder.Descending, cancellationToken: cancellationToken).ConfigureAwait(false))
|
||||
await foreach (ThreadRun? run in this._client.Runs.GetRunsAsync(threadId, limit: 1, ListSortOrder.Descending, cancellationToken: cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (run.Status != RunStatus.Completed && run.Status != RunStatus.Cancelled && run.Status != RunStatus.Failed && run.Status != RunStatus.Expired)
|
||||
{
|
||||
@@ -149,7 +153,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
|
||||
// Process each update.
|
||||
string? responseId = null;
|
||||
await foreach (var update in updates.ConfigureAwait(false))
|
||||
await foreach (StreamingUpdate? update in updates.ConfigureAwait(false))
|
||||
{
|
||||
switch (update)
|
||||
{
|
||||
@@ -226,8 +230,8 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
/// Creates the <see cref="ThreadAndRunOptions"/> to use for the request and extracts any function result contents
|
||||
/// that need to be submitted as tool results.
|
||||
/// </summary>
|
||||
private (ThreadAndRunOptions RunOptions, List<FunctionResultContent>? ToolResults) CreateRunOptions(
|
||||
IEnumerable<ChatMessage> messages, ChatOptions? options)
|
||||
private async ValueTask<(ThreadAndRunOptions RunOptions, List<FunctionResultContent>? ToolResults)> CreateRunOptionsAsync(
|
||||
IEnumerable<ChatMessage> messages, ChatOptions? options, CancellationToken cancellationToken)
|
||||
{
|
||||
// Create the options instance to populate, either a fresh or using one the caller provides.
|
||||
ThreadAndRunOptions runOptions =
|
||||
@@ -244,12 +248,32 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
runOptions.ParallelToolCalls ??= options.AllowMultipleToolCalls;
|
||||
// Ignored: options.TopK, options.FrequencyPenalty, options.Seed, options.StopSequences
|
||||
|
||||
// TODO: When moved to Azure.AI.Agents.Persistent, merge agent tools with override tools, in similar way like here:
|
||||
// https://github.com/dotnet/extensions/blob/694b95ef75c6bd9de00ef761dadae4e70ee8739f/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs#L263-L279
|
||||
if (options.Tools is { Count: > 0 } tools)
|
||||
{
|
||||
// The caller can provide tools in the supplied ThreadAndRunOptions. Augment it with any supplied via ChatOptions.Tools.
|
||||
IList<ToolDefinition> toolDefinitions = runOptions.OverrideTools is not null ? [.. runOptions.OverrideTools] : [];
|
||||
List<ToolDefinition> toolDefinitions = [];
|
||||
|
||||
// If the caller has provided any tool overrides, we'll assume they don't want to use the agent's tools.
|
||||
// But if they haven't, the only way we can provide our tools is via an override, whereas we'd really like to
|
||||
// just add them. To handle that, we'll get all of the agent's tools and add them to the override list
|
||||
// along with our tools.
|
||||
if (runOptions.OverrideTools is null || !runOptions.OverrideTools.Any())
|
||||
{
|
||||
if (this._agentTools is null)
|
||||
{
|
||||
PersistentAgent agent = await this._client.Administration.GetAgentAsync(this._agentId, cancellationToken).ConfigureAwait(false);
|
||||
this._agentTools = agent.Tools;
|
||||
}
|
||||
|
||||
toolDefinitions.AddRange(this._agentTools);
|
||||
}
|
||||
|
||||
// The caller can provide tools in the supplied ThreadAndRunOptions.
|
||||
if (runOptions.OverrideTools is not null)
|
||||
{
|
||||
toolDefinitions.AddRange(runOptions.OverrideTools);
|
||||
}
|
||||
|
||||
// Now add the tools from ChatOptions.Tools.
|
||||
foreach (AITool tool in tools)
|
||||
{
|
||||
switch (tool)
|
||||
@@ -318,7 +342,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
|
||||
runOptions.ThreadOptions ??= new();
|
||||
|
||||
foreach (var chatMessage in messages)
|
||||
foreach (ChatMessage chatMessage in messages)
|
||||
{
|
||||
List<MessageInputContentBlock> messageContents = [];
|
||||
|
||||
@@ -326,7 +350,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
chatMessage.Role == new ChatRole("developer"))
|
||||
{
|
||||
instructions ??= new();
|
||||
foreach (var textContent in chatMessage.Contents.OfType<TextContent>())
|
||||
foreach (TextContent textContent in chatMessage.Contents.OfType<TextContent>())
|
||||
{
|
||||
_ = instructions.Append(textContent);
|
||||
}
|
||||
@@ -347,7 +371,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
break;
|
||||
|
||||
case UriContent image when image.HasTopLevelMediaType("image"):
|
||||
messageContents.Add(new MessageInputImageUriBlock(new MessageImageUriParam(image.Uri.ToString())));
|
||||
messageContents.Add(new MessageInputImageUriBlock(new MessageImageUriParam(image.Uri.AbsoluteUri)));
|
||||
break;
|
||||
|
||||
case FunctionResultContent result:
|
||||
@@ -379,7 +403,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
return (runOptions, functionResults);
|
||||
}
|
||||
|
||||
/// <summary>Convert <see cref="FunctionResultContent"/> instances to <see cref="ToolOutput"/> instances."/></summary>
|
||||
/// <summary>Convert <see cref="FunctionResultContent"/> instances to <see cref="ToolOutput"/> instances.</summary>
|
||||
/// <param name="toolResults">The tool results to process.</param>
|
||||
/// <param name="toolOutputs">The generated list of tool outputs, if any could be created.</param>
|
||||
/// <returns>The run ID associated with the corresponding function call requests.</returns>
|
||||
@@ -389,7 +413,7 @@ public sealed partial class PersistentAgentsChatClient : IChatClient
|
||||
toolOutputs = null;
|
||||
if (toolResults?.Count > 0)
|
||||
{
|
||||
foreach (var frc in toolResults)
|
||||
foreach (FunctionResultContent frc in toolResults)
|
||||
{
|
||||
// When creating the FunctionCallContext, we created it with a CallId == [runId, callId].
|
||||
// We need to extract the run ID and ensure that the ToolOutput we send back to Azure
|
||||
|
||||
Reference in New Issue
Block a user