规划

拥有多个插件后,需要一种方法让 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. 使用聊天历史记录和内核调用聊天完成服务

小窍门

下面的代码示例使用LightsPlugin此处定义的代码。

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() 
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

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.connectors.ai import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai import (
    AzureChatCompletion,
    AzureChatPromptExecutionSettings,
)
from semantic_kernel.contents import ChatHistory
from semantic_kernel.functions import kernel_function

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

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

    # 2. Enable automatic function calling
    execution_settings = AzureChatPromptExecutionSettings()
    execution_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

    # 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_content(
            chat_history=history,
            settings=execution_settings,
            kernel=kernel,
        )

        # 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())

    OpenAIAsyncClient client = new OpenAIClientBuilder()
        .credential(new AzureKeyCredential(AZURE_CLIENT_KEY))
        .endpoint(CLIENT_ENDPOINT)
        .buildAsyncClient();

    // Import the LightsPlugin
    KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
        "LightsPlugin");

    // Create your AI service client
    ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder()
        .withModelId(MODEL_ID)
        .withOpenAIAsyncClient(client)
        .build();

    // Create a kernel with Azure OpenAI chat completion and plugin
    Kernel kernel = Kernel.builder()
        .withAIService(ChatCompletionService.class, chatCompletionService)
        .withPlugin(lightPlugin)
        .build();

    // Add a converter to the kernel to show it how to serialise LightModel objects into a prompt
    ContextVariableTypes
        .addGlobalConverter(
            ContextVariableTypeConverter.builder(LightModel.class)
                .toPromptString(new Gson()::toJson)
                .build());

    // Enable planning
    InvocationContext invocationContext = new InvocationContext.Builder()
        .withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY)
        .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
        .build();

    // Create a history to store the conversation
    ChatHistory history = new ChatHistory();

    // Initiate a back-and-forth chat
    Scanner scanner = new Scanner(System.in);
    String userInput;
    do {
      // Collect user input
      System.out.print("User > ");

      userInput = scanner.nextLine();
      // Add user input
      history.addUserMessage(userInput);

      // Prompt AI for response to users input
      List<ChatMessageContent<?>> results = chatCompletionService
          .getChatMessageContentsAsync(history, kernel, invocationContext)
          .block();

      for (ChatMessageContent<?> result : results) {
        // Print the results
        if (result.getAuthorRole() == AuthorRole.ASSISTANT && result.getContent() != null) {
          System.out.println("Assistant > " + result);
        }
        // Add the message from the agent to the chat history
        history.addMessage(result);
      }
    } while (userInput != null && !userInput.isEmpty());

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

Stepwise 和 Handlebars 规划器发生了什么情况?

Stepwise和Handlebars规划器已被弃用,并从语义内核包中移除。 Python、.NET 或 Java 不再支持这些规划器。

建议使用 函数调用,对于大多数方案而言,函数调用功能更加强大且更易于使用。

若要更新现有解决方案,请遵循 我们的 Stepwise Planner 迁移指南

小窍门

对于新的 AI 代理,请使用函数调用,而不是弃用的规划器。 它提供了更好的灵活性、内置工具支持和更简单的开发体验。

后续步骤

在您了解规划器如何在语义内核中工作后,您可以进一步探索如何引导 AI 代理,以便其能够最佳规划和执行任务,为您的用户提供服务。