diff --git a/src/Agents/AddAIAgentsExtensions.cs b/src/Agents/AddAIAgentsExtensions.cs
index 74d2e48..6b5e6b6 100644
--- a/src/Agents/AddAIAgentsExtensions.cs
+++ b/src/Agents/AddAIAgentsExtensions.cs
@@ -22,7 +22,8 @@ public static class AddAIAgentsExtensions
/// Optional action to configure options for each agent.
/// The configuration prefix for agents, defaults to "ai:agents".
/// The host application builder with AI agents added.
- public static IHostApplicationBuilder AddAIAgents(this IHostApplicationBuilder builder, Action? configurePipeline = default, Action? configureOptions = default, string prefix = "ai:agents")
+ public static TBuilder AddAIAgents(this TBuilder builder, Action? configurePipeline = default, Action? configureOptions = default, string prefix = "ai:agents")
+ where TBuilder : IHostApplicationBuilder
{
builder.AddChatClients();
diff --git a/src/Agents/Agents.csproj b/src/Agents/Agents.csproj
index ae66455..559793e 100644
--- a/src/Agents/Agents.csproj
+++ b/src/Agents/Agents.csproj
@@ -11,6 +11,10 @@
OSMFEULA.txt
true
true
+ true
+
+
+ $(NoWarn);CS0436;SYSLIB1100;SYSLIB1101
diff --git a/src/Agents/ConfigurableAIAgent.cs b/src/Agents/ConfigurableAIAgent.cs
index b0fe1c5..ea08ee6 100644
--- a/src/Agents/ConfigurableAIAgent.cs
+++ b/src/Agents/ConfigurableAIAgent.cs
@@ -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;
@@ -80,10 +82,13 @@ public override IAsyncEnumerable 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(options?.Client
- ?? throw new InvalidOperationException($"A client must be specified for agent '{name}' in configuration section '{section}'."));
+ var client = services.GetKeyedService(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();
+#pragma warning disable SYSLIB1100
+ var chat = configSection.GetSection("options").Get();
+#pragma warning restore SYSLIB1100
if (chat is not null)
options.ChatOptions = chat;
@@ -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; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Extensions/AddChatClientsExtensions.cs b/src/Extensions/AddChatClientsExtensions.cs
index dd06c4b..b25705f 100644
--- a/src/Extensions/AddChatClientsExtensions.cs
+++ b/src/Extensions/AddChatClientsExtensions.cs
@@ -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; }
diff --git a/src/Extensions/ChatExtensions.cs b/src/Extensions/ChatExtensions.cs
index ae2f987..97b58d0 100644
--- a/src/Extensions/ChatExtensions.cs
+++ b/src/Extensions/ChatExtensions.cs
@@ -64,4 +64,24 @@ public Verbosity? Verbosity
}
}
}
+}
+
+// Workaround to get the config binder to set these extension properties.
+///
+/// Defines extended we provide via extension properties.
+///
+/// This should ideally even be auto-generated from the available extensions so it's always in sync.
+[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;
+ }
}
\ No newline at end of file
diff --git a/src/Extensions/ConfigurableChatClient.cs b/src/Extensions/ConfigurableChatClient.cs
index c1d59d3..a483190 100644
--- a/src/Extensions/ConfigurableChatClient.cs
+++ b/src/Extensions/ConfigurableChatClient.cs
@@ -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; }
diff --git a/src/Extensions/Extensions.csproj b/src/Extensions/Extensions.csproj
index 89edffc..7be9277 100644
--- a/src/Extensions/Extensions.csproj
+++ b/src/Extensions/Extensions.csproj
@@ -3,7 +3,6 @@
net8.0;net9.0;net10.0
Preview
- $(NoWarn);OPENAI001
Devlooped.Extensions.AI
$(AssemblyName)
$(AssemblyName)
@@ -12,6 +11,11 @@
OSMFEULA.txt
true
true
+ true
+
+
+
+ $(NoWarn);OPENAI001;AOAI001;SYSLIB1100;SYSLIB1101
@@ -36,6 +40,7 @@
+
diff --git a/src/Tests/ConfigurableAgentTests.cs b/src/Tests/ConfigurableAgentTests.cs
index 392a259..3f5f4ee 100644
--- a/src/Tests/ConfigurableAgentTests.cs
+++ b/src/Tests/ConfigurableAgentTests.cs
@@ -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
+ {
+ ["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("bot");
+ var options = agent.GetService();
+
+ Assert.Equal(Verbosity.Low, options?.ChatOptions?.Verbosity);
+ Assert.Equal(ReasoningEffort.Minimal, options?.ChatOptions?.ReasoningEffort);
+ }
}