什么是 Planner?

拥有多个插件后,需要一种方法让 AI 代理将它们一起使用来解决用户的需求。 这是计划出现的地方。

在早期,语义内核引入了规划器的概念,这些规划器使用提示请求 AI 来选择要调用的函数。 但是,由于引入了语义内核,OpenAI 引入了一种本机方法来调用或“调用”函数:函数。 自那以后,Gemini、Claude 和 Mistral 等其他 AI 模型采用函数调用作为核心功能,使其成为跨模型支持的功能。

由于这些进步,语义内核已演变为使用函数调用作为计划和执行任务的主要方法。

重要

函数调用仅在 0613 或更高版本的 OpenAI 模型中可用。 如果使用较旧的模型(例如 0314),此功能将返回错误。 建议使用最新的 OpenAI 模型来利用此功能。

函数调用如何创建“计划”?

从最简单的意义上说,函数调用只是 AI 调用具有正确参数的函数的一种方法。 例如,用户想要打开灯泡。 假设 AI 具有正确的插件,它可以调用函数以打开灯。

角色 消息
🔵用户 请打开浅色 #1
🔴助手(函数调用) Lights.change_state(1, { "isOn": true })
🟢工具 { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴助手 灯现已打开

但是,如果用户不知道光线的 ID,该怎么办? 或者,如果用户想要打开所有灯,该怎么办? 这是计划出现的地方。 当今的 LLM 模型能够以迭代方式调用函数来解决用户的需求。 这是通过创建反馈循环来实现的,其中 AI 可以调用函数、检查结果,然后决定下一步做什么。

例如,用户可能会要求 AI“切换”灯泡。 在决定是否将其打开或关闭之前,AI 首先需要检查灯泡的状态。

角色 消息
🔵用户 请切换所有灯
🔴助手(函数调用) Lights.get_lights()
🟢工具 { "lights": [ { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }, { "id": 2, "name": "Ceiling Light", "isOn": false, "brightness": 0, "hex": "FFFFFF" } ] }
🔴助手(函数调用) Lights.change_state(1, { "isOn": false }) Lights.change_state(2, { "isOn": true })
🟢工具 { "id": 1, "name": "Table Lamp", "isOn": false, "brightness": 0, "hex": "FFFFFF" }
🟢工具 { "id": 2, "name": "Ceiling Light", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴助手 灯已切换

注意

在此示例中,还看到了并行函数调用。 这是 AI 可以同时调用多个函数的地方。 这是一项功能强大的功能,可帮助 AI 更快地解决复杂的任务。 它于 1106 年添加到 OpenAI 模型。

自动计划循环

不支持在没有语义内核的情况下调用函数相对复杂。 需要编写一个可完成以下操作的循环:

  1. 为每个函数创建 JSON 架构
  2. 为 LLM 提供以前的聊天历史记录和函数架构
  3. 分析 LLM 的响应,以确定它是否想要回复消息或调用函数
  4. 如果 LLM 想要调用函数,则需要分析 LLM 响应中的函数名称和参数
  5. 使用正确的参数调用函数
  6. 返回函数的结果,以便 LLM 可以确定它接下来应执行的操作
  7. 重复步骤 2-6,直到 LLM 决定已完成任务或需要用户帮助

在语义内核中,通过自动执行此循环,可以轻松使用函数调用。 这使你可以专注于构建解决用户需求所需的插件。

注意

了解函数调用循环的工作原理对于生成高性能和可靠的 AI 代理至关重要。 有关循环工作原理的深入探讨,请参阅 函数调用 文章。

使用自动函数调用

若要在语义内核中使用自动函数调用,需要执行以下操作:

  1. 将插件注册到内核
  2. 创建一个执行设置对象,该对象指示 AI 自动调用函数
  3. 使用聊天历史记录和内核调用聊天完成服务
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

// 1. Create the kernel with the Lights plugin
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);
builder.Plugins.AddFromType<LightsPlugin>("Lights");
Kernel kernel = builder.Build();

var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

// 2. Enable automatic function calling
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() 
{
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

var history = new ChatHistory();

string? userInput;
do {
    // Collect user input
    Console.Write("User > ");
    userInput = Console.ReadLine();

    // Add user input
    history.AddUserMessage(userInput);

    // 3. Get the response from the AI with automatic function calling
    var result = await chatCompletionService.GetChatMessageContentAsync(
        history,
        executionSettings: openAIPromptExecutionSettings,
        kernel: kernel);

    // Print the results
    Console.WriteLine("Assistant > " + result);

    // Add the message from the agent to the chat history
    history.AddMessage(result.Role, result.Content ?? string.Empty);
} while (userInput is not null)
import asyncio

from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments

from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
    AzureChatPromptExecutionSettings,
)

async def main():
    # 1. Create the kernel with the Lights plugin
    kernel = Kernel()
    kernel.add_service(AzureChatCompletion(
        deployment_name="your_models_deployment_name",
        api_key="your_api_key",
        base_url="your_base_url",
    ))
    kernel.add_plugin(
        LightsPlugin(),
        plugin_name="Lights",
    )

    chat_completion : AzureChatCompletion = kernel.get_service(type=ChatCompletionClientBase)

    # 2. Enable automatic function calling
    execution_settings = AzureChatPromptExecutionSettings(tool_choice="auto")
    execution_settings.function_call_behavior = FunctionCallBehavior.EnableFunctions(auto_invoke=True, filters={})

    # Create a history of the conversation
    history = ChatHistory()

    userInput = None
    while True:
        # Collect user input
        userInput = input("User > ")

        # Terminate the loop if the user says "exit"
        if userInput == "exit":
            break

        # Add user input to the history
        history.add_user_message(userInput)

        # 3. Get the response from the AI with automatic function calling
        result = (await chat_completion.get_chat_message_contents(
            chat_history=history,
            settings=execution_settings,
            kernel=kernel,
            arguments=KernelArguments(),
        ))[0]

        # Print the results
        print("Assistant > " + str(result))

        # Add the message from the agent to the chat history
        history.add_message(result)

# Run the main function
if __name__ == "__main__":
    asyncio.run(main())

使用自动函数调用时,会自动规划循环中的所有步骤都会为你处理并添加到 ChatHistory 对象。 函数调用循环完成后,可以检查 ChatHistory 对象以查看语义内核提供的所有函数调用和结果。

函数调用分步和句柄栏规划器呢?

分步和 Handlebars 规划器仍可在语义内核中使用。 但是,我们建议对大多数任务使用函数调用,因为它更强大且更易于使用。 后续版本的语义内核中将弃用分步和 Handlebars 规划器。

在弃用这些规划器之前,我们将提供有关如何将现有规划器迁移到函数调用的指导。 如果对此过程有任何疑问,请在语义内核 GitHub 存储库中的讨论板上联系我们

注意

如果要生成新的 AI 代理,建议 不要 使用 Stepwise 或 Handlebars planners。 请改用函数调用,因为它更强大且更易于使用。

后续步骤

了解规划器在语义内核中的工作方式后,可以详细了解 AI 代理如何影响 AI 代理,以便他们代表用户最好地规划和执行任务。