diff --git a/dotnet/samples/GettingStarted/AgentSample.cs b/dotnet/samples/GettingStarted/AgentSample.cs index a8617cb759..620c395d51 100644 --- a/dotnet/samples/GettingStarted/AgentSample.cs +++ b/dotnet/samples/GettingStarted/AgentSample.cs @@ -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) /// 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 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()); + /// /// 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 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 GetOpenAIResponsesClientAsync() => Task.FromResult( - new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + OpenAIClient .GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId) .AsIChatClient()); private async Task 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 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 diff --git a/dotnet/samples/GettingStarted/GettingStarted.csproj b/dotnet/samples/GettingStarted/GettingStarted.csproj index 7539526f82..292b9fa311 100644 --- a/dotnet/samples/GettingStarted/GettingStarted.csproj +++ b/dotnet/samples/GettingStarted/GettingStarted.csproj @@ -34,4 +34,10 @@ - \ No newline at end of file + + + + Always + + + diff --git a/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs b/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs index 95799fcd70..1f4805dcba 100644 --- a/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs +++ b/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs @@ -20,8 +20,8 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A /// a unique interaction with no conversation history between them. /// [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 where a conversation history is maintained. /// [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 . /// [Theory] - [InlineData(ChatClientProviders.OpenAI)] [InlineData(ChatClientProviders.AzureOpenAI)] [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] [InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)] diff --git a/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingTools.cs b/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingTools.cs index 88a2145ea3..603476503e 100644 --- a/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingTools.cs +++ b/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingTools.cs @@ -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. diff --git a/dotnet/samples/GettingStarted/Tools/Abstractions/NewHostedCodeInterpreterTool.cs b/dotnet/samples/GettingStarted/Tools/Abstractions/NewHostedCodeInterpreterTool.cs new file mode 100644 index 0000000000..8f7ec5dedc --- /dev/null +++ b/dotnet/samples/GettingStarted/Tools/Abstractions/NewHostedCodeInterpreterTool.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; + +namespace GettingStarted.Tools.Abstractions; + +/// +/// Proposal for abstraction updates based on the common code interpreter tool properties. +/// Based on the decision, the abstraction can be updated in M.E.AI or specific SDK if some properties are not common. +/// +public class NewHostedCodeInterpreterTool : HostedCodeInterpreterTool +{ + public IList? FileIds { get; set; } +} diff --git a/dotnet/samples/GettingStarted/Tools/CodeInterpreterTools.cs b/dotnet/samples/GettingStarted/Tools/CodeInterpreterTools.cs new file mode 100644 index 0000000000..a3444a48cd --- /dev/null +++ b/dotnet/samples/GettingStarted/Tools/CodeInterpreterTools.cs @@ -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 + + /// + /// 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. + /// + private static ChatOptions TransformChatOptions(ChatOptions chatOptions, ChatClientProviders provider) + { + return provider switch + { + ChatClientProviders.OpenAIAssistant => chatOptions.ToOpenAIAssistantChatOptions(), + ChatClientProviders.AzureAIAgentsPersistent => chatOptions.ToAzureAIPersistentAgentChatOptions(), + _ => chatOptions + }; + } + + private Task UploadTestFileAsync(ChatClientProviders provider) + { + var filePath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "Tools", "Files", "groceries.txt")); + return UploadFileAsync(filePath, provider); + } + + private async Task 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().SelectMany(l => l.Logs))); + } + + break; + } + } + + private OpenAIFileClient GetOpenAIFileClient() => OpenAIClient.GetOpenAIFileClient(); + + #endregion +} diff --git a/dotnet/samples/GettingStarted/Tools/Extensions/AzureAIPersistentAgentChatOptionsExtensions.cs b/dotnet/samples/GettingStarted/Tools/Extensions/AzureAIPersistentAgentChatOptionsExtensions.cs new file mode 100644 index 0000000000..1398403935 --- /dev/null +++ b/dotnet/samples/GettingStarted/Tools/Extensions/AzureAIPersistentAgentChatOptionsExtensions.cs @@ -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; + +/// +/// conversion for Azure AI Persistent Agent. +/// When abstraction is in place, this logic should go to Azure AI Persistent Agents SDK. +/// +internal static class AzureAIPersistentAgentChatOptionsExtensions +{ + public static ChatOptions ToAzureAIPersistentAgentChatOptions(this ChatOptions chatOptions) + { + var fileIds = new List(); + + 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; + } +} diff --git a/dotnet/samples/GettingStarted/Tools/Extensions/OpenAIAssistantChatOptionsExtensions.cs b/dotnet/samples/GettingStarted/Tools/Extensions/OpenAIAssistantChatOptionsExtensions.cs new file mode 100644 index 0000000000..bdba77517d --- /dev/null +++ b/dotnet/samples/GettingStarted/Tools/Extensions/OpenAIAssistantChatOptionsExtensions.cs @@ -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; + +/// +/// conversion for OpenAI Assistants. +/// When abstraction is in place, this logic should go to OpenAI Assistants SDK. +/// +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(); + + 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; + } +} diff --git a/dotnet/samples/GettingStarted/Tools/Files/groceries.txt b/dotnet/samples/GettingStarted/Tools/Files/groceries.txt new file mode 100644 index 0000000000..350cfba875 --- /dev/null +++ b/dotnet/samples/GettingStarted/Tools/Files/groceries.txt @@ -0,0 +1,6 @@ +Item Quantity +apple 3 +banana 2 +orange 5 +apple 1 +banana 3 diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsChatClient.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsChatClient.cs index dea65abd9e..21c383071a 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsChatClient.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsChatClient.cs @@ -30,10 +30,13 @@ public sealed partial class PersistentAgentsChatClient : IChatClient private readonly string _agentId; /// The thread ID to use if none is supplied in . - private readonly string? _threadId; + private readonly string? _defaultThreadId; + + /// List of tools associated with the agent. + private IReadOnlyList? _agentTools; /// Initializes a new instance of the class for the specified . - 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? toolResults) = this.CreateRunOptions(messages, options); + (ThreadAndRunOptions runOptions, List? 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 to use for the request and extracts any function result contents /// that need to be submitted as tool results. /// - private (ThreadAndRunOptions RunOptions, List? ToolResults) CreateRunOptions( - IEnumerable messages, ChatOptions? options) + private async ValueTask<(ThreadAndRunOptions RunOptions, List? ToolResults)> CreateRunOptionsAsync( + IEnumerable 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 toolDefinitions = runOptions.OverrideTools is not null ? [.. runOptions.OverrideTools] : []; + List 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 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()) + foreach (TextContent textContent in chatMessage.Contents.OfType()) { _ = 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); } - /// Convert instances to instances."/> + /// Convert instances to instances. /// The tool results to process. /// The generated list of tool outputs, if any could be created. /// The run ID associated with the corresponding function call requests. @@ -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