Description
When an agent uses strict structured output, AgentSkillsProvider causes OpenAI/Azure OpenAI to reject the request before model execution.
AgentSkillsProvider always exposes run_skill_script for a file-based skill, even when the skill contains no scripts. In Microsoft.Agents.AI 1.20.0, the generated JSON Schema for its optional arguments parameter is:
Because the property has no type, OpenAI/Azure OpenAI rejects the complete request when strict schema validation is enabled for the response:
HTTP 400 (invalid_request_error: invalid_function_parameters)
Parameter: tools[...].function.parameters
Invalid schema for function 'run_skill_script':
In context=('properties', 'arguments'), schema must have a 'type' key.
Without strict structured output, the same request is accepted by Azure OpenAI. Enabling strict output with:
chatOptions.AdditionalProperties["strict"] = true;
exposes the invalid tool schema. Although strict mode is configured for the response schema, the service validates every function-tool schema in the same request.
This also affects scriptless file skills because AgentSkillsProviderBuilder.UseFileSkill(s) requires a script runner, and the provider advertises run_skill_script regardless of whether any discovered skill contains scripts.
Minimal reproduction
The following is the only file required. Run it with the .NET 10 SDK:
// Program.cs
#:package Microsoft.Agents.AI@1.20.0
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var rootDirectory = Path.Combine(Path.GetTempPath(), $"agent-framework-repro-{Guid.NewGuid():N}");
var skillDirectory = Path.Combine(rootDirectory, "hello");
Directory.CreateDirectory(skillDirectory);
File.WriteAllText(
Path.Combine(skillDirectory, "SKILL.md"),
"""
---
name: hello
description: Says hello.
---
Say hello to the user.
""");
try
{
using var skills = new AgentSkillsProviderBuilder()
.UseFileSkills([skillDirectory])
.UseFileScriptRunner((_, _, _, _, _) =>
throw new NotSupportedException("This skill has no scripts."))
.UseOptions(options =>
{
options.DisableLoadSkillApproval = true;
options.DisableReadSkillResourceApproval = true;
})
.Build();
using var client = new CaptureChatClient();
using var outputSchema = JsonDocument.Parse(
"""{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}""");
var chatOptions = new ChatOptions
{
ResponseFormat = ChatResponseFormat.ForJsonSchema(outputSchema.RootElement.Clone()),
AdditionalProperties = new AdditionalPropertiesDictionary
{
["strict"] = true,
},
};
var agent = client.AsAIAgent(
new ChatClientAgentOptions
{
AIContextProviders = [skills],
ChatOptions = chatOptions,
});
await agent.RunAsync([new ChatMessage(ChatRole.User, "Hello")]);
var function = (AIFunction)client.Options!.Tools!
.Single(tool => tool.Name == AgentSkillsProvider.RunSkillScriptToolName);
var argumentsSchema = function.JsonSchema
.GetProperty("properties")
.GetProperty("arguments");
Console.WriteLine(argumentsSchema);
Console.WriteLine($"Has type: {argumentsSchema.TryGetProperty("type", out _)}");
}
finally
{
Directory.Delete(rootDirectory, recursive: true);
}
sealed class CaptureChatClient : IChatClient
{
public ChatOptions? Options { get; private set; }
public Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
Options = options;
return Task.FromResult(
new ChatResponse(new ChatMessage(ChatRole.Assistant, "Hello!")));
}
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Options = options;
await Task.Yield();
yield return new ChatResponseUpdate(ChatRole.Assistant, "Hello!");
}
public object? GetService(Type serviceType, object? serviceKey = null) =>
serviceType.IsInstanceOfType(this) ? this : null;
public void Dispose()
{
}
}
Actual output:
{"default":null}
Has type: False
Expected behavior
Either:
- Do not expose
run_skill_script when discovered file skills contain no scripts, or provide an option to disable script execution; and/or
- Generate a valid schema for
arguments, for example an object schema that OpenAI-compatible providers accept.
Package and runtime versions
Microsoft.Agents.AI: 1.20.0
- .NET SDK: 10.0.303
- OS: Windows
Description
When an agent uses strict structured output,
AgentSkillsProvidercauses OpenAI/Azure OpenAI to reject the request before model execution.AgentSkillsProvideralways exposesrun_skill_scriptfor a file-based skill, even when the skill contains no scripts. InMicrosoft.Agents.AI1.20.0, the generated JSON Schema for its optionalargumentsparameter is:{"default":null}Because the property has no
type, OpenAI/Azure OpenAI rejects the complete request when strict schema validation is enabled for the response:Without strict structured output, the same request is accepted by Azure OpenAI. Enabling strict output with:
exposes the invalid tool schema. Although strict mode is configured for the response schema, the service validates every function-tool schema in the same request.
This also affects scriptless file skills because
AgentSkillsProviderBuilder.UseFileSkill(s)requires a script runner, and the provider advertisesrun_skill_scriptregardless of whether any discovered skill contains scripts.Minimal reproduction
The following is the only file required. Run it with the .NET 10 SDK:
Actual output:
Expected behavior
Either:
run_skill_scriptwhen discovered file skills contain no scripts, or provide an option to disable script execution; and/orarguments, for example an object schema that OpenAI-compatible providers accept.Package and runtime versions
Microsoft.Agents.AI: 1.20.0