mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Support custom types in skill resource and script functions (#5152)
* .NET: Add JsonSerializerOptions support to programmatic skill APIs Allow callers to pass custom JsonSerializerOptions when creating inline resources and scripts via AgentInlineSkill, AgentClassSkill, AgentInlineSkillResource, and AgentInlineSkillScript. A skill-level default can be set on AgentInlineSkill and overridden per-resource/ script call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/TestSkillTypes.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
18d1ba3624
commit
7010dd7439
@@ -1,6 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net10.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
@@ -36,6 +64,34 @@
|
||||
<Right>lib/net10.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net472/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
@@ -71,6 +127,34 @@
|
||||
<Right>lib/net472/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net8.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
@@ -106,6 +190,34 @@
|
||||
<Right>lib/net8.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/net9.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
@@ -141,6 +253,34 @@
|
||||
<Right>lib/net9.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String)</Target>
|
||||
<Left>lib/netstandard2.0/Microsoft.Agents.AI.dll</Left>
|
||||
<Right>lib/netstandard2.0/Microsoft.Agents.AI.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[])</Target>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
@@ -13,7 +15,7 @@ namespace Microsoft.Agents.AI;
|
||||
/// <para>
|
||||
/// Inherit from this class to create a self-contained skill definition. Override the abstract
|
||||
/// properties to provide name, description, and instructions. Use <see cref="CreateResource(string, object, string?)"/>,
|
||||
/// <see cref="CreateResource(string, Delegate, string?)"/>, and <see cref="CreateScript"/> to define
|
||||
/// <see cref="CreateResource(string, Delegate, string?, JsonSerializerOptions?)"/>, and <see cref="CreateScript"/> to define
|
||||
/// inline resources and scripts.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
@@ -79,9 +81,13 @@ public abstract class AgentClassSkill : AgentSkill
|
||||
/// <param name="name">The resource name.</param>
|
||||
/// <param name="method">A method that produces the resource value when requested.</param>
|
||||
/// <param name="description">An optional description of the resource.</param>
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> used to marshal the delegate's parameters and return value.
|
||||
/// When <see langword="null"/>, <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
/// <returns>A new <see cref="AgentSkillResource"/> instance.</returns>
|
||||
protected static AgentSkillResource CreateResource(string name, Delegate method, string? description = null)
|
||||
=> new AgentInlineSkillResource(name, method, description);
|
||||
protected static AgentSkillResource CreateResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
|
||||
=> new AgentInlineSkillResource(name, method, description, serializerOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skill script backed by a delegate.
|
||||
@@ -89,7 +95,11 @@ public abstract class AgentClassSkill : AgentSkill
|
||||
/// <param name="name">The script name.</param>
|
||||
/// <param name="method">A method to execute when the script is invoked.</param>
|
||||
/// <param name="description">An optional description of the script.</param>
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> used to marshal the delegate's parameters and return value.
|
||||
/// When <see langword="null"/>, <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
/// <returns>A new <see cref="AgentSkillScript"/> instance.</returns>
|
||||
protected static AgentSkillScript CreateScript(string name, Delegate method, string? description = null)
|
||||
=> new AgentInlineSkillScript(name, method, description);
|
||||
protected static AgentSkillScript CreateScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
|
||||
=> new AgentInlineSkillScript(name, method, description, serializerOptions);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
@@ -14,7 +15,7 @@ namespace Microsoft.Agents.AI;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All calls to <see cref="AddResource(string, object, string?)"/>,
|
||||
/// <see cref="AddResource(string, Delegate, string?)"/>, and <see cref="AddScript"/>
|
||||
/// <see cref="AddResource(string, Delegate, string?, JsonSerializerOptions?)"/>, and <see cref="AddScript"/>
|
||||
/// must be made before the skill's <see cref="Content"/> is first accessed.
|
||||
/// Calls made after that point will not be reflected in the generated
|
||||
/// <see cref="Content"/>. In typical usage, this means configuring all
|
||||
@@ -25,6 +26,7 @@ namespace Microsoft.Agents.AI;
|
||||
public sealed class AgentInlineSkill : AgentSkill
|
||||
{
|
||||
private readonly string _instructions;
|
||||
private readonly JsonSerializerOptions? _serializerOptions;
|
||||
private List<AgentInlineSkillResource>? _resources;
|
||||
private List<AgentInlineSkillScript>? _scripts;
|
||||
private string? _cachedContent;
|
||||
@@ -35,10 +37,16 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
/// </summary>
|
||||
/// <param name="frontmatter">The skill frontmatter containing name, description, and other metadata.</param>
|
||||
/// <param name="instructions">Skill instructions text.</param>
|
||||
public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions)
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> applied by default to all scripts and delegate resources
|
||||
/// added to this skill. Individual <see cref="AddScript"/> and <see cref="AddResource(string, Delegate, string?, JsonSerializerOptions?)"/>
|
||||
/// calls can override this default. When <see langword="null"/>, <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions, JsonSerializerOptions? serializerOptions = null)
|
||||
{
|
||||
this.Frontmatter = Throw.IfNull(frontmatter);
|
||||
this._instructions = Throw.IfNullOrWhitespace(instructions);
|
||||
this._serializerOptions = serializerOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,6 +60,11 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
/// <param name="compatibility">Optional compatibility information (max 500 chars).</param>
|
||||
/// <param name="allowedTools">Optional space-delimited list of pre-approved tools.</param>
|
||||
/// <param name="metadata">Optional arbitrary key-value metadata.</param>
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> applied by default to all scripts and delegate resources
|
||||
/// added to this skill. Individual <see cref="AddScript"/> and <see cref="AddResource(string, Delegate, string?, JsonSerializerOptions?)"/>
|
||||
/// calls can override this default. When <see langword="null"/>, <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
public AgentInlineSkill(
|
||||
string name,
|
||||
string description,
|
||||
@@ -59,7 +72,8 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
string? license = null,
|
||||
string? compatibility = null,
|
||||
string? allowedTools = null,
|
||||
AdditionalPropertiesDictionary? metadata = null)
|
||||
AdditionalPropertiesDictionary? metadata = null,
|
||||
JsonSerializerOptions? serializerOptions = null)
|
||||
: this(
|
||||
new AgentSkillFrontmatter(name, description, compatibility)
|
||||
{
|
||||
@@ -67,7 +81,8 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
AllowedTools = allowedTools,
|
||||
Metadata = metadata,
|
||||
},
|
||||
instructions)
|
||||
instructions,
|
||||
serializerOptions)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -103,10 +118,14 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
/// <param name="name">The resource name.</param>
|
||||
/// <param name="method">A method that produces the resource value when requested.</param>
|
||||
/// <param name="description">An optional description of the resource.</param>
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> for this resource's delegate marshaling.
|
||||
/// When <see langword="null"/>, the skill-level default (if any) is used; otherwise <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
/// <returns>This instance, for chaining.</returns>
|
||||
public AgentInlineSkill AddResource(string name, Delegate method, string? description = null)
|
||||
public AgentInlineSkill AddResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
|
||||
{
|
||||
(this._resources ??= []).Add(new AgentInlineSkillResource(name, method, description));
|
||||
(this._resources ??= []).Add(new AgentInlineSkillResource(name, method, description, serializerOptions ?? this._serializerOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -117,10 +136,14 @@ public sealed class AgentInlineSkill : AgentSkill
|
||||
/// <param name="name">The script name.</param>
|
||||
/// <param name="method">A method to execute when the script is invoked.</param>
|
||||
/// <param name="description">An optional description of the script.</param>
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> for this script's delegate marshaling.
|
||||
/// When <see langword="null"/>, the skill-level default (if any) is used; otherwise <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
/// <returns>This instance, for chaining.</returns>
|
||||
public AgentInlineSkill AddScript(string name, Delegate method, string? description = null)
|
||||
public AgentInlineSkill AddScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
|
||||
{
|
||||
(this._scripts ??= []).Add(new AgentInlineSkillScript(name, method, description));
|
||||
(this._scripts ??= []).Add(new AgentInlineSkillScript(name, method, description, serializerOptions ?? this._serializerOptions));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -40,11 +41,17 @@ internal sealed class AgentInlineSkillResource : AgentSkillResource
|
||||
/// <param name="name">The resource name.</param>
|
||||
/// <param name="method">A method that produces the resource value when requested.</param>
|
||||
/// <param name="description">An optional description of the resource.</param>
|
||||
public AgentInlineSkillResource(string name, Delegate method, string? description = null)
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> used to marshal the delegate's parameters and return value.
|
||||
/// When <see langword="null"/>, <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
public AgentInlineSkillResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
|
||||
: base(name, description)
|
||||
{
|
||||
Throw.IfNull(method);
|
||||
this._function = AIFunctionFactory.Create(method, name: this.Name);
|
||||
|
||||
var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions };
|
||||
this._function = AIFunctionFactory.Create(method, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -26,11 +26,17 @@ internal sealed class AgentInlineSkillScript : AgentSkillScript
|
||||
/// <param name="name">The script name.</param>
|
||||
/// <param name="method">A method to execute when the script is invoked. Parameters are automatically deserialized from JSON.</param>
|
||||
/// <param name="description">An optional description of the script.</param>
|
||||
public AgentInlineSkillScript(string name, Delegate method, string? description = null)
|
||||
/// <param name="serializerOptions">
|
||||
/// Optional <see cref="JsonSerializerOptions"/> used to marshal the delegate's parameters and return value.
|
||||
/// When <see langword="null"/>, <see cref="AIJsonUtilities.DefaultOptions"/> is used.
|
||||
/// </param>
|
||||
public AgentInlineSkillScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
|
||||
: base(Throw.IfNullOrWhitespace(name), description)
|
||||
{
|
||||
Throw.IfNull(method);
|
||||
this._function = AIFunctionFactory.Create(method, name: this.Name);
|
||||
|
||||
var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions };
|
||||
this._function = AIFunctionFactory.Create(method, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
|
||||
|
||||
@@ -374,9 +375,68 @@ public sealed class AgentClassSkillTests
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<object?> RunAsync(AgentSkill skill, Extensions.AI.AIFunctionArguments arguments, CancellationToken cancellationToken = default)
|
||||
public override Task<object?> RunAsync(AgentSkill skill, AIFunctionArguments arguments, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<object?>("script-result");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Fact]
|
||||
public async Task CreateScript_WithSerializerOptions_DeserializesCustomInputTypeAsync()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new CustomTypeSkill();
|
||||
var jso = SkillTestJsonContext.Default.Options;
|
||||
|
||||
// Act — pass a custom type as JSON; the JSO enables deserialization
|
||||
var script = skill.Scripts![0];
|
||||
var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "test", MaxResults = 5 }, jso);
|
||||
var args = new AIFunctionArguments { ["request"] = inputJson };
|
||||
var result = await script.RunAsync(skill, args, CancellationToken.None);
|
||||
|
||||
// Assert — the custom input type was deserialized and the response was produced
|
||||
Assert.NotNull(result);
|
||||
var resultText = result!.ToString()!;
|
||||
Assert.Contains("result for test", resultText);
|
||||
Assert.Contains("5", resultText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateResource_WithSerializerOptions_SerializesReturnsCustomTypeAsync()
|
||||
{
|
||||
// Arrange
|
||||
var skill = new CustomTypeSkill();
|
||||
|
||||
// Act
|
||||
var result = await skill.Resources![0].ReadAsync();
|
||||
|
||||
// Assert — the custom type was returned successfully
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("dark", result!.ToString()!);
|
||||
}
|
||||
|
||||
private sealed class CustomTypeSkill : AgentClassSkill
|
||||
{
|
||||
public override AgentSkillFrontmatter Frontmatter { get; } = new("custom-type-skill", "Skill with custom-typed scripts and resources.");
|
||||
|
||||
protected override string Instructions => "Body.";
|
||||
|
||||
public override IReadOnlyList<AgentSkillResource>? Resources =>
|
||||
[
|
||||
CreateResource("config", () => new SkillConfig
|
||||
{
|
||||
Theme = "dark",
|
||||
Verbose = true
|
||||
}, serializerOptions: SkillTestJsonContext.Default.Options),
|
||||
];
|
||||
|
||||
public override IReadOnlyList<AgentSkillScript>? Scripts =>
|
||||
[
|
||||
CreateScript("Lookup", (LookupRequest request) => new LookupResponse
|
||||
{
|
||||
Items = [$"result for {request.Query}"],
|
||||
TotalCount = request.MaxResults,
|
||||
}, serializerOptions: SkillTestJsonContext.Default.Options),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -139,6 +139,21 @@ public sealed class AgentInlineSkillResourceTests
|
||||
Assert.Equal("Dynamic resource.", resource.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithSerializerOptions_SerializesReturnCustomTypeAsync()
|
||||
{
|
||||
// Arrange — delegate resource returns a custom type; the JSO includes a source-generated context for it
|
||||
var jso = SkillTestJsonContext.Default.Options;
|
||||
var resource = new AgentInlineSkillResource("config", () => new SkillConfig { Theme = "dark", Verbose = true }, serializerOptions: jso);
|
||||
|
||||
// Act
|
||||
var result = await resource.ReadAsync();
|
||||
|
||||
// Assert — the custom type was returned successfully
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("dark", result!.ToString()!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_SupportsCancellationTokenAsync()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -115,6 +116,28 @@ public sealed class AgentInlineSkillScriptTests
|
||||
new AgentInlineSkillScript("my-script", null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithSerializerOptions_MarshalsCustomTypesAsync()
|
||||
{
|
||||
// Arrange — script accepts a custom type; the JSO includes a source-generated context for it
|
||||
var jso = SkillTestJsonContext.Default.Options;
|
||||
var script = new AgentInlineSkillScript("lookup", (LookupRequest request) => new LookupResponse
|
||||
{
|
||||
Items = ["result-1", "result-2"],
|
||||
TotalCount = request.MaxResults,
|
||||
}, serializerOptions: jso);
|
||||
var skill = new AgentInlineSkill("test-skill", "Test.", "Instructions.");
|
||||
var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "test", MaxResults = 5 }, jso);
|
||||
var args = new AIFunctionArguments { ["request"] = inputJson };
|
||||
|
||||
// Act
|
||||
var result = await script.RunAsync(skill, args, CancellationToken.None);
|
||||
|
||||
// Assert — the custom input type was deserialized and the response was produced
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("5", result!.ToString()!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_StringParameter_WorksAsync()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
|
||||
@@ -417,4 +420,82 @@ public sealed class AgentInlineSkillTests
|
||||
Assert.Contains("description=\"A described resource.\"", content);
|
||||
Assert.DoesNotContain("no-desc\" description", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddScript_SkillLevelSerializerOptions_AppliedToScriptAsync()
|
||||
{
|
||||
// Arrange — skill-level JSO with source-generated context for custom types
|
||||
var jso = SkillTestJsonContext.Default.Options;
|
||||
var skill = new AgentInlineSkill("jso-skill", "JSO test.", "Instructions.", serializerOptions: jso);
|
||||
skill.AddScript("lookup", (LookupRequest request) => new LookupResponse
|
||||
{
|
||||
Items = [$"result for {request.Query}"],
|
||||
TotalCount = request.MaxResults,
|
||||
});
|
||||
var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "test", MaxResults = 3 }, jso);
|
||||
var args = new AIFunctionArguments { ["request"] = inputJson };
|
||||
|
||||
// Act
|
||||
var result = await skill.Scripts![0].RunAsync(skill, args, CancellationToken.None);
|
||||
|
||||
// Assert — the custom input was deserialized via skill-level JSO and response was produced
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("result for test", result!.ToString()!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddScript_PerScriptSerializerOptions_OverridesSkillLevelAsync()
|
||||
{
|
||||
// Arrange — skill-level JSO uses snake_case naming; per-script JSO overrides with source-generated context
|
||||
var skillJso = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
|
||||
var scriptJso = SkillTestJsonContext.Default.Options;
|
||||
var skill = new AgentInlineSkill("override-skill", "Override test.", "Instructions.", serializerOptions: skillJso);
|
||||
skill.AddScript("lookup", (LookupRequest request) => new LookupResponse
|
||||
{
|
||||
Items = [$"found {request.Query}"],
|
||||
TotalCount = request.MaxResults,
|
||||
}, serializerOptions: scriptJso);
|
||||
var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "override", MaxResults = 7 }, scriptJso);
|
||||
var args = new AIFunctionArguments { ["request"] = inputJson };
|
||||
|
||||
// Act
|
||||
var result = await skill.Scripts![0].RunAsync(skill, args, CancellationToken.None);
|
||||
|
||||
// Assert — per-script JSO takes effect and custom types are properly marshaled
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("found override", result!.ToString()!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddResource_SkillLevelSerializerOptions_AppliedToDelegateResourceAsync()
|
||||
{
|
||||
// Arrange — skill-level JSO with source-generated context; delegate resource returns a custom type
|
||||
var jso = SkillTestJsonContext.Default.Options;
|
||||
var skill = new AgentInlineSkill("custom-type-resource-skill", "Custom type resource test.", "Instructions.", serializerOptions: jso);
|
||||
skill.AddResource("config", () => new SkillConfig { Theme = "dark", Verbose = true });
|
||||
|
||||
// Act
|
||||
var result = await skill.Resources![0].ReadAsync();
|
||||
|
||||
// Assert — the custom type was returned successfully via skill-level JSO
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("dark", result!.ToString()!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddResource_PerResourceSerializerOptions_OverridesSkillLevelAsync()
|
||||
{
|
||||
// Arrange — skill-level JSO uses snake_case naming; per-resource JSO overrides with source-generated context
|
||||
var skillJso = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
|
||||
var resourceJso = SkillTestJsonContext.Default.Options;
|
||||
var skill = new AgentInlineSkill("override-resource-skill", "Override resource test.", "Instructions.", serializerOptions: skillJso);
|
||||
skill.AddResource("config", () => new SkillConfig { Theme = "dark", Verbose = true }, serializerOptions: resourceJso);
|
||||
|
||||
// Act
|
||||
var result = await skill.Resources![0].ReadAsync();
|
||||
|
||||
// Assert — per-resource JSO takes effect and custom type is properly marshaled
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("dark", result!.ToString()!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -70,3 +71,52 @@ internal sealed class TestAgentSkillsSource : AgentSkillsSource
|
||||
return Task.FromResult(this._skills);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom input type accepted by skill script delegates in JSO tests.
|
||||
/// </summary>
|
||||
internal sealed class LookupRequest
|
||||
{
|
||||
/// <summary>Gets or sets the search query.</summary>
|
||||
public string Query { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the maximum number of results.</summary>
|
||||
public int MaxResults { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom output type returned by skill script delegates in JSO tests.
|
||||
/// </summary>
|
||||
internal sealed class LookupResponse
|
||||
{
|
||||
/// <summary>Gets or sets the items found.</summary>
|
||||
public IList<string> Items { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the total number of matches.</summary>
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom output type returned by skill resource delegates in JSO tests.
|
||||
/// </summary>
|
||||
internal sealed class SkillConfig
|
||||
{
|
||||
/// <summary>Gets or sets the theme name.</summary>
|
||||
public string Theme { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets whether verbose mode is enabled.</summary>
|
||||
public bool Verbose { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source-generated JSON serializer context for skill test types.
|
||||
/// Provides serialization support for <see cref="LookupRequest"/>, <see cref="LookupResponse"/>,
|
||||
/// and <see cref="SkillConfig"/> without requiring runtime reflection.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions]
|
||||
[JsonSerializable(typeof(LookupRequest))]
|
||||
[JsonSerializable(typeof(LookupResponse))]
|
||||
[JsonSerializable(typeof(SkillConfig))]
|
||||
internal sealed partial class SkillTestJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user