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:
@@ -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