Funktionssamtal med chatten klar
Den mest kraftfulla funktionen i chatten är möjligheten att anropa funktioner från modellen. På så sätt kan du skapa en chattrobot som kan interagera med din befintliga kod, vilket gör det möjligt att automatisera affärsprocesser, skapa kodfragment med mera.
Med Semantic Kernel förenklar vi processen med att använda funktionsanrop genom att automatiskt beskriva dina funktioner och deras parametrar för modellen och sedan hantera kommunikationen fram och tillbaka mellan modellen och koden.
När du använder funktionsanrop är det dock bra att förstå vad som faktiskt händer i bakgrunden så att du kan optimera koden och få ut mesta möjliga av den här funktionen.
Så här fungerar funktionsanrop
När du gör en begäran till en modell med funktionsanrop aktiverat utför Semantic Kernel följande steg:
Steg | Description | |
---|---|---|
1 | Serialisera funktioner | Alla tillgängliga funktioner (och dess indataparametrar) i kerneln serialiseras med JSON-schema. |
2 | Skicka meddelanden och funktioner till modellen | De serialiserade funktionerna (och den aktuella chatthistoriken) skickas till modellen som en del av indata. |
3 | Modellen bearbetar indata | Modellen bearbetar indata och genererar ett svar. Svaret kan antingen vara ett chattmeddelande eller ett funktionsanrop |
4 | Hantera svaret | Om svaret är ett chattmeddelande returneras det till utvecklaren för att skriva ut svaret på skärmen. Om svaret är ett funktionsanrop extraherar semantisk kernel funktionsnamnet och dess parametrar. |
5 | Anropa funktionen | Det extraherade funktionsnamnet och parametrarna används för att anropa funktionen i kerneln. |
6 | Returnera funktionsresultatet | Resultatet av funktionen skickas sedan tillbaka till modellen som en del av chatthistoriken. Steg 2–6 upprepas sedan tills modellen skickar en avslutningssignal |
Följande diagram illustrerar processen för funktionsanrop:
I följande avsnitt används ett konkret exempel för att illustrera hur funktionsanrop fungerar i praktiken.
Exempel: Beställa en pizza
Anta att du har ett plugin-program som gör att en användare kan beställa en pizza. Plugin-programmet har följande funktioner:
get_pizza_menu
: Returnerar en lista över tillgängliga pizzoradd_pizza_to_cart
: Lägger till en pizza i användarens kundvagnremove_pizza_from_cart
: Tar bort en pizza från användarens kundvagnget_pizza_from_cart
: Returnerar den specifika informationen om en pizza i användarens kundvagnget_cart
: Returnerar användarens aktuella kundvagncheckout
: Checkar ut användarens kundvagn
I C# kan plugin-programmet se ut så här:
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);
}
}
Du skulle sedan lägga till det här plugin-programmet i kerneln så här:
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();
I Python kan plugin-programmet se ut så här:
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)
Du skulle sedan lägga till det här plugin-programmet i kerneln så här:
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")
I Java kan plugin-programmet se ut så här:
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));
}
}
Du skulle sedan lägga till det här plugin-programmet i kerneln så här:
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) Serialisera funktionerna
När du skapar en kernel med OrderPizzaPlugin
serialiserar kerneln automatiskt funktionerna och deras parametrar. Detta är nödvändigt så att modellen kan förstå funktionerna och deras indata.
För plugin-programmet ovan ser serialiserade funktioner ut så här:
[
{
"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": []
}
}
}
]
Det finns några saker att notera här som kan påverka både prestanda och kvaliteten på chattens slutförande:
Funktionsschemats utförlighet – Serialiseringsfunktioner för modellen som ska användas är inte kostnadsfria. Ju mer utförligt schemat är, desto fler token måste modellen bearbeta, vilket kan göra svarstiden långsammare och öka kostnaderna.
Dricks
Håll dina funktioner så enkla som möjligt. I exemplet ovan ser du att inte alla funktioner har beskrivningar där funktionsnamnet är självförklarande. Detta är avsiktligt för att minska antalet token. Parametrarna hålls också enkla. något som modellen inte behöver veta (som
cartId
ellerpaymentId
) hålls dolda. Den här informationen tillhandahålls i stället av interna tjänster.Kommentar
Det enda du inte behöver oroa dig för är komplexiteten hos returtyperna. Du ser att returtyperna inte serialiseras i schemat. Det beror på att modellen inte behöver känna till returtypen för att generera ett svar. I steg 6 får vi dock se hur alltför utförliga returtyper kan påverka kvaliteten på chattens slutförande.
Parametertyper – Med schemat kan du ange typ av varje parameter. Detta är viktigt för att modellen ska förstå förväntade indata. I exemplet ovan är parametern
size
en uppräkning och parameterntoppings
är en matris med uppräkningar. Detta hjälper modellen att generera mer exakta svar.Dricks
Undvik, där det är möjligt, att använda
string
som parametertyp. Modellen kan inte härleda typen av sträng, vilket kan leda till tvetydiga svar. Använd i stället uppräkningar eller andra typer (t.ex. ,int
float
, och komplexa typer) där det är möjligt.Obligatoriska parametrar – Du kan också ange vilka parametrar som krävs. Detta är viktigt för att modellen ska förstå vilka parametrar som faktiskt krävs för att funktionen ska fungera. Senare i steg 3 använder modellen den här informationen för att tillhandahålla så minimal information som behövs för att anropa funktionen.
Dricks
Markera endast parametrar som krävs om de faktiskt krävs. Detta hjälper modellen att anropa funktioner snabbare och mer exakt.
Funktionsbeskrivningar – Funktionsbeskrivningar är valfria men kan hjälpa modellen att generera mer exakta svar. I synnerhet kan beskrivningar tala om för modellen vad som förväntas av svaret eftersom returtypen inte serialiseras i schemat. Om modellen använder funktioner felaktigt kan du också lägga till beskrivningar för att ge exempel och vägledning.
I
get_pizza_from_cart
funktionen instruerar beskrivningen till exempel användaren att använda den här funktionen i stället för att förlita sig på tidigare meddelanden. Detta är viktigt eftersom kundvagnen kan ha ändrats sedan det senaste meddelandet.Dricks
Innan du lägger till en beskrivning kan du fråga dig själv om modellen behöver den här informationen för att generera ett svar. Om inte kan du överväga att utelämna det för att minska verbositeten. Du kan alltid lägga till beskrivningar senare om modellen har svårt att använda funktionen korrekt.
Plugin-namn – Som du kan se i de serialiserade funktionerna har varje funktion en
name
egenskap. Semantisk kernel använder plugin-namnet för att namnge funktionerna. Detta är viktigt eftersom du kan ha flera plugin-program med funktioner med samma namn. Du kan till exempel ha plugin-program för flera söktjänster, var och en med sin egensearch
funktion. Genom att namnge funktionerna kan du undvika konflikter och göra det enklare för modellen att förstå vilken funktion som ska anropas.Om du vet detta bör du välja ett plugin-namn som är unikt och beskrivande. I exemplet ovan är
OrderPizza
plugin-namnet . Detta gör det tydligt att funktionerna är relaterade till att beställa pizza.Dricks
När du väljer ett plugin-namn rekommenderar vi att du tar bort överflödiga ord som "plugin" eller "service". Detta hjälper till att minska verbositeten och gör plugin-namnet lättare att förstå för modellen.
2) Skicka meddelanden och funktioner till modellen
När funktionerna har serialiserats skickas de till modellen tillsammans med den aktuella chatthistoriken. På så sätt kan modellen förstå kontexten för konversationen och de tillgängliga funktionerna.
I det här scenariot kan vi föreställa oss att användaren ber assistenten att lägga till en pizza i kundvagnen:
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!");
Vi kan sedan skicka den här chatthistoriken och de serialiserade funktionerna till modellen. Modellen använder den här informationen för att fastställa det bästa sättet att svara.
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();
Kommentar
I det FunctionChoiceBehavior.Auto()
här exemplet används beteendet, en av de få tillgängliga. Mer information om andra funktionsvalsbeteenden finns i artikeln funktionsvalsbeteenden.
3) Modellen bearbetar indata
Med både chatthistoriken och de serialiserade funktionerna kan modellen bestämma det bästa sättet att svara. I det här fallet identifierar modellen att användaren vill beställa en pizza. Modellen skulle förmodligen vilja anropa add_pizza_to_cart
funktionen, men eftersom vi angav storleken och toppingarna som obligatoriska parametrar ber modellen användaren om den här informationen:
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?"
Eftersom modellen vill att användaren ska svara härnäst skickas en avslutningssignal till semantisk kernel för att stoppa automatiska funktionsanrop tills användaren svarar.
I det här läget kan användaren svara med storleken och topparna på pizzan som de vill beställa:
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();
Nu när modellen har nödvändig information kan den add_pizza_to_cart
nu anropa funktionen med användarens indata. I bakgrunden läggs ett nytt meddelande till i chatthistoriken som ser ut så här:
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "OrderPizzaPlugin-add_pizza_to_cart",
"arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
}
}
]
Dricks
Det är bra att komma ihåg att varje argument som du behöver måste genereras av modellen. Det innebär utgiftstoken för att generera svaret. Undvik argument som kräver många token (till exempel ett GUID). Observera till exempel att vi använder en int
för pizzaId
. Det är mycket enklare att be modellen att skicka ett en-till-tvåsiffrigt tal än att be om ett GUID.
Viktigt!
Det här steget är det som gör funktionsanropet så kraftfullt. Tidigare var AI-apputvecklare tvungna att skapa separata processer för att extrahera avsikts- och fackfyllningsfunktioner. Med funktionsanrop kan modellen bestämma när en funktion ska anropas och vilken information som ska tillhandahållas.
4) Hantera svaret
När Semantic Kernel tar emot svaret från modellen kontrollerar den om svaret är ett funktionsanrop. I så fall extraherar Semantic Kernel funktionsnamnet och dess parametrar. I det här fallet är OrderPizzaPlugin-add_pizza_to_cart
funktionsnamnet , och argumenten är pizzans storlek och toppings.
Med den här informationen kan semantisk kernel konvertera indata till lämpliga typer och skicka dem till add_pizza_to_cart
funktionen i OrderPizzaPlugin
. I det här exemplet kommer argumenten från en JSON-sträng men deserialiseras av semantisk kernel till en PizzaSize
uppräkning och en List<PizzaToppings>
.
Kommentar
Att konvertera indata till rätt typer är en av de viktigaste fördelarna med att använda semantisk kernel. Allt från modellen kommer in som ett JSON-objekt, men semantisk kernel kan automatiskt deserialisera dessa objekt till rätt typer för dina funktioner.
När du har samlat indata kan semantisk kernel också lägga till funktionsanropet i chatthistoriken:
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"]})
)
]
)
)
Semantisk kernel för Java hanterar funktionsanrop på ett annat sätt än C# och Python när det automatiska anropsbeteendet för verktygsanrop är falskt. Du lägger inte till funktionsanropsinnehåll i chatthistoriken. I stället ansvarar programmet för att anropa funktionsanropen. Hoppa till nästa avsnitt, "Invoke the function", för ett exempel på hantering av funktionsanrop i Java när automatisk anrop är falskt.
5) Anropa funktionen
När semantisk kernel har rätt typer kan den add_pizza_to_cart
slutligen anropa funktionen. Eftersom plugin-programmet använder beroendeinmatning kan funktionen interagera med externa tjänster som pizzaService
och userContext
lägga till pizzan i användarens kundvagn.
Alla funktioner kommer dock inte att lyckas. Om funktionen misslyckas kan semantisk kernel hantera felet och ge ett standardsvar till modellen. På så sätt kan modellen förstå vad som gick fel och generera ett svar till användaren.
Dricks
För att säkerställa att en modell kan korrigeras själv är det viktigt att tillhandahålla felmeddelanden som tydligt kommunicerar vad som gick fel och hur du åtgärdar det. Detta kan hjälpa modellen att försöka utföra funktionsanropet igen med rätt information.
Kommentar
Semantisk kernel anropar automatiskt funktioner som standard. Men om du föredrar att hantera funktionsanrop manuellt kan du aktivera manuellt funktionsanropsläge. Mer information om hur du gör detta finns i artikeln funktionsanrop.
6) Returnera funktionsresultatet
När funktionen har anropats skickas funktionsresultatet tillbaka till modellen som en del av chatthistoriken. På så sätt kan modellen förstå konversationens kontext och generera ett efterföljande svar.
I bakgrunden lägger Semantic Kernel till ett nytt meddelande i chatthistoriken från verktygsrollen som ser ut så här:
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\"] } ] }"
)
]
)
)
Om automatisk anrop är inaktiverat i verktygets anropsbeteende måste ett Java-program anropa funktionsanropen och lägga till funktionsresultatet som ett AuthorRole.TOOL
meddelande i chatthistoriken.
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()));
});
Observera att resultatet är en JSON-sträng som modellen sedan behöver bearbeta. Precis som tidigare måste modellen spendera token som använder den här informationen. Därför är det viktigt att hålla returtyperna så enkla som möjligt. I det här fallet innehåller returen endast de nya objekt som lagts till i kundvagnen, inte hela kundvagnen.
Dricks
Var så kortfattad som möjligt med dina returer. Om möjligt returnerar du bara den information som modellen behöver eller sammanfattar informationen med hjälp av en annan LLM-prompt innan den returneras.
Upprepa steg 2–6
När resultatet har returnerats till modellen upprepas processen. Modellen bearbetar den senaste chatthistoriken och genererar ett svar. I det här fallet kan modellen fråga användaren om de vill lägga till ytterligare en pizza i kundvagnen eller om de vill checka ut.
Parallella funktionsanrop
I exemplet ovan visade vi hur en LLM kan anropa en enda funktion. Detta kan ofta vara långsamt om du behöver anropa flera funktioner i följd. För att påskynda processen stöder flera LLM:er parallella funktionsanrop. Detta gör att LLM kan anropa flera funktioner samtidigt, vilket påskyndar processen.
Om en användare till exempel vill beställa flera pizzor kan LLM anropa add_pizza_to_cart
funktionen för varje pizza samtidigt. Detta kan avsevärt minska antalet tur- och returresor till LLM och påskynda beställningsprocessen.
Nästa steg
Nu när du förstår hur funktionsanrop fungerar kan du fortsätta att lära dig hur du konfigurerar olika aspekter av funktionsanrop som bättre motsvarar dina specifika scenarier genom att referera till artikeln funktionsvalsbeteende