使用代理选择上下文函数

重要

此功能处于实验阶段。 此阶段的功能正在积极开发中,在升级到预览版或候选发布阶段之前可能会发生重大变化。

概述

上下文函数选择是语义内核代理框架中的一项高级功能,使代理能够基于当前聊天上下文动态选择和播发最相关的函数。 此功能使用 Retrieval-Augmented 生成(RAG)筛选并仅显示与用户请求最相关的函数,而不是向 AI 模型公开所有可用函数。

此方法解决了处理大量可用函数时函数选择的挑战,其中 AI 模型可能难以选择适当的函数,从而导致混淆和性能欠佳。

警告

使用 ContextualFunctionProvider 时,代理上的 UseImmutableKernel 设置必须设为 true,因为该功能需要在调用代理时克隆内核。 请注意,设置为UseImmutableKerneltrue意味着在代理调用期间完成的任何内核数据修改(例如插件)都不会在调用完成后保留。

上下文函数选择的工作原理

当代理配置有上下文函数选择时,它将利用矢量存储和嵌入生成器,以语义方式将当前聊天上下文(包括以前的消息和用户输入)与可用函数的说明和名称匹配。 然后,将最相关的函数在达到指定限制时推送给 AI 模型进行调用。

此机制对于能够访问大量插件或工具的代理特别有用,确保在每个步骤中只考虑上下文适当的操作。

用法示例

以下示例演示如何将代理配置为使用上下文函数选择。 代理被设置用于汇总客户评论,但每次调用仅向 AI 模型提供最相关的功能。 该方法 GetAvailableFunctions 有意包括相关和无关的函数,以突出显示上下文选择的优点。

// Create an embedding generator for function vectorization
var embeddingGenerator = new AzureOpenAIClient(new Uri("<endpoint>"), new ApiKeyCredential("<api-key>"))
    .GetEmbeddingClient("<deployment-name>")
    .AsIEmbeddingGenerator();

// Create kernel and register AzureOpenAI chat completion service
var kernel = Kernel.CreateBuilder()
    .AddAzureOpenAIChatCompletion("<deployment-name>", "<endpoint>", "<api-key>");
    .Build();

// Create a chat completion agent
ChatCompletionAgent agent = new()
{
    Name = "ReviewGuru",
    Instructions = "You are a friendly assistant that summarizes key points and sentiments from customer reviews. For each response, list available functions.",
    Kernel = kernel,
    Arguments = new(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new FunctionChoiceBehaviorOptions { RetainArgumentTypes = true }) }),
    // This setting must be set to true when using the ContextualFunctionProvider
    UseImmutableKernel = true
};

// Create the agent thread and register the contextual function provider
ChatHistoryAgentThread agentThread = new();

agentThread.AIContextProviders.Add(
    new ContextualFunctionProvider(
        vectorStore: new InMemoryVectorStore(new InMemoryVectorStoreOptions() { EmbeddingGenerator = embeddingGenerator }),
        vectorDimensions: 1536,
        functions: AvailableFunctions(),
        maxNumberOfFunctions: 3, // Only the top 3 relevant functions are advertised
        loggerFactory: LoggerFactory
    )
);


// Invoke the agent
ChatMessageContent message = await agent.InvokeAsync("Get and summarize customer review.", agentThread).FirstAsync();
Console.WriteLine(message.Content);

// Output
/*
    Customer Reviews:
    -----------------
    1. John D. - ★★★★★
       Comment: Great product and fast shipping!
       Date: 2023-10-01

    Summary:
    --------
    The reviews indicate high customer satisfaction,
    highlighting product quality and shipping speed.

    Available functions:
    --------------------
    - Tools-GetCustomerReviews
    - Tools-Summarize
    - Tools-CollectSentiments
*/

IReadOnlyList<AIFunction> GetAvailableFunctions()
{
    // Only a few functions are directly related to the prompt; the majority are unrelated to demonstrate the benefits of contextual filtering.
    return new List<AIFunction>
    {
        // Relevant functions
        AIFunctionFactory.Create(() => "[ { 'reviewer': 'John D.', 'date': '2023-10-01', 'rating': 5, 'comment': 'Great product and fast shipping!' } ]", "GetCustomerReviews"),
        AIFunctionFactory.Create((string text) => "Summary generated based on input data: key points include customer satisfaction.", "Summarize"),
        AIFunctionFactory.Create((string text) => "The collected sentiment is mostly positive.", "CollectSentiments"),

        // Irrelevant functions
        AIFunctionFactory.Create(() => "Current weather is sunny.", "GetWeather"),
        AIFunctionFactory.Create(() => "Email sent.", "SendEmail"),
        AIFunctionFactory.Create(() => "The current stock price is $123.45.", "GetStockPrice"),
        AIFunctionFactory.Create(() => "The time is 12:00 PM.", "GetCurrentTime")
    };
}

矢量存储

提供程序主要设计用于处理内存中矢量存储,从而提供简单性。 但是,如果使用其他类型的矢量存储,请务必注意处理数据同步和一致性的责任落在宿主应用程序中。

每当函数列表更改或修改函数嵌入源时,都需要同步。 例如,如果代理最初具有三个函数(f1、f2、f3),这些函数(f1、f2、f3)被矢量化并存储在云向量存储中,并且以后的 f3 将从代理的函数列表中删除,则必须更新向量存储以仅反映代理具有的当前函数(f1 和 f2)。 未能更新向量存储可能会导致不相关的函数作为结果返回。 同样,如果用于矢量化的数据(如函数名称、说明等)发生更改,则应根据更新的信息清除矢量存储,并使用新的嵌入重新填充。

在外部或分布式向量存储中管理数据同步可能比较复杂且容易出错,尤其是在不同服务或实例可以独立运行且需要对相同数据的一致访问的分布式应用程序中。 相比之下,使用内存中存储简化了此过程:当函数列表或向量化源发生更改时,可以使用新的函数集及其嵌入轻松重新创建内存中存储,确保尽量保持一致性。

函数的指定

上下文功能提供者必须提供一系列函数,以便其能够根据当前上下文选择最相关的函数。 这可以通过向构造函数的参数functions提供函数ContextualFunctionProvider列表来实现。

除了函数,还必须指定使用 maxNumberOfFunctions 参数返回的相关函数的最大数目。 此参数确定提供程序在为当前上下文选择最相关的函数时将考虑的函数数。 指定的数字并非精确;相反,它用作依赖于特定方案的上限。

设置此值太低可能会阻止代理访问方案所需的所有功能,这可能会导致方案失败。 相反,设置太高可能会使代理不知所措,其功能过多,这可能会导致幻觉、输入令牌消耗过多和性能欠佳。

// Create the provider with a list of functions and a maximum number of functions to return
ContextualFunctionProvider provider = new (
    vectorStore: new InMemoryVectorStore(new InMemoryVectorStoreOptions { EmbeddingGenerator = embeddingGenerator }),
    vectorDimensions: 1536,
    functions: [AIFunctionFactory.Create((string text) => $"Echo: {text}", "Echo"), <other functions>]
    maxNumberOfFunctions: 3 // Only the top 3 relevant functions are advertised
);

上下文函数提供程序选项

可以使用 ContextualFunctionProviderOptions 类来配置提供程序,从而自定义提供程序操作方式的各个方面。

// Create options for the contextual function provider
ContextualFunctionProviderOptions options = new ()
{
    ...
};

// Create the provider with options
ContextualFunctionProvider provider = new (
    ...
    options: options // Pass the options
);

上下文大小

在为新调用形成上下文时,上下文容量决定了包含多少来自以前代理调用的最近消息。 提供者会收集从以前调用中获取的所有消息(最多为指定数量),并将这些消息放在新消息前面,以形成上下文。

将最近的消息与新消息一起使用对于需要对话中早期步骤信息的任务特别有用。 例如,如果代理在一个调用中预配资源并将其部署到下一个调用中,则部署步骤可以访问预配步骤中的详细信息,以获取部署的预配资源信息。

上下文中最近消息数的默认值为 2,但可以通过在以下项NumberOfRecentMessagesInContext中指定ContextualFunctionProviderOptions属性来根据需要配置此值:

ContextualFunctionProviderOptions options = new ()
{
    NumberOfRecentMessagesInContext = 1 // Only the last message will be included in the context
};

上下文嵌入源值

若要执行上下文函数选择,提供程序需要向量化当前上下文,以便可以将其与向量存储中的可用函数进行比较。 默认情况下,提供程序通过将所有非空最近消息和新消息串联成单个字符串来创建此上下文,该字符串随后进行矢量化,并用于搜索相关函数。

在某些情况下,可能需要自定义以下行为:

  • 专注于特定消息类型(例如,仅用户消息)
  • 从上下文中排除某些信息
  • 在矢量化之前预处理或汇总上下文(例如,应用提示重写)

为此,可以将自定义委托分配给ContextEmbeddingValueProvider。 此委托接收最近和新消息,并返回一个字符串值,用作上下文嵌入的源:

ContextualFunctionProviderOptions options = new()
{
    ContextEmbeddingValueProvider = async (recentMessages, newMessages, cancellationToken) =>
    {
        // Example: Only include user messages in the embedding
        var allUserMessages = recentMessages.Concat(newMessages)
            .Where(m => m.Role == "user")
            .Select(m => m.Content)
            .Where(content => !string.IsNullOrWhiteSpace(content));
        return string.Join("\n", allUserMessages);
    }
};

自定义上下文嵌入可以提高函数选择的相关性,尤其是在复杂或高度专用的代理方案中。

函数嵌入源值

提供程序需要向量化每个可用函数,以便将其与上下文进行比较,并选择最相关的函数。 默认情况下,提供程序通过将函数的名称和说明串联为单个字符串来创建一个函数嵌入,该字符串随后将矢量化并存储在向量存储中。

可以使用EmbeddingValueProviderContextualFunctionProviderOptions的属性自定义此行为。 此属性允许您指定一个回调,该回调接收函数和取消令牌,并返回一个字符串,作为函数嵌入的源。 这很有用,如果您想要:

  • 向嵌入源添加其他函数元数据
  • 在矢量化之前预处理、筛选或重新格式化函数信息
ContextualFunctionProviderOptions options = new()
{
    EmbeddingValueProvider = async (function, cancellationToken) =>
    {
        // Example: Use only the function name for embedding
        return function.Name;
    }
};

自定义函数嵌入源值可以提高函数选择的准确性,尤其是在函数具有丰富、上下文相关的元数据或希望将搜索集中在每个函数的特定方面时。

后续步骤

浏览上下文函数选择示例

即将推出

更多信息即将推出。

即将推出

更多信息即将推出。