mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into javiercn/mapagui-hosting-overloads
This commit is contained in:
@@ -1636,4 +1636,4 @@ The property mapping guide from a `AutoFunctionInvocationContext` to a `Function
|
||||
| Result | Use `return` from the delegate |
|
||||
| Terminate | Terminate |
|
||||
| CancellationToken | provided via argument to middleware delegate |
|
||||
| Arguments | Arguments |
|
||||
| Arguments | Arguments |
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Aspire.* -->
|
||||
<PackageVersion Include="Anthropic" Version="10.4.0" />
|
||||
<PackageVersion Include="Anthropic.Foundry" Version="0.0.2" />
|
||||
<PackageVersion Include="Anthropic" Version="11.0.0" />
|
||||
<PackageVersion Include="Anthropic.Foundry" Version="0.1.0" />
|
||||
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
|
||||
@@ -21,7 +21,7 @@
|
||||
<!-- Azure.* -->
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="1.2.0-beta.3" />
|
||||
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="1.0.0-beta.4" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.7" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.8" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.7.0-beta.1" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
|
||||
@@ -46,6 +46,7 @@
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Threading.Channels" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
|
||||
<!-- OpenTelemetry -->
|
||||
<PackageVersion Include="OpenTelemetry" Version="1.13.1" />
|
||||
<PackageVersion Include="OpenTelemetry.Api" Version="1.13.1" />
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
<File Path="samples/GettingStarted/AgentWithOpenAI/README.md" />
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/Purview/" />
|
||||
<Folder Name="/Samples/Purview/AgentWithPurview/">
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251125.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251125.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.251125.1</GitTag>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251204.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251204.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.251204.1</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -14,11 +14,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
|
||||
-5
@@ -15,11 +15,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
|
||||
+1
@@ -11,6 +11,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Anthropic.Foundry" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
// This sample shows how to create and use an AI agent with Anthropic as the backend.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.Net.Http.Headers;
|
||||
using Anthropic;
|
||||
using Anthropic.Core;
|
||||
using Anthropic.Foundry;
|
||||
using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
@@ -15,8 +14,8 @@ var deploymentName = Environment.GetEnvironmentVariable("ANTHROPIC_DEPLOYMENT_NA
|
||||
|
||||
// The resource is the subdomain name / first name coming before '.services.ai.azure.com' in the endpoint Uri
|
||||
// ie: https://(resource name).services.ai.azure.com/anthropic/v1/chat/completions
|
||||
var resource = Environment.GetEnvironmentVariable("ANTHROPIC_RESOURCE");
|
||||
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
|
||||
string? resource = Environment.GetEnvironmentVariable("ANTHROPIC_RESOURCE");
|
||||
string? apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
|
||||
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
const string JokerName = "JokerAgent";
|
||||
@@ -24,8 +23,8 @@ const string JokerName = "JokerAgent";
|
||||
AnthropicClient? client = (resource is null)
|
||||
? new AnthropicClient() { APIKey = apiKey ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is required when no ANTHROPIC_RESOURCE is provided") } // If no resource is provided, use Anthropic public API
|
||||
: (apiKey is not null)
|
||||
? new AnthropicFoundryClient(resource, new ApiKeyCredential(apiKey)) // If an apiKey is provided, use Foundry with ApiKey authentication
|
||||
: new AnthropicFoundryClient(resource, new AzureCliCredential()); // Otherwise, use Foundry with Azure Client authentication
|
||||
? new AnthropicFoundryClient(new AnthropicFoundryApiKeyCredentials(apiKey, resource)) // If an apiKey is provided, use Foundry with ApiKey authentication
|
||||
: new AnthropicFoundryClient(new AnthropicAzureTokenCredential(new AzureCliCredential(), resource)); // Otherwise, use Foundry with Azure Client authentication
|
||||
|
||||
AIAgent agent = client.CreateAIAgent(model: deploymentName, instructions: JokerInstructions, name: JokerName);
|
||||
|
||||
@@ -35,67 +34,41 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
namespace Sample
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for invoking the Azure hosted Anthropic api.
|
||||
/// Provides methods for invoking the Azure hosted Anthropic models using <see cref="TokenCredential"/> types.
|
||||
/// </summary>
|
||||
public class AnthropicFoundryClient : AnthropicClient
|
||||
public sealed class AnthropicAzureTokenCredential : IAnthropicFoundryCredentials
|
||||
{
|
||||
private readonly TokenCredential _tokenCredential;
|
||||
private readonly string _resourceName;
|
||||
private readonly Lock _lock = new();
|
||||
private AccessToken? _cachedAccessToken;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ResourceName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AnthropicFoundryClient"/>.
|
||||
/// Creates a new instance of the <see cref="AnthropicAzureTokenCredential"/>.
|
||||
/// </summary>
|
||||
/// <param name="resourceName">The service resource subdomain name to use in the anthropic azure endpoint</param>
|
||||
/// <param name="tokenCredential">The credential provider. Use any specialization of <see cref="TokenCredential"/> to get your access token in supported environments.</param>
|
||||
/// <param name="options">Set of <see cref="Anthropic.Core.ClientOptions"/> client option configurations</param>
|
||||
/// <exception cref="ArgumentNullException">Resource is null</exception>
|
||||
/// <exception cref="ArgumentNullException">TokenCredential is null</exception>
|
||||
/// <remarks>
|
||||
/// Any <see cref="Anthropic.Core.ClientOptions"/> APIKey or Bearer token provided will be ignored in favor of the <see cref="TokenCredential"/> provided in the constructor
|
||||
/// </remarks>
|
||||
public AnthropicFoundryClient(string resourceName, TokenCredential tokenCredential, Anthropic.Core.ClientOptions? options = null) : base(options ?? new())
|
||||
/// <param name="resourceName">The service resource subdomain name to use in the anthropic azure endpoint</param>
|
||||
internal AnthropicAzureTokenCredential(TokenCredential tokenCredential, string resourceName)
|
||||
{
|
||||
this._resourceName = resourceName ?? throw new ArgumentNullException(nameof(resourceName));
|
||||
this.ResourceName = resourceName ?? throw new ArgumentNullException(nameof(resourceName));
|
||||
this._tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential));
|
||||
this.BaseUrl = new Uri($"https://{this._resourceName}.services.ai.azure.com/anthropic", UriKind.Absolute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AnthropicFoundryClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="resourceName">The service resource subdomain name to use in the anthropic azure endpoint</param>
|
||||
/// <param name="apiKeyCredential">The api key.</param>
|
||||
/// <param name="options">Set of <see cref="Anthropic.Core.ClientOptions"/> client option configurations</param>
|
||||
/// <exception cref="ArgumentNullException">Resource is null</exception>
|
||||
/// <exception cref="ArgumentNullException">Api key is null</exception>
|
||||
/// <remarks>
|
||||
/// Any <see cref="Anthropic.Core.ClientOptions"/> APIKey or Bearer token provided will be ignored in favor of the <see cref="ApiKeyCredential"/> provided in the constructor
|
||||
/// </remarks>
|
||||
public AnthropicFoundryClient(string resourceName, ApiKeyCredential apiKeyCredential, Anthropic.Core.ClientOptions? options = null) :
|
||||
this(resourceName, apiKeyCredential is null
|
||||
? throw new ArgumentNullException(nameof(apiKeyCredential))
|
||||
: DelegatedTokenCredential.Create((_, _) =>
|
||||
{
|
||||
apiKeyCredential.Deconstruct(out string dangerousCredential);
|
||||
return new AccessToken(dangerousCredential, DateTimeOffset.MaxValue);
|
||||
}),
|
||||
options)
|
||||
{ }
|
||||
|
||||
public override IAnthropicClient WithOptions(Func<Anthropic.Core.ClientOptions, Anthropic.Core.ClientOptions> modifier)
|
||||
=> this;
|
||||
|
||||
protected override ValueTask BeforeSend<T>(
|
||||
HttpRequest<T> request,
|
||||
HttpRequestMessage requestMessage,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
/// <inheritdoc/>
|
||||
public void Apply(HttpRequestMessage requestMessage)
|
||||
{
|
||||
var accessToken = this._tokenCredential.GetToken(new TokenRequestContext(scopes: ["https://ai.azure.com/.default"]), cancellationToken);
|
||||
lock (this._lock)
|
||||
{
|
||||
// Add a 5-minute buffer to avoid using tokens that are about to expire
|
||||
if (this._cachedAccessToken is null || this._cachedAccessToken.Value.ExpiresOn <= DateTimeOffset.Now.AddMinutes(5))
|
||||
{
|
||||
this._cachedAccessToken = this._tokenCredential.GetToken(new TokenRequestContext(scopes: ["https://ai.azure.com/.default"]), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken.Token);
|
||||
|
||||
return default;
|
||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", this._cachedAccessToken.Value.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);IDE0059</NoWarn>
|
||||
<NoWarn>$(NoWarn);IDE0059;NU1510</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -14,6 +14,10 @@
|
||||
<PackageReference Include="Mscc.GenerativeAI.Microsoft" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="System.Net.Security" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are good at telling jokes.",
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
AIContextProviderFactory = (ctx) => new ChatHistoryMemoryProvider(
|
||||
vectorStore,
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details.",
|
||||
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
|
||||
AIContextProviderFactory = ctx => ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
|
||||
// If each thread should have its own Mem0 scope, you can create a new id per thread here:
|
||||
// ? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ThreadId = Guid.NewGuid().ToString() })
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
// and its storage to that user id.
|
||||
AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
Instructions = "You are a friendly assistant. Always address the user by their name.",
|
||||
ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
|
||||
AIContextProviderFactory = ctx => new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions)
|
||||
});
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to create an AI agent directly from an OpenAI.Chat.ChatClient instance using OpenAIChatClientAgent.
|
||||
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
|
||||
string model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
|
||||
// Create a ChatClient directly from OpenAIClient
|
||||
ChatClient chatClient = new OpenAIClient(apiKey).GetChatClient(model);
|
||||
|
||||
// Create an agent directly from the ChatClient using OpenAIChatClientAgent
|
||||
OpenAIChatClientAgent agent = new(chatClient, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
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.
|
||||
IAsyncEnumerable<StreamingChatCompletionUpdate> completionUpdates = agent.RunStreamingAsync([chatMessage]);
|
||||
await foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)
|
||||
{
|
||||
if (completionUpdate.ContentUpdate.Count > 0)
|
||||
{
|
||||
Console.WriteLine(completionUpdate.ContentUpdate[0].Text);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Creating an Agent from a ChatClient
|
||||
|
||||
This sample demonstrates how to create an AI agent directly from an `OpenAI.Chat.ChatClient` instance using the `OpenAIChatClientAgent` class.
|
||||
|
||||
## What This Sample Shows
|
||||
|
||||
- **Direct ChatClient Creation**: Shows how to create an `OpenAI.Chat.ChatClient` from `OpenAI.OpenAIClient` and then use it to instantiate an agent
|
||||
- **OpenAIChatClientAgent**: Demonstrates using the OpenAI SDK primitives instead of the ones from Microsoft.Extensions.AI and Microsoft.Agents.AI abstractions
|
||||
- **Full Agent Capabilities**: Shows both regular and streaming invocation of the agent
|
||||
|
||||
## Running the Sample
|
||||
|
||||
1. Set the required environment variables:
|
||||
```bash
|
||||
set OPENAI_API_KEY=your_api_key_here
|
||||
set OPENAI_MODEL=gpt-4o-mini
|
||||
```
|
||||
|
||||
2. Run the sample:
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to create OpenAIResponseClientAgent directly from an OpenAIResponseClient instance.
|
||||
|
||||
using OpenAI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
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";
|
||||
|
||||
// Create an OpenAIResponseClient directly from OpenAIClient
|
||||
OpenAIResponseClient responseClient = new OpenAIClient(apiKey).GetOpenAIResponseClient(model);
|
||||
|
||||
// Create an agent directly from the OpenAIResponseClient using OpenAIResponseClientAgent
|
||||
OpenAIResponseClientAgent agent = new(responseClient, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
ResponseItem userMessage = ResponseItem.CreateUserMessageItem("Tell me a joke about a pirate.");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
OpenAIResponse response = await agent.RunAsync([userMessage]);
|
||||
Console.WriteLine(response.GetOutputText());
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
IAsyncEnumerable<StreamingResponseUpdate> responseUpdates = agent.RunStreamingAsync([userMessage]);
|
||||
await foreach (StreamingResponseUpdate responseUpdate in responseUpdates)
|
||||
{
|
||||
if (responseUpdate is StreamingResponseOutputTextDeltaUpdate textUpdate)
|
||||
{
|
||||
Console.WriteLine(textUpdate.Delta);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Creating an Agent from an OpenAIResponseClient
|
||||
|
||||
This sample demonstrates how to create an AI agent directly from an `OpenAI.Responses.OpenAIResponseClient` instance using the `OpenAIResponseClientAgent` class.
|
||||
|
||||
## What This Sample Shows
|
||||
|
||||
- **Direct OpenAIResponseClient Creation**: Shows how to create an `OpenAI.Responses.OpenAIResponseClient` from `OpenAI.OpenAIClient` and then use it to instantiate an agent
|
||||
- **OpenAIResponseClientAgent**: Demonstrates using the OpenAI SDK primitives instead of the ones from Microsoft.Extensions.AI and Microsoft.Agents.AI abstractions
|
||||
- **Full Agent Capabilities**: Shows both regular and streaming invocation of the agent
|
||||
|
||||
## Running the Sample
|
||||
|
||||
1. Set the required environment variables:
|
||||
```bash
|
||||
set OPENAI_API_KEY=your_api_key_here
|
||||
set OPENAI_MODEL=gpt-4o-mini
|
||||
```
|
||||
|
||||
2. Run the sample:
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
@@ -10,5 +10,7 @@ Agent Framework provides additional support to allow OpenAI developers to use th
|
||||
|
||||
|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.|
|
||||
|
||||
|[Creating an AIAgent](./Agent_OpenAI_Step01_Running/)|This sample demonstrates how to create and run a basic agent with native OpenAI SDK types. Shows both regular and streaming invocation of the agent.|
|
||||
|[Using Reasoning Capabilities](./Agent_OpenAI_Step02_Reasoning/)|This sample demonstrates how to create an AI agent with reasoning capabilities using OpenAI's reasoning models and response types.|
|
||||
|[Creating an Agent from a ChatClient](./Agent_OpenAI_Step03_CreateFromChatClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Chat.ChatClient instance using OpenAIChatClientAgent.|
|
||||
|[Creating an Agent from an OpenAIResponseClient](./Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Responses.OpenAIResponseClient instance using OpenAIResponseClientAgent.|
|
||||
+1
-2
@@ -8,7 +8,6 @@
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Data;
|
||||
using Microsoft.Agents.AI.Samples;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
@@ -62,7 +61,7 @@ AIAgent agent = azureOpenAIClient
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
|
||||
ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
|
||||
AIContextProviderFactory = ctx => new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
|
||||
});
|
||||
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Data;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.Qdrant;
|
||||
@@ -71,7 +70,7 @@ AIAgent agent = azureOpenAIClient
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief.",
|
||||
ChatOptions = new() { Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief." },
|
||||
AIContextProviderFactory = ctx => new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
|
||||
});
|
||||
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Data;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
|
||||
@@ -29,7 +28,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
|
||||
ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
|
||||
AIContextProviderFactory = ctx => new TextSearchProvider(MockSearchAsync, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
// Create the ChatClientAgent with the specified name and instructions.
|
||||
ChatClientAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant."));
|
||||
ChatClientAgent agent = chatClient.CreateAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
|
||||
|
||||
// Set PersonInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke the agent with some unstructured input.
|
||||
AgentRunResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
@@ -34,12 +34,10 @@ Console.WriteLine($"Age: {response.Result.Age}");
|
||||
Console.WriteLine($"Occupation: {response.Result.Occupation}");
|
||||
|
||||
// Create the ChatClientAgent with the specified name, instructions, and expected structured output the agent should produce.
|
||||
ChatClientAgent agentWithPersonInfo = chatClient.CreateAIAgent(new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.")
|
||||
ChatClientAgent agentWithPersonInfo = chatClient.CreateAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<PersonInfo>()
|
||||
}
|
||||
Name = "HelpfulAssistant",
|
||||
ChatOptions = new() { Instructions = "You are a helpful assistant.", ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<PersonInfo>() }
|
||||
});
|
||||
|
||||
// Invoke the agent with some unstructured input while streaming, to extract the structured information from.
|
||||
|
||||
@@ -28,7 +28,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are good at telling jokes.",
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
ChatMessageStoreFactory = ctx =>
|
||||
{
|
||||
|
||||
@@ -18,8 +18,7 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Add agent options to the service collection.
|
||||
builder.Services.AddSingleton(
|
||||
new ChatClientAgentOptions(instructions: "You are good at telling jokes.", name: "Joker"));
|
||||
builder.Services.AddSingleton(new ChatClientAgentOptions() { Name = "Joker", ChatOptions = new() { Instructions = "You are good at telling jokes." } });
|
||||
|
||||
// Add a chat client to the service collection.
|
||||
builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient(
|
||||
|
||||
-4
@@ -16,10 +16,6 @@
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI.Persistent\Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -21,7 +21,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are good at telling jokes.",
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)
|
||||
});
|
||||
|
||||
+6
-2
@@ -24,10 +24,12 @@ AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential(
|
||||
// Create ChatClientAgent directly
|
||||
ChatClientAgent agent = await aiProjectClient.CreateAIAgentAsync(
|
||||
model: deploymentName,
|
||||
new ChatClientAgentOptions(name: AssistantName, instructions: AssistantInstructions)
|
||||
new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AssistantName,
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AssistantInstructions,
|
||||
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<PersonInfo>()
|
||||
}
|
||||
});
|
||||
@@ -44,10 +46,12 @@ Console.WriteLine($"Occupation: {response.Result.Occupation}");
|
||||
// Create the ChatClientAgent with the specified name, instructions, and expected structured output the agent should produce.
|
||||
ChatClientAgent agentWithPersonInfo = aiProjectClient.CreateAIAgent(
|
||||
model: deploymentName,
|
||||
new ChatClientAgentOptions(name: AssistantName, instructions: AssistantInstructions)
|
||||
new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AssistantName,
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AssistantInstructions,
|
||||
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<PersonInfo>()
|
||||
}
|
||||
});
|
||||
|
||||
-4
@@ -17,10 +17,6 @@
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
+2
-2
@@ -34,9 +34,9 @@ AIAgent agent = await persistentAgentsClient.CreateAIAgentAsync(
|
||||
options: new()
|
||||
{
|
||||
Name = "MicrosoftLearnAgent",
|
||||
Instructions = "You answer questions by searching the Microsoft Learn content only.",
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You answer questions by searching the Microsoft Learn content only.",
|
||||
Tools = [mcpTool]
|
||||
},
|
||||
});
|
||||
@@ -67,9 +67,9 @@ AIAgent agentWithRequiredApproval = await persistentAgentsClient.CreateAIAgentAs
|
||||
options: new()
|
||||
{
|
||||
Name = "MicrosoftLearnAgentWithApproval",
|
||||
Instructions = "You answer questions by searching the Microsoft Learn content only.",
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You answer questions by searching the Microsoft Learn content only.",
|
||||
Tools = [mcpToolWithApproval]
|
||||
},
|
||||
});
|
||||
|
||||
@@ -118,10 +118,11 @@ internal sealed class SloganWriterExecutor : Executor
|
||||
/// <param name="chatClient">The chat client to use for the AI agent.</param>
|
||||
public SloganWriterExecutor(string id, IChatClient chatClient) : base(id)
|
||||
{
|
||||
ChatClientAgentOptions agentOptions = new(instructions: "You are a professional slogan writer. You will be given a task to create a slogan.")
|
||||
ChatClientAgentOptions agentOptions = new()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are a professional slogan writer. You will be given a task to create a slogan.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<SloganResult>()
|
||||
}
|
||||
};
|
||||
@@ -193,10 +194,11 @@ internal sealed class FeedbackExecutor : Executor<SloganResult>
|
||||
/// <param name="chatClient">The chat client to use for the AI agent.</param>
|
||||
public FeedbackExecutor(string id, IChatClient chatClient) : base(id)
|
||||
{
|
||||
ChatClientAgentOptions agentOptions = new(instructions: "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.")
|
||||
ChatClientAgentOptions agentOptions = new()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<FeedbackResult>()
|
||||
}
|
||||
};
|
||||
|
||||
+4
-2
@@ -85,10 +85,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for spam detection</returns>
|
||||
private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are a spam detection assistant that identifies spam emails.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<DetectionResult>()
|
||||
}
|
||||
});
|
||||
@@ -98,10 +99,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for email assistance</returns>
|
||||
private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<EmailResponse>()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -100,10 +100,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for spam detection</returns>
|
||||
private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails. Be less confident in your assessments.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are a spam detection assistant that identifies spam emails. Be less confident in your assessments.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<DetectionResult>()
|
||||
}
|
||||
});
|
||||
@@ -113,10 +114,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for email assistance</returns>
|
||||
private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<EmailResponse>()
|
||||
}
|
||||
});
|
||||
|
||||
+6
-3
@@ -140,10 +140,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for email analysis</returns>
|
||||
private static ChatClientAgent GetEmailAnalysisAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are a spam detection assistant that identifies spam emails.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<AnalysisResult>()
|
||||
}
|
||||
});
|
||||
@@ -153,10 +154,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for email assistance</returns>
|
||||
private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<EmailResponse>()
|
||||
}
|
||||
});
|
||||
@@ -166,10 +168,11 @@ public static class Program
|
||||
/// </summary>
|
||||
/// <returns>A ChatClientAgent configured for email summarization</returns>
|
||||
private static ChatClientAgent GetEmailSummaryAgent(IChatClient chatClient) =>
|
||||
new(chatClient, new ChatClientAgentOptions(instructions: "You are an assistant that helps users summarize emails.")
|
||||
new(chatClient, new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = "You are an assistant that helps users summarize emails.",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<EmailSummary>()
|
||||
}
|
||||
});
|
||||
|
||||
+11
-11
@@ -285,19 +285,19 @@ internal sealed class CriticExecutor : Executor<ChatMessage, CriticDecision>
|
||||
this._agent = new ChatClientAgent(chatClient, new ChatClientAgentOptions
|
||||
{
|
||||
Name = "Critic",
|
||||
Instructions = """
|
||||
You are a constructive critic. Review the content and provide specific feedback.
|
||||
Always try to provide actionable suggestions for improvement and strive to identify improvement points.
|
||||
Only approve if the content is high quality, clear, and meets the original requirements and you see no improvement points.
|
||||
|
||||
Provide your decision as structured output with:
|
||||
- approved: true if content is good, false if revisions needed
|
||||
- feedback: specific improvements needed (empty if approved)
|
||||
|
||||
Be concise but specific in your feedback.
|
||||
""",
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = """
|
||||
You are a constructive critic. Review the content and provide specific feedback.
|
||||
Always try to provide actionable suggestions for improvement and strive to identify improvement points.
|
||||
Only approve if the content is high quality, clear, and meets the original requirements and you see no improvement points.
|
||||
|
||||
Provide your decision as structured output with:
|
||||
- approved: true if content is good, false if revisions needed
|
||||
- feedback: specific improvements needed (empty if approved)
|
||||
|
||||
Be concise but specific in your feedback.
|
||||
""",
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<CriticDecision>()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,9 +33,9 @@ public class WeatherForecastAgent : DelegatingAIAgent
|
||||
new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AgentName,
|
||||
Instructions = AgentInstructions,
|
||||
ChatOptions = new ChatOptions()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))],
|
||||
// We want the agent to return structured output in a known format
|
||||
// so that we can easily create adaptive cards from the response.
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Net.Http.Json" />
|
||||
<PackageReference Include="System.Threading.Channels" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -32,7 +32,7 @@ internal sealed class RunAgentInput
|
||||
[JsonPropertyName("context")]
|
||||
public AGUIContextItem[] Context { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("forwardedProperties")]
|
||||
[JsonPropertyName("forwardedProps")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public JsonElement ForwardedProperties { get; set; }
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public abstract class AIContextProvider
|
||||
/// that will be used. Context providers can use this information to determine what additional context
|
||||
/// should be provided for the invocation.
|
||||
/// </remarks>
|
||||
public class InvokingContext
|
||||
public sealed class InvokingContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvokingContext"/> class with the specified request messages.
|
||||
@@ -153,7 +153,7 @@ public abstract class AIContextProvider
|
||||
/// request messages that were used and the response messages that were generated. It also indicates
|
||||
/// whether the invocation succeeded or failed.
|
||||
/// </remarks>
|
||||
public class InvokedContext
|
||||
public sealed class InvokedContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvokedContext"/> class with the specified request messages.
|
||||
|
||||
@@ -45,14 +45,20 @@ public static class AnthropicBetaServiceExtensions
|
||||
{
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = instructions,
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(instructions))
|
||||
{
|
||||
options.ChatOptions ??= new();
|
||||
options.ChatOptions.Instructions = instructions;
|
||||
}
|
||||
|
||||
if (tools is { Count: > 0 })
|
||||
{
|
||||
options.ChatOptions = new ChatOptions { Tools = tools };
|
||||
options.ChatOptions ??= new();
|
||||
options.ChatOptions.Tools = tools;
|
||||
}
|
||||
|
||||
var chatClient = betaService.AsIChatClient(model, defaultMaxTokens ?? DefaultMaxTokens);
|
||||
|
||||
@@ -45,14 +45,20 @@ public static class AnthropicClientExtensions
|
||||
{
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = instructions,
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(instructions))
|
||||
{
|
||||
options.ChatOptions ??= new();
|
||||
options.ChatOptions.Instructions = instructions;
|
||||
}
|
||||
|
||||
if (tools is { Count: > 0 })
|
||||
{
|
||||
options.ChatOptions = new ChatOptions { Tools = tools };
|
||||
options.ChatOptions ??= new();
|
||||
options.ChatOptions.Tools = tools;
|
||||
}
|
||||
|
||||
var chatClient = client.AsIChatClient(model, defaultMaxTokens ?? DefaultMaxTokens);
|
||||
|
||||
+14
-4
@@ -67,12 +67,17 @@ public static class PersistentAgentsClientExtensions
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(persistentAgentMetadata.Instructions) && chatOptions?.Instructions is null)
|
||||
{
|
||||
chatOptions ??= new ChatOptions();
|
||||
chatOptions.Instructions = persistentAgentMetadata.Instructions;
|
||||
}
|
||||
|
||||
return new ChatClientAgent(chatClient, options: new()
|
||||
{
|
||||
Id = persistentAgentMetadata.Id,
|
||||
Name = persistentAgentMetadata.Name,
|
||||
Description = persistentAgentMetadata.Description,
|
||||
Instructions = persistentAgentMetadata.Instructions,
|
||||
ChatOptions = chatOptions
|
||||
}, services: services);
|
||||
}
|
||||
@@ -207,12 +212,17 @@ public static class PersistentAgentsClientExtensions
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(persistentAgentMetadata.Instructions) && options.ChatOptions?.Instructions is null)
|
||||
{
|
||||
options.ChatOptions ??= new ChatOptions();
|
||||
options.ChatOptions.Instructions = persistentAgentMetadata.Instructions;
|
||||
}
|
||||
|
||||
var agentOptions = new ChatClientAgentOptions()
|
||||
{
|
||||
Id = persistentAgentMetadata.Id,
|
||||
Name = options.Name ?? persistentAgentMetadata.Name,
|
||||
Description = options.Description ?? persistentAgentMetadata.Description,
|
||||
Instructions = options.Instructions ?? persistentAgentMetadata.Instructions,
|
||||
ChatOptions = options.ChatOptions,
|
||||
AIContextProviderFactory = options.AIContextProviderFactory,
|
||||
ChatMessageStoreFactory = options.ChatMessageStoreFactory,
|
||||
@@ -453,7 +463,7 @@ public static class PersistentAgentsClientExtensions
|
||||
model: model,
|
||||
name: options.Name,
|
||||
description: options.Description,
|
||||
instructions: options.Instructions,
|
||||
instructions: options.ChatOptions?.Instructions,
|
||||
tools: toolDefinitionsAndResources.ToolDefinitions,
|
||||
toolResources: toolDefinitionsAndResources.ToolResources,
|
||||
temperature: null,
|
||||
@@ -513,7 +523,7 @@ public static class PersistentAgentsClientExtensions
|
||||
model: model,
|
||||
name: options.Name,
|
||||
description: options.Description,
|
||||
instructions: options.Instructions,
|
||||
instructions: options.ChatOptions?.Instructions,
|
||||
tools: toolDefinitionsAndResources.ToolDefinitions,
|
||||
toolResources: toolDefinitionsAndResources.ToolResources,
|
||||
temperature: null,
|
||||
|
||||
@@ -393,7 +393,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
|
||||
PromptAgentDefinition agentDefinition = new(model)
|
||||
{
|
||||
Instructions = options.Instructions,
|
||||
Instructions = options.ChatOptions?.Instructions,
|
||||
Temperature = options.ChatOptions?.Temperature,
|
||||
TopP = options.ChatOptions?.TopP,
|
||||
TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
|
||||
@@ -459,7 +459,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
|
||||
PromptAgentDefinition agentDefinition = new(model)
|
||||
{
|
||||
Instructions = options.Instructions,
|
||||
Instructions = options.ChatOptions?.Instructions,
|
||||
Temperature = options.ChatOptions?.Temperature,
|
||||
TopP = options.ChatOptions?.TopP,
|
||||
TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
|
||||
@@ -822,10 +822,9 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
|
||||
{
|
||||
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
|
||||
agentOptions.Instructions = promptAgentDefinition.Instructions;
|
||||
agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
|
||||
agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
|
||||
agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
|
||||
agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
|
||||
}
|
||||
|
||||
if (agentTools is { Count: > 0 })
|
||||
|
||||
@@ -38,7 +38,6 @@ public sealed class ChatClientPromptAgentFactory : PromptAgentFactory
|
||||
{
|
||||
Name = promptAgent.Name,
|
||||
Description = promptAgent.Description,
|
||||
Instructions = promptAgent.Instructions?.ToTemplateString(),
|
||||
ChatOptions = promptAgent.GetChatOptions(this.Engine, this._functions),
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public static class PromptAgentExtensions
|
||||
|
||||
return new ChatOptions()
|
||||
{
|
||||
Instructions = promptAgent.ResponseInstructions?.ToTemplateString(),
|
||||
Instructions = promptAgent.Instructions?.ToTemplateString(),
|
||||
Temperature = (float?)modelOptions?.Temperature?.Eval(engine),
|
||||
MaxOutputTokens = (int?)modelOptions?.MaxOutputTokens?.Eval(engine),
|
||||
TopP = (float?)modelOptions?.TopP?.Eval(engine),
|
||||
|
||||
-2
@@ -14,8 +14,6 @@
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
<PackageReference Include="A2A" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Agents.AI.Hosting\Microsoft.Agents.AI.Hosting.csproj" />
|
||||
|
||||
+4
-5
@@ -25,16 +25,15 @@
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.Agents.AI.AGUI\Shared\**\*.cs" LinkBase="Shared" />
|
||||
<Compile Remove="ServerSentEventsResult.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '10.0'))" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests" />
|
||||
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests" />
|
||||
|
||||
-2
@@ -26,8 +26,6 @@
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+18
-8
@@ -77,12 +77,17 @@ public static class OpenAIAssistantClientExtensions
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(assistantMetadata.Instructions) && chatOptions?.Instructions is null)
|
||||
{
|
||||
chatOptions ??= new ChatOptions();
|
||||
chatOptions.Instructions = assistantMetadata.Instructions;
|
||||
}
|
||||
|
||||
return new ChatClientAgent(chatClient, options: new()
|
||||
{
|
||||
Id = assistantMetadata.Id,
|
||||
Name = assistantMetadata.Name,
|
||||
Description = assistantMetadata.Description,
|
||||
Instructions = assistantMetadata.Instructions,
|
||||
ChatOptions = chatOptions
|
||||
}, services: services);
|
||||
}
|
||||
@@ -215,12 +220,17 @@ public static class OpenAIAssistantClientExtensions
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.ChatOptions?.Instructions) && !string.IsNullOrWhiteSpace(assistantMetadata.Instructions))
|
||||
{
|
||||
options.ChatOptions ??= new ChatOptions();
|
||||
options.ChatOptions.Instructions = assistantMetadata.Instructions;
|
||||
}
|
||||
|
||||
var mergedOptions = new ChatClientAgentOptions()
|
||||
{
|
||||
Id = assistantMetadata.Id,
|
||||
Name = options.Name ?? assistantMetadata.Name,
|
||||
Description = options.Description ?? assistantMetadata.Description,
|
||||
Instructions = options.Instructions ?? assistantMetadata.Instructions,
|
||||
ChatOptions = options.ChatOptions,
|
||||
AIContextProviderFactory = options.AIContextProviderFactory,
|
||||
ChatMessageStoreFactory = options.ChatMessageStoreFactory,
|
||||
@@ -339,10 +349,10 @@ public static class OpenAIAssistantClientExtensions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = tools is null ? null : new ChatOptions()
|
||||
ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
|
||||
{
|
||||
Tools = tools,
|
||||
Instructions = instructions
|
||||
}
|
||||
},
|
||||
clientFactory,
|
||||
@@ -377,7 +387,7 @@ public static class OpenAIAssistantClientExtensions
|
||||
{
|
||||
Name = options.Name,
|
||||
Description = options.Description,
|
||||
Instructions = options.Instructions,
|
||||
Instructions = options.ChatOptions?.Instructions,
|
||||
};
|
||||
|
||||
// Convert AITools to ToolDefinitions and ToolResources
|
||||
@@ -443,10 +453,10 @@ public static class OpenAIAssistantClientExtensions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = tools is null ? null : new ChatOptions()
|
||||
ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
|
||||
{
|
||||
Tools = tools,
|
||||
Instructions = instructions,
|
||||
}
|
||||
},
|
||||
clientFactory,
|
||||
@@ -484,7 +494,7 @@ public static class OpenAIAssistantClientExtensions
|
||||
{
|
||||
Name = options.Name,
|
||||
Description = options.Description,
|
||||
Instructions = options.Instructions,
|
||||
Instructions = options.ChatOptions?.Instructions,
|
||||
};
|
||||
|
||||
// Convert AITools to ToolDefinitions and ToolResources
|
||||
|
||||
@@ -47,9 +47,9 @@ public static class OpenAIChatClientExtensions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = tools is null ? null : new ChatOptions()
|
||||
ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
|
||||
{
|
||||
Instructions = instructions,
|
||||
Tools = tools,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -50,9 +50,9 @@ public static class OpenAIResponseClientExtensions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = tools is null ? null : new ChatOptions()
|
||||
ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
|
||||
{
|
||||
Instructions = instructions,
|
||||
Tools = tools,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@ public class OpenAIChatClientAgent : DelegatingAIAgent
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new ChatOptions() { Instructions = instructions },
|
||||
}, loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new ChatOptions() { Instructions = instructions },
|
||||
}, loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.AI.Data;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
|
||||
@@ -59,13 +59,13 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
chatClient,
|
||||
new ChatClientAgentOptions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
ChatOptions = tools is null ? null : new ChatOptions
|
||||
ChatOptions = (tools is null && string.IsNullOrWhiteSpace(instructions)) ? null : new ChatOptions
|
||||
{
|
||||
Tools = tools,
|
||||
}
|
||||
Instructions = instructions
|
||||
},
|
||||
Name = name,
|
||||
Description = description
|
||||
},
|
||||
loggerFactory,
|
||||
services)
|
||||
@@ -141,7 +141,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// These instructions are typically provided to the AI model as system messages to establish
|
||||
/// the context and expected behavior for the agent's responses.
|
||||
/// </remarks>
|
||||
public string? Instructions => this._agentOptions?.Instructions;
|
||||
public string? Instructions => this._agentOptions?.ChatOptions?.Instructions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets of the default <see cref="ChatOptions"/> used by the agent.
|
||||
@@ -492,7 +492,6 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
requestChatOptions.AllowMultipleToolCalls ??= this._agentOptions.ChatOptions.AllowMultipleToolCalls;
|
||||
requestChatOptions.ConversationId ??= this._agentOptions.ChatOptions.ConversationId;
|
||||
requestChatOptions.FrequencyPenalty ??= this._agentOptions.ChatOptions.FrequencyPenalty;
|
||||
requestChatOptions.Instructions ??= this._agentOptions.ChatOptions.Instructions;
|
||||
requestChatOptions.MaxOutputTokens ??= this._agentOptions.ChatOptions.MaxOutputTokens;
|
||||
requestChatOptions.ModelId ??= this._agentOptions.ChatOptions.ModelId;
|
||||
requestChatOptions.PresencePenalty ??= this._agentOptions.ChatOptions.PresencePenalty;
|
||||
@@ -503,6 +502,13 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
requestChatOptions.TopK ??= this._agentOptions.ChatOptions.TopK;
|
||||
requestChatOptions.ToolMode ??= this._agentOptions.ChatOptions.ToolMode;
|
||||
|
||||
// Merge instructions by concatenating them if both are present.
|
||||
requestChatOptions.Instructions = !string.IsNullOrWhiteSpace(requestChatOptions.Instructions) && !string.IsNullOrWhiteSpace(this.Instructions)
|
||||
? $"{this.Instructions}\n{requestChatOptions.Instructions}"
|
||||
: (!string.IsNullOrWhiteSpace(requestChatOptions.Instructions)
|
||||
? requestChatOptions.Instructions
|
||||
: this.Instructions);
|
||||
|
||||
// Merge only the additional properties from the agent if they are not already set in the request options.
|
||||
if (requestChatOptions.AdditionalProperties is not null && this._agentOptions.ChatOptions.AdditionalProperties is not null)
|
||||
{
|
||||
@@ -685,12 +691,6 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
""");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.Instructions))
|
||||
{
|
||||
chatOptions ??= new();
|
||||
chatOptions.Instructions = string.IsNullOrWhiteSpace(chatOptions.Instructions) ? this.Instructions : $"{this.Instructions}\n{chatOptions.Instructions}";
|
||||
}
|
||||
|
||||
// Only create or update ChatOptions if we have an id on the thread and we don't have the same one already in ChatOptions.
|
||||
if (!string.IsNullOrWhiteSpace(typedThread.ConversationId) && typedThread.ConversationId != chatOptions?.ConversationId)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
@@ -15,37 +14,8 @@ namespace Microsoft.Agents.AI;
|
||||
/// identifier, display name, operational instructions, and a descriptive summary. It can be used to store and transfer
|
||||
/// agent-related metadata within a chat application.
|
||||
/// </remarks>
|
||||
public class ChatClientAgentOptions
|
||||
public sealed class ChatClientAgentOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatClientAgentOptions"/> class.
|
||||
/// </summary>
|
||||
public ChatClientAgentOptions()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatClientAgentOptions"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <remarks>If <paramref name="tools"/> is provided, a new <see cref="ChatOptions"/> instance is created
|
||||
/// with the specified instructions and tools.</remarks>
|
||||
/// <param name="instructions">The instructions or guidelines for the chat client agent. Can be <see langword="null"/> if not specified.</param>
|
||||
/// <param name="name">The name of the chat client agent. Can be <see langword="null"/> if not specified.</param>
|
||||
/// <param name="description">The description of the chat client agent. Can be <see langword="null"/> if not specified.</param>
|
||||
/// <param name="tools">A list of <see cref="AITool"/> instances available to the chat client agent. Can be <see langword="null"/> if no
|
||||
/// tools are specified.</param>
|
||||
public ChatClientAgentOptions(string? instructions, string? name = null, string? description = null, IList<AITool>? tools = null)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Instructions = instructions;
|
||||
this.Description = description;
|
||||
|
||||
if (tools is not null)
|
||||
{
|
||||
(this.ChatOptions ??= new()).Tools = tools;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the agent id.
|
||||
/// </summary>
|
||||
@@ -56,11 +26,6 @@ public class ChatClientAgentOptions
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the agent instructions.
|
||||
/// </summary>
|
||||
public string? Instructions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the agent description.
|
||||
/// </summary>
|
||||
@@ -106,7 +71,6 @@ public class ChatClientAgentOptions
|
||||
{
|
||||
Id = this.Id,
|
||||
Name = this.Name,
|
||||
Instructions = this.Instructions,
|
||||
Description = this.Description,
|
||||
ChatOptions = this.ChatOptions?.Clone(),
|
||||
ChatMessageStoreFactory = this.ChatMessageStoreFactory,
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Data;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// A text search context provider that performs a search over external knowledge
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Data;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Options controlling the behavior of <see cref="TextSearchProvider"/>.
|
||||
-2
@@ -13,8 +13,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+1
-2
@@ -84,8 +84,7 @@ public class AnthropicChatCompletionFixture : IChatClientAgentFixture
|
||||
return Task.FromResult(new ChatClientAgent(chatClient, options: new()
|
||||
{
|
||||
Name = name,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new() { Tools = aiTools }
|
||||
ChatOptions = new() { Instructions = instructions, Tools = aiTools }
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,16 +37,20 @@ public class AIProjectClientCreateTests
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
description: AgentDescription)),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AgentName,
|
||||
Description = AgentDescription,
|
||||
ChatOptions = new() { Instructions = AgentInstructions }
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
|
||||
model: s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
description: AgentDescription)),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AgentName,
|
||||
Description = AgentDescription,
|
||||
ChatOptions = new() { Instructions = AgentInstructions }
|
||||
}),
|
||||
"CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
name: AgentName,
|
||||
creationOptions: new AgentVersionCreationOptions(new PromptAgentDefinition(s_config.DeploymentName) { Instructions = AgentInstructions }) { Description = AgentDescription }),
|
||||
@@ -239,16 +243,18 @@ public class AIProjectClientCreateTests
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AgentName,
|
||||
ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
Name = AgentName,
|
||||
ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
|
||||
}),
|
||||
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
|
||||
};
|
||||
|
||||
|
||||
+102
-27
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
@@ -34,16 +35,20 @@ public class AzureAIAgentsPersistentCreateTests
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
description: AgentDescription)),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = AgentInstructions },
|
||||
Name = AgentName,
|
||||
Description = AgentDescription
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
description: AgentDescription)),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = AgentInstructions },
|
||||
Name = AgentName,
|
||||
Description = AgentDescription
|
||||
}),
|
||||
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
instructions: AgentInstructions,
|
||||
@@ -104,19 +109,32 @@ public class AzureAIAgentsPersistentCreateTests
|
||||
);
|
||||
var vectorStoreMetadata = await this._persistentAgentsClient.VectorStores.CreateVectorStoreAsync([uploadedAgentFile.Id], name: "WordCodeLookup_VectorStore");
|
||||
|
||||
// Wait for vector store indexing to complete before using it
|
||||
await this.WaitForVectorStoreReadyAsync(this._persistentAgentsClient, vectorStoreMetadata.Value.Id);
|
||||
|
||||
// Act.
|
||||
var agent = createMechanism switch
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]
|
||||
}
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]
|
||||
}
|
||||
}),
|
||||
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
instructions: AgentInstructions,
|
||||
@@ -179,15 +197,24 @@ public class AzureAIAgentsPersistentCreateTests
|
||||
// Hosted tool path (tools supplied via ChatClientAgentOptions)
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]
|
||||
}
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])),
|
||||
// Foundry (definitions + resources provided directly)
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]
|
||||
}
|
||||
}),
|
||||
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
instructions: AgentInstructions,
|
||||
@@ -232,14 +259,24 @@ public class AzureAIAgentsPersistentCreateTests
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [weatherFunction]
|
||||
}
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [weatherFunction]
|
||||
}
|
||||
}),
|
||||
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
|
||||
};
|
||||
|
||||
@@ -259,4 +296,42 @@ public class AzureAIAgentsPersistentCreateTests
|
||||
await this._persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for a vector store to complete indexing by polling its status.
|
||||
/// </summary>
|
||||
/// <param name="client">The persistent agents client.</param>
|
||||
/// <param name="vectorStoreId">The ID of the vector store.</param>
|
||||
/// <param name="maxWaitSeconds">Maximum time to wait in seconds (default: 30).</param>
|
||||
/// <returns>A task that completes when the vector store is ready or throws on timeout/failure.</returns>
|
||||
private async Task WaitForVectorStoreReadyAsync(
|
||||
PersistentAgentsClient client,
|
||||
string vectorStoreId,
|
||||
int maxWaitSeconds = 30)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
while (sw.Elapsed.TotalSeconds < maxWaitSeconds)
|
||||
{
|
||||
PersistentAgentsVectorStore vectorStore = await client.VectorStores.GetVectorStoreAsync(vectorStoreId);
|
||||
|
||||
if (vectorStore.Status == VectorStoreStatus.Completed)
|
||||
{
|
||||
if (vectorStore.FileCounts.Failed > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Vector store indexing failed for some files");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (vectorStore.Status == VectorStoreStatus.Expired)
|
||||
{
|
||||
throw new InvalidOperationException("Vector store has expired");
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Vector store did not complete indexing within {maxWaitSeconds}s");
|
||||
}
|
||||
}
|
||||
|
||||
-5
@@ -1,10 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
-3
@@ -1,9 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
-2
@@ -13,8 +13,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ public sealed class AnthropicBetaServiceExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "Test description",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ public sealed class AnthropicClientExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "Test description",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+6
-6
@@ -310,7 +310,7 @@ public sealed class PersistentAgentsClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -337,7 +337,7 @@ public sealed class PersistentAgentsClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -386,7 +386,7 @@ public sealed class PersistentAgentsClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -413,7 +413,7 @@ public sealed class PersistentAgentsClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -557,7 +557,7 @@ public sealed class PersistentAgentsClientExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "Test description",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -584,7 +584,7 @@ public sealed class PersistentAgentsClientExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "Test description",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+7
-8
@@ -752,7 +752,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -775,7 +775,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
TestChatClient? testChatClient = null;
|
||||
|
||||
@@ -803,7 +803,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -826,7 +826,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
TestChatClient? testChatClient = null;
|
||||
|
||||
@@ -1575,8 +1575,8 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Custom instructions",
|
||||
Description = "Custom description"
|
||||
Description = "Custom description",
|
||||
ChatOptions = new ChatOptions { Instructions = "Custom instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -1610,8 +1610,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test",
|
||||
ChatOptions = new ChatOptions { Tools = tools }
|
||||
ChatOptions = new ChatOptions { Instructions = "Test", Tools = tools }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
@@ -49,8 +49,7 @@ public class AzureAIProjectChatClientTests
|
||||
new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { ConversationId = "conv_12345" }
|
||||
ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_12345" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -99,7 +98,7 @@ public class AzureAIProjectChatClientTests
|
||||
new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { Instructions = "Test instructions" },
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -148,8 +147,7 @@ public class AzureAIProjectChatClientTests
|
||||
new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { ConversationId = "conv_should_not_use_default" }
|
||||
ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_should_not_use_default" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -198,7 +196,7 @@ public class AzureAIProjectChatClientTests
|
||||
new ChatClientAgentOptions
|
||||
{
|
||||
Name = "test-agent",
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { Instructions = "Test instructions" },
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public sealed class ChatClientAgentFactoryTests
|
||||
Assert.IsType<ChatClientAgent>(agent);
|
||||
var chatClientAgent = agent as ChatClientAgent;
|
||||
Assert.NotNull(chatClientAgent?.ChatOptions);
|
||||
Assert.Equal("Provide detailed and accurate responses.", chatClientAgent?.ChatOptions?.Instructions);
|
||||
Assert.Equal("You are a helpful assistant.", chatClientAgent?.ChatOptions?.Instructions);
|
||||
Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.Temperature);
|
||||
Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.FrequencyPenalty);
|
||||
Assert.Equal(1024, chatClientAgent?.ChatOptions?.MaxOutputTokens);
|
||||
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.ServerSentEvents;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests;
|
||||
|
||||
public sealed class ForwardedPropertiesTests : IAsyncDisposable
|
||||
{
|
||||
private WebApplication? _app;
|
||||
private HttpClient? _client;
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_AreParsedAndPassedToAgent_WhenProvidedInRequestAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
// Create request JSON with forwardedProps (per AG-UI protocol spec)
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test forwarded props" }],
|
||||
"forwardedProps": { "customProp": "customValue", "sessionId": "test-session-123" }
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
|
||||
// Assert
|
||||
response.IsSuccessStatusCode.Should().BeTrue();
|
||||
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("customProp").GetString().Should().Be("customValue");
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("sessionId").GetString().Should().Be("test-session-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_WithNestedObjects_AreCorrectlyParsedAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test nested props" }],
|
||||
"forwardedProps": {
|
||||
"user": { "id": "user-1", "name": "Test User" },
|
||||
"metadata": { "version": "1.0", "feature": "test" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
|
||||
// Assert
|
||||
response.IsSuccessStatusCode.Should().BeTrue();
|
||||
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
|
||||
|
||||
JsonElement user = fakeAgent.ReceivedForwardedProperties.GetProperty("user");
|
||||
user.GetProperty("id").GetString().Should().Be("user-1");
|
||||
user.GetProperty("name").GetString().Should().Be("Test User");
|
||||
|
||||
JsonElement metadata = fakeAgent.ReceivedForwardedProperties.GetProperty("metadata");
|
||||
metadata.GetProperty("version").GetString().Should().Be("1.0");
|
||||
metadata.GetProperty("feature").GetString().Should().Be("test");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_WithArrays_AreCorrectlyParsedAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test array props" }],
|
||||
"forwardedProps": {
|
||||
"tags": ["tag1", "tag2", "tag3"],
|
||||
"scores": [1, 2, 3, 4, 5]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
|
||||
// Assert
|
||||
response.IsSuccessStatusCode.Should().BeTrue();
|
||||
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
|
||||
|
||||
JsonElement tags = fakeAgent.ReceivedForwardedProperties.GetProperty("tags");
|
||||
tags.GetArrayLength().Should().Be(3);
|
||||
tags[0].GetString().Should().Be("tag1");
|
||||
|
||||
JsonElement scores = fakeAgent.ReceivedForwardedProperties.GetProperty("scores");
|
||||
scores.GetArrayLength().Should().Be(5);
|
||||
scores[2].GetInt32().Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_WhenEmpty_DoesNotCauseErrorsAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test empty props" }],
|
||||
"forwardedProps": {}
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
|
||||
// Assert
|
||||
response.IsSuccessStatusCode.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_WhenNotProvided_AgentStillWorksAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test no props" }]
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
|
||||
// Assert
|
||||
response.IsSuccessStatusCode.Should().BeTrue();
|
||||
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Undefined);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_ReturnsValidSSEResponse_WithTextDeltaEventsAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test response" }],
|
||||
"forwardedProps": { "customProp": "value" }
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
List<SseItem<string>> events = [];
|
||||
await foreach (SseItem<string> item in SseParser.Create(stream).EnumerateAsync())
|
||||
{
|
||||
events.Add(item);
|
||||
}
|
||||
|
||||
// Assert
|
||||
events.Should().NotBeEmpty();
|
||||
|
||||
// SSE events have EventType = "message" and the actual type is in the JSON data
|
||||
// Should have run_started event
|
||||
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_STARTED\""));
|
||||
|
||||
// Should have text_message_start event
|
||||
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_START\""));
|
||||
|
||||
// Should have text_message_content event with the response text
|
||||
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_CONTENT\""));
|
||||
|
||||
// Should have run_finished event
|
||||
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_FINISHED\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForwardedProps_WithMixedTypes_AreCorrectlyParsedAsync()
|
||||
{
|
||||
// Arrange
|
||||
FakeForwardedPropsAgent fakeAgent = new();
|
||||
await this.SetupTestServerAsync(fakeAgent);
|
||||
|
||||
const string RequestJson = """
|
||||
{
|
||||
"threadId": "thread-123",
|
||||
"runId": "run-456",
|
||||
"messages": [{ "id": "msg-1", "role": "user", "content": "test mixed types" }],
|
||||
"forwardedProps": {
|
||||
"stringProp": "text",
|
||||
"numberProp": 42,
|
||||
"boolProp": true,
|
||||
"nullProp": null,
|
||||
"arrayProp": [1, "two", false],
|
||||
"objectProp": { "nested": "value" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
|
||||
|
||||
// Assert
|
||||
response.IsSuccessStatusCode.Should().BeTrue();
|
||||
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
|
||||
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("stringProp").GetString().Should().Be("text");
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("numberProp").GetInt32().Should().Be(42);
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("boolProp").GetBoolean().Should().BeTrue();
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("nullProp").ValueKind.Should().Be(JsonValueKind.Null);
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("arrayProp").GetArrayLength().Should().Be(3);
|
||||
fakeAgent.ReceivedForwardedProperties.GetProperty("objectProp").GetProperty("nested").GetString().Should().Be("value");
|
||||
}
|
||||
|
||||
private async Task SetupTestServerAsync(FakeForwardedPropsAgent fakeAgent)
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
||||
builder.Services.AddAGUI();
|
||||
builder.WebHost.UseTestServer();
|
||||
|
||||
this._app = builder.Build();
|
||||
|
||||
this._app.MapAGUI("/agent", fakeAgent);
|
||||
|
||||
await this._app.StartAsync();
|
||||
|
||||
TestServer testServer = this._app.Services.GetRequiredService<IServer>() as TestServer
|
||||
?? throw new InvalidOperationException("TestServer not found");
|
||||
|
||||
this._client = testServer.CreateClient();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
this._client?.Dispose();
|
||||
if (this._app != null)
|
||||
{
|
||||
await this._app.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated in tests")]
|
||||
internal sealed class FakeForwardedPropsAgent : AIAgent
|
||||
{
|
||||
public FakeForwardedPropsAgent()
|
||||
{
|
||||
}
|
||||
|
||||
public override string? Description => "Agent for forwarded properties testing";
|
||||
|
||||
public JsonElement ReceivedForwardedProperties { get; private set; }
|
||||
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Extract forwarded properties from ChatOptions.AdditionalProperties (set by AG-UI hosting layer)
|
||||
if (options is ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } &&
|
||||
properties.TryGetValue("ag_ui_forwarded_properties", out object? propsObj) &&
|
||||
propsObj is JsonElement forwardedProps)
|
||||
{
|
||||
this.ReceivedForwardedProperties = forwardedProps;
|
||||
}
|
||||
|
||||
// Always return a text response
|
||||
string messageId = Guid.NewGuid().ToString("N");
|
||||
yield return new AgentRunResponseUpdate
|
||||
{
|
||||
MessageId = messageId,
|
||||
Role = ChatRole.Assistant,
|
||||
Contents = [new TextContent("Forwarded props processed")]
|
||||
};
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override AgentThread GetNewThread() => new FakeInMemoryAgentThread();
|
||||
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions);
|
||||
}
|
||||
|
||||
private sealed class FakeInMemoryAgentThread : InMemoryAgentThread
|
||||
{
|
||||
public FakeInMemoryAgentThread()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public FakeInMemoryAgentThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
: base(serializedThread, jsonSerializerOptions)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override object? GetService(Type serviceType, object? serviceKey = null) => null;
|
||||
}
|
||||
-4
@@ -20,10 +20,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
|
||||
-2
@@ -9,8 +9,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+6
-6
@@ -92,7 +92,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "Test description",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -223,7 +223,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -250,7 +250,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -299,7 +299,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -326,7 +326,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
Name = "Override Name",
|
||||
Description = "Override Description",
|
||||
Instructions = "Override Instructions"
|
||||
ChatOptions = new() { Instructions = "Override Instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -498,7 +498,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ public sealed class OpenAIChatClientExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "Test description",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+1
-1
@@ -208,7 +208,7 @@ public sealed class OpenAIResponseClientExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+45
-84
@@ -19,7 +19,6 @@ public class ChatClientAgentOptionsTests
|
||||
|
||||
// Assert
|
||||
Assert.Null(options.Name);
|
||||
Assert.Null(options.Instructions);
|
||||
Assert.Null(options.Description);
|
||||
Assert.Null(options.ChatOptions);
|
||||
Assert.Null(options.ChatMessageStoreFactory);
|
||||
@@ -27,90 +26,44 @@ public class ChatClientAgentOptionsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_WithNullValues_SetsPropertiesCorrectly()
|
||||
public void Constructor_WithNullValues_SetsPropertiesCorrectly()
|
||||
{
|
||||
// Act
|
||||
var options = new ChatClientAgentOptions(
|
||||
instructions: null,
|
||||
name: null,
|
||||
description: null,
|
||||
tools: null);
|
||||
var options = new ChatClientAgentOptions() { Name = null, Description = null, ChatOptions = new() { Tools = null, Instructions = null } };
|
||||
|
||||
// Assert
|
||||
Assert.Null(options.Name);
|
||||
Assert.Null(options.Instructions);
|
||||
Assert.Null(options.Description);
|
||||
Assert.Null(options.ChatOptions);
|
||||
Assert.Null(options.AIContextProviderFactory);
|
||||
Assert.Null(options.ChatMessageStoreFactory);
|
||||
Assert.NotNull(options.ChatOptions);
|
||||
Assert.Null(options.ChatOptions.Instructions);
|
||||
Assert.Null(options.ChatOptions.Tools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_WithInstructionsOnly_SetsChatOptionsWithInstructions()
|
||||
{
|
||||
// Arrange
|
||||
const string Instructions = "Test instructions";
|
||||
|
||||
// Act
|
||||
var options = new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
name: null,
|
||||
description: null,
|
||||
tools: null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(options.Name);
|
||||
Assert.Equal(Instructions, options.Instructions);
|
||||
Assert.Null(options.Description);
|
||||
Assert.Null(options.ChatOptions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_WithToolsOnly_SetsChatOptionsWithTools()
|
||||
public void Constructor_WithToolsOnly_SetsChatOptionsWithTools()
|
||||
{
|
||||
// Arrange
|
||||
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
|
||||
|
||||
// Act
|
||||
var options = new ChatClientAgentOptions(
|
||||
instructions: null,
|
||||
name: null,
|
||||
description: null,
|
||||
tools: tools);
|
||||
var options = new ChatClientAgentOptions()
|
||||
{
|
||||
Name = null,
|
||||
Description = null,
|
||||
ChatOptions = new() { Tools = tools }
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Null(options.Name);
|
||||
Assert.Null(options.Instructions);
|
||||
Assert.Null(options.Description);
|
||||
Assert.NotNull(options.ChatOptions);
|
||||
Assert.Null(options.ChatOptions.Instructions);
|
||||
Assert.Same(tools, options.ChatOptions.Tools);
|
||||
AssertSameTools(tools, options.ChatOptions.Tools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_WithInstructionsAndTools_SetsChatOptionsWithBoth()
|
||||
{
|
||||
// Arrange
|
||||
const string Instructions = "Test instructions";
|
||||
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
|
||||
|
||||
// Act
|
||||
var options = new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
name: null,
|
||||
description: null,
|
||||
tools: tools);
|
||||
|
||||
// Assert
|
||||
Assert.Null(options.Name);
|
||||
Assert.Equal(Instructions, options.Instructions);
|
||||
Assert.Null(options.Description);
|
||||
Assert.NotNull(options.ChatOptions);
|
||||
Assert.Null(options.ChatOptions.Instructions);
|
||||
Assert.Same(tools, options.ChatOptions.Tools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_WithAllParameters_SetsAllPropertiesCorrectly()
|
||||
public void Constructor_WithAllParameters_SetsAllPropertiesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
const string Instructions = "Test instructions";
|
||||
@@ -119,38 +72,37 @@ public class ChatClientAgentOptionsTests
|
||||
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
|
||||
|
||||
// Act
|
||||
var options = new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
name: Name,
|
||||
description: Description,
|
||||
tools: tools);
|
||||
var options = new ChatClientAgentOptions()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
ChatOptions = new() { Tools = tools, Instructions = Instructions }
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Name, options.Name);
|
||||
Assert.Equal(Instructions, options.Instructions);
|
||||
Assert.Equal(Instructions, options.ChatOptions.Instructions);
|
||||
Assert.Equal(Description, options.Description);
|
||||
Assert.NotNull(options.ChatOptions);
|
||||
Assert.Null(options.ChatOptions.Instructions);
|
||||
Assert.Same(tools, options.ChatOptions.Tools);
|
||||
AssertSameTools(tools, options.ChatOptions.Tools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions()
|
||||
public void Constructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions()
|
||||
{
|
||||
// Arrange
|
||||
const string Name = "Test name";
|
||||
const string Description = "Test description";
|
||||
|
||||
// Act
|
||||
var options = new ChatClientAgentOptions(
|
||||
instructions: null,
|
||||
name: Name,
|
||||
description: Description,
|
||||
tools: null);
|
||||
var options = new ChatClientAgentOptions()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Name, options.Name);
|
||||
Assert.Null(options.Instructions);
|
||||
Assert.Equal(Description, options.Description);
|
||||
Assert.Null(options.ChatOptions);
|
||||
}
|
||||
@@ -159,7 +111,6 @@ public class ChatClientAgentOptionsTests
|
||||
public void Clone_CreatesDeepCopyWithSameValues()
|
||||
{
|
||||
// Arrange
|
||||
const string Instructions = "Test instructions";
|
||||
const string Name = "Test name";
|
||||
const string Description = "Test description";
|
||||
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
|
||||
@@ -171,8 +122,11 @@ public class ChatClientAgentOptionsTests
|
||||
ChatClientAgentOptions.AIContextProviderFactoryContext ctx) =>
|
||||
new Mock<AIContextProvider>().Object;
|
||||
|
||||
var original = new ChatClientAgentOptions(Instructions, Name, Description, tools)
|
||||
var original = new ChatClientAgentOptions()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
ChatOptions = new() { Tools = tools },
|
||||
Id = "test-id",
|
||||
ChatMessageStoreFactory = ChatMessageStoreFactory,
|
||||
AIContextProviderFactory = AIContextProviderFactory
|
||||
@@ -185,7 +139,6 @@ public class ChatClientAgentOptionsTests
|
||||
Assert.NotSame(original, clone);
|
||||
Assert.Equal(original.Id, clone.Id);
|
||||
Assert.Equal(original.Name, clone.Name);
|
||||
Assert.Equal(original.Instructions, clone.Instructions);
|
||||
Assert.Equal(original.Description, clone.Description);
|
||||
Assert.Same(original.ChatMessageStoreFactory, clone.ChatMessageStoreFactory);
|
||||
Assert.Same(original.AIContextProviderFactory, clone.AIContextProviderFactory);
|
||||
@@ -197,14 +150,13 @@ public class ChatClientAgentOptionsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clone_WithNullChatOptions_ClonesCorrectly()
|
||||
public void Clone_WithoutProvidingChatOptions_ClonesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var original = new ChatClientAgentOptions
|
||||
{
|
||||
Id = "test-id",
|
||||
Name = "Test name",
|
||||
Instructions = "Test instructions",
|
||||
Description = "Test description"
|
||||
};
|
||||
|
||||
@@ -215,10 +167,19 @@ public class ChatClientAgentOptionsTests
|
||||
Assert.NotSame(original, clone);
|
||||
Assert.Equal(original.Id, clone.Id);
|
||||
Assert.Equal(original.Name, clone.Name);
|
||||
Assert.Equal(original.Instructions, clone.Instructions);
|
||||
Assert.Equal(original.Description, clone.Description);
|
||||
Assert.Null(clone.ChatOptions);
|
||||
Assert.Null(original.ChatOptions);
|
||||
Assert.Null(clone.ChatMessageStoreFactory);
|
||||
Assert.Null(clone.AIContextProviderFactory);
|
||||
}
|
||||
|
||||
private static void AssertSameTools(IList<AITool>? expected, IList<AITool>? actual)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var tool in expected ?? [])
|
||||
{
|
||||
Assert.Same(tool, actual?[index]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public partial class ChatClientAgentTests
|
||||
Id = "test-agent-id",
|
||||
Name = "test name",
|
||||
Description = "test description",
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
});
|
||||
|
||||
// Assert
|
||||
@@ -65,7 +65,7 @@ public partial class ChatClientAgentTests
|
||||
ChatClientAgent agent =
|
||||
new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions"
|
||||
ChatOptions = new() { Instructions = "base instructions" },
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -99,7 +99,7 @@ public partial class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
ChatClientAgent agent = new(chatClient, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(chatClient, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => agent.RunAsync((IReadOnlyCollection<ChatMessage>)null!));
|
||||
@@ -120,7 +120,7 @@ public partial class ChatClientAgentTests
|
||||
It.Is<ChatOptions>(opts => opts.MaxOutputTokens == 100),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
// Act
|
||||
await agent.RunAsync([new(ChatRole.User, "test")], options: new ChatClientAgentRunOptions(chatOptions));
|
||||
@@ -181,7 +181,7 @@ public partial class ChatClientAgentTests
|
||||
capturedMessages.AddRange(msgs))
|
||||
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions" } });
|
||||
var runOptions = new AgentRunOptions();
|
||||
|
||||
// Act
|
||||
@@ -212,7 +212,7 @@ public partial class ChatClientAgentTests
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse(responseMessages));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions", Name = authorName });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, Name = authorName });
|
||||
|
||||
// Act
|
||||
var result = await agent.RunAsync([new(ChatRole.User, "test")]);
|
||||
@@ -239,7 +239,7 @@ public partial class ChatClientAgentTests
|
||||
capturedMessages.AddRange(msgs))
|
||||
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
// Create a thread using the agent's GetNewThread method
|
||||
var thread = agent.GetNewThread();
|
||||
@@ -270,7 +270,7 @@ public partial class ChatClientAgentTests
|
||||
capturedMessages.AddRange(msgs))
|
||||
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = null });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = null } });
|
||||
|
||||
// Act
|
||||
await agent.RunAsync([new(ChatRole.User, "test message")]);
|
||||
@@ -300,7 +300,7 @@ public partial class ChatClientAgentTests
|
||||
capturedMessages.AddRange(msgs))
|
||||
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
// Act
|
||||
await agent.RunAsync([]);
|
||||
@@ -326,7 +326,7 @@ public partial class ChatClientAgentTests
|
||||
It.Is<ChatOptions>(opts => opts.ConversationId == "ConvId"),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" });
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
ChatClientAgentThread thread = new() { ConversationId = "ConvId" };
|
||||
|
||||
@@ -346,7 +346,7 @@ public partial class ChatClientAgentTests
|
||||
var chatOptions = new ChatOptions { ConversationId = "ConvId" };
|
||||
Mock<IChatClient> mockService = new();
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
ChatClientAgentThread thread = new() { ConversationId = "ThreadId" };
|
||||
|
||||
@@ -369,7 +369,7 @@ public partial class ChatClientAgentTests
|
||||
It.Is<ChatOptions>(opts => opts.MaxOutputTokens == 100 && opts.ConversationId == "ConvId"),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" });
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
ChatClientAgentThread thread = new() { ConversationId = "ConvId" };
|
||||
|
||||
@@ -394,7 +394,7 @@ public partial class ChatClientAgentTests
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
|
||||
ChatClientAgentThread thread = new() { ConversationId = "ConvId" };
|
||||
|
||||
@@ -415,7 +415,7 @@ public partial class ChatClientAgentTests
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
ChatClientAgentThread thread = new();
|
||||
|
||||
// Act
|
||||
@@ -442,7 +442,7 @@ public partial class ChatClientAgentTests
|
||||
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
ChatMessageStoreFactory = mockFactory.Object
|
||||
});
|
||||
|
||||
@@ -473,7 +473,7 @@ public partial class ChatClientAgentTests
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -508,7 +508,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
ChatMessageStoreFactory = mockFactory.Object
|
||||
});
|
||||
|
||||
@@ -539,7 +539,7 @@ public partial class ChatClientAgentTests
|
||||
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
ChatMessageStoreFactory = mockFactory.Object
|
||||
});
|
||||
|
||||
@@ -592,7 +592,7 @@ public partial class ChatClientAgentTests
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
|
||||
// Act
|
||||
var thread = agent.GetNewThread() as ChatClientAgentThread;
|
||||
@@ -654,7 +654,7 @@ public partial class ChatClientAgentTests
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => agent.RunAsync(requestMessages));
|
||||
@@ -700,7 +700,7 @@ public partial class ChatClientAgentTests
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new AIContext());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
|
||||
// Act
|
||||
await agent.RunAsync([new(ChatRole.User, "user message")]);
|
||||
@@ -907,7 +907,7 @@ public partial class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
var metadata = new ChatClientAgentOptions { Instructions = "You are a helpful assistant" };
|
||||
var metadata = new ChatClientAgentOptions { ChatOptions = new() { Instructions = "You are a helpful assistant" } };
|
||||
ChatClientAgent agent = new(chatClient, metadata);
|
||||
|
||||
// Act & Assert
|
||||
@@ -936,7 +936,7 @@ public partial class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
var metadata = new ChatClientAgentOptions { Instructions = null };
|
||||
var metadata = new ChatClientAgentOptions { ChatOptions = new() { Instructions = null } };
|
||||
ChatClientAgent agent = new(chatClient, metadata);
|
||||
|
||||
// Act & Assert
|
||||
@@ -967,10 +967,10 @@ public partial class ChatClientAgentTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatOptions property returns null when no params are provided that require a ChatOptions instance.
|
||||
/// Verify that ChatOptions is created with instructions when instructions are provided and no tools are provided.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatOptionsReturnsNullWhenConstructorToolsNotProvided()
|
||||
public void ChatOptionsCreatedWithInstructionsEvenWhenConstructorToolsNotProvided()
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
@@ -980,7 +980,8 @@ public partial class ChatClientAgentTests
|
||||
Assert.Equal("TestInstructions", agent.Instructions);
|
||||
Assert.Equal("TestName", agent.Name);
|
||||
Assert.Equal("TestDescription", agent.Description);
|
||||
Assert.Null(agent.ChatOptions);
|
||||
Assert.NotNull(agent.ChatOptions);
|
||||
Assert.Equal("TestInstructions", agent.ChatOptions.Instructions);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1071,7 +1072,7 @@ public partial class ChatClientAgentTests
|
||||
public async Task ChatOptionsMergingUsesAgentOptionsWhenRequestHasNoneAsync()
|
||||
{
|
||||
// Arrange
|
||||
var agentChatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f };
|
||||
var agentChatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f, Instructions = "test instructions" };
|
||||
Mock<IChatClient> mockService = new();
|
||||
ChatOptions? capturedChatOptions = null;
|
||||
mockService.Setup(
|
||||
@@ -1085,7 +1086,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = agentChatOptions
|
||||
});
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
@@ -1114,7 +1114,7 @@ public partial class ChatClientAgentTests
|
||||
capturedChatOptions = opts)
|
||||
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new("test instructions"));
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
|
||||
// Act
|
||||
@@ -1167,6 +1167,7 @@ public partial class ChatClientAgentTests
|
||||
// Arrange
|
||||
var agentChatOptions = new ChatOptions
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
MaxOutputTokens = 100,
|
||||
Temperature = 0.7f,
|
||||
TopP = 0.9f,
|
||||
@@ -1204,7 +1205,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = agentChatOptions
|
||||
});
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
@@ -1263,6 +1263,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agentChatOptions = new ChatOptions
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
Tools = [agentTool]
|
||||
};
|
||||
var requestChatOptions = new ChatOptions
|
||||
@@ -1283,7 +1284,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = agentChatOptions
|
||||
});
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
@@ -1312,6 +1312,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agentChatOptions = new ChatOptions
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
Tools = [agentTool]
|
||||
};
|
||||
var requestChatOptions = new ChatOptions
|
||||
@@ -1333,7 +1334,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = agentChatOptions
|
||||
});
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
@@ -1360,6 +1360,7 @@ public partial class ChatClientAgentTests
|
||||
// Arrange
|
||||
var agentChatOptions = new ChatOptions
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
RawRepresentationFactory = _ => agentSetting
|
||||
};
|
||||
var requestChatOptions = new ChatOptions
|
||||
@@ -1380,7 +1381,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = agentChatOptions
|
||||
});
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
@@ -1436,7 +1436,7 @@ public partial class ChatClientAgentTests
|
||||
TopK = 50,
|
||||
PresencePenalty = 0.1f,
|
||||
FrequencyPenalty = 0.2f,
|
||||
Instructions = "test instructions\nrequest instructions",
|
||||
Instructions = "agent instructions\nrequest instructions",
|
||||
ModelId = "agent-model",
|
||||
Seed = 12345,
|
||||
ConversationId = "agent-conversation",
|
||||
@@ -1459,7 +1459,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = agentChatOptions
|
||||
});
|
||||
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
|
||||
@@ -1509,7 +1508,7 @@ public partial class ChatClientAgentTests
|
||||
{
|
||||
Id = "test-agent-id",
|
||||
Name = "TestAgent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1532,7 +1531,7 @@ public partial class ChatClientAgentTests
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1556,7 +1555,7 @@ public partial class ChatClientAgentTests
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1582,7 +1581,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1606,7 +1605,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1632,7 +1631,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1661,7 +1660,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1694,7 +1693,7 @@ public partial class ChatClientAgentTests
|
||||
{
|
||||
Id = "test-agent-id",
|
||||
Name = "TestAgent",
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1721,7 +1720,7 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1756,12 +1755,12 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var chatClientAgent1 = new ChatClientAgent(mockChatClient1.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions 1"
|
||||
ChatOptions = new() { Instructions = "Test instructions 1" }
|
||||
});
|
||||
|
||||
var chatClientAgent2 = new ChatClientAgent(mockChatClient2.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions 2"
|
||||
ChatOptions = new() { Instructions = "Test instructions 2" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1796,7 +1795,7 @@ public partial class ChatClientAgentTests
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1820,7 +1819,7 @@ public partial class ChatClientAgentTests
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1845,7 +1844,7 @@ public partial class ChatClientAgentTests
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act - Request IChatClient with a service key (base.GetService will return null due to serviceKey)
|
||||
@@ -1870,7 +1869,7 @@ public partial class ChatClientAgentTests
|
||||
mockChatClient.Setup(c => c.GetService(typeof(string), "some-key")).Returns("test-result");
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
ChatOptions = new() { Instructions = "Test instructions" }
|
||||
});
|
||||
|
||||
// Act - Request string with a service key (base.GetService will return null due to serviceKey)
|
||||
@@ -1911,7 +1910,7 @@ public partial class ChatClientAgentTests
|
||||
ChatClientAgent agent =
|
||||
new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions"
|
||||
ChatOptions = new() { Instructions = "test instructions" }
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -1958,7 +1957,7 @@ public partial class ChatClientAgentTests
|
||||
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
ChatMessageStoreFactory = mockFactory.Object
|
||||
});
|
||||
|
||||
@@ -1996,7 +1995,7 @@ public partial class ChatClientAgentTests
|
||||
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
Instructions = "test instructions",
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
ChatMessageStoreFactory = mockFactory.Object
|
||||
});
|
||||
|
||||
@@ -2049,7 +2048,7 @@ public partial class ChatClientAgentTests
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object });
|
||||
|
||||
// Act
|
||||
var thread = agent.GetNewThread() as ChatClientAgentThread;
|
||||
@@ -2112,7 +2111,7 @@ public partial class ChatClientAgentTests
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object });
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ public class ChatClientAgent_DeserializeThreadTests
|
||||
var factoryCalled = false;
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { Instructions = "Test instructions" },
|
||||
AIContextProviderFactory = _ =>
|
||||
{
|
||||
factoryCalled = true;
|
||||
@@ -53,7 +53,7 @@ public class ChatClientAgent_DeserializeThreadTests
|
||||
var factoryCalled = false;
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { Instructions = "Test instructions" },
|
||||
ChatMessageStoreFactory = _ =>
|
||||
{
|
||||
factoryCalled = true;
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ public class ChatClientAgent_GetNewThreadTests
|
||||
var factoryCalled = false;
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { Instructions = "Test instructions" },
|
||||
AIContextProviderFactory = _ =>
|
||||
{
|
||||
factoryCalled = true;
|
||||
@@ -46,7 +46,7 @@ public class ChatClientAgent_GetNewThreadTests
|
||||
var factoryCalled = false;
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Test instructions",
|
||||
ChatOptions = new() { Instructions = "Test instructions" },
|
||||
ChatMessageStoreFactory = _ =>
|
||||
{
|
||||
factoryCalled = true;
|
||||
|
||||
+4
-4
@@ -90,7 +90,7 @@ public sealed class ChatClientBuilderExtensionsTests
|
||||
{
|
||||
Name = "AgentWithOptions",
|
||||
Description = "Desc",
|
||||
Instructions = "Instr",
|
||||
ChatOptions = new() { Instructions = "Instr" },
|
||||
UseProvidedChatClientAsIs = true
|
||||
};
|
||||
|
||||
@@ -115,7 +115,7 @@ public sealed class ChatClientBuilderExtensionsTests
|
||||
var options = new ChatClientAgentOptions
|
||||
{
|
||||
Name = "ServiceAgent",
|
||||
Instructions = "Service instructions"
|
||||
ChatOptions = new() { Instructions = "Service instructions" }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -148,7 +148,7 @@ public sealed class ChatClientBuilderExtensionsTests
|
||||
ChatClientBuilder builder = null!;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => builder.BuildAIAgent(options: new() { Instructions = "instructions" }));
|
||||
Assert.Throws<ArgumentNullException>(() => builder.BuildAIAgent(options: new() { ChatOptions = new() { Instructions = "instructions" } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -166,7 +166,7 @@ public sealed class ChatClientBuilderExtensionsTests
|
||||
var agent = builder.BuildAIAgent(
|
||||
new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "Middleware test",
|
||||
ChatOptions = new() { Instructions = "Middleware test" },
|
||||
UseProvidedChatClientAsIs = true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class ChatClientExtensionsTests
|
||||
{
|
||||
Name = "AgentWithOptions",
|
||||
Description = "Desc",
|
||||
Instructions = "Instr",
|
||||
ChatOptions = new() { Instructions = "Instr" },
|
||||
UseProvidedChatClientAsIs = true
|
||||
};
|
||||
|
||||
@@ -89,6 +89,6 @@ public sealed class ChatClientExtensionsTests
|
||||
IChatClient chatClient = null!;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => chatClient.CreateAIAgent(options: new() { Instructions = "instructions" }));
|
||||
Assert.Throws<ArgumentNullException>(() => chatClient.CreateAIAgent(options: new() { ChatOptions = new() { Instructions = "instructions" } }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Data;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
@@ -40,6 +40,12 @@ internal sealed class MockAgentProvider : Mock<WorkflowAgentProvider>
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(ToAsyncEnumerableAsync(testMessages));
|
||||
|
||||
this.Setup(provider => provider.CreateMessageAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<ChatMessage>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult(testMessages.First()));
|
||||
}
|
||||
|
||||
private string CreateConversationId()
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="AddConversationMessageExecutor"/>.
|
||||
/// </summary>
|
||||
public sealed class AddConversationMessageExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(AgentMessageRole.User)]
|
||||
[InlineData(AgentMessageRole.Agent)]
|
||||
public async Task AddMessageSuccessfullyAsync(AgentMessageRole role)
|
||||
{
|
||||
// Arrange, Act, Assert
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(AddMessageSuccessfullyAsync),
|
||||
variableName: "TestMessage",
|
||||
role: AgentMessageRoleWrapper.Get(role),
|
||||
messageText: $"Hello from {role}");
|
||||
}
|
||||
|
||||
private async Task ExecuteTestAsync(
|
||||
string displayName,
|
||||
string variableName,
|
||||
AgentMessageRoleWrapper role,
|
||||
string messageText)
|
||||
{
|
||||
// Arrange
|
||||
MockAgentProvider mockAgentProvider = new();
|
||||
AddConversationMessage model = this.CreateModel(
|
||||
this.FormatDisplayName(displayName),
|
||||
FormatVariablePath(variableName),
|
||||
"TestConversationId",
|
||||
role,
|
||||
messageText);
|
||||
|
||||
AddConversationMessageExecutor action = new(model, mockAgentProvider.Object, this.State);
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
ChatMessage? testMessage = mockAgentProvider.TestMessages?.FirstOrDefault();
|
||||
Assert.NotNull(testMessage);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState(variableName, testMessage.ToRecord());
|
||||
}
|
||||
|
||||
private AddConversationMessage CreateModel(
|
||||
string displayName,
|
||||
string messageVariable,
|
||||
string conversationId,
|
||||
AgentMessageRoleWrapper role,
|
||||
string messageText)
|
||||
{
|
||||
AddConversationMessage.Builder actionBuilder =
|
||||
new()
|
||||
{
|
||||
Id = this.CreateActionId(),
|
||||
DisplayName = this.FormatDisplayName(displayName),
|
||||
Message = PropertyPath.Create(messageVariable),
|
||||
ConversationId = StringExpression.Literal(conversationId),
|
||||
Role = role,
|
||||
};
|
||||
|
||||
actionBuilder.Content.Add(new AddConversationMessageContent.Builder
|
||||
{
|
||||
Type = AgentMessageContentType.Text,
|
||||
Value = TemplateLine.Parse(messageText)
|
||||
});
|
||||
|
||||
return AssignParent<AddConversationMessage>(actionBuilder);
|
||||
}
|
||||
}
|
||||
+91
-18
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
@@ -37,14 +38,24 @@ public class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync(
|
||||
model: s_config.ChatModelId!,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [weatherFunction]
|
||||
}
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent(
|
||||
model: s_config.ChatModelId!,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = AgentInstructions,
|
||||
Tools = [weatherFunction]
|
||||
}
|
||||
}),
|
||||
"CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync(
|
||||
model: s_config.ChatModelId!,
|
||||
instructions: AgentInstructions,
|
||||
@@ -94,14 +105,24 @@ public class OpenAIAssistantClientExtensionsTests
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync(
|
||||
model: s_config.ChatModelId!,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
tools: [codeInterpreterTool])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = Instructions,
|
||||
Tools = [codeInterpreterTool]
|
||||
}
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent(
|
||||
model: s_config.ChatModelId!,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
tools: [codeInterpreterTool])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = Instructions,
|
||||
Tools = [codeInterpreterTool]
|
||||
}
|
||||
}),
|
||||
"CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync(
|
||||
model: s_config.ChatModelId!,
|
||||
instructions: Instructions,
|
||||
@@ -153,20 +174,33 @@ public class OpenAIAssistantClientExtensionsTests
|
||||
});
|
||||
string vectorStoreId = vectorStoreCreate.Value.Id;
|
||||
|
||||
// Wait for vector store indexing to complete before using it
|
||||
await WaitForVectorStoreReadyAsync(vectorStoreClient, vectorStoreId);
|
||||
|
||||
var fileSearchTool = new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreId)] };
|
||||
|
||||
var agent = createMechanism switch
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync(
|
||||
model: s_config.ChatModelId!,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
tools: [fileSearchTool])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = Instructions,
|
||||
Tools = [fileSearchTool]
|
||||
}
|
||||
}),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent(
|
||||
model: s_config.ChatModelId!,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: Instructions,
|
||||
tools: [fileSearchTool])),
|
||||
options: new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = Instructions,
|
||||
Tools = [fileSearchTool]
|
||||
}
|
||||
}),
|
||||
"CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync(
|
||||
model: s_config.ChatModelId!,
|
||||
instructions: Instructions,
|
||||
@@ -189,4 +223,43 @@ public class OpenAIAssistantClientExtensionsTests
|
||||
File.Delete(searchFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for a vector store to complete indexing by polling its status.
|
||||
/// </summary>
|
||||
/// <param name="client">The vector store client.</param>
|
||||
/// <param name="vectorStoreId">The ID of the vector store.</param>
|
||||
/// <param name="maxWaitSeconds">Maximum time to wait in seconds (default: 30).</param>
|
||||
/// <returns>A task that completes when the vector store is ready or throws on timeout/failure.</returns>
|
||||
private static async Task WaitForVectorStoreReadyAsync(
|
||||
VectorStoreClient client,
|
||||
string vectorStoreId,
|
||||
int maxWaitSeconds = 30)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
while (sw.Elapsed.TotalSeconds < maxWaitSeconds)
|
||||
{
|
||||
VectorStore vectorStore = await client.GetVectorStoreAsync(vectorStoreId);
|
||||
VectorStoreStatus status = vectorStore.Status;
|
||||
|
||||
if (status == VectorStoreStatus.Completed)
|
||||
{
|
||||
if (vectorStore.FileCounts.Failed > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Vector store indexing failed for some files");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == VectorStoreStatus.Expired)
|
||||
{
|
||||
throw new InvalidOperationException("Vector store has expired");
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Vector store did not complete indexing within {maxWaitSeconds}s");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ public class OpenAIChatCompletionFixture : IChatClientAgentFixture
|
||||
return Task.FromResult(new ChatClientAgent(chatClient, options: new()
|
||||
{
|
||||
Name = name,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new() { Tools = aiTools }
|
||||
ChatOptions = new() { Instructions = instructions, Tools = aiTools }
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -73,9 +73,9 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
|
||||
options: new()
|
||||
{
|
||||
Name = name,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
Instructions = instructions,
|
||||
Tools = aiTools,
|
||||
RawRepresentationFactory = new Func<IChatClient, object>(_ => new ResponseCreationOptions() { StoredOutputEnabled = store })
|
||||
},
|
||||
|
||||
+34
-1
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.0b251204] - 2025-12-04
|
||||
|
||||
### Added
|
||||
|
||||
- **agent-framework-core**: Add support for Pydantic `BaseModel` as function call result (#2606)
|
||||
- **agent-framework-core**: Executor events now include I/O data (#2591)
|
||||
- **samples**: Inline YAML declarative sample (#2582)
|
||||
- **samples**: Handoff-as-agent with HITL sample (#2534)
|
||||
|
||||
### Changed
|
||||
|
||||
- **agent-framework-core**: [BREAKING] Support Magentic agent tool call approvals and plan stalling HITL behavior (#2569)
|
||||
- **agent-framework-core**: [BREAKING] Standardize orchestration outputs as list of `ChatMessage`; allow agent as group chat manager (#2291)
|
||||
- **agent-framework-core**: [BREAKING] Respond with `AgentRunResponse` including serialized structured output (#2285)
|
||||
- **observability**: Use `executor_id` and `edge_group_id` as span names for clearer traces (#2538)
|
||||
- **agent-framework-devui**: Add multimodal input support for workflows and refactor chat input (#2593)
|
||||
- **docs**: Update Python orchestration documentation (#2087)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **observability**: Resolve mypy error in observability module (#2641)
|
||||
- **agent-framework-core**: Fix `AgentRunResponse.created_at` returning local datetime labeled as UTC (#2590)
|
||||
- **agent-framework-core**: Emit `ExecutorFailedEvent` before `WorkflowFailedEvent` when executor throws (#2537)
|
||||
- **agent-framework-core**: Fix MagenticAgentExecutor producing `repr` string for tool call content (#2566)
|
||||
- **agent-framework-core**: Fixed empty text content Pydantic validation failure (#2539)
|
||||
- **agent-framework-azure-ai**: Added support for application endpoints in Azure AI client (#2460)
|
||||
- **agent-framework-azurefunctions**: Add MCP tool support (#2385)
|
||||
- **agent-framework-core**: Preserve MCP array items schema in Pydantic field generation (#2382)
|
||||
- **agent-framework-devui**: Make tool call view optional and fix links (#2243)
|
||||
- **agent-framework-core**: Always include output in function call result messages (#2414)
|
||||
- **agent-framework-redis**: Fix TypeError (#2411)
|
||||
|
||||
## [1.0.0b251120] - 2025-11-20
|
||||
|
||||
### Added
|
||||
@@ -290,7 +322,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
For more information, see the [announcement blog post](https://devblogs.microsoft.com/foundry/introducing-microsoft-agent-framework-the-open-source-engine-for-agentic-ai-apps/).
|
||||
|
||||
[Unreleased]: https://github.com/microsoft/agent-framework/compare/python-1.0.0b251120...HEAD
|
||||
[Unreleased]: https://github.com/microsoft/agent-framework/compare/python-1.0.0b251204...HEAD
|
||||
[1.0.0b251204]: https://github.com/microsoft/agent-framework/compare/python-1.0.0b251120...python-1.0.0b251204
|
||||
[1.0.0b251120]: https://github.com/microsoft/agent-framework/compare/python-1.0.0b251117...python-1.0.0b251120
|
||||
[1.0.0b251117]: https://github.com/microsoft/agent-framework/compare/python-1.0.0b251114...python-1.0.0b251117
|
||||
[1.0.0b251114]: https://github.com/microsoft/agent-framework/compare/python-1.0.0b251112.post1...python-1.0.0b251114
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "A2A integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b251120"
|
||||
version = "1.0.0b251204"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
|
||||
@@ -134,6 +134,13 @@ class AgentFrameworkEventBridge:
|
||||
logger.info(f" Suppressed summary length={len(self.suppressed_summary)}")
|
||||
return events
|
||||
|
||||
# Skip empty text chunks to avoid emitting
|
||||
# TextMessageContentEvent with an empty `delta` which fails
|
||||
# Pydantic validation (AG-UI requires non-empty strings).
|
||||
if not content.text:
|
||||
logger.info(" SKIPPING TextContent: empty chunk")
|
||||
return events
|
||||
|
||||
if not self.current_message_id:
|
||||
self.current_message_id = generate_event_id()
|
||||
start_event = TextMessageStartEvent(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "agent-framework-ag-ui"
|
||||
version = "1.0.0b251120"
|
||||
version = "1.0.0b251204"
|
||||
description = "AG-UI protocol integration for Agent Framework"
|
||||
readme = "README.md"
|
||||
license-files = ["LICENSE"]
|
||||
|
||||
@@ -68,6 +68,37 @@ async def test_skip_text_content_for_structured_outputs():
|
||||
assert len(events) == 0
|
||||
|
||||
|
||||
async def test_skip_text_content_for_empty_text():
|
||||
"""Test streaming TextContent with empty chunks."""
|
||||
from agent_framework_ag_ui._events import AgentFrameworkEventBridge
|
||||
|
||||
bridge = AgentFrameworkEventBridge(run_id="test_run", thread_id="test_thread")
|
||||
|
||||
update1 = AgentRunResponseUpdate(contents=[TextContent(text="Hello ")])
|
||||
update2 = AgentRunResponseUpdate(contents=[TextContent(text="")]) # Empty chunk
|
||||
update3 = AgentRunResponseUpdate(contents=[TextContent(text="world")])
|
||||
|
||||
events1 = await bridge.from_agent_run_update(update1)
|
||||
events2 = await bridge.from_agent_run_update(update2)
|
||||
events3 = await bridge.from_agent_run_update(update3)
|
||||
|
||||
# First update: START + CONTENT
|
||||
assert len(events1) == 2
|
||||
assert events1[0].type == "TEXT_MESSAGE_START"
|
||||
assert events1[1].delta == "Hello "
|
||||
|
||||
# Second update: should skip empty chunk, no events
|
||||
assert len(events2) == 0
|
||||
|
||||
# Third update: just CONTENT (same message)
|
||||
assert len(events3) == 1
|
||||
assert events3[0].type == "TEXT_MESSAGE_CONTENT"
|
||||
assert events3[0].delta == "world"
|
||||
|
||||
# Both content events should have same message_id
|
||||
assert events1[1].message_id == events3[0].message_id
|
||||
|
||||
|
||||
async def test_tool_call_with_name():
|
||||
"""Test FunctionCallContent with name emits ToolCallStartEvent."""
|
||||
from agent_framework_ag_ui._events import AgentFrameworkEventBridge
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Anthropic integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b251120"
|
||||
version = "1.0.0b251204"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
|
||||
+118
-44
@@ -5,7 +5,7 @@ import sys
|
||||
from collections.abc import Awaitable, Callable, MutableSequence
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
||||
|
||||
from agent_framework import ChatMessage, Context, ContextProvider, Role
|
||||
from agent_framework import AGENT_FRAMEWORK_USER_AGENT, ChatMessage, Context, ContextProvider, Role
|
||||
from agent_framework._logging import get_logger
|
||||
from agent_framework._pydantic import AFBaseSettings
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -129,6 +129,8 @@ class AzureAISearchSettings(AFBaseSettings):
|
||||
Can be set via environment variable AZURE_SEARCH_ENDPOINT.
|
||||
index_name: Name of the search index.
|
||||
Can be set via environment variable AZURE_SEARCH_INDEX_NAME.
|
||||
knowledge_base_name: Name of an existing Knowledge Base (for agentic mode).
|
||||
Can be set via environment variable AZURE_SEARCH_KNOWLEDGE_BASE_NAME.
|
||||
api_key: API key for authentication (optional, use managed identity if not provided).
|
||||
Can be set via environment variable AZURE_SEARCH_API_KEY.
|
||||
env_file_path: If provided, the .env settings are read from this file path location.
|
||||
@@ -158,6 +160,7 @@ class AzureAISearchSettings(AFBaseSettings):
|
||||
|
||||
endpoint: str | None = None
|
||||
index_name: str | None = None
|
||||
knowledge_base_name: str | None = None
|
||||
api_key: SecretStr | None = None
|
||||
|
||||
|
||||
@@ -239,7 +242,6 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
embedding_function: Callable[[str], Awaitable[list[float]]] | None = None,
|
||||
context_prompt: str | None = None,
|
||||
# Agentic mode parameters (Knowledge Base)
|
||||
azure_ai_project_endpoint: str | None = None,
|
||||
azure_openai_resource_url: str | None = None,
|
||||
model_deployment_name: str | None = None,
|
||||
model_name: str | None = None,
|
||||
@@ -277,22 +279,18 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
Required if vector_field_name is specified and no server-side vectorization.
|
||||
context_prompt: Custom prompt to prepend to retrieved context.
|
||||
Default: "Use the following context to answer the question:"
|
||||
azure_ai_project_endpoint: Azure AI Foundry project endpoint URL.
|
||||
This is NOT the same as azure_openai_resource_url - the project endpoint is used
|
||||
for Azure AI Foundry services, while the OpenAI endpoint is used by the Knowledge
|
||||
Base to call the model for query planning. Required for agentic mode.
|
||||
Example: "https://myproject.services.ai.azure.com/api/projects/myproject"
|
||||
azure_openai_resource_url: Azure OpenAI resource URL for Knowledge Base model calls.
|
||||
This is the OpenAI endpoint used by the Knowledge Base to call the LLM for
|
||||
query planning and reasoning. This is separate from the project endpoint because
|
||||
the Knowledge Base directly calls Azure OpenAI for its internal operations.
|
||||
Required for agentic mode. Example: "https://myresource.openai.azure.com"
|
||||
Required when using agentic mode with index_name (to auto-create Knowledge Base).
|
||||
Not required when using an existing knowledge_base_name.
|
||||
Example: "https://myresource.openai.azure.com"
|
||||
model_deployment_name: Model deployment name in Azure OpenAI for Knowledge Base.
|
||||
This is the deployment name the Knowledge Base uses to call the LLM.
|
||||
Required for agentic mode.
|
||||
Required when using agentic mode with index_name (to auto-create Knowledge Base).
|
||||
Not required when using an existing knowledge_base_name.
|
||||
model_name: The underlying model name (e.g., "gpt-4o", "gpt-4o-mini").
|
||||
If not provided, defaults to model_deployment_name. Used for Knowledge Base configuration.
|
||||
knowledge_base_name: Name for the Knowledge Base. Required for agentic mode.
|
||||
knowledge_base_name: Name of an existing Knowledge Base to use.
|
||||
Required for agentic mode if not providing index_name.
|
||||
Supports KBs with any source type (web, blob, index, etc.).
|
||||
retrieval_instructions: Custom instructions for the Knowledge Base's
|
||||
retrieval planning. Only used in agentic mode.
|
||||
azure_openai_api_key: Azure OpenAI API key for Knowledge Base to call the model.
|
||||
@@ -340,6 +338,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
settings = AzureAISearchSettings(
|
||||
endpoint=endpoint,
|
||||
index_name=index_name,
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
api_key=api_key if isinstance(api_key, str) else None,
|
||||
env_file_path=env_file_path,
|
||||
env_file_encoding=env_file_encoding,
|
||||
@@ -353,11 +352,36 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
"Azure AI Search endpoint is required. Set via 'endpoint' parameter "
|
||||
"or 'AZURE_SEARCH_ENDPOINT' environment variable."
|
||||
)
|
||||
if not settings.index_name:
|
||||
raise ServiceInitializationError(
|
||||
"Azure AI Search index name is required. Set via 'index_name' parameter "
|
||||
"or 'AZURE_SEARCH_INDEX_NAME' environment variable."
|
||||
)
|
||||
|
||||
# Validate index_name and knowledge_base_name based on mode
|
||||
# Note: settings.* contains the resolved value (explicit param OR env var)
|
||||
if mode == "semantic":
|
||||
# Semantic mode: always requires index_name
|
||||
if not settings.index_name:
|
||||
raise ServiceInitializationError(
|
||||
"Azure AI Search index name is required for semantic mode. "
|
||||
"Set via 'index_name' parameter or 'AZURE_SEARCH_INDEX_NAME' environment variable."
|
||||
)
|
||||
elif mode == "agentic":
|
||||
# Agentic mode: requires exactly ONE of index_name or knowledge_base_name
|
||||
if settings.index_name and settings.knowledge_base_name:
|
||||
raise ServiceInitializationError(
|
||||
"For agentic mode, provide either 'index_name' OR 'knowledge_base_name', not both. "
|
||||
"Use 'index_name' to auto-create a Knowledge Base, or 'knowledge_base_name' to use an existing one."
|
||||
)
|
||||
if not settings.index_name and not settings.knowledge_base_name:
|
||||
raise ServiceInitializationError(
|
||||
"For agentic mode, provide either 'index_name' (to auto-create Knowledge Base) "
|
||||
"or 'knowledge_base_name' (to use existing Knowledge Base). "
|
||||
"Set via parameters or environment variables "
|
||||
"AZURE_SEARCH_INDEX_NAME / AZURE_SEARCH_KNOWLEDGE_BASE_NAME."
|
||||
)
|
||||
# If using index_name to create KB, model config is required
|
||||
if settings.index_name and not model_deployment_name:
|
||||
raise ServiceInitializationError(
|
||||
"model_deployment_name is required for agentic mode when creating Knowledge Base from index. "
|
||||
"This is the Azure OpenAI deployment used by the Knowledge Base for query planning."
|
||||
)
|
||||
|
||||
# Determine the credential to use
|
||||
resolved_credential: AzureKeyCredential | AsyncTokenCredential
|
||||
@@ -389,14 +413,27 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
self.azure_openai_deployment_name = model_deployment_name
|
||||
# If model_name not provided, default to deployment name
|
||||
self.model_name = model_name or model_deployment_name
|
||||
self.knowledge_base_name = knowledge_base_name
|
||||
# Use resolved KB name (from explicit param or env var)
|
||||
self.knowledge_base_name = settings.knowledge_base_name
|
||||
self.retrieval_instructions = retrieval_instructions
|
||||
self.azure_openai_api_key = azure_openai_api_key
|
||||
self.azure_ai_project_endpoint = azure_ai_project_endpoint
|
||||
self.knowledge_base_output_mode = knowledge_base_output_mode
|
||||
self.retrieval_reasoning_effort = retrieval_reasoning_effort
|
||||
self.agentic_message_history_count = agentic_message_history_count
|
||||
|
||||
# Determine if using existing Knowledge Base or auto-creating from index
|
||||
# Since validation ensures exactly one of index_name/knowledge_base_name for agentic mode:
|
||||
# - knowledge_base_name provided: use existing KB
|
||||
# - index_name provided: auto-create KB from index
|
||||
self._use_existing_knowledge_base = False
|
||||
if mode == "agentic":
|
||||
if settings.knowledge_base_name:
|
||||
# Use existing KB directly (supports any source type: web, blob, index, etc.)
|
||||
self._use_existing_knowledge_base = True
|
||||
else:
|
||||
# Auto-generate KB name from index name
|
||||
self.knowledge_base_name = f"{settings.index_name}-kb"
|
||||
|
||||
# Auto-discover vector field if not specified
|
||||
self._auto_discovered_vector_field = False
|
||||
self._use_vectorizable_query = False # Will be set to True if server-side vectorization detected
|
||||
@@ -415,22 +452,24 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
"Agentic retrieval requires azure-search-documents >= 11.7.0b1 with Knowledge Base support. "
|
||||
"Please upgrade: pip install azure-search-documents>=11.7.0b1"
|
||||
)
|
||||
if not self.azure_openai_resource_url:
|
||||
# Only require OpenAI resource URL if NOT using existing KB
|
||||
# (existing KB already has its model configuration)
|
||||
# Note: model_deployment_name is already validated at initialization
|
||||
if not self._use_existing_knowledge_base and not self.azure_openai_resource_url:
|
||||
raise ValueError(
|
||||
"azure_openai_resource_url is required for agentic mode. "
|
||||
"azure_openai_resource_url is required for agentic mode when creating Knowledge Base from index. "
|
||||
"This should be your Azure OpenAI endpoint (e.g., 'https://myresource.openai.azure.com')"
|
||||
)
|
||||
if not self.azure_openai_deployment_name:
|
||||
raise ValueError("model_deployment_name is required for agentic mode")
|
||||
if not knowledge_base_name:
|
||||
raise ValueError("knowledge_base_name is required for agentic mode")
|
||||
|
||||
# Create search client for semantic mode
|
||||
self._search_client = SearchClient(
|
||||
endpoint=self.endpoint,
|
||||
index_name=self.index_name,
|
||||
credential=self.credential,
|
||||
)
|
||||
# Create search client for semantic mode (only if index_name is available)
|
||||
self._search_client: SearchClient | None = None
|
||||
if self.index_name:
|
||||
self._search_client = SearchClient(
|
||||
endpoint=self.endpoint,
|
||||
index_name=self.index_name,
|
||||
credential=self.credential,
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
|
||||
# Create index client and retrieval client for agentic mode (Knowledge Base)
|
||||
self._index_client: SearchIndexClient | None = None
|
||||
@@ -439,6 +478,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
self._index_client = SearchIndexClient(
|
||||
endpoint=self.endpoint,
|
||||
credential=self.credential,
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
# Retrieval client will be created after Knowledge Base initialization
|
||||
|
||||
@@ -574,10 +614,19 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
try:
|
||||
# Use existing index client or create temporary one
|
||||
if not self._index_client:
|
||||
self._index_client = SearchIndexClient(endpoint=self.endpoint, credential=self.credential)
|
||||
self._index_client = SearchIndexClient(
|
||||
endpoint=self.endpoint,
|
||||
credential=self.credential,
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
index_client = self._index_client
|
||||
|
||||
# Get index schema
|
||||
# Get index schema (index_name is guaranteed to be set for semantic mode)
|
||||
if not self.index_name:
|
||||
logger.warning("Cannot auto-discover vector field: index_name is not set.")
|
||||
self._auto_discovered_vector_field = True
|
||||
return
|
||||
|
||||
index = await index_client.get_index(self.index_name)
|
||||
|
||||
# Step 1: Find all vector fields
|
||||
@@ -694,7 +743,10 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
search_params["semantic_configuration_name"] = self.semantic_configuration_name
|
||||
search_params["query_caption"] = QueryCaptionType.EXTRACTIVE
|
||||
|
||||
# Execute search
|
||||
# Execute search (search client is guaranteed to exist for semantic mode)
|
||||
if not self._search_client:
|
||||
raise RuntimeError("Search client is not initialized. This should not happen in semantic mode.")
|
||||
|
||||
results = await self._search_client.search(**search_params) # type: ignore[reportUnknownVariableType]
|
||||
|
||||
# Format results with citations
|
||||
@@ -711,27 +763,48 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
return formatted_results
|
||||
|
||||
async def _ensure_knowledge_base(self) -> None:
|
||||
"""Ensure Knowledge Base and knowledge source are created.
|
||||
"""Ensure Knowledge Base and knowledge source are created or use existing KB.
|
||||
|
||||
This method is idempotent - it will only create resources if they don't exist.
|
||||
|
||||
Note: Azure SDK uses KnowledgeAgent classes internally, but the feature
|
||||
is marketed as "Knowledge Bases" in Azure AI Search.
|
||||
"""
|
||||
if self._knowledge_base_initialized or not self._index_client:
|
||||
if self._knowledge_base_initialized:
|
||||
return
|
||||
|
||||
# Runtime validation for agentic mode parameters
|
||||
# Runtime validation
|
||||
if not self.knowledge_base_name:
|
||||
raise ValueError("knowledge_base_name is required for agentic mode")
|
||||
if not self.azure_openai_resource_url:
|
||||
raise ValueError("azure_openai_resource_url is required for agentic mode")
|
||||
if not self.azure_openai_deployment_name:
|
||||
raise ValueError("model_deployment_name is required for agentic mode")
|
||||
|
||||
knowledge_base_name = self.knowledge_base_name
|
||||
|
||||
# Step 1: Create or get knowledge source
|
||||
# Path 1: Use existing Knowledge Base directly (no index needed)
|
||||
# This supports KB with any source type (web, blob, index, etc.)
|
||||
if self._use_existing_knowledge_base:
|
||||
# Just create the retrieval client - KB already exists with its own sources
|
||||
if _agentic_retrieval_available and self._retrieval_client is None:
|
||||
self._retrieval_client = KnowledgeBaseRetrievalClient(
|
||||
endpoint=self.endpoint,
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
credential=self.credential,
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
self._knowledge_base_initialized = True
|
||||
return
|
||||
|
||||
# Path 2: Auto-create Knowledge Base from search index
|
||||
# Requires index_client and OpenAI configuration
|
||||
if not self._index_client:
|
||||
raise ValueError("Index client is required when creating Knowledge Base from index")
|
||||
if not self.azure_openai_resource_url:
|
||||
raise ValueError("azure_openai_resource_url is required when creating Knowledge Base from index")
|
||||
if not self.azure_openai_deployment_name:
|
||||
raise ValueError("model_deployment_name is required when creating Knowledge Base from index")
|
||||
if not self.index_name:
|
||||
raise ValueError("index_name is required when creating Knowledge Base from index")
|
||||
|
||||
# Step 1: Create or get knowledge source from index
|
||||
knowledge_source_name = f"{self.index_name}-source"
|
||||
|
||||
try:
|
||||
@@ -794,6 +867,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
endpoint=self.endpoint,
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
credential=self.credential,
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
|
||||
async def _agentic_search(self, messages: list[ChatMessage]) -> list[str]:
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Azure AI Search integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b251118"
|
||||
version = "1.0.0b251204"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user