通过聊天完成进行函数调用

聊天完成功能最强大的功能是能够从模型调用函数。 这样,便可以创建可与现有代码交互的聊天机器人,从而可以自动化业务流程、创建代码片段等。

借助语义内核,我们通过向模型自动描述函数及其参数,然后处理模型与代码之间的来回通信,从而简化使用函数调用的过程。

但是,使用函数调用时,最好了解后台实际发生的情况,以便优化代码并充分利用此功能。

函数调用的工作原理

向启用了函数调用的模型发出请求时,语义内核将执行以下步骤:

步骤 说明
1 序列化函数 内核中的所有可用函数(及其输入参数)都使用 JSON 架构进行序列化。
2 将消息和函数发送到模型 序列化的函数(和当前聊天历史记录)作为输入的一部分发送到模型。
3 模型处理输入 模型处理输入并生成响应。 响应可以是聊天消息或函数调用
4 处理响应 如果响应是聊天消息,则会将响应返回到开发人员,以便将响应打印到屏幕。 但是,如果响应是函数调用,则语义内核将提取函数名称和其参数。
5 调用函数 提取的函数名称和参数用于调用内核中的函数。
6 返回函数结果 然后,函数的结果作为聊天历史记录的一部分发送回模型。 然后重复步骤 2-6,直到模型发送终止信号

下图演示了函数调用的过程:

语义内核函数调用

以下部分将使用具体示例来说明函数调用在实践中的工作原理。

示例:订购披萨

假设你有一个插件,允许用户订购披萨。 该插件具有以下功能:

  1. get_pizza_menu:返回可用披萨的列表
  2. add_pizza_to_cart:将披萨添加到用户的购物车
  3. remove_pizza_from_cart:从用户的购物车中删除披萨
  4. get_pizza_from_cart:返回用户购物车中披萨的特定详细信息
  5. get_cart:返回用户的当前购物车
  6. 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": []
      }
    }
  }
]

此处需要注意的一些事项可能会影响聊天完成的性能和质量:

  1. 函数架构 的详细程度 – 序列化模型要使用的函数不会免费。 架构越详细,模型必须处理的令牌越多,这可能会降低响应时间并增加成本。

    提示

    尽可能简单地保留函数。 在上面的示例中,你会注意到,并非所有函数都有说明,其中函数名称是自我解释的。 这是有意减少令牌数。 参数也保持简单;模型不需要知道的任何内容(如 cartIdpaymentId)都隐藏。 此信息由内部服务提供。

    注意

    不需要担心的一件事是返回类型的复杂性。 你会注意到,返回类型未在架构中序列化。 这是因为模型不需要知道返回类型来生成响应。 但是,在步骤 6 中,我们将了解详细返回类型如何影响聊天完成的质量。

  2. 参数类型 – 使用架构,可以指定每个参数的类型。 对于模型了解预期的输入,这一点非常重要。 在上面的示例中,参数 size 是枚举,参数 toppings 是枚举数组。 这有助于模型生成更准确的响应。

    提示

    尽可能避免用作 string 参数类型。 模型无法推断字符串的类型,这可能会导致不明确的响应。 请尽可能使用枚举或其他类型(例如, intfloat和复杂类型)。

  3. 必需参数 - 还可以指定需要哪些参数。 这对于模型了解函数实际需要哪些参数非常重要。 稍后在步骤 3 中,模型将使用此信息来尽可能少地提供调用函数所需的信息。

    提示

    仅当实际需要参数时,才将其标记为必需。 这有助于模型调用函数更快、更准确。

  4. 函数说明 – 函数说明 是可选的,但可以帮助模型生成更准确的响应。 具体而言,说明可以告知模型响应预期,因为返回类型未在架构中序列化。 如果模型未正确使用函数,还可以添加说明来提供示例和指南。

    例如,在函数中 get_pizza_from_cart ,说明告知用户使用此函数,而不是依赖以前的消息。 这一点很重要,因为购物车可能自上一封邮件以来发生了更改。

    提示

    在添加说明之前,请询问模型是否需要此信息来生成响应。 如果没有,请考虑将其排除在外以减少详细程度。 如果模型难以正确使用函数,则以后始终可以添加说明。

  5. 插件名称 – 正如在序列化函数中看到的那样,每个函数都有一个 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 。 由于插件使用依赖项注入,因此该函数可以与外部服务交互,并pizzaServiceuserContext向其购物车添加披萨。

但是,并非所有函数都会成功。 如果函数失败,语义内核可以处理错误,并为模型提供默认响应。 这样,模型就可以了解出了什么问题,并生成对用户的响应。

提示

为了确保模型可以自行更正,请务必提供错误消息,明确传达出错误的内容以及如何修复错误。 这有助于模型使用正确的信息重试函数调用。

注意

语义内核默认自动调用函数。 但是,如果想要手动管理函数调用,则可以启用手动函数调用模式。 有关如何执行此操作的更多详细信息,请参阅 函数调用文章

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 的往返次数,并加快排序过程。

后续步骤

了解函数调用的工作原理后,可以继续了解如何通过参考 函数选择行为文章来配置函数调用的各个方面,以便更好地与特定方案相对应