Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Agents/AddAIAgentsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public static class AddAIAgentsExtensions
/// <param name="configureOptions">Optional action to configure options for each agent.</param>
/// <param name="prefix">The configuration prefix for agents, defaults to "ai:agents".</param>
/// <returns>The host application builder with AI agents added.</returns>
public static IHostApplicationBuilder AddAIAgents(this IHostApplicationBuilder builder, Action<string, AIAgentBuilder>? configurePipeline = default, Action<string, ChatClientAgentOptions>? configureOptions = default, string prefix = "ai:agents")
public static TBuilder AddAIAgents<TBuilder>(this TBuilder builder, Action<string, AIAgentBuilder>? configurePipeline = default, Action<string, ChatClientAgentOptions>? configureOptions = default, string prefix = "ai:agents")
where TBuilder : IHostApplicationBuilder
{
builder.AddChatClients();

Expand Down
4 changes: 4 additions & 0 deletions src/Agents/Agents.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<PackageLicenseFile>OSMFEULA.txt</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<!-- CS0436: IVT Extensions > Agents -->
<!-- SYSLIB1100/SYSLIB1101: not all ChatOptions are supported for binder gen: ResponseFormat, ToolMode, Tools -->
<NoWarn>$(NoWarn);CS0436;SYSLIB1100;SYSLIB1101</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
19 changes: 12 additions & 7 deletions src/Agents/ConfigurableAIAgent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.Json;
using System.ComponentModel;
using System.Text.Json;
using Devlooped.Extensions.AI;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -80,10 +82,13 @@ public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnum
if (configuration[$"{section}:name"] is { } newname && newname != name)
throw new InvalidOperationException($"The name of a configured agent cannot be changed at runtime. Expected '{name}' but was '{newname}'.");

var client = services.GetRequiredKeyedService<IChatClient>(options?.Client
?? throw new InvalidOperationException($"A client must be specified for agent '{name}' in configuration section '{section}'."));
var client = services.GetKeyedService<IChatClient>(options?.Client
?? throw new InvalidOperationException($"A client must be specified for agent '{name}' in configuration section '{section}'."))
?? throw new InvalidOperationException($"Specified chat client '{options?.Client}' for agent '{name}' is not registered.");

var chat = configSection.GetSection("options").Get<ChatOptions>();
#pragma warning disable SYSLIB1100
var chat = configSection.GetSection("options").Get<ExtendedChatOptions>();
#pragma warning restore SYSLIB1100
if (chat is not null)
options.ChatOptions = chat;

Expand Down Expand Up @@ -124,8 +129,8 @@ void OnReload(object? state)
[LoggerMessage(LogLevel.Information, "AIAgent '{Id}' configured.")]
private partial void LogConfigured(string id);

class AgentClientOptions : ChatClientAgentOptions
internal class AgentClientOptions : ChatClientAgentOptions
{
public required string Client { get; set; }
public string? Client { get; set; }
}
}
}
2 changes: 1 addition & 1 deletion src/Extensions/AddChatClientsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static IServiceCollection AddChatClients(this IServiceCollection services
return services;
}

class ChatClientOptions : OpenAIClientOptions
internal class ChatClientOptions : OpenAIClientOptions
{
public string? ApiKey { get; set; }
public string? ModelId { get; set; }
Expand Down
20 changes: 20 additions & 0 deletions src/Extensions/ChatExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,24 @@ public Verbosity? Verbosity
}
}
}
}

// Workaround to get the config binder to set these extension properties.
/// <summary>
/// Defines extended <see cref="ChatOptions"/> we provide via extension properties.
/// </summary>
/// <devdoc>This should ideally even be auto-generated from the available extensions so it's always in sync.</devdoc>
[EditorBrowsable(EditorBrowsableState.Never)]
public class ExtendedChatOptions : ChatOptions
{
public ReasoningEffort? ReasoningEffort
{
get => ((ChatOptions)this).ReasoningEffort;
set => ((ChatOptions)this).ReasoningEffort = value;
}
public Verbosity? Verbosity
{
get => ((ChatOptions)this).Verbosity;
set => ((ChatOptions)this).Verbosity = value;
}
}
6 changes: 3 additions & 3 deletions src/Extensions/ConfigurableChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,19 @@ void OnReload(object? state)
[LoggerMessage(LogLevel.Information, "ChatClient '{Id}' configured.")]
private partial void LogConfigured(string id);

class ConfigurableClientOptions : OpenAIClientOptions
internal class ConfigurableClientOptions : OpenAIClientOptions
{
public string? ApiKey { get; set; }
public string? ModelId { get; set; }
}

class ConfigurableInferenceOptions : AzureAIInferenceClientOptions
internal class ConfigurableInferenceOptions : AzureAIInferenceClientOptions
{
public string? ApiKey { get; set; }
public string? ModelId { get; set; }
}

class ConfigurableAzureOptions : AzureOpenAIClientOptions
internal class ConfigurableAzureOptions : AzureOpenAIClientOptions
{
public string? ApiKey { get; set; }
public string? ModelId { get; set; }
Expand Down
7 changes: 6 additions & 1 deletion src/Extensions/Extensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>Preview</LangVersion>
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
<AssemblyName>Devlooped.Extensions.AI</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<PackageId>$(AssemblyName)</PackageId>
Expand All @@ -12,6 +11,11 @@
<PackageLicenseFile>OSMFEULA.txt</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<!-- OPENAI001: responses API -->
<!-- AOAI001: Azure.AI.OpenAI.AzureOpenAIClientOptions.DefaultHeaders -->
<!-- SYSLIB1100/SYSLIB1101: not all ChatOptions are supported for binder gen: ResponseFormat, ToolMode, Tools -->
<NoWarn>$(NoWarn);OPENAI001;AOAI001;SYSLIB1100;SYSLIB1101</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -36,6 +40,7 @@
<None Update="Devlooped.Extensions.AI.targets" PackFolder="build" />
<None Update="Devlooped.Extensions.AI.props" PackFolder="build" />
<None Include="..\..\osmfeula.txt" Link="osmfeula.txt" PackagePath="OSMFEULA.txt" />
<InternalsVisibleTo Include="Devlooped.Agents.AI" />
</ItemGroup>

<Target Name="UpdateSdkPreviewVersion" BeforeTargets="GetPackageContents">
Expand Down
22 changes: 22 additions & 0 deletions src/Tests/ConfigurableAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,27 @@ public void AssignsMessageStoreFactoryFromService()
Assert.NotNull(options?.ChatMessageStoreFactory);
Assert.Same(context, options?.ChatMessageStoreFactory?.Invoke(new()));
}

[Fact]
public void CanSetOpenAIReasoningAndVerbosity()
{
var builder = new HostApplicationBuilder();

builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["ai:clients:openai:modelid"] = "gpt-4.1",
["ai:clients:openai:apikey"] = "sk-asdfasdf",
["ai:agents:bot:client"] = "openai",
["ai:agents:bot:options:reasoningeffort"] = "minimal",
["ai:agents:bot:options:verbosity"] = "low",
});

var app = builder.AddAIAgents().Build();
var agent = app.Services.GetRequiredKeyedService<AIAgent>("bot");
var options = agent.GetService<ChatClientAgentOptions>();

Assert.Equal(Verbosity.Low, options?.ChatOptions?.Verbosity);
Assert.Equal(ReasoningEffort.Minimal, options?.ChatOptions?.ReasoningEffort);
}
}