mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Getting started samples which use OpenAI exchange types (#598)
* Getting started samples which use OpenAI exchange types * Update dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/Extensions/AgentRunResponseUpdateExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/Extensions/AgentRunResponseUpdateExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/ChatCompletion/StreamingUpdatePipelineResponse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/AgentWithOpenAI/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix pipeline response * Update dotnet/samples/GettingStarted/AgentWithOpenAI/README.md Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> * Update comment to reflect OpenAI backend usage --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
24ad03af6f
commit
d54edf20c9
@@ -41,6 +41,9 @@
|
||||
<Project Path="samples/GettingStarted/Agents/Agent_Step08_Telemetry/Agent_Step08_Telemetry.csproj" />
|
||||
<Project Path="samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Agent_Step09_DependencyInjection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/AgentWithOpenAI/">
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/Telemetry/">
|
||||
<Project Path="samples/GettingStarted/AgentOpenTelemetry/AgentOpenTelemetry.csproj" />
|
||||
</Folder>
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with OpenAI as the backend.
|
||||
|
||||
using System;
|
||||
using System.ClientModel;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new OpenAIClient(apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
|
||||
UserChatMessage chatMessage = new("Tell me a joke about a pirate.");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
ChatCompletion chatCompletion = await agent.RunAsync(chatMessage);
|
||||
Console.WriteLine(chatCompletion.Content.Last().Text);
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
AsyncCollectionResult<StreamingChatCompletionUpdate> completionUpdates = agent.RunStreamingAsync(chatMessage);
|
||||
await foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)
|
||||
{
|
||||
if (completionUpdate.ContentUpdate.Count > 0)
|
||||
{
|
||||
Console.WriteLine(completionUpdate.ContentUpdate[0].Text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Agent Framework with OpenAI
|
||||
|
||||
These samples show how to use the Agent Framework with the OpenAI exchange types.
|
||||
|
||||
By default, the .Net version of Agent Framework uses the [Microsoft.Extensions.AI.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.AI.Abstractions/) exchange types.
|
||||
|
||||
For developers who are using the [OpenAI SDK](https://www.nuget.org/packages/OpenAI) this can be problematic because there are conflicting exchange types which can cause confusion.
|
||||
|
||||
Agent Framework provides additional support to allow OpenAI developers to use the OpenAI exchange types.
|
||||
|
||||
|Sample|Description|
|
||||
|---|---|
|
||||
|[Creating an AIAgent](./Agent_OpenAI_Step01_Running/)|This sample demonstrates how to create and run a basic agent instructions with native OpenAI SDK types.|
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.OpenAI.ChatCompletion;
|
||||
|
||||
internal sealed class AsyncStreamingUpdateCollectionResult : AsyncCollectionResult<StreamingChatCompletionUpdate>
|
||||
{
|
||||
private readonly IAsyncEnumerable<AgentRunResponseUpdate> _updates;
|
||||
|
||||
internal AsyncStreamingUpdateCollectionResult(IAsyncEnumerable<AgentRunResponseUpdate> updates)
|
||||
{
|
||||
this._updates = updates;
|
||||
}
|
||||
|
||||
public override ContinuationToken? GetContinuationToken(ClientResult page) => null;
|
||||
|
||||
public override IAsyncEnumerable<ClientResult> GetRawPagesAsync()
|
||||
{
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
return AsyncEnumerable.Repeat(ClientResult.FromValue(this._updates, new StreamingUpdatePipelineResponse(this._updates)), 1);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
}
|
||||
|
||||
protected async override IAsyncEnumerable<StreamingChatCompletionUpdate> GetValuesFromPageAsync(ClientResult page)
|
||||
{
|
||||
var updates = ((ClientResult<IAsyncEnumerable<AgentRunResponseUpdate>>)page).Value;
|
||||
|
||||
await foreach (var update in updates.ConfigureAwait(false))
|
||||
{
|
||||
yield return update.AsStreamingChatCompletionUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel.Primitives;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.OpenAI.ChatCompletion;
|
||||
|
||||
internal sealed class StreamingUpdatePipelineResponse : PipelineResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the HTTP status code. For streaming responses, this is typically 200.
|
||||
/// </summary>
|
||||
public override int Status => 200;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason phrase. For streaming responses, this is typically "OK".
|
||||
/// </summary>
|
||||
public override string ReasonPhrase => "OK";
|
||||
|
||||
/// <summary>
|
||||
/// Streaming responses do not support direct content stream access.
|
||||
/// </summary>
|
||||
public override Stream? ContentStream
|
||||
{
|
||||
get => null;
|
||||
set { /* no-op */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Streaming responses do not support direct content access.
|
||||
/// </summary>
|
||||
public override BinaryData Content => BinaryData.FromString(string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Streaming responses do not have headers.
|
||||
/// </summary>
|
||||
protected override PipelineResponseHeaders HeadersCore => new EmptyPipelineResponseHeaders();
|
||||
|
||||
/// <summary>
|
||||
/// Buffering content is not supported for streaming responses.
|
||||
/// </summary>
|
||||
public override BinaryData BufferContent(CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotSupportedException("Buffering content is not supported for streaming responses.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffering content asynchronously is not supported for streaming responses.
|
||||
/// </summary>
|
||||
public override ValueTask<BinaryData> BufferContentAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotSupportedException("Buffering content asynchronously is not supported for streaming responses.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes resources. No resources to dispose for streaming response.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
// No resources to dispose.
|
||||
}
|
||||
|
||||
internal StreamingUpdatePipelineResponse(IAsyncEnumerable<AgentRunResponseUpdate> updates)
|
||||
{
|
||||
this._updates = updates;
|
||||
}
|
||||
|
||||
private readonly IAsyncEnumerable<AgentRunResponseUpdate> _updates;
|
||||
|
||||
private sealed class EmptyPipelineResponseHeaders : PipelineResponseHeaders
|
||||
{
|
||||
public override bool TryGetValue(string name, out string? value)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
public override bool TryGetValues(string name, out IEnumerable<string>? values)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
public override IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -1,8 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Extensions.AI.Agents.OpenAI.ChatCompletion;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
using OpenAI.Chat;
|
||||
|
||||
@@ -74,6 +76,58 @@ public static class AIAgentWithOpenAIExtensions
|
||||
return chatCompletion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the AI agent with a single OpenAI chat message and returns the response as collection of native OpenAI <see cref="StreamingChatCompletionUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="agent">The AI agent to run.</param>
|
||||
/// <param name="message">The OpenAI chat message to send to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided message and agent response.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="Task{ChatCompletion}"/> representing the asynchronous operation that returns a native OpenAI <see cref="ChatCompletion"/> response.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agent"/> or <paramref name="message"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the agent's response cannot be converted to a <see cref="ChatCompletion"/>, typically when the underlying representation is not an OpenAI response.</exception>
|
||||
/// <exception cref="NotSupportedException">Thrown when the <paramref name="message"/> type is not supported by the message conversion method.</exception>
|
||||
/// <remarks>
|
||||
/// This method converts the OpenAI chat message to the Microsoft Extensions AI format using the appropriate conversion method,
|
||||
/// runs the agent, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsChatCompletion"/>.
|
||||
/// </remarks>
|
||||
public static AsyncCollectionResult<StreamingChatCompletionUpdate> RunStreamingAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(agent);
|
||||
Throw.IfNull(message);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> response = agent.RunStreamingAsync(message.AsChatMessage(), thread, options, cancellationToken);
|
||||
|
||||
return new AsyncStreamingUpdateCollectionResult(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the AI agent with a single OpenAI chat message and returns the response as collection of native OpenAI <see cref="StreamingChatCompletionUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="agent">The AI agent to run.</param>
|
||||
/// <param name="messages">The collection of OpenAI chat messages to send to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided message and agent response.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="Task{ChatCompletion}"/> representing the asynchronous operation that returns a native OpenAI <see cref="ChatCompletion"/> response.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agent"/> or <paramref name="messages"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the agent's response cannot be converted to a <see cref="ChatCompletion"/>, typically when the underlying representation is not an OpenAI response.</exception>
|
||||
/// <exception cref="NotSupportedException">Thrown when the <paramref name="messages"/> type is not supported by the message conversion method.</exception>
|
||||
/// <remarks>
|
||||
/// This method converts the OpenAI chat message to the Microsoft Extensions AI format using the appropriate conversion method,
|
||||
/// runs the agent, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsChatCompletion"/>.
|
||||
/// </remarks>
|
||||
public static AsyncCollectionResult<StreamingChatCompletionUpdate> RunStreamingAsync(this AIAgent agent, IEnumerable<OpenAI.Chat.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(agent);
|
||||
Throw.IfNull(messages);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
|
||||
|
||||
return new AsyncStreamingUpdateCollectionResult(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sequence of <see cref="Microsoft.Extensions.AI.ChatMessage"/> instances from the specified OpenAI input messages.
|
||||
/// </summary>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.OpenAI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="AgentRunResponseUpdate"/> to extract native OpenAI response objects
|
||||
/// from the Microsoft Extensions AI Agent framework responses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These extensions enable developers to access the underlying OpenAI SDK objects when working with
|
||||
/// AI agents that are backed by OpenAI services. The methods extract strongly-typed OpenAI responses
|
||||
/// from the <see cref="AgentRunResponseUpdate.RawRepresentation"/> property, providing a bridge between
|
||||
/// the Microsoft Extensions AI framework and the native OpenAI SDK types.
|
||||
/// </remarks>
|
||||
public static class AgentRunResponseUpdateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts a native OpenAI <see cref="StreamingChatCompletionUpdate"/> object from an <see cref="AgentRunResponseUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="agentResponseUpdate">The agent response containing the raw OpenAI representation.</param>
|
||||
/// <returns>The native OpenAI <see cref="StreamingChatCompletionUpdate"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentResponseUpdate"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the <see cref="AgentRunResponseUpdate.RawRepresentation"/> is not a <see cref="ChatResponseUpdate"/> object,
|
||||
/// or when the nested <see cref="ChatResponseUpdate.RawRepresentation"/> is not a <see cref="StreamingChatCompletionUpdate"/> object.
|
||||
/// This typically occurs when the agent response was not generated by an OpenAI streaming chat completion service
|
||||
/// or when the underlying representation has been modified or corrupted.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method provides access to the native OpenAI <see cref="StreamingChatCompletionUpdate"/> object that was used
|
||||
/// to generate the agent response. This is useful when you need to access OpenAI-specific properties
|
||||
/// or metadata that are not exposed through the Microsoft Extensions AI abstractions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static StreamingChatCompletionUpdate AsStreamingChatCompletionUpdate(this AgentRunResponseUpdate agentResponseUpdate)
|
||||
{
|
||||
Throw.IfNull(agentResponseUpdate);
|
||||
|
||||
if (agentResponseUpdate.RawRepresentation is ChatResponseUpdate chatResponseUpdate)
|
||||
{
|
||||
return chatResponseUpdate.RawRepresentation is StreamingChatCompletionUpdate streamingChatCompletionUpdate
|
||||
? streamingChatCompletionUpdate
|
||||
: throw new ArgumentException("ChatResponseUpdate.RawRepresentation must be a StreamingChatCompletionUpdate");
|
||||
}
|
||||
throw new ArgumentException("AgentRunResponseUpdate.RawRepresentation must be a ChatResponseUpdate");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user