diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunction.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunction.cs
index a4b5ecb5378..84cde4bc82a 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunction.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunction.cs
@@ -3,6 +3,8 @@
using System.Collections.Generic;
using System.Diagnostics;
+using System.Reflection;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Collections;
@@ -13,8 +15,52 @@ namespace Microsoft.Extensions.AI;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public abstract class AIFunction : AITool
{
- /// Gets metadata describing the function.
- public abstract AIFunctionMetadata Metadata { get; }
+ /// Gets the name of the function.
+ public abstract string Name { get; }
+
+ /// Gets a description of the function, suitable for use in describing the purpose to a model.
+ public abstract string Description { get; }
+
+ /// Gets a JSON Schema describing the function and its input parameters.
+ ///
+ ///
+ /// When specified, declares a self-contained JSON schema document that describes the function and its input parameters.
+ /// A simple example of a JSON schema for a function that adds two numbers together is shown below:
+ ///
+ ///
+ /// {
+ /// "title" : "addNumbers",
+ /// "description": "A simple function that adds two numbers together.",
+ /// "type": "object",
+ /// "properties": {
+ /// "a" : { "type": "number" },
+ /// "b" : { "type": "number", "default": 1 }
+ /// },
+ /// "required" : ["a"]
+ /// }
+ ///
+ ///
+ /// The metadata present in the schema document plays an important role in guiding AI function invocation.
+ ///
+ ///
+ /// When no schema is specified, consuming chat clients should assume the "{}" or "true" schema, indicating that any JSON input is admissible.
+ ///
+ ///
+ public virtual JsonElement JsonSchema => AIJsonUtilities.DefaultJsonSchema;
+
+ ///
+ /// Gets the underlying that this might be wrapping.
+ ///
+ ///
+ /// Provides additional metadata on the function and its signature. Implementations not wrapping .NET methods may return .
+ ///
+ public virtual MethodInfo? UnderlyingMethod => null;
+
+ /// Gets any additional properties associated with the function.
+ public virtual IReadOnlyDictionary AdditionalProperties => EmptyReadOnlyDictionary.Instance;
+
+ /// Gets a that can be used to marshal function parameters.
+ public virtual JsonSerializerOptions? JsonSerializerOptions => AIJsonUtilities.DefaultOptions;
/// Invokes the and returns its result.
/// The arguments to pass to the function's invocation.
@@ -30,7 +76,7 @@ public abstract class AIFunction : AITool
}
///
- public override string ToString() => Metadata.Name;
+ public override string ToString() => Name;
/// Invokes the and returns its result.
/// The arguments to pass to the function's invocation.
@@ -42,8 +88,5 @@ public abstract class AIFunction : AITool
/// Gets the string to display in the debugger for this instance.
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private string DebuggerDisplay =>
- string.IsNullOrWhiteSpace(Metadata.Description) ?
- Metadata.Name :
- $"{Metadata.Name} ({Metadata.Description})";
+ private string DebuggerDisplay => string.IsNullOrWhiteSpace(Description) ? Name : $"{Name} ({Description})";
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionMetadata.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionMetadata.cs
deleted file mode 100644
index d6e5279d589..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionMetadata.cs
+++ /dev/null
@@ -1,153 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text.Json;
-using Microsoft.Shared.Collections;
-using Microsoft.Shared.Diagnostics;
-
-namespace Microsoft.Extensions.AI;
-
-///
-/// Provides read-only metadata for an .
-///
-public sealed class AIFunctionMetadata
-{
- /// The name of the function.
- private readonly string _name = string.Empty;
-
- /// The description of the function.
- private readonly string _description = string.Empty;
-
- /// The JSON schema describing the function and its input parameters.
- private readonly JsonElement _schema = AIJsonUtilities.DefaultJsonSchema;
-
- /// The function's parameters.
- private readonly IReadOnlyList _parameters = [];
-
- /// The function's return parameter.
- private readonly AIFunctionReturnParameterMetadata _returnParameter = AIFunctionReturnParameterMetadata.Empty;
-
- /// Optional additional properties in addition to the named properties already available on this class.
- private readonly IReadOnlyDictionary _additionalProperties = EmptyReadOnlyDictionary.Instance;
-
- /// indexed by name, lazily initialized.
- private Dictionary? _parametersByName;
-
- /// Initializes a new instance of the class for a function with the specified name.
- /// The name of the function.
- /// The was null.
- public AIFunctionMetadata(string name)
- {
- _name = Throw.IfNullOrWhitespace(name);
- }
-
- /// Initializes a new instance of the class as a copy of another .
- /// The was null.
- ///
- /// This creates a shallow clone of . The new instance's and
- /// properties will return the same objects as in the original instance.
- ///
- public AIFunctionMetadata(AIFunctionMetadata metadata)
- {
- Name = Throw.IfNull(metadata).Name;
- Description = metadata.Description;
- Parameters = metadata.Parameters;
- ReturnParameter = metadata.ReturnParameter;
- AdditionalProperties = metadata.AdditionalProperties;
- Schema = metadata.Schema;
- }
-
- /// Gets the name of the function.
- public string Name
- {
- get => _name;
- init => _name = Throw.IfNullOrWhitespace(value);
- }
-
- /// Gets a description of the function, suitable for use in describing the purpose to a model.
- [AllowNull]
- public string Description
- {
- get => _description;
- init => _description = value ?? string.Empty;
- }
-
- /// Gets the metadata for the parameters to the function.
- /// If the function has no parameters, the returned list is empty.
- public IReadOnlyList Parameters
- {
- get => _parameters;
- init => _parameters = Throw.IfNull(value);
- }
-
- /// Gets the for a parameter by its name.
- /// The name of the parameter.
- /// The corresponding , if found; otherwise, null.
- public AIFunctionParameterMetadata? GetParameter(string name)
- {
- Dictionary? parametersByName = _parametersByName ??= _parameters.ToDictionary(p => p.Name);
-
- return parametersByName.TryGetValue(name, out AIFunctionParameterMetadata? parameter) ?
- parameter :
- null;
- }
-
- /// Gets parameter metadata for the return parameter.
- /// If the function has no return parameter, the value is a default instance of an .
- public AIFunctionReturnParameterMetadata ReturnParameter
- {
- get => _returnParameter;
- init => _returnParameter = Throw.IfNull(value);
- }
-
- /// Gets a JSON Schema describing the function and its input parameters.
- ///
- ///
- /// When specified, declares a self-contained JSON schema document that describes the function and its input parameters.
- /// A simple example of a JSON schema for a function that adds two numbers together is shown below:
- ///
- ///
- /// {
- /// "title" : "addNumbers",
- /// "description": "A simple function that adds two numbers together.",
- /// "type": "object",
- /// "properties": {
- /// "a" : { "type": "number" },
- /// "b" : { "type": "number", "default": 1 }
- /// },
- /// "required" : ["a"]
- /// }
- ///
- ///
- /// The metadata present in the schema document plays an important role in guiding AI function invocation.
- /// Functions should incorporate as much detail as possible. The arity of the "properties" keyword should
- /// also match the length of the list.
- ///
- ///
- /// When no schema is specified, consuming chat clients should assume the "{}" or "true" schema, indicating that any JSON input is admissible.
- ///
- ///
- public JsonElement Schema
- {
- get => _schema;
- init
- {
- AIJsonUtilities.ValidateSchemaDocument(value);
- _schema = value;
- }
- }
-
- /// Gets any additional properties associated with the function.
- public IReadOnlyDictionary AdditionalProperties
- {
- get => _additionalProperties;
- init => _additionalProperties = Throw.IfNull(value);
- }
-
- /// Gets a that can be used to marshal function parameters.
- public JsonSerializerOptions? JsonSerializerOptions { get; init; }
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionParameterMetadata.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionParameterMetadata.cs
deleted file mode 100644
index 372083fd799..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionParameterMetadata.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using Microsoft.Shared.Diagnostics;
-
-namespace Microsoft.Extensions.AI;
-
-///
-/// Provides read-only metadata for an parameter.
-///
-public sealed class AIFunctionParameterMetadata
-{
- private readonly string _name;
-
- /// Initializes a new instance of the class for a parameter with the specified name.
- /// The name of the parameter.
- /// The was null.
- /// The was empty or composed entirely of whitespace.
- public AIFunctionParameterMetadata(string name)
- {
- _name = Throw.IfNullOrWhitespace(name);
- }
-
- /// Initializes a new instance of the class as a copy of another .
- /// The was null.
- /// This constructor creates a shallow clone of .
- public AIFunctionParameterMetadata(AIFunctionParameterMetadata metadata)
- {
- _ = Throw.IfNull(metadata);
- _ = Throw.IfNullOrWhitespace(metadata.Name);
-
- _name = metadata.Name;
-
- Description = metadata.Description;
- HasDefaultValue = metadata.HasDefaultValue;
- DefaultValue = metadata.DefaultValue;
- IsRequired = metadata.IsRequired;
- ParameterType = metadata.ParameterType;
- }
-
- /// Gets the name of the parameter.
- public string Name
- {
- get => _name;
- init => _name = Throw.IfNullOrWhitespace(value);
- }
-
- /// Gets a description of the parameter, suitable for use in describing the purpose to a model.
- public string? Description { get; init; }
-
- /// Gets a value indicating whether the parameter has a default value.
- public bool HasDefaultValue { get; init; }
-
- /// Gets the default value of the parameter.
- public object? DefaultValue { get; init; }
-
- /// Gets a value indicating whether the parameter is required.
- public bool IsRequired { get; init; }
-
- /// Gets the .NET type of the parameter.
- public Type? ParameterType { get; init; }
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionReturnParameterMetadata.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionReturnParameterMetadata.cs
deleted file mode 100644
index 9ca5b3b6e49..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionReturnParameterMetadata.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Text.Json;
-using Microsoft.Shared.Diagnostics;
-
-namespace Microsoft.Extensions.AI;
-
-///
-/// Provides read-only metadata for an 's return parameter.
-///
-public sealed class AIFunctionReturnParameterMetadata
-{
- /// Gets an empty return parameter metadata instance.
- public static AIFunctionReturnParameterMetadata Empty { get; } = new();
-
- /// The JSON schema describing the function and its input parameters.
- private readonly JsonElement _schema = AIJsonUtilities.DefaultJsonSchema;
-
- /// Initializes a new instance of the class.
- public AIFunctionReturnParameterMetadata()
- {
- }
-
- /// Initializes a new instance of the class as a copy of another .
- public AIFunctionReturnParameterMetadata(AIFunctionReturnParameterMetadata metadata)
- {
- Description = Throw.IfNull(metadata).Description;
- ParameterType = metadata.ParameterType;
- Schema = metadata.Schema;
- }
-
- /// Gets a description of the return parameter, suitable for use in describing the purpose to a model.
- public string? Description { get; init; }
-
- /// Gets the .NET type of the return parameter.
- public Type? ParameterType { get; init; }
-
- /// Gets a JSON Schema describing the type of the return parameter.
- public JsonElement Schema
- {
- get => _schema;
- init
- {
- AIJsonUtilities.ValidateSchemaDocument(value);
- _schema = value;
- }
- }
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs
index ee2e497638a..805c121b326 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs
@@ -2,11 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -46,39 +46,47 @@ public static partial class AIJsonUtilities
private static readonly string[] _schemaKeywordsDisallowedByAIVendors = ["minLength", "maxLength", "pattern", "format"];
///
- /// Determines a JSON schema for the provided AI function parameter metadata.
+ /// Determines a JSON schema for the provided method.
///
+ /// The method from which to extract schema information.
/// The title keyword used by the method schema.
/// The description keyword used by the method schema.
- /// The AI function parameter metadata.
/// The options used to extract the schema from the specified type.
/// The options controlling schema inference.
/// A JSON schema document encoded as a .
public static JsonElement CreateFunctionJsonSchema(
+ MethodBase method,
string? title = null,
string? description = null,
- IReadOnlyList? parameters = null,
JsonSerializerOptions? serializerOptions = null,
AIJsonSchemaCreateOptions? inferenceOptions = null)
{
+ _ = Throw.IfNull(method);
serializerOptions ??= DefaultOptions;
inferenceOptions ??= AIJsonSchemaCreateOptions.Default;
+ title ??= method.Name;
+ description ??= method.GetCustomAttribute()?.Description;
JsonObject parameterSchemas = new();
JsonArray? requiredProperties = null;
- foreach (AIFunctionParameterMetadata parameter in parameters ?? [])
+ foreach (ParameterInfo parameter in method.GetParameters())
{
+ if (string.IsNullOrWhiteSpace(parameter.Name))
+ {
+ Throw.ArgumentException(nameof(parameter), "Parameter is missing a name.");
+ }
+
JsonNode parameterSchema = CreateJsonSchemaCore(
- parameter.ParameterType,
- parameter.Name,
- parameter.Description,
- parameter.HasDefaultValue,
- parameter.DefaultValue,
+ type: parameter.ParameterType,
+ parameterName: parameter.Name,
+ description: parameter.GetCustomAttribute(inherit: true)?.Description,
+ hasDefaultValue: parameter.HasDefaultValue,
+ defaultValue: parameter.HasDefaultValue ? parameter.DefaultValue : null,
serializerOptions,
inferenceOptions);
parameterSchemas.Add(parameter.Name, parameterSchema);
- if (parameter.IsRequired)
+ if (!parameter.IsOptional)
{
(requiredProperties ??= []).Add((JsonNode)parameter.Name);
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs
index 27e6298e057..bb23e5da7e5 100644
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs
@@ -380,11 +380,11 @@ private ChatCompletionsOptions ToAzureAIOptions(IList chatContents,
private static ChatCompletionsToolDefinition ToAzureAIChatTool(AIFunction aiFunction)
{
// Map to an intermediate model so that redundant properties are skipped.
- var tool = JsonSerializer.Deserialize(aiFunction.Metadata.Schema, JsonContext.Default.AzureAIChatToolJson)!;
+ var tool = JsonSerializer.Deserialize(aiFunction.JsonSchema, JsonContext.Default.AzureAIChatToolJson)!;
var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool, JsonContext.Default.AzureAIChatToolJson));
- return new(new FunctionDefinition(aiFunction.Metadata.Name)
+ return new(new FunctionDefinition(aiFunction.Name)
{
- Description = aiFunction.Metadata.Description,
+ Description = aiFunction.Description,
Parameters = functionParameters,
});
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Ollama/OllamaChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.Ollama/OllamaChatClient.cs
index efeff58d592..727d00f05c8 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Ollama/OllamaChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Ollama/OllamaChatClient.cs
@@ -476,9 +476,9 @@ private static OllamaTool ToOllamaTool(AIFunction function)
Type = "function",
Function = new OllamaFunctionTool
{
- Name = function.Metadata.Name,
- Description = function.Metadata.Description,
- Parameters = JsonSerializer.Deserialize(function.Metadata.Schema, JsonContext.Default.OllamaFunctionToolParameters)!,
+ Name = function.Name,
+ Description = function.Description,
+ Parameters = JsonSerializer.Deserialize(function.JsonSchema, JsonContext.Default.OllamaFunctionToolParameters)!,
}
};
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs
index 110ea0bf7fe..96bd316d746 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs
@@ -215,16 +215,16 @@ private static (RunCreationOptions RunOptions, List? Tool
if (tool is AIFunction aiFunction)
{
bool? strict =
- aiFunction.Metadata.AdditionalProperties.TryGetValue("Strict", out object? strictObj) &&
+ aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) &&
strictObj is bool strictValue ?
strictValue : null;
var functionParameters = BinaryData.FromBytes(
JsonSerializer.SerializeToUtf8Bytes(
- JsonSerializer.Deserialize(aiFunction.Metadata.Schema, OpenAIJsonContext.Default.OpenAIChatToolJson)!,
+ JsonSerializer.Deserialize(aiFunction.JsonSchema, OpenAIJsonContext.Default.OpenAIChatToolJson)!,
OpenAIJsonContext.Default.OpenAIChatToolJson));
- runOptions.ToolsOverride.Add(ToolDefinition.CreateFunction(aiFunction.Metadata.Name, aiFunction.Metadata.Description, functionParameters, strict));
+ runOptions.ToolsOverride.Add(ToolDefinition.CreateFunction(aiFunction.Name, aiFunction.Description, functionParameters, strict));
}
}
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs
index 170f8cbe06e..c1e0189c8cd 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs
@@ -26,8 +26,6 @@ namespace Microsoft.Extensions.AI;
internal static partial class OpenAIModelMappers
{
- internal static JsonElement DefaultParameterSchema { get; } = JsonDocument.Parse("{}").RootElement;
-
public static ChatCompletion ToOpenAIChatCompletion(ChatResponse response, JsonSerializerOptions options)
{
_ = Throw.IfNull(response);
@@ -418,50 +416,32 @@ private static AITool FromOpenAIChatTool(ChatTool chatTool)
}
OpenAIChatToolJson openAiChatTool = JsonSerializer.Deserialize(chatTool.FunctionParameters.ToMemory().Span, OpenAIJsonContext.Default.OpenAIChatToolJson)!;
- List parameters = new(openAiChatTool.Properties.Count);
- foreach (KeyValuePair property in openAiChatTool.Properties)
- {
- parameters.Add(new(property.Key)
- {
- IsRequired = openAiChatTool.Required.Contains(property.Key),
- });
- }
-
- AIFunctionMetadata metadata = new(chatTool.FunctionName)
- {
- Description = chatTool.FunctionDescription,
- AdditionalProperties = additionalProperties,
- Parameters = parameters,
- Schema = JsonSerializer.SerializeToElement(openAiChatTool, OpenAIJsonContext.Default.OpenAIChatToolJson),
- ReturnParameter = new()
- {
- Description = "Return parameter",
- Schema = DefaultParameterSchema,
- }
- };
-
- return new MetadataOnlyAIFunction(metadata);
+ JsonElement schema = JsonSerializer.SerializeToElement(openAiChatTool, OpenAIJsonContext.Default.OpenAIChatToolJson);
+ return new MetadataOnlyAIFunction(chatTool.FunctionName, chatTool.FunctionDescription, schema, additionalProperties);
}
- private sealed class MetadataOnlyAIFunction(AIFunctionMetadata metadata) : AIFunction
+ private sealed class MetadataOnlyAIFunction(string name, string description, JsonElement schema, IReadOnlyDictionary additionalProps) : AIFunction
{
- public override AIFunctionMetadata Metadata => metadata;
+ public override string Name => name;
+ public override string Description => description;
+ public override JsonElement JsonSchema => schema;
+ public override IReadOnlyDictionary AdditionalProperties => additionalProps;
protected override Task