使用聊天完成呼叫函式
聊天完成功能最強大的功能是從模型呼叫函式的能力。 這可讓您建立可與現有程式代碼互動的聊天機器人,讓商務程式自動化、建立代碼段等等。
透過 Semantic Kernel,我們藉由自動將函式及其參數描述至模型,然後處理模型與程式碼之間的來回通訊,來簡化使用函式呼叫的程式。
不過,使用函式呼叫時,最好瞭解幕後實際發生的情況,以便您將程式代碼優化並充分利用此功能。
函式呼叫的運作方式
當您對已啟用函式呼叫的模型提出要求時,Semantic Kernel 會執行下列步驟:
步驟 | 描述: | |
---|---|---|
1 | 串行化函式 | 核心中的所有可用函式(及其輸入參數)都會使用 JSON 架構串行化。 |
2 | 將訊息和函式傳送至模型 | 串行化函式(和目前的聊天記錄)會以輸入的一部分傳送至模型。 |
3 | 模型會處理輸入 | 模型會處理輸入併產生回應。 回應可以是聊天訊息或函式呼叫 |
4 | 處理回應 | 如果回應是聊天訊息,則會傳回給開發人員,以將回應列印到畫面。 不過,如果回應是函數調用,Semantic Kernel 會擷取函式名稱和其參數。 |
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
。 這清楚說明函式與訂購披薩有關。提示
選擇外掛程式名稱時,建議您移除「外掛程式」或「服務」等多餘的字組。 這有助於減少詳細資訊,並讓外掛程式名稱更容易瞭解模型。
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?"
由於模型希望使用者下一步回應,因此終止訊號會傳送至 Semantic Kernel,以停止自動呼叫函式,直到使用者響應為止。
此時,使用者可以回應他們想要訂購的披薩大小和配料:
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) 處理回應
當 Semantic Kernel 收到來自模型的回應時,它會檢查回應是否為函式呼叫。 如果是,語意核心會擷取函式名稱和其參數。 在此情況下,函式名稱為 OrderPizzaPlugin-add_pizza_to_cart
,而自變數是披薩的大小和配料。
利用這項資訊,Semantic Kernel 可以將輸入封送處理至適當的類型,並將其傳遞至 中的OrderPizzaPlugin
函add_pizza_to_cart
式。 在這裡範例中,自變數會以 JSON 字串的形式產生,但由 Semantic Kernel 還原串行化為 PizzaSize
列舉和 List<PizzaToppings>
。
注意
將輸入封送處理到正確的類型是使用語意核心的主要優點之一。 模型的所有專案都以 JSON 物件的形式出現,但 Semantic Kernel 可以自動將這些物件還原串行化為函式的正確類型。
封送處理輸入之後,Semantic Kernel 也可以將函式呼叫新增至聊天記錄:
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"]})
)
]
)
)
Java 的語意核心會在自動叫用工具呼叫行為為 false 時處理與 C# 和 Python 不同的函式。 您不會將函數調用內容新增至聊天記錄;相反地,應用程式會負責叫用函式呼叫。 跳至下一節「叫用函式」,以在自動叫用為 false 時處理 Java 中的函式呼叫範例。
5) 叫用函式
一旦 Semantic Kernel 具有正確的類型,它最後就可以叫用函 add_pizza_to_cart
式。 因為外掛程式使用相依性插入,因此函式可以與外部服務互動,例如 pizzaService
,並將 userContext
披薩新增至使用者的購物車。
不過,並非所有函式都會成功。 如果函式失敗,語意核心可以處理錯誤,並提供模型的默認回應。 這可讓模型了解發生錯誤,併產生對用戶的回應。
提示
為了確保模型可以自行更正,請務必提供錯誤訊息,以清楚傳達發生錯誤的內容,以及如何修正錯誤。 這可協助模型使用正確的資訊重試函式呼叫。
注意
語意核心預設會自動叫用函式。 不過,如果您想要手動管理函式調用,您可以啟用手動函式調用模式。 如需如何執行這項操作的詳細資訊,請參閱函 式調用一文。
6) 傳回函式結果
叫用函式之後,函式結果會傳回至模型作為聊天記錄的一部分。 這可讓模型瞭解交談的內容,併產生後續的回應。
在幕後,Semantic Kernel 會從工具角色將新訊息新增至聊天記錄,如下所示:
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 往返次數,並加快訂購程式。
下一步
既然您已瞭解函式呼叫的運作方式,您可以繼續瞭解如何透過參考 函式選擇行為一文,設定函式呼叫的各種層面,以更妥善地對應至您的特定案例