通过聊天完成进行函数调用
聊天完成功能最强大的功能是能够从模型调用函数。 这样,便可以创建可与现有代码交互的聊天机器人,从而可以自动化业务流程、创建代码片段等。
借助语义内核,我们通过向模型自动描述函数及其参数,然后处理模型与代码之间的来回通信,从而简化使用函数调用的过程。
但是,使用函数调用时,最好了解后台实际发生的情况,以便优化代码并充分利用此功能。
函数调用的工作原理
向启用了函数调用的模型发出请求时,语义内核将执行以下步骤:
步骤 | 说明 | |
---|---|---|
1 | 序列化函数 | 内核中的所有可用函数(及其输入参数)都使用 JSON 架构进行序列化。 |
2 | 将消息和函数发送到模型 | 序列化的函数(和当前聊天历史记录)作为输入的一部分发送到模型。 |
3 | 模型处理输入 | 模型处理输入并生成响应。 响应可以是聊天消息或函数调用 |
4 | 处理响应 | 如果响应是聊天消息,则会将响应返回到开发人员,以便将响应打印到屏幕。 但是,如果响应是函数调用,则语义内核将提取函数名称和其参数。 |
5 | 调用函数 | 提取的函数名称和参数用于调用内核中的函数。 |
6 | 返回函数结果 | 然后,函数的结果作为聊天历史记录的一部分发送回模型。 然后重复步骤 2-6,直到模型发送终止信号 |
下图演示了函数调用的过程:
以下部分将使用具体示例来说明函数调用在实践中的工作原理。
示例:订购披萨
假设你有一个插件,允许用户订购披萨。 该插件具有以下功能:
get_pizza_menu
:返回可用披萨的列表add_pizza_to_cart
:将披萨添加到用户的购物车remove_pizza_from_cart
:从用户的购物车中删除披萨get_pizza_from_cart
:返回用户购物车中披萨的特定详细信息get_cart
:返回用户的当前购物车checkout
:签出用户的购物车
在 C# 中,插件可能如下所示:
public class OrderPizzaPlugin(
IPizzaService pizzaService,
IUserContext userContext,
IPaymentService paymentService)
{
[KernelFunction("get_pizza_menu")]
public async Task<Menu> GetPizzaMenuAsync()
{
return await pizzaService.GetMenu();
}
[KernelFunction("add_pizza_to_cart")]
[Description("Add a pizza to the user's cart; returns the new item and updated cart")]
public async Task<CartDelta> AddPizzaToCart(
PizzaSize size,
List<PizzaToppings> toppings,
int quantity = 1,
string specialInstructions = ""
)
{
Guid cartId = userContext.GetCartId();
return await pizzaService.AddPizzaToCart(
cartId: cartId,
size: size,
toppings: toppings,
quantity: quantity,
specialInstructions: specialInstructions);
}
[KernelFunction("remove_pizza_from_cart")]
public async Task<RemovePizzaResponse> RemovePizzaFromCart(int pizzaId)
{
Guid cartId = userContext.GetCartId();
return await pizzaService.RemovePizzaFromCart(cartId, pizzaId);
}
[KernelFunction("get_pizza_from_cart")]
[Description("Returns the specific details of a pizza in the user's cart; use this instead of relying on previous messages since the cart may have changed since then.")]
public async Task<Pizza> GetPizzaFromCart(int pizzaId)
{
Guid cartId = await userContext.GetCartIdAsync();
return await pizzaService.GetPizzaFromCart(cartId, pizzaId);
}
[KernelFunction("get_cart")]
[Description("Returns the user's current cart, including the total price and items in the cart.")]
public async Task<Cart> GetCart()
{
Guid cartId = await userContext.GetCartIdAsync();
return await pizzaService.GetCart(cartId);
}
[KernelFunction("checkout")]
[Description("Checkouts the user's cart; this function will retrieve the payment from the user and complete the order.")]
public async Task<CheckoutResponse> Checkout()
{
Guid cartId = await userContext.GetCartIdAsync();
Guid paymentId = await paymentService.RequestPaymentFromUserAsync(cartId);
return await pizzaService.Checkout(cartId, paymentId);
}
}
然后,将此插件添加到内核,如下所示:
IKernelBuilder kernelBuilder = new KernelBuilder();
kernelBuilder..AddAzureOpenAIChatCompletion(
deploymentName: "NAME_OF_YOUR_DEPLOYMENT",
apiKey: "YOUR_API_KEY",
endpoint: "YOUR_AZURE_ENDPOINT"
);
kernelBuilder.Plugins.AddFromType<OrderPizzaPlugin>("OrderPizza");
Kernel kernel = kernelBuilder.Build();
在 Python 中,插件可能如下所示:
from semantic_kernel.functions import kernel_function
class OrderPizzaPlugin:
def __init__(self, pizza_service, user_context, payment_service):
self.pizza_service = pizza_service
self.user_context = user_context
self.payment_service = payment_service
@kernel_function
async def get_pizza_menu(self):
return await self.pizza_service.get_menu()
@kernel_function(
description="Add a pizza to the user's cart; returns the new item and updated cart"
)
async def add_pizza_to_cart(self, size: PizzaSize, toppings: List[PizzaToppings], quantity: int = 1, special_instructions: str = ""):
cart_id = await self.user_context.get_cart_id()
return await self.pizza_service.add_pizza_to_cart(cart_id, size, toppings, quantity, special_instructions)
@kernel_function(
description="Remove a pizza from the user's cart; returns the updated cart"
)
async def remove_pizza_from_cart(self, pizza_id: int):
cart_id = await self.user_context.get_cart_id()
return await self.pizza_service.remove_pizza_from_cart(cart_id, pizza_id)
@kernel_function(
description="Returns the specific details of a pizza in the user's cart; use this instead of relying on previous messages since the cart may have changed since then."
)
async def get_pizza_from_cart(self, pizza_id: int):
cart_id = await self.user_context.get_cart_id()
return await self.pizza_service.get_pizza_from_cart(cart_id, pizza_id)
@kernel_function(
description="Returns the user's current cart, including the total price and items in the cart."
)
async def get_cart(self):
cart_id = await self.user_context.get_cart_id()
return await self.pizza_service.get_cart(cart_id)
@kernel_function(
description="Checkouts the user's cart; this function will retrieve the payment from the user and complete the order."
)
async def checkout(self):
cart_id = await self.user_context.get_cart_id()
payment_id = await self.payment_service.request_payment_from_user(cart_id)
return await self.pizza_service.checkout(cart_id, payment_id)
然后,将此插件添加到内核,如下所示:
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
kernel = Kernel()
kernel.add_service(AzureChatCompletion(model_id, endpoint, api_key))
# Create the services needed for the plugin: pizza_service, user_context, and payment_service
# ...
# Add the plugin to the kernel
kernel.add_plugin(OrderPizzaPlugin(pizza_service, user_context, payment_service), plugin_name="OrderPizza")
在 Java 中,插件可能如下所示:
public class OrderPizzaPlugin {
private final PizzaService pizzaService;
private final HttpSession userContext;
private final PaymentService paymentService;
public OrderPizzaPlugin(
PizzaService pizzaService,
UserContext userContext,
PaymentService paymentService)
{
this.pizzaService = pizzaService;
this.userContext = userContext;
this.paymentService = paymentService;
}
@DefineKernelFunction(name = "get_pizza_menu", description = "Get the pizza menu.", returnType = "com.pizzashop.Menu")
public Mono<Menu> getPizzaMenuAsync()
{
return pizzaService.getMenu();
}
@DefineKernelFunction(
name = "add_pizza_to_cart",
description = "Add a pizza to the user's cart",
returnDescription = "Returns the new item and updated cart",
returnType = "com.pizzashop.CartDelta")
public Mono<CartDelta> addPizzaToCart(
@KernelFunctionParameter(name = "size", description = "The size of the pizza", type = com.pizzashopo.PizzaSize.class, required = true)
PizzaSize size,
@KernelFunctionParameter(name = "toppings", description = "The toppings to add to the the pizza", type = com.pizzashopo.PizzaToppings.class)
List<PizzaToppings> toppings,
@KernelFunctionParameter(name = "quantity", description = "How many of this pizza to order", type = Integer.class, defaultValue = "1")
int quantity,
@KernelFunctionParameter(name = "specialInstructions", description = "Special instructions for the order",)
String specialInstructions
)
{
UUID cartId = userContext.getCartId();
return pizzaService.addPizzaToCart(
cartId,
size,
toppings,
quantity,
specialInstructions);
}
@DefineKernelFunction(name = "remove_pizza_from_cart", description = "Remove a pizza from the cart.", returnType = "com.pizzashop.RemovePizzaResponse")
public Mono<RemovePizzaResponse> removePizzaFromCart(
@KernelFunctionParameter(name = "pizzaId", description = "Id of the pizza to remove from the cart", type = Integer.class, required = true)
int pizzaId)
{
UUID cartId = userContext.getCartId();
return pizzaService.removePizzaFromCart(cartId, pizzaId);
}
@DefineKernelFunction(
name = "get_pizza_from_cart",
description = "Returns the specific details of a pizza in the user's cart; use this instead of relying on previous messages since the cart may have changed since then.",
returnType = "com.pizzashop.Pizza")
public Mono<Pizza> getPizzaFromCart(
@KernelFunctionParameter(name = "pizzaId", description = "Id of the pizza to get from the cart", type = Integer.class, required = true)
int pizzaId)
{
UUID cartId = userContext.getCartId();
return pizzaService.getPizzaFromCart(cartId, pizzaId);
}
@DefineKernelFunction(
name = "get_cart",
description = "Returns the user's current cart, including the total price and items in the cart.",
returnType = "com.pizzashop.Cart")
public Mono<Cart> getCart()
{
UUID cartId = userContext.getCartId();
return pizzaService.getCart(cartId);
}
@DefineKernelFunction(
name = "checkout",
description = "Checkouts the user's cart; this function will retrieve the payment from the user and complete the order.",
returnType = "com.pizzashop.CheckoutResponse")
public Mono<CheckoutResponse> Checkout()
{
UUID cartId = userContext.getCartId();
return paymentService.requestPaymentFromUser(cartId)
.flatMap(paymentId -> pizzaService.checkout(cartId, paymentId));
}
}
然后,将此插件添加到内核,如下所示:
OpenAIAsyncClient client = new OpenAIClientBuilder()
.credential(openAIClientCredentials)
.buildAsyncClient();
ChatCompletionService chat = OpenAIChatCompletion.builder()
.withModelId(modelId)
.withOpenAIAsyncClient(client)
.build();
KernelPlugin plugin = KernelPluginFactory.createFromObject(
new OrderPizzaPlugin(pizzaService, userContext, paymentService),
"OrderPizzaPlugin"
);
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chat)
.withPlugin(plugin)
.build();
1) 序列化函数
使用 OrderPizzaPlugin
创建内核时,内核将自动序列化函数及其参数。 这是必要的,以便模型可以理解函数及其输入。
对于上述插件,序列化函数如下所示:
[
{
"type": "function",
"function": {
"name": "OrderPizza-get_pizza_menu",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "OrderPizza-add_pizza_to_cart",
"description": "Add a pizza to the user's cart; returns the new item and updated cart",
"parameters": {
"type": "object",
"properties": {
"size": {
"type": "string",
"enum": ["Small", "Medium", "Large"]
},
"toppings": {
"type": "array",
"items": {
"type": "string",
"enum": ["Cheese", "Pepperoni", "Mushrooms"]
}
},
"quantity": {
"type": "integer",
"default": 1,
"description": "Quantity of pizzas"
},
"specialInstructions": {
"type": "string",
"default": "",
"description": "Special instructions for the pizza"
}
},
"required": ["size", "toppings"]
}
}
},
{
"type": "function",
"function": {
"name": "OrderPizza-remove_pizza_from_cart",
"parameters": {
"type": "object",
"properties": {
"pizzaId": {
"type": "integer"
}
},
"required": ["pizzaId"]
}
}
},
{
"type": "function",
"function": {
"name": "OrderPizza-get_pizza_from_cart",
"description": "Returns the specific details of a pizza in the user's cart; use this instead of relying on previous messages since the cart may have changed since then.",
"parameters": {
"type": "object",
"properties": {
"pizzaId": {
"type": "integer"
}
},
"required": ["pizzaId"]
}
}
},
{
"type": "function",
"function": {
"name": "OrderPizza-get_cart",
"description": "Returns the user's current cart, including the total price and items in the cart.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "OrderPizza-checkout",
"description": "Checkouts the user's cart; this function will retrieve the payment from the user and complete the order.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
}
]
此处需要注意的一些事项可能会影响聊天完成的性能和质量:
函数架构 的详细程度 – 序列化模型要使用的函数不会免费。 架构越详细,模型必须处理的令牌越多,这可能会降低响应时间并增加成本。
提示
尽可能简单地保留函数。 在上面的示例中,你会注意到,并非所有函数都有说明,其中函数名称是自我解释的。 这是有意减少令牌数。 参数也保持简单;模型不需要知道的任何内容(如
cartId
或paymentId
)都隐藏。 此信息由内部服务提供。注意
不需要担心的一件事是返回类型的复杂性。 你会注意到,返回类型未在架构中序列化。 这是因为模型不需要知道返回类型来生成响应。 但是,在步骤 6 中,我们将了解详细返回类型如何影响聊天完成的质量。
参数类型 – 使用架构,可以指定每个参数的类型。 对于模型了解预期的输入,这一点非常重要。 在上面的示例中,参数
size
是枚举,参数toppings
是枚举数组。 这有助于模型生成更准确的响应。提示
尽可能避免用作
string
参数类型。 模型无法推断字符串的类型,这可能会导致不明确的响应。 请尽可能使用枚举或其他类型(例如,int
、float
和复杂类型)。必需参数 - 还可以指定需要哪些参数。 这对于模型了解函数实际需要哪些参数非常重要。 稍后在步骤 3 中,模型将使用此信息来尽可能少地提供调用函数所需的信息。
提示
仅当实际需要参数时,才将其标记为必需。 这有助于模型调用函数更快、更准确。
函数说明 – 函数说明 是可选的,但可以帮助模型生成更准确的响应。 具体而言,说明可以告知模型响应预期,因为返回类型未在架构中序列化。 如果模型未正确使用函数,还可以添加说明来提供示例和指南。
例如,在函数中
get_pizza_from_cart
,说明告知用户使用此函数,而不是依赖以前的消息。 这一点很重要,因为购物车可能自上一封邮件以来发生了更改。提示
在添加说明之前,请询问模型是否需要此信息来生成响应。 如果没有,请考虑将其排除在外以减少详细程度。 如果模型难以正确使用函数,则以后始终可以添加说明。
插件名称 – 正如在序列化函数中看到的那样,每个函数都有一个
name
属性。 语义内核使用插件名称来命名空间函数。 这一点很重要,因为它允许具有多个具有相同名称的函数的插件。 例如,你可能具有多个搜索服务的插件,每个服务都有其自己的search
功能。 通过对函数进行命名,可以避免冲突,并使模型更容易理解要调用的函数。知道这一点,你应该选择一个唯一和描述性的插件名称。 在上面的示例中,插件名称为
OrderPizza
. 这清楚地表明,这些函数与订购披萨有关。提示
选择插件名称时,建议删除“plugin”或“service”等多余的字词。 这有助于减少详细程度,并使插件名称更易于理解模型。
2) 将消息和函数发送到模型
序列化函数后,它们将连同当前聊天历史记录一起发送到模型。 这样,模型就可以了解聊天上下文和可用函数。
在此方案中,我们可以想象用户要求助手将披萨添加到购物车中:
ChatHistory chatHistory = [];
chatHistory.AddUserMessage("I'd like to order a pizza!");
chat_history = ChatHistory()
chat_history.add_user_message("I'd like to order a pizza!")
ChatHistory chatHistory = new ChatHistory();
chatHistory.addUserMessage("I'd like to order a pizza!");
然后,我们可以将此聊天历史记录和序列化函数发送到模型。 模型将使用此信息来确定响应的最佳方法。
IChatCompletionService chatCompletion = kernel.GetRequiredService<IChatCompletionService>();
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
ChatResponse response = await chatCompletion.GetChatMessageContentAsync(
chatHistory,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel)
chat_completion = kernel.get_service(type=ChatCompletionClientBase)
execution_settings = AzureChatPromptExecutionSettings()
execution_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
response = (await chat_completion.get_chat_message_contents(
chat_history=history,
settings=execution_settings,
kernel=kernel,
arguments=KernelArguments(),
))[0]
ChatCompletionService chatCompletion = kernel.getService(I)ChatCompletionService.class);
InvocationContext invocationContext = InvocationContext.builder()
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false));
List<ChatResponse> responses = chatCompletion.getChatMessageContentsAsync(
chatHistory,
kernel,
invocationContext).block();
注意
此示例使用 FunctionChoiceBehavior.Auto()
行为,即少数可用的行为之一。 有关其他函数选择行为的详细信息,请查看 函数选择行为一文。
3) 模型处理输入
借助聊天历史记录和序列化函数,模型可以确定响应的最佳方法。 在这种情况下,模型可识别用户想要订购披萨。 该模型可能 想要 调用 add_pizza_to_cart
函数,但由于我们指定了大小和顶端作为所需参数,因此模型将向用户请求此信息:
Console.WriteLine(response);
chatHistory.AddAssistantMessage(response);
// "Before I can add a pizza to your cart, I need to
// know the size and toppings. What size pizza would
// you like? Small, medium, or large?"
print(response)
chat_history.add_assistant_message(response)
# "Before I can add a pizza to your cart, I need to
# know the size and toppings. What size pizza would
# you like? Small, medium, or large?"
responses.forEach(response -> System.out.printlin(response.getContent());
chatHistory.addAll(responses);
// "Before I can add a pizza to your cart, I need to
// know the size and toppings. What size pizza would
// you like? Small, medium, or large?"
由于模型希望用户下次响应,因此终止信号将发送到语义内核,以停止自动调用函数,直到用户响应。
此时,用户可以响应他们想要订购的披萨的大小和配料:
chatHistory.AddUserMessage("I'd like a medium pizza with cheese and pepperoni, please.");
response = await chatCompletion.GetChatMessageContentAsync(
chatHistory,
kernel: kernel)
chat_history.add_user_message("I'd like a medium pizza with cheese and pepperoni, please.")
response = (await chat_completion.get_chat_message_contents(
chat_history=history,
settings=execution_settings,
kernel=kernel,
arguments=KernelArguments(),
))[0]
chatHistory.addUserMessage("I'd like a medium pizza with cheese and pepperoni, please.");
responses = chatCompletion.GetChatMessageContentAsync(
chatHistory,
kernel,
null).block();
模型具有所需的信息后,现在可以使用用户的输入调用 add_pizza_to_cart
函数。 在后台,它会向聊天历史记录添加新消息,如下所示:
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "OrderPizzaPlugin-add_pizza_to_cart",
"arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
}
}
]
提示
请务必记住,所需的每个参数都必须由模型生成。 这意味着用于生成响应的支出令牌。 避免需要许多令牌(如 GUID)的参数。 例如,请注意,我们使用一个int
用于 .pizzaId
要求模型发送一到两位数的数字比要求 GUID 要容易得多。
重要
此步骤使函数调用如此强大。 以前,AI 应用开发人员必须创建单独的流程来提取意向和槽填充函数。 通过函数调用,模型可以决定 何时 调用函数以及 要提供的信息 。
4) 处理响应
当语义内核从模型接收响应时,它会检查响应是否为函数调用。 如果是,语义内核将提取函数名称及其参数。 在这种情况下,函数名称为 OrderPizzaPlugin-add_pizza_to_cart
,参数是披萨的大小和顶端。
利用此信息,语义内核可以将输入封送到add_pizza_to_cart
相应的类型中,并将其传递给函数。OrderPizzaPlugin
在此示例中,参数源自 JSON 字符串,但由语义内核反序列化为 PizzaSize
枚举和 List<PizzaToppings>
枚举。
注意
将输入封送到正确的类型是使用语义内核的主要优势之一。 模型的所有内容都作为 JSON 对象传入,但语义内核可以自动将这些对象反序列化为函数的正确类型。
封送输入后,语义内核还可以将函数调用添加到聊天历史记录:
chatHistory.Add(
new() {
Role = AuthorRole.Assistant,
Items = [
new FunctionCallContent(
functionName: "add_pizza_to_cart",
pluginName: "OrderPizza",
id: "call_abc123",
arguments: new () { {"size", "Medium"}, {"toppings", ["Cheese", "Pepperoni"]} }
)
]
}
);
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent
from semantic_kernel.contents.utils.author_role import AuthorRole
chat_history.add_message(
ChatMessageContent(
role=AuthorRole.ASSISTANT,
items=[
FunctionCallContent(
name="OrderPizza-add_pizza_to_cart",
id="call_abc123",
arguments=str({"size": "Medium", "toppings": ["Cheese", "Pepperoni"]})
)
]
)
)
当自动调用工具调用行为为 false 时,Java 的语义内核处理调用方式与 C# 和 Python 不同的函数。 不向聊天历史记录添加函数调用内容;相反,应用程序负责调用函数调用。 跳到下一部分“调用函数”,例如在自动调用为 false 时处理 Java 中的函数调用。
5) 调用函数
语义内核具有正确的类型后,它最终可以调用函数 add_pizza_to_cart
。 由于插件使用依赖项注入,因此该函数可以与外部服务交互,并pizzaService
userContext
向其购物车添加披萨。
但是,并非所有函数都会成功。 如果函数失败,语义内核可以处理错误,并为模型提供默认响应。 这样,模型就可以了解出了什么问题,并生成对用户的响应。
提示
为了确保模型可以自行更正,请务必提供错误消息,明确传达出错误的内容以及如何修复错误。 这有助于模型使用正确的信息重试函数调用。
注意
语义内核默认自动调用函数。 但是,如果想要手动管理函数调用,则可以启用手动函数调用模式。 有关如何执行此操作的更多详细信息,请参阅 函数调用文章。
6) 返回函数结果
调用函数后,函数结果作为聊天历史记录的一部分发送回模型。 这允许模型了解会话的上下文并生成后续响应。
在后台,语义内核从工具角色向聊天历史记录添加新消息,如下所示:
chatHistory.Add(
new() {
Role = AuthorRole.Tool,
Items = [
new FunctionResultContent(
functionName: "add_pizza_to_cart",
pluginName: "OrderPizza",
id: "0001",
result: "{ \"new_items\": [ { \"id\": 1, \"size\": \"Medium\", \"toppings\": [\"Cheese\",\"Pepperoni\"] } ] }"
)
]
}
);
from semantic_kernel.contents import ChatMessageContent, FunctionResultContent
from semantic_kernel.contents.utils.author_role import AuthorRole
chat_history.add_message(
ChatMessageContent(
role=AuthorRole.TOOL,
items=[
FunctionResultContent(
name="OrderPizza-add_pizza_to_cart",
id="0001",
result="{ \"new_items\": [ { \"id\": 1, \"size\": \"Medium\", \"toppings\": [\"Cheese\",\"Pepperoni\"] } ] }"
)
]
)
)
如果在工具调用行为中禁用了自动调用,Java 应用程序必须调用函数调用并将函数结果作为 AuthorRole.TOOL
消息添加到聊天历史记录。
messages.stream()
.filter (it -> it instanceof OpenAIChatMessageContent)
.map(it -> ((OpenAIChatMessageContent<?>) it).getToolCall())
.flatMap(List::stream)
.forEach(toolCall -> {
String content;
try {
// getFunction will throw an exception if the function is not found
var fn = kernel.getFunction(toolCall.getPluginName(),
toolCall.getFunctionName());
FunctionResult<?> fnResult = fn
.invokeAsync(kernel, toolCall.getArguments(), null, null).block();
content = (String) fnResult.getResult();
} catch (IllegalArgumentException e) {
content = "Unable to find function. Please try again!";
}
chatHistory.addMessage(
AuthorRole.TOOL,
content,
StandardCharsets.UTF_8,
FunctionResultMetadata.build(toolCall.getId()));
});
请注意,结果是模型随后需要处理的 JSON 字符串。 与以前一样,模型需要花费使用此信息的令牌。 这就是为什么尽可能简单地保留返回类型很重要。 在这种情况下,返回仅包括添加到购物车的新项目,而不是整个购物车。
提示
尽可能简洁地处理您的退货。 在可能的情况下,只有在返回模型需要的信息,或者在返回之前使用另一个 LLM 提示汇总信息。
重复步骤 2-6
将结果返回到模型后,该过程将重复。 模型处理最新的聊天历史记录并生成响应。 在这种情况下,模型可能会询问用户是否想要将另一个披萨添加到购物车,或者是否要签出。
并行函数调用
在上面的示例中,我们演示了 LLM 如何调用单个函数。 如果需要按顺序调用多个函数,这通常是缓慢的。 为了加快此过程,多个 LLM 支持并行函数调用。 这样,LLM 就可以一次性调用多个函数,从而加快进程速度。
例如,如果用户想要订购多个披萨,LLM 可以同时为每个披萨调用 add_pizza_to_cart
函数。 这可以显著减少到 LLM 的往返次数,并加快排序过程。
后续步骤
了解函数调用的工作原理后,可以继续了解如何通过参考 函数选择行为文章来配置函数调用的各个方面,以便更好地与特定方案相对应