拥有多个插件后,需要一种方法让 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 自动调用函数
- 使用聊天历史记录和内核调用聊天完成服务
小窍门
下面的代码示例使用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 代理,以便其能够最佳规划和执行任务,为您的用户提供服务。