mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5786b2c5b | ||
|
|
d858c23bab | ||
|
|
365e438981 | ||
|
|
01a04a3114 | ||
|
|
ee340bef1d | ||
|
|
19ac6e57c0 | ||
|
|
292a2078fa | ||
|
|
ae06c9e4bb | ||
|
|
f7a9005235 | ||
|
|
b86c130411 | ||
|
|
d774b64df0 | ||
|
|
42ffe59592 | ||
|
|
a683c0e865 | ||
|
|
a597a925cb | ||
|
|
9c2e800189 | ||
|
|
a26e9d6274 | ||
|
|
411ee7a60f | ||
|
|
6835161f2d | ||
|
|
3cfd34836f | ||
|
|
a81279d960 | ||
|
|
931197ba95 | ||
|
|
d57303cf53 | ||
|
|
b5595f6f70 | ||
|
|
6232dd8305 |
@@ -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 |
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
+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)
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -62,7 +62,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
-1
@@ -71,7 +71,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
-1
@@ -29,7 +29,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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
-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);
|
||||
|
||||
-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" } }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -148,74 +148,105 @@ class TestSearchProviderInitialization:
|
||||
vector_field_name="embedding",
|
||||
)
|
||||
|
||||
def test_init_agentic_mode_requires_azure_openai_resource_url(self) -> None:
|
||||
"""Test that agentic mode requires azure_openai_resource_url."""
|
||||
with pytest.raises(ValueError, match="azure_openai_resource_url"):
|
||||
def test_init_agentic_mode_with_kb_only(self) -> None:
|
||||
"""Test agentic mode with existing knowledge_base_name (simplest path)."""
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
assert provider.mode == "agentic"
|
||||
assert provider.knowledge_base_name == "test-kb"
|
||||
assert provider._use_existing_knowledge_base is True
|
||||
|
||||
def test_init_agentic_mode_with_index_requires_model(self) -> None:
|
||||
"""Test that agentic mode with index_name requires model_deployment_name."""
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with (
|
||||
patch.dict(os.environ, clean_env, clear=True),
|
||||
pytest.raises(ServiceInitializationError, match="model_deployment_name"),
|
||||
):
|
||||
AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
def test_init_agentic_mode_requires_model_deployment_name(self) -> None:
|
||||
"""Test that agentic mode requires model_deployment_name."""
|
||||
with pytest.raises(ValueError, match="model_deployment_name"):
|
||||
AzureAISearchContextProvider(
|
||||
def test_init_agentic_mode_with_index_and_model(self) -> None:
|
||||
"""Test agentic mode with index_name (auto-create KB path)."""
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
|
||||
def test_init_agentic_mode_requires_knowledge_base_name(self) -> None:
|
||||
"""Test that agentic mode requires knowledge_base_name."""
|
||||
with pytest.raises(ValueError, match="knowledge_base_name"):
|
||||
AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
assert provider.mode == "agentic"
|
||||
assert provider.index_name == "test-index"
|
||||
assert provider.knowledge_base_name == "test-index-kb" # Auto-generated
|
||||
assert provider._use_existing_knowledge_base is False
|
||||
|
||||
def test_init_agentic_mode_rejects_both_index_and_kb(self) -> None:
|
||||
"""Test that agentic mode rejects both index_name AND knowledge_base_name."""
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with (
|
||||
patch.dict(os.environ, clean_env, clear=True),
|
||||
pytest.raises(ServiceInitializationError, match="either 'index_name' OR 'knowledge_base_name', not both"),
|
||||
):
|
||||
AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
model_deployment_name="gpt-4o",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
def test_init_agentic_mode_with_all_params(self) -> None:
|
||||
"""Test initialization with all agentic mode parameters."""
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="my-gpt-4o-deployment",
|
||||
model_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
assert provider.mode == "agentic"
|
||||
assert provider.azure_ai_project_endpoint == "https://test.services.ai.azure.com"
|
||||
assert provider.azure_openai_resource_url == "https://test.openai.azure.com"
|
||||
assert provider.azure_openai_deployment_name == "my-gpt-4o-deployment"
|
||||
assert provider.model_name == "gpt-4o"
|
||||
assert provider.knowledge_base_name == "test-kb"
|
||||
def test_init_agentic_mode_requires_index_or_kb(self) -> None:
|
||||
"""Test that agentic mode requires either index_name or knowledge_base_name."""
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with (
|
||||
patch.dict(os.environ, clean_env, clear=True),
|
||||
pytest.raises(ServiceInitializationError, match="provide either 'index_name'.*or 'knowledge_base_name'"),
|
||||
):
|
||||
AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
def test_init_model_name_defaults_to_deployment_name(self) -> None:
|
||||
"""Test that model_name defaults to deployment_name if not provided."""
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
assert provider.model_name == "gpt-4o"
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
model_deployment_name="gpt-4o",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
assert provider.model_name == "gpt-4o"
|
||||
|
||||
def test_init_with_custom_context_prompt(self) -> None:
|
||||
"""Test initialization with custom context prompt."""
|
||||
@@ -335,7 +366,7 @@ class TestKnowledgeBaseSetup:
|
||||
async def test_ensure_knowledge_base_creates_when_not_exists(
|
||||
self, mock_search_class: MagicMock, mock_index_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that Knowledge Base is created when it doesn't exist."""
|
||||
"""Test that Knowledge Base is created when it doesn't exist (index_name path)."""
|
||||
# Setup mocks
|
||||
mock_index_client = AsyncMock()
|
||||
mock_index_client.get_knowledge_source.side_effect = ResourceNotFoundError("Not found")
|
||||
@@ -347,57 +378,58 @@ class TestKnowledgeBaseSetup:
|
||||
mock_search_client = AsyncMock()
|
||||
mock_search_class.return_value = mock_search_client
|
||||
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
model_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
# Use index_name path (auto-create KB)
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
model_deployment_name="gpt-4o",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
await provider._ensure_knowledge_base()
|
||||
await provider._ensure_knowledge_base()
|
||||
|
||||
# Verify knowledge source was created
|
||||
mock_index_client.create_knowledge_source.assert_called_once()
|
||||
# Verify Knowledge Base was created
|
||||
mock_index_client.create_or_update_knowledge_base.assert_called_once()
|
||||
# Verify knowledge source was created
|
||||
mock_index_client.create_knowledge_source.assert_called_once()
|
||||
# Verify Knowledge Base was created
|
||||
mock_index_client.create_or_update_knowledge_base.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("agent_framework_azure_ai_search._search_provider.SearchIndexClient")
|
||||
@patch("agent_framework_azure_ai_search._search_provider.SearchClient")
|
||||
async def test_ensure_knowledge_base_skips_when_exists(
|
||||
async def test_ensure_knowledge_base_skips_when_using_existing_kb(
|
||||
self, mock_search_class: MagicMock, mock_index_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that Knowledge Base setup is skipped when already exists."""
|
||||
"""Test that KB setup is skipped when using existing knowledge_base_name."""
|
||||
# Setup mocks
|
||||
mock_index_client = AsyncMock()
|
||||
mock_index_client.get_knowledge_source.return_value = MagicMock() # Exists
|
||||
mock_index_client.get_knowledge_base.return_value = MagicMock() # Exists
|
||||
mock_index_class.return_value = mock_index_client
|
||||
|
||||
mock_search_client = AsyncMock()
|
||||
mock_search_class.return_value = mock_search_client
|
||||
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
# Use knowledge_base_name path (existing KB)
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
await provider._ensure_knowledge_base()
|
||||
await provider._ensure_knowledge_base()
|
||||
|
||||
# Verify nothing was created
|
||||
mock_index_client.create_knowledge_source.assert_not_called()
|
||||
mock_index_client.create_agent.assert_not_called()
|
||||
# Verify nothing was created (using existing KB)
|
||||
mock_index_client.create_knowledge_source.assert_not_called()
|
||||
mock_index_client.create_or_update_knowledge_base.assert_not_called()
|
||||
|
||||
|
||||
class TestContextProviderLifecycle:
|
||||
@@ -437,21 +469,22 @@ class TestContextProviderLifecycle:
|
||||
mock_retrieval_client.close = AsyncMock()
|
||||
mock_retrieval_class.return_value = mock_retrieval_client
|
||||
|
||||
async with AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
) as provider:
|
||||
# Simulate retrieval client being created
|
||||
provider._retrieval_client = mock_retrieval_client
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
# Use knowledge_base_name path (existing KB)
|
||||
async with AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
env_file_path="", # Disable .env file loading
|
||||
) as provider:
|
||||
# Simulate retrieval client being created
|
||||
provider._retrieval_client = mock_retrieval_client
|
||||
|
||||
# Verify cleanup was called
|
||||
mock_retrieval_client.close.assert_called_once()
|
||||
# Verify cleanup was called
|
||||
mock_retrieval_client.close.assert_called_once()
|
||||
|
||||
def test_string_api_key_conversion(self) -> None:
|
||||
"""Test that string api_key is converted to AzureKeyCredential."""
|
||||
@@ -579,9 +612,6 @@ class TestAgenticSearch:
|
||||
|
||||
# Setup index client mock
|
||||
mock_index_client = AsyncMock()
|
||||
mock_index_client.get_knowledge_source.side_effect = ResourceNotFoundError("Not found")
|
||||
mock_index_client.create_knowledge_source = AsyncMock()
|
||||
mock_index_client.create_or_update_knowledge_base = AsyncMock()
|
||||
mock_index_class.return_value = mock_index_client
|
||||
|
||||
# Setup retrieval client mock with response
|
||||
@@ -603,22 +633,23 @@ class TestAgenticSearch:
|
||||
mock_retrieval_client.close = AsyncMock()
|
||||
mock_retrieval_class.return_value = mock_retrieval_client
|
||||
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
# Use knowledge_base_name path (existing KB)
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
context = await provider.invoking(sample_messages)
|
||||
context = await provider.invoking(sample_messages)
|
||||
|
||||
assert isinstance(context, Context)
|
||||
# Should have at least the prompt message
|
||||
assert len(context.messages) >= 1
|
||||
assert isinstance(context, Context)
|
||||
# Should have at least the prompt message
|
||||
assert len(context.messages) >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("agent_framework_azure_ai_search._search_provider.KnowledgeBaseRetrievalClient")
|
||||
@@ -637,9 +668,6 @@ class TestAgenticSearch:
|
||||
mock_search_class.return_value = mock_search_client
|
||||
|
||||
mock_index_client = AsyncMock()
|
||||
mock_index_client.get_knowledge_source.side_effect = ResourceNotFoundError("Not found")
|
||||
mock_index_client.create_knowledge_source = AsyncMock()
|
||||
mock_index_client.create_or_update_knowledge_base = AsyncMock()
|
||||
mock_index_class.return_value = mock_index_client
|
||||
|
||||
# Empty response
|
||||
@@ -650,22 +678,23 @@ class TestAgenticSearch:
|
||||
mock_retrieval_client.close = AsyncMock()
|
||||
mock_retrieval_class.return_value = mock_retrieval_client
|
||||
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
)
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
# Use knowledge_base_name path (existing KB)
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
context = await provider.invoking(sample_messages)
|
||||
context = await provider.invoking(sample_messages)
|
||||
|
||||
assert isinstance(context, Context)
|
||||
# Should have fallback message
|
||||
assert len(context.messages) >= 1
|
||||
assert isinstance(context, Context)
|
||||
# Should have fallback message
|
||||
assert len(context.messages) >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("agent_framework_azure_ai_search._search_provider.KnowledgeBaseRetrievalClient")
|
||||
@@ -684,9 +713,6 @@ class TestAgenticSearch:
|
||||
mock_search_class.return_value = mock_search_client
|
||||
|
||||
mock_index_client = AsyncMock()
|
||||
mock_index_client.get_knowledge_source.side_effect = ResourceNotFoundError("Not found")
|
||||
mock_index_client.create_knowledge_source = AsyncMock()
|
||||
mock_index_client.create_or_update_knowledge_base = AsyncMock()
|
||||
mock_index_class.return_value = mock_index_client
|
||||
|
||||
mock_retrieval_client = AsyncMock()
|
||||
@@ -706,22 +732,23 @@ class TestAgenticSearch:
|
||||
mock_retrieval_client.close = AsyncMock()
|
||||
mock_retrieval_class.return_value = mock_retrieval_client
|
||||
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
index_name="test-index",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
azure_ai_project_endpoint="https://test.services.ai.azure.com",
|
||||
model_deployment_name="gpt-4o",
|
||||
knowledge_base_name="test-kb",
|
||||
azure_openai_resource_url="https://test.openai.azure.com",
|
||||
retrieval_reasoning_effort="medium", # Test medium reasoning
|
||||
)
|
||||
# Clear environment to ensure no env vars interfere
|
||||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("AZURE_SEARCH_")}
|
||||
with patch.dict(os.environ, clean_env, clear=True):
|
||||
# Use knowledge_base_name path (existing KB)
|
||||
provider = AzureAISearchContextProvider(
|
||||
endpoint="https://test.search.windows.net",
|
||||
api_key="test-key",
|
||||
mode="agentic",
|
||||
knowledge_base_name="test-kb",
|
||||
retrieval_reasoning_effort="medium", # Test medium reasoning
|
||||
env_file_path="", # Disable .env file loading
|
||||
)
|
||||
|
||||
context = await provider.invoking(sample_messages)
|
||||
context = await provider.invoking(sample_messages)
|
||||
|
||||
assert isinstance(context, Context)
|
||||
assert len(context.messages) >= 1
|
||||
assert isinstance(context, Context)
|
||||
assert len(context.messages) >= 1
|
||||
|
||||
|
||||
class TestVectorFieldAutoDiscovery:
|
||||
|
||||
@@ -118,6 +118,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
agents_client: AgentsClient | None = None,
|
||||
agent_id: str | None = None,
|
||||
agent_name: str | None = None,
|
||||
agent_description: str | None = None,
|
||||
thread_id: str | None = None,
|
||||
project_endpoint: str | None = None,
|
||||
model_deployment_name: str | None = None,
|
||||
@@ -135,6 +136,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
a new agent will be created (and deleted after the request). If neither agents_client
|
||||
nor agent_id is provided, both will be created and managed automatically.
|
||||
agent_name: The name to use when creating new agents.
|
||||
agent_description: The description to use when creating new agents.
|
||||
thread_id: Default thread ID to use for conversations. Can be overridden by
|
||||
conversation_id property when making a request.
|
||||
project_endpoint: The Azure AI Project endpoint URL.
|
||||
@@ -215,6 +217,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
self.credential = async_credential
|
||||
self.agent_id = agent_id
|
||||
self.agent_name = agent_name
|
||||
self.agent_description = agent_description
|
||||
self.model_id = azure_ai_settings.model_deployment_name
|
||||
self.thread_id = thread_id
|
||||
self.should_cleanup_agent = should_cleanup_agent # Track whether we should delete the agent
|
||||
@@ -311,6 +314,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
args: dict[str, Any] = {
|
||||
"model": run_options["model"],
|
||||
"name": agent_name,
|
||||
"description": self.agent_description,
|
||||
}
|
||||
if "tools" in run_options:
|
||||
args["tools"] = run_options["tools"]
|
||||
@@ -1038,16 +1042,19 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
|
||||
return run_id, tool_outputs, tool_approvals
|
||||
|
||||
def _update_agent_name(self, agent_name: str | None) -> None:
|
||||
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None) -> None:
|
||||
"""Update the agent name in the chat client.
|
||||
|
||||
Args:
|
||||
agent_name: The new name for the agent.
|
||||
description: The new description for the agent.
|
||||
"""
|
||||
# This is a no-op in the base class, but can be overridden by subclasses
|
||||
# to update the agent name in the client.
|
||||
if agent_name and not self.agent_name:
|
||||
self.agent_name = agent_name
|
||||
if description and not self.agent_description:
|
||||
self.agent_description = description
|
||||
|
||||
def service_url(self) -> str:
|
||||
"""Get the service URL for the chat client.
|
||||
|
||||
@@ -62,6 +62,7 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
project_client: AIProjectClient | None = None,
|
||||
agent_name: str | None = None,
|
||||
agent_version: str | None = None,
|
||||
agent_description: str | None = None,
|
||||
conversation_id: str | None = None,
|
||||
project_endpoint: str | None = None,
|
||||
model_deployment_name: str | None = None,
|
||||
@@ -77,6 +78,7 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
project_client: An existing AIProjectClient to use. If not provided, one will be created.
|
||||
agent_name: The name to use when creating new agents or using existing agents.
|
||||
agent_version: The version of the agent to use.
|
||||
agent_description: The description to use when creating new agents.
|
||||
conversation_id: Default conversation ID to use for conversations. Can be overridden by
|
||||
conversation_id property when making a request.
|
||||
project_endpoint: The Azure AI Project endpoint URL.
|
||||
@@ -150,6 +152,7 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
# Initialize instance variables
|
||||
self.agent_name = agent_name
|
||||
self.agent_version = agent_version
|
||||
self.agent_description = agent_description
|
||||
self.use_latest_version = use_latest_version
|
||||
self.project_client = project_client
|
||||
self.credential = async_credential
|
||||
@@ -280,7 +283,9 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
args["instructions"] = "".join(combined_instructions)
|
||||
|
||||
created_agent = await self.project_client.agents.create_version(
|
||||
agent_name=self.agent_name, definition=PromptAgentDefinition(**args)
|
||||
agent_name=self.agent_name,
|
||||
definition=PromptAgentDefinition(**args),
|
||||
description=self.agent_description,
|
||||
)
|
||||
|
||||
self.agent_version = created_agent.version
|
||||
@@ -352,16 +357,19 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
"""Initialize OpenAI client."""
|
||||
self.client = self.project_client.get_openai_client() # type: ignore
|
||||
|
||||
def _update_agent_name(self, agent_name: str | None) -> None:
|
||||
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None = None) -> None:
|
||||
"""Update the agent name in the chat client.
|
||||
|
||||
Args:
|
||||
agent_name: The new name for the agent.
|
||||
description: The new description for the agent.
|
||||
"""
|
||||
# This is a no-op in the base class, but can be overridden by subclasses
|
||||
# to update the agent name in the client.
|
||||
if agent_name and not self.agent_name:
|
||||
self.agent_name = agent_name
|
||||
if description and not self.agent_description:
|
||||
self.agent_description = description
|
||||
|
||||
def get_mcp_tool(self, tool: HostedMCPTool) -> Any:
|
||||
"""Get MCP tool from HostedMCPTool."""
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Azure AI Foundry 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"
|
||||
|
||||
@@ -86,6 +86,7 @@ def create_test_azure_ai_chat_client(
|
||||
client.credential = None
|
||||
client.agent_id = agent_id
|
||||
client.agent_name = agent_name
|
||||
client.agent_description = None
|
||||
client.model_id = azure_ai_settings.model_deployment_name
|
||||
client.thread_id = thread_id
|
||||
client.should_cleanup_agent = should_cleanup_agent
|
||||
@@ -441,34 +442,43 @@ async def test_azure_ai_chat_client_close_client_when_should_close_false(mock_ag
|
||||
mock_agents_client.close.assert_not_called()
|
||||
|
||||
|
||||
def test_azure_ai_chat_client_update_agent_name_when_current_is_none(mock_agents_client: MagicMock) -> None:
|
||||
"""Test _update_agent_name updates name when current agent_name is None."""
|
||||
def test_azure_ai_chat_client_update_agent_name_and_description_when_current_is_none(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test _update_agent_name_and_description updates name when current agent_name is None."""
|
||||
chat_client = create_test_azure_ai_chat_client(mock_agents_client)
|
||||
chat_client.agent_name = None # type: ignore
|
||||
|
||||
chat_client._update_agent_name("NewAgentName") # type: ignore
|
||||
chat_client._update_agent_name_and_description("NewAgentName", "description") # type: ignore
|
||||
|
||||
assert chat_client.agent_name == "NewAgentName"
|
||||
assert chat_client.agent_description == "description"
|
||||
|
||||
|
||||
def test_azure_ai_chat_client_update_agent_name_when_current_exists(mock_agents_client: MagicMock) -> None:
|
||||
"""Test _update_agent_name does not update when current agent_name exists."""
|
||||
def test_azure_ai_chat_client_update_agent_name_and_description_when_current_exists(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test _update_agent_name_and_description does not update when current agent_name exists."""
|
||||
chat_client = create_test_azure_ai_chat_client(mock_agents_client)
|
||||
chat_client.agent_name = "ExistingName" # type: ignore
|
||||
chat_client.agent_description = "ExistingDescription" # type: ignore
|
||||
|
||||
chat_client._update_agent_name("NewAgentName") # type: ignore
|
||||
chat_client._update_agent_name_and_description("NewAgentName", "description") # type: ignore
|
||||
|
||||
assert chat_client.agent_name == "ExistingName"
|
||||
assert chat_client.agent_description == "ExistingDescription"
|
||||
|
||||
|
||||
def test_azure_ai_chat_client_update_agent_name_with_none_input(mock_agents_client: MagicMock) -> None:
|
||||
"""Test _update_agent_name with None input."""
|
||||
def test_azure_ai_chat_client_update_agent_name_and_description_with_none_input(mock_agents_client: MagicMock) -> None:
|
||||
"""Test _update_agent_name_and_description with None input."""
|
||||
chat_client = create_test_azure_ai_chat_client(mock_agents_client)
|
||||
chat_client.agent_name = None # type: ignore
|
||||
chat_client.agent_description = None # type: ignore
|
||||
|
||||
chat_client._update_agent_name(None) # type: ignore
|
||||
chat_client._update_agent_name_and_description(None, None) # type: ignore
|
||||
|
||||
assert chat_client.agent_name is None
|
||||
assert chat_client.agent_description is None
|
||||
|
||||
|
||||
async def test_azure_ai_chat_client_create_run_options_with_messages(mock_agents_client: MagicMock) -> None:
|
||||
|
||||
@@ -84,6 +84,7 @@ def create_test_azure_ai_client(
|
||||
client.credential = None
|
||||
client.agent_name = agent_name
|
||||
client.agent_version = agent_version
|
||||
client.agent_description = None
|
||||
client.use_latest_version = use_latest_version
|
||||
client.model_id = azure_ai_settings.model_deployment_name
|
||||
client.conversation_id = conversation_id
|
||||
@@ -397,14 +398,14 @@ async def test_azure_ai_client_initialize_client(mock_project_client: MagicMock)
|
||||
mock_project_client.get_openai_client.assert_called_once()
|
||||
|
||||
|
||||
def test_azure_ai_client_update_agent_name(mock_project_client: MagicMock) -> None:
|
||||
"""Test _update_agent_name method."""
|
||||
def test_azure_ai_client_update_agent_name_and_description(mock_project_client: MagicMock) -> None:
|
||||
"""Test _update_agent_name_and_description method."""
|
||||
client = create_test_azure_ai_client(mock_project_client)
|
||||
|
||||
# Test updating agent name when current is None
|
||||
with patch.object(client, "_update_agent_name") as mock_update:
|
||||
with patch.object(client, "_update_agent_name_and_description") as mock_update:
|
||||
mock_update.return_value = None
|
||||
client._update_agent_name("new-agent") # type: ignore
|
||||
client._update_agent_name_and_description("new-agent") # type: ignore
|
||||
mock_update.assert_called_once_with("new-agent")
|
||||
|
||||
# Test behavior when agent name is updated
|
||||
@@ -412,9 +413,9 @@ def test_azure_ai_client_update_agent_name(mock_project_client: MagicMock) -> No
|
||||
client.agent_name = "test-agent" # Manually set for the test
|
||||
|
||||
# Test with None input
|
||||
with patch.object(client, "_update_agent_name") as mock_update:
|
||||
with patch.object(client, "_update_agent_name_and_description") as mock_update:
|
||||
mock_update.return_value = None
|
||||
client._update_agent_name(None) # type: ignore
|
||||
client._update_agent_name_and_description(None) # type: ignore
|
||||
mock_update.assert_called_once_with(None)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Azure Functions 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"
|
||||
|
||||
@@ -20,6 +20,37 @@ pip install agent-framework-chatkit --pre
|
||||
|
||||
This will install `agent-framework-core` and `openai-chatkit` as dependencies.
|
||||
|
||||
## Requirements and Limitations
|
||||
|
||||
### Frontend Requirements
|
||||
|
||||
The ChatKit integration requires the OpenAI ChatKit frontend library, which has the following requirements:
|
||||
|
||||
1. **Internet Connectivity Required**: The ChatKit UI is loaded from OpenAI's CDN (`cdn.platform.openai.com`). This library cannot be self-hosted or bundled locally.
|
||||
|
||||
2. **External Network Requests**: The ChatKit frontend makes requests to:
|
||||
- `cdn.platform.openai.com` - UI library (required)
|
||||
- `chatgpt.com/ces/v1/projects/oai/settings` - Configuration
|
||||
- `api-js.mixpanel.com` - Telemetry (metadata only, not user messages)
|
||||
|
||||
3. **Domain Registration for Production**: Production deployments require registering your domain at [platform.openai.com](https://platform.openai.com/settings/organization/security/domain-allowlist) and configuring a domain key.
|
||||
|
||||
### Air-Gapped / Regulated Environments
|
||||
|
||||
**The ChatKit frontend is not suitable for air-gapped or highly-regulated environments** where outbound connections to OpenAI domains are restricted.
|
||||
|
||||
**What IS self-hostable:**
|
||||
|
||||
- The backend components (`chatkit-python`, `agent-framework-chatkit`) are fully open source and have no external dependencies
|
||||
|
||||
**What is NOT self-hostable:**
|
||||
|
||||
- The frontend UI (`chatkit.js`) requires connectivity to OpenAI's CDN
|
||||
|
||||
For environments with network restrictions, consider building a custom frontend that consumes the ChatKit server protocol, or using alternative UI libraries like `ai-sdk`.
|
||||
|
||||
See [openai/chatkit-js#57](https://github.com/openai/chatkit-js/issues/57) for tracking self-hosting feature requests.
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's a minimal example showing how to integrate Agent Framework with ChatKit:
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "OpenAI ChatKit 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"
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Copilot Studio 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"
|
||||
|
||||
@@ -719,7 +719,7 @@ class ChatAgent(BaseAgent):
|
||||
additional_properties=additional_chat_options or {}, # type: ignore
|
||||
)
|
||||
self._async_exit_stack = AsyncExitStack()
|
||||
self._update_agent_name()
|
||||
self._update_agent_name_and_description()
|
||||
|
||||
async def __aenter__(self) -> "Self":
|
||||
"""Enter the async context manager.
|
||||
@@ -755,15 +755,17 @@ class ChatAgent(BaseAgent):
|
||||
"""
|
||||
await self._async_exit_stack.aclose()
|
||||
|
||||
def _update_agent_name(self) -> None:
|
||||
def _update_agent_name_and_description(self) -> None:
|
||||
"""Update the agent name in the chat client.
|
||||
|
||||
Checks if the chat client supports agent name updates. The implementation
|
||||
should check if there is already an agent name defined, and if not
|
||||
set it to this value.
|
||||
"""
|
||||
if hasattr(self.chat_client, "_update_agent_name") and callable(self.chat_client._update_agent_name): # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
self.chat_client._update_agent_name(self.name) # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
if hasattr(self.chat_client, "_update_agent_name_and_description") and callable(
|
||||
self.chat_client._update_agent_name_and_description
|
||||
): # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
self.chat_client._update_agent_name_and_description(self.name, self.description) # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
|
||||
async def run(
|
||||
self,
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
import re
|
||||
import sys
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Collection
|
||||
from collections.abc import Collection, Sequence
|
||||
from contextlib import AsyncExitStack, _AsyncGeneratorContextManager # type: ignore
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
@@ -22,7 +22,16 @@ from mcp.shared.session import RequestResponder
|
||||
from pydantic import BaseModel, Field, create_model
|
||||
|
||||
from ._tools import AIFunction, HostedMCPSpecificApproval
|
||||
from ._types import ChatMessage, Contents, DataContent, Role, TextContent, UriContent
|
||||
from ._types import (
|
||||
ChatMessage,
|
||||
Contents,
|
||||
DataContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
TextContent,
|
||||
UriContent,
|
||||
)
|
||||
from .exceptions import ToolException, ToolExecutionException
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -61,7 +70,7 @@ def _mcp_prompt_message_to_chat_message(
|
||||
"""Convert a MCP container type to a Agent Framework type."""
|
||||
return ChatMessage(
|
||||
role=Role(value=mcp_type.role),
|
||||
contents=[_mcp_type_to_ai_content(mcp_type.content)],
|
||||
contents=_mcp_type_to_ai_content(mcp_type.content),
|
||||
raw_representation=mcp_type,
|
||||
)
|
||||
|
||||
@@ -87,8 +96,7 @@ def _mcp_call_tool_result_to_ai_contents(
|
||||
A list of Agent Framework content items with metadata merged into
|
||||
additional_properties.
|
||||
"""
|
||||
# Extract _meta field using getattr for compatibility
|
||||
meta_data = getattr(mcp_type, "_meta", None)
|
||||
meta_data = mcp_type.meta
|
||||
|
||||
# Prepare merged metadata once if present
|
||||
merged_meta_props = None
|
||||
@@ -104,53 +112,104 @@ def _mcp_call_tool_result_to_ai_contents(
|
||||
# Convert each content item and merge metadata
|
||||
result_contents = []
|
||||
for item in mcp_type.content:
|
||||
content = _mcp_type_to_ai_content(item)
|
||||
contents = _mcp_type_to_ai_content(item)
|
||||
|
||||
if merged_meta_props:
|
||||
existing_props = getattr(content, "additional_properties", None) or {}
|
||||
# Merge with content-specific properties, letting content-specific props override
|
||||
final_props = merged_meta_props.copy()
|
||||
final_props.update(existing_props)
|
||||
content.additional_properties = final_props
|
||||
result_contents.append(content)
|
||||
|
||||
for content in contents:
|
||||
existing_props = getattr(content, "additional_properties", None) or {}
|
||||
# Merge with content-specific properties, letting content-specific props override
|
||||
final_props = merged_meta_props.copy()
|
||||
final_props.update(existing_props)
|
||||
content.additional_properties = final_props
|
||||
result_contents.extend(contents)
|
||||
return result_contents
|
||||
|
||||
|
||||
def _mcp_type_to_ai_content(
|
||||
mcp_type: types.ImageContent | types.TextContent | types.AudioContent | types.EmbeddedResource | types.ResourceLink,
|
||||
) -> Contents:
|
||||
mcp_type: types.ImageContent
|
||||
| types.TextContent
|
||||
| types.AudioContent
|
||||
| types.EmbeddedResource
|
||||
| types.ResourceLink
|
||||
| types.ToolUseContent
|
||||
| types.ToolResultContent
|
||||
| Sequence[
|
||||
types.ImageContent
|
||||
| types.TextContent
|
||||
| types.AudioContent
|
||||
| types.EmbeddedResource
|
||||
| types.ResourceLink
|
||||
| types.ToolUseContent
|
||||
| types.ToolResultContent
|
||||
],
|
||||
) -> list[Contents]:
|
||||
"""Convert a MCP type to a Agent Framework type."""
|
||||
match mcp_type:
|
||||
case types.TextContent():
|
||||
return TextContent(text=mcp_type.text, raw_representation=mcp_type)
|
||||
case types.ImageContent() | types.AudioContent():
|
||||
return DataContent(
|
||||
uri=mcp_type.data,
|
||||
media_type=mcp_type.mimeType,
|
||||
raw_representation=mcp_type,
|
||||
)
|
||||
case types.ResourceLink():
|
||||
return UriContent(
|
||||
uri=str(mcp_type.uri),
|
||||
media_type=mcp_type.mimeType or "application/json",
|
||||
raw_representation=mcp_type,
|
||||
)
|
||||
case _:
|
||||
match mcp_type.resource:
|
||||
case types.TextResourceContents():
|
||||
return TextContent(
|
||||
text=mcp_type.resource.text,
|
||||
mcp_types = mcp_type if isinstance(mcp_type, Sequence) else [mcp_type]
|
||||
return_types: list[Contents] = []
|
||||
for mcp_type in mcp_types:
|
||||
match mcp_type:
|
||||
case types.TextContent():
|
||||
return_types.append(TextContent(text=mcp_type.text, raw_representation=mcp_type))
|
||||
case types.ImageContent() | types.AudioContent():
|
||||
return_types.append(
|
||||
DataContent(
|
||||
uri=mcp_type.data,
|
||||
media_type=mcp_type.mimeType,
|
||||
raw_representation=mcp_type,
|
||||
additional_properties=(mcp_type.annotations.model_dump() if mcp_type.annotations else None),
|
||||
)
|
||||
case types.BlobResourceContents():
|
||||
return DataContent(
|
||||
uri=mcp_type.resource.blob,
|
||||
media_type=mcp_type.resource.mimeType,
|
||||
)
|
||||
case types.ResourceLink():
|
||||
return_types.append(
|
||||
UriContent(
|
||||
uri=str(mcp_type.uri),
|
||||
media_type=mcp_type.mimeType or "application/json",
|
||||
raw_representation=mcp_type,
|
||||
additional_properties=(mcp_type.annotations.model_dump() if mcp_type.annotations else None),
|
||||
)
|
||||
)
|
||||
case types.ToolUseContent():
|
||||
return_types.append(
|
||||
FunctionCallContent(
|
||||
call_id=mcp_type.id,
|
||||
name=mcp_type.name,
|
||||
arguments=mcp_type.input,
|
||||
raw_representation=mcp_type,
|
||||
)
|
||||
)
|
||||
case types.ToolResultContent():
|
||||
return_types.append(
|
||||
FunctionResultContent(
|
||||
call_id=mcp_type.toolUseId,
|
||||
result=_mcp_type_to_ai_content(mcp_type.content)
|
||||
if mcp_type.content
|
||||
else mcp_type.structuredContent,
|
||||
exception=Exception() if mcp_type.isError else None,
|
||||
raw_representation=mcp_type,
|
||||
)
|
||||
)
|
||||
case types.EmbeddedResource():
|
||||
match mcp_type.resource:
|
||||
case types.TextResourceContents():
|
||||
return_types.append(
|
||||
TextContent(
|
||||
text=mcp_type.resource.text,
|
||||
raw_representation=mcp_type,
|
||||
additional_properties=(
|
||||
mcp_type.annotations.model_dump() if mcp_type.annotations else None
|
||||
),
|
||||
)
|
||||
)
|
||||
case types.BlobResourceContents():
|
||||
return_types.append(
|
||||
DataContent(
|
||||
uri=mcp_type.resource.blob,
|
||||
media_type=mcp_type.resource.mimeType,
|
||||
raw_representation=mcp_type,
|
||||
additional_properties=(
|
||||
mcp_type.annotations.model_dump() if mcp_type.annotations else None
|
||||
),
|
||||
)
|
||||
)
|
||||
return return_types
|
||||
|
||||
|
||||
def _ai_content_to_mcp_types(
|
||||
|
||||
@@ -1805,6 +1805,8 @@ def _prepare_function_call_results_as_dumpable(content: Contents | Any | list[Co
|
||||
return [_prepare_function_call_results_as_dumpable(item) for item in content]
|
||||
if isinstance(content, dict):
|
||||
return {k: _prepare_function_call_results_as_dumpable(v) for k, v in content.items()}
|
||||
if isinstance(content, BaseModel):
|
||||
return content.model_dump()
|
||||
if hasattr(content, "to_dict"):
|
||||
return content.to_dict(exclude={"raw_representation", "additional_properties"})
|
||||
return content
|
||||
|
||||
@@ -74,10 +74,15 @@ from ._magentic import (
|
||||
ORCH_MSG_KIND_USER_TASK,
|
||||
MagenticBuilder,
|
||||
MagenticContext,
|
||||
MagenticHumanInputRequest,
|
||||
MagenticHumanInterventionDecision,
|
||||
MagenticHumanInterventionKind,
|
||||
MagenticHumanInterventionReply,
|
||||
MagenticHumanInterventionRequest,
|
||||
MagenticManagerBase,
|
||||
MagenticPlanReviewDecision,
|
||||
MagenticPlanReviewReply,
|
||||
MagenticPlanReviewRequest,
|
||||
MagenticStallInterventionDecision,
|
||||
MagenticStallInterventionReply,
|
||||
MagenticStallInterventionRequest,
|
||||
StandardMagenticManager,
|
||||
)
|
||||
from ._orchestration_state import OrchestrationState
|
||||
@@ -144,10 +149,15 @@ __all__ = [
|
||||
"InProcRunnerContext",
|
||||
"MagenticBuilder",
|
||||
"MagenticContext",
|
||||
"MagenticHumanInputRequest",
|
||||
"MagenticHumanInterventionDecision",
|
||||
"MagenticHumanInterventionKind",
|
||||
"MagenticHumanInterventionReply",
|
||||
"MagenticHumanInterventionRequest",
|
||||
"MagenticManagerBase",
|
||||
"MagenticPlanReviewDecision",
|
||||
"MagenticPlanReviewReply",
|
||||
"MagenticPlanReviewRequest",
|
||||
"MagenticStallInterventionDecision",
|
||||
"MagenticStallInterventionReply",
|
||||
"MagenticStallInterventionRequest",
|
||||
"ManagerDirectiveModel",
|
||||
"ManagerSelectionRequest",
|
||||
"ManagerSelectionResponse",
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
import uuid
|
||||
from collections.abc import AsyncIterable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -244,7 +244,7 @@ class WorkflowAgent(BaseAgent):
|
||||
case AgentRunUpdateEvent(data=update):
|
||||
# Direct pass-through of update in an agent streaming event
|
||||
if update:
|
||||
return cast(AgentRunResponseUpdate, update)
|
||||
return update
|
||||
return None
|
||||
|
||||
case RequestInfoEvent(request_id=request_id):
|
||||
@@ -269,7 +269,7 @@ class WorkflowAgent(BaseAgent):
|
||||
author_name=self.name,
|
||||
response_id=response_id,
|
||||
message_id=str(uuid.uuid4()),
|
||||
created_at=datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
created_at=datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
)
|
||||
case _:
|
||||
# Ignore workflow-internal events
|
||||
|
||||
@@ -367,6 +367,8 @@ class ExecutorFailedEvent(ExecutorEvent):
|
||||
class AgentRunUpdateEvent(ExecutorEvent):
|
||||
"""Event triggered when an agent is streaming messages."""
|
||||
|
||||
data: AgentRunResponseUpdate | None
|
||||
|
||||
def __init__(self, executor_id: str, data: AgentRunResponseUpdate | None = None):
|
||||
"""Initialize the agent streaming event."""
|
||||
super().__init__(executor_id, data)
|
||||
@@ -379,6 +381,8 @@ class AgentRunUpdateEvent(ExecutorEvent):
|
||||
class AgentRunEvent(ExecutorEvent):
|
||||
"""Event triggered when an agent run is completed."""
|
||||
|
||||
data: AgentRunResponse | None
|
||||
|
||||
def __init__(self, executor_id: str, data: AgentRunResponse | None = None):
|
||||
"""Initialize the agent run event."""
|
||||
super().__init__(executor_id, data)
|
||||
|
||||
@@ -264,7 +264,7 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
|
||||
# Invoke the handler with the message and context
|
||||
with _framework_event_origin():
|
||||
invoke_event = ExecutorInvokedEvent(self.id)
|
||||
invoke_event = ExecutorInvokedEvent(self.id, message)
|
||||
await context.add_event(invoke_event)
|
||||
try:
|
||||
await handler(message, context)
|
||||
@@ -275,7 +275,9 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
await context.add_event(failure_event)
|
||||
raise
|
||||
with _framework_event_origin():
|
||||
completed_event = ExecutorCompletedEvent(self.id)
|
||||
# Include sent messages as the completion data
|
||||
sent_messages = context.get_sent_messages()
|
||||
completed_event = ExecutorCompletedEvent(self.id, sent_messages if sent_messages else None)
|
||||
await context.add_event(completed_event)
|
||||
|
||||
def _create_context_for_handler(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user