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
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release History

## NOT YET RELEASED

- Added non-invocable `AIFunctionDeclaration` (base class for `AIFunction`), `AIFunctionFactory.CreateDeclaration`, and `AIFunction.AsDeclarationOnly`.

## 9.8.0

- Added `AIAnnotation` and related types to represent citations and other annotations in chat messages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ private protected ChatToolMode()

/// <summary>
/// Instantiates a <see cref="ChatToolMode"/> indicating that tool usage is required,
/// and that the specified <see cref="AIFunction"/> must be selected. The function name
/// must match an entry in <see cref="ChatOptions.Tools"/>.
/// and that the specified function name must be selected.
/// </summary>
/// <param name="functionName">The name of the required function.</param>
/// <returns>An instance of <see cref="RequiredChatToolMode"/> for the specified function name.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ namespace Microsoft.Extensions.AI;
public sealed class RequiredChatToolMode : ChatToolMode
{
/// <summary>
/// Gets the name of a specific <see cref="AIFunction"/> that must be called.
/// Gets the name of a specific tool that must be called.
/// </summary>
/// <remarks>
/// If the value is <see langword="null"/>, any available function can be selected (but at least one must be).
/// If the value is <see langword="null"/>, any available tool can be selected (but at least one must be).
/// </remarks>
public string? RequiredFunctionName { get; }

/// <summary>
/// Initializes a new instance of the <see cref="RequiredChatToolMode"/> class that requires a specific function to be called.
/// Initializes a new instance of the <see cref="RequiredChatToolMode"/> class that requires a specific tool to be called.
/// </summary>
/// <param name="requiredFunctionName">The name of the function that must be called.</param>
/// <param name="requiredFunctionName">The name of the tool that must be called.</param>
/// <exception cref="ArgumentException"><paramref name="requiredFunctionName"/> is empty or composed entirely of whitespace.</exception>
/// <remarks>
/// <paramref name="requiredFunctionName"/> can be <see langword="null"/>. However, it's preferable to use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,17 @@
using System.Threading;
using System.Threading.Tasks;

#pragma warning disable SA1202 // Elements should be ordered by access

namespace Microsoft.Extensions.AI;

/// <summary>Represents a function that can be described to an AI service and invoked.</summary>
public abstract class AIFunction : AITool
public abstract class AIFunction : AIFunctionDeclaration
{
/// <summary>Gets a JSON Schema describing the function and its input parameters.</summary>
/// <remarks>
/// <para>
/// 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:
/// </para>
/// <code>
/// {
/// "title" : "addNumbers",
/// "description": "A simple function that adds two numbers together.",
/// "type": "object",
/// "properties": {
/// "a" : { "type": "number" },
/// "b" : { "type": "number", "default": 1 }
/// },
/// "required" : ["a"]
/// }
/// </code>
/// <para>
/// The metadata present in the schema document plays an important role in guiding AI function invocation.
/// </para>
/// <para>
/// When no schema is specified, consuming chat clients should assume the "{}" or "true" schema, indicating that any JSON input is admissible.
/// </para>
/// </remarks>
public virtual JsonElement JsonSchema => AIJsonUtilities.DefaultJsonSchema;

/// <summary>Gets a JSON Schema describing the function's return value.</summary>
/// <remarks>
/// A <see langword="null"/> typically reflects a function that doesn't specify a return schema
/// or a function that returns <see cref="void"/>, <see cref="Task"/>, or <see cref="ValueTask"/>.
/// </remarks>
public virtual JsonElement? ReturnJsonSchema => null;
/// <summary>Initializes a new instance of the <see cref="AIFunction"/> class.</summary>
protected AIFunction()
{
}

/// <summary>
/// Gets the underlying <see cref="MethodInfo"/> that this <see cref="AIFunction"/> might be wrapping.
Expand Down Expand Up @@ -72,4 +45,14 @@ public abstract class AIFunction : AITool
protected abstract ValueTask<object?> InvokeCoreAsync(
AIFunctionArguments arguments,
CancellationToken cancellationToken);

/// <summary>Creates a <see cref="AIFunctionDeclaration"/> representation of this <see cref="AIFunction"/> that can't be invoked.</summary>
/// <returns>The created instance.</returns>
/// <remarks>
/// <see cref="AIFunction"/> derives from <see cref="AIFunctionDeclaration"/>, layering on the ability to invoke the function in addition
/// to describing it. <see cref="AsDeclarationOnly"/> creates a new object that describes the function but that can't be invoked.
/// </remarks>
public AIFunctionDeclaration AsDeclarationOnly() => new NonInvocableAIFunction(this);

private sealed class NonInvocableAIFunction(AIFunction function) : DelegatingAIFunctionDeclaration(function);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Threading.Tasks;

#pragma warning disable S1694 // An abstract class should have both abstract and concrete methods

namespace Microsoft.Extensions.AI;

/// <summary>Represents a function that can be described to an AI service.</summary>
/// <remarks>
/// <see cref="AIFunctionDeclaration"/> is the base class for <see cref="AIFunction"/>, which
/// adds the ability to invoke the function. Components may type test <see cref="AITool"/> instances
/// for <see cref="AIFunctionDeclaration"/> to determine whether they can be described as functions,
/// and may type test for <see cref="AIFunction"/> to determine whether they can be invoked.
/// </remarks>
public abstract class AIFunctionDeclaration : AITool
{
/// <summary>Initializes a new instance of the <see cref="AIFunctionDeclaration"/> class.</summary>
protected AIFunctionDeclaration()
{
}

/// <summary>Gets a JSON Schema describing the function and its input parameters.</summary>
/// <remarks>
/// <para>
/// 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:
/// </para>
/// <code>
/// {
/// "title" : "addNumbers",
/// "description": "A simple function that adds two numbers together.",
/// "type": "object",
/// "properties": {
/// "a" : { "type": "number" },
/// "b" : { "type": "number", "default": 1 }
/// },
/// "required" : ["a"]
/// }
/// </code>
/// <para>
/// The metadata present in the schema document plays an important role in guiding AI function invocation.
/// </para>
/// <para>
/// When no schema is specified, consuming chat clients should assume the "{}" or "true" schema, indicating that any JSON input is admissible.
/// </para>
/// </remarks>
public virtual JsonElement JsonSchema => AIJsonUtilities.DefaultJsonSchema;

/// <summary>Gets a JSON Schema describing the function's return value.</summary>
/// <remarks>
/// A <see langword="null"/> typically reflects a function that doesn't specify a return schema
/// or a function that returns <see cref="void"/>, <see cref="Task"/>, or <see cref="ValueTask"/>.
/// </remarks>
public virtual JsonElement? ReturnJsonSchema => null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static partial class AIFunctionFactory
/// <para>
/// By default, any parameters to <paramref name="method"/> are sourced from the <see cref="AIFunctionArguments"/>'s dictionary
/// of key/value pairs and are represented in the JSON schema for the function, as exposed in the returned <see cref="AIFunction"/>'s
/// <see cref="AIFunction.JsonSchema"/>. There are a few exceptions to this:
/// <see cref="AIFunctionDeclaration.JsonSchema"/>. There are a few exceptions to this:
/// <list type="bullet">
/// <item>
/// <description>
Expand Down Expand Up @@ -131,7 +131,7 @@ public static AIFunction Create(Delegate method, AIFunctionFactoryOptions? optio
/// <para>
/// Any parameters to <paramref name="method"/> are sourced from the <see cref="AIFunctionArguments"/>'s dictionary
/// of key/value pairs and are represented in the JSON schema for the function, as exposed in the returned <see cref="AIFunction"/>'s
/// <see cref="AIFunction.JsonSchema"/>. There are a few exceptions to this:
/// <see cref="AIFunctionDeclaration.JsonSchema"/>. There are a few exceptions to this:
/// <list type="bullet">
/// <item>
/// <description>
Expand Down Expand Up @@ -212,7 +212,7 @@ public static AIFunction Create(Delegate method, string? name = null, string? de
/// <para>
/// By default, any parameters to <paramref name="method"/> are sourced from the <see cref="AIFunctionArguments"/>'s dictionary
/// of key/value pairs and are represented in the JSON schema for the function, as exposed in the returned <see cref="AIFunction"/>'s
/// <see cref="AIFunction.JsonSchema"/>. There are a few exceptions to this:
/// <see cref="AIFunctionDeclaration.JsonSchema"/>. There are a few exceptions to this:
/// <list type="bullet">
/// <item>
/// <description>
Expand Down Expand Up @@ -304,7 +304,7 @@ public static AIFunction Create(MethodInfo method, object? target, AIFunctionFac
/// <para>
/// Any parameters to <paramref name="method"/> are sourced from the <see cref="AIFunctionArguments"/>'s dictionary
/// of key/value pairs and are represented in the JSON schema for the function, as exposed in the returned <see cref="AIFunction"/>'s
/// <see cref="AIFunction.JsonSchema"/>. There are a few exceptions to this:
/// <see cref="AIFunctionDeclaration.JsonSchema"/>. There are a few exceptions to this:
/// <list type="bullet">
/// <item>
/// <description>
Expand Down Expand Up @@ -398,7 +398,7 @@ public static AIFunction Create(MethodInfo method, object? target, string? name
/// <para>
/// By default, any parameters to <paramref name="method"/> are sourced from the <see cref="AIFunctionArguments"/>'s dictionary
/// of key/value pairs and are represented in the JSON schema for the function, as exposed in the returned <see cref="AIFunction"/>'s
/// <see cref="AIFunction.JsonSchema"/>. There are a few exceptions to this:
/// <see cref="AIFunctionDeclaration.JsonSchema"/>. There are a few exceptions to this:
/// <list type="bullet">
/// <item>
/// <description>
Expand Down Expand Up @@ -467,6 +467,39 @@ public static AIFunction Create(
AIFunctionFactoryOptions? options = null) =>
ReflectionAIFunction.Build(method, createInstanceFunc, options ?? _defaultOptions);

/// <summary>Creates an <see cref="AIFunctionDeclaration"/> using the specified parameters as the implementation of its corresponding properties.</summary>
/// <param name="name">The name of the function.</param>
/// <param name="description">A description of the function, suitable for use in describing the purpose to a model.</param>
/// <param name="jsonSchema">A JSON schema describing the function and its input parameters.</param>
/// <param name="returnJsonSchema">A JSON schema describing the function's return value.</param>
/// <returns>The created <see cref="AIFunctionDeclaration"/> that describes a function.</returns>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
/// <remarks>
/// <see cref="CreateDeclaration"/> creates an <see cref="AIFunctionDeclaration"/> that can be used to describe a function
/// but not invoke it. To create an invocable <see cref="AIFunction"/>, use Create. A non-invocable <see cref="AIFunctionDeclaration"/>
/// may also be created from an invocable <see cref="AIFunction"/> using that function's <see cref="AIFunction.AsDeclarationOnly"/> method.
/// </remarks>
public static AIFunctionDeclaration CreateDeclaration(
string name,
string? description,
JsonElement jsonSchema,
JsonElement? returnJsonSchema = null) =>
new DefaultAIFunctionDeclaration(
Throw.IfNullOrEmpty(name),
description ?? string.Empty,
jsonSchema,
returnJsonSchema);

private sealed class DefaultAIFunctionDeclaration(
string name, string description, JsonElement jsonSchema, JsonElement? returnJsonSchema) :
AIFunctionDeclaration
{
public override string Name => name;
public override string Description => description;
public override JsonElement JsonSchema => jsonSchema;
public override JsonElement? ReturnJsonSchema => returnJsonSchema;
}

private sealed class ReflectionAIFunction : AIFunction
{
public static ReflectionAIFunction Build(MethodInfo method, object? target, AIFunctionFactoryOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ public AIFunctionFactoryOptions()
public Func<object?, Type?, CancellationToken, ValueTask<object?>>? MarshalResult { get; set; }

/// <summary>
/// Gets or sets a value indicating whether a schema should be created for the function's result type, if possible, and included as <see cref="AIFunction.ReturnJsonSchema" />.
/// Gets or sets a value indicating whether a schema should be created for the function's result type, if possible, and included as <see cref="AIFunctionDeclaration.ReturnJsonSchema" />.
/// </summary>
/// <remarks>
/// <para>
/// The default value is <see langword="false"/>.
/// </para>
/// <para>
/// When set to <see langword="true"/>, results in the produced <see cref="AIFunction.ReturnJsonSchema"/> to always be <see langword="null"/>.
/// When set to <see langword="true"/>, results in the produced <see cref="AIFunctionDeclaration.ReturnJsonSchema"/> to always be <see langword="null"/>.
/// </para>
/// </remarks>
public bool ExcludeResultSchema { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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.Text.Json;
using Microsoft.Shared.Diagnostics;

#pragma warning disable SA1202 // Elements should be ordered by access

namespace Microsoft.Extensions.AI;

/// <summary>
/// Provides an optional base class for an <see cref="AIFunctionDeclaration"/> that passes through calls to another instance.
/// </summary>
internal class DelegatingAIFunctionDeclaration : AIFunctionDeclaration // could be made public in the future if there's demand
{
/// <summary>
/// Initializes a new instance of the <see cref="DelegatingAIFunctionDeclaration"/> class as a wrapper around <paramref name="innerFunction"/>.
/// </summary>
/// <param name="innerFunction">The inner AI function to which all calls are delegated by default.</param>
/// <exception cref="ArgumentNullException"><paramref name="innerFunction"/> is <see langword="null"/>.</exception>
protected DelegatingAIFunctionDeclaration(AIFunctionDeclaration innerFunction)
{
InnerFunction = Throw.IfNull(innerFunction);
}

/// <summary>Gets the inner <see cref="AIFunctionDeclaration" />.</summary>
protected AIFunctionDeclaration InnerFunction { get; }

/// <inheritdoc />
public override string Name => InnerFunction.Name;

/// <inheritdoc />
public override string Description => InnerFunction.Description;

/// <inheritdoc />
public override JsonElement JsonSchema => InnerFunction.JsonSchema;

/// <inheritdoc />
public override JsonElement? ReturnJsonSchema => InnerFunction.ReturnJsonSchema;

/// <inheritdoc />
public override IReadOnlyDictionary<string, object?> AdditionalProperties => InnerFunction.AdditionalProperties;

/// <inheritdoc />
public override string ToString() => InnerFunction.ToString();
}
Loading
Loading