什么是 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 模型。
自动计划循环
不支持在没有语义内核的情况下调用函数相对复杂。 需要编写一个可完成以下操作的循环:
- 为每个函数创建 JSON 架构
- 为 LLM 提供以前的聊天历史记录和函数架构
- 分析 LLM 的响应,以确定它是否想要回复消息或调用函数
- 如果 LLM 想要调用函数,则需要分析 LLM 响应中的函数名称和参数
- 使用正确的参数调用函数
- 返回函数的结果,以便 LLM 可以确定它接下来应执行的操作
- 重复步骤 2-6,直到 LLM 决定已完成任务或需要用户帮助
在语义内核中,通过自动执行此循环,可以轻松使用函数调用。 这使你可以专注于构建解决用户需求所需的插件。
注意
了解函数调用循环的工作原理对于生成高性能和可靠的 AI 代理至关重要。 有关循环工作原理的深入探讨,请参阅 函数调用 文章。
使用自动函数调用
若要在语义内核中使用自动函数调用,需要执行以下操作:
- 将插件注册到内核
- 创建一个执行设置对象,该对象指示 AI 自动调用函数
- 使用聊天历史记录和内核调用聊天完成服务
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.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
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()
execution_settings.function_call_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_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())
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
对象以查看语义内核提供的所有函数调用和结果。
函数调用分步和句柄栏规划器呢?
分步和 Handlebars 规划器仍可在语义内核中使用。 但是,我们建议对大多数任务使用函数调用,因为它更强大且更易于使用。 后续版本的语义内核中将弃用分步和 Handlebars 规划器。
了解如何将 Stepwise Planner 迁移到自动函数调用。
注意
如果要生成新的 AI 代理,建议 不要 使用 Stepwise 或 Handlebars planners。 请改用函数调用,因为它更强大且更易于使用。
后续步骤
了解规划器在语义内核中的工作方式后,可以详细了解 AI 代理如何影响 AI 代理,以便他们代表用户最好地规划和执行任务。