Functie aanroepen met voltooiing van chat
De krachtigste functie van chatvoltooiing is de mogelijkheid om functies van het model aan te roepen. Hiermee kunt u een chatbot maken die kan communiceren met uw bestaande code, zodat u bedrijfsprocessen kunt automatiseren, codefragmenten kunt maken en meer.
Met Semantische kernel vereenvoudigen we het proces van het aanroepen van functies door uw functies en hun parameters automatisch aan het model te beschrijven en vervolgens de back-and-forth communicatie tussen het model en uw code af te handelen.
Wanneer u functie aanroepen gebruikt, is het echter goed om te begrijpen wat er achter de schermen gebeurt , zodat u uw code kunt optimaliseren en optimaal gebruik kunt maken van deze functie.
Hoe functie aanroepen werkt
Wanneer u een aanvraag indient bij een model waarvoor functie aanroepen is ingeschakeld, voert Semantische Kernel de volgende stappen uit:
Stap | Omschrijving | |
---|---|---|
1 | Functies serialiseren | Alle beschikbare functies (en de invoerparameters) in de kernel worden geserialiseerd met behulp van het JSON-schema. |
2 | De berichten en functies naar het model verzenden | De geserialiseerde functies (en de huidige chatgeschiedenis) worden als onderdeel van de invoer naar het model verzonden. |
3 | Model verwerkt de invoer | Het model verwerkt de invoer en genereert een antwoord. Het antwoord kan een chatbericht of een functieoproep zijn |
4 | Het antwoord verwerken | Als het antwoord een chatbericht is, wordt het teruggezet naar de ontwikkelaar om het antwoord af te drukken op het scherm. Als het antwoord een functie-aanroep is, extraheert Semantische Kernel echter de functienaam en de bijbehorende parameters. |
5 | De functie aanroepen | De geëxtraheerde functienaam en -parameters worden gebruikt om de functie in de kernel aan te roepen. |
6 | Het resultaat van de functie retourneren | Het resultaat van de functie wordt vervolgens teruggestuurd naar het model als onderdeel van de chatgeschiedenis. Stap 2-6 wordt vervolgens herhaald totdat het model een beëindigingssignaal verzendt |
In het volgende diagram ziet u het proces van het aanroepen van functies:
In de volgende sectie wordt een concreet voorbeeld gebruikt om te laten zien hoe functie-aanroepen in de praktijk werkt.
Voorbeeld: Een pizza bestellen
Stel dat u een plugin hebt waarmee een gebruiker een pizza kan bestellen. De invoegtoepassing heeft de volgende functies:
get_pizza_menu
: retourneert een lijst met beschikbare pizza'sadd_pizza_to_cart
: Voegt een pizza toe aan de winkelwagen van de gebruikerremove_pizza_from_cart
: Verwijdert een pizza uit de winkelwagen van de gebruikerget_pizza_from_cart
: retourneert de specifieke details van een pizza in de winkelwagen van de gebruikerget_cart
: retourneert de huidige winkelwagen van de gebruikercheckout
: Controleert de winkelwagen van de gebruiker
In C# kan de invoegtoepassing er als volgt uitzien:
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);
}
}
Vervolgens voegt u deze invoegtoepassing als volgt toe aan de kernel:
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();
In Python kan de invoegtoepassing er als volgt uitzien:
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)
Vervolgens voegt u deze invoegtoepassing als volgt toe aan de kernel:
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")
In Java kan de invoegtoepassing er als volgt uitzien:
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));
}
}
Vervolgens voegt u deze invoegtoepassing als volgt toe aan de kernel:
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) De functies serialiseren
Wanneer u een kernel maakt met de OrderPizzaPlugin
kernel, worden de functies en de bijbehorende parameters automatisch geserialiseerd. Dit is nodig zodat het model inzicht kan hebben in de functies en de bijbehorende invoer.
Voor de bovenstaande invoegtoepassing ziet de geserialiseerde functies er als volgt uit:
[
{
"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": []
}
}
}
]
Hier ziet u enkele dingen die van invloed kunnen zijn op zowel de prestaties als de kwaliteit van de voltooiing van de chat:
Uitgebreidheid van functieschema : het serialiseren van functies voor het te gebruiken model komt niet gratis. Hoe uitgebreider het schema, hoe meer tokens het model moet verwerken, wat de reactietijd kan vertragen en de kosten kan verhogen.
Tip
Houd uw functies zo eenvoudig mogelijk. In het bovenstaande voorbeeld ziet u dat niet alle functies beschrijvingen hebben waarin de naam van de functie duidelijk is. Dit is opzettelijk om het aantal tokens te verminderen. De parameters worden ook eenvoudig gehouden; alles wat het model niet hoeft te weten (zoals de
cartId
ofpaymentId
) wordt verborgen. Deze informatie wordt in plaats daarvan verstrekt door interne services.Notitie
Het enige waar u zich geen zorgen over hoeft te maken, is de complexiteit van de retourtypen. U ziet dat de retourtypen niet in het schema worden geserialiseerd. Dit komt doordat het model het retourtype niet hoeft te kennen om een antwoord te genereren. In stap 6 zien we echter hoe uitgebreide retourtypen van invloed kunnen zijn op de kwaliteit van de voltooiing van de chat.
Parametertypen : met het schema kunt u het type van elke parameter opgeven. Dit is belangrijk voor het model om inzicht te hebben in de verwachte invoer. In het bovenstaande voorbeeld is de
size
parameter een enum en detoppings
parameter is een matrix met opsommingen. Dit helpt het model nauwkeurigere antwoorden te genereren.Tip
Vermijd, indien mogelijk, het gebruik
string
als parametertype. Het model kan het type tekenreeks niet afleiden, wat kan leiden tot dubbelzinnige antwoorden. Gebruik in plaats daarvan waar mogelijk opsommingen of andere typen (bijvoorbeeldint
,float
en complexe typen).Vereiste parameters : u kunt ook opgeven welke parameters vereist zijn. Dit is belangrijk voor het model om te begrijpen welke parameters daadwerkelijk nodig zijn om de functie te laten werken. Verderop in stap 3 gebruikt het model deze informatie om zo min mogelijk informatie op te geven om de functie aan te roepen.
Tip
Markeer parameters alleen zoals vereist als ze daadwerkelijk vereist zijn. Hierdoor kunnen de modeloproepfuncties sneller en nauwkeuriger worden aangeroepen.
Functiebeschrijvingen: functiebeschrijvingen zijn optioneel, maar kunnen het model helpen nauwkeurigere antwoorden te genereren. Beschrijvingen kunnen met name het model vertellen wat u van het antwoord kunt verwachten, omdat het retourtype niet in het schema wordt geserialiseerd. Als het model functies onjuist gebruikt, kunt u ook beschrijvingen toevoegen om voorbeelden en richtlijnen te bieden.
In de
get_pizza_from_cart
functie geeft de beschrijving de gebruiker bijvoorbeeld de opdracht om deze functie te gebruiken in plaats van te vertrouwen op eerdere berichten. Dit is belangrijk omdat de winkelwagen mogelijk is gewijzigd sinds het laatste bericht.Tip
Voordat u een beschrijving toevoegt, vraagt u zich af of dit model deze informatie nodig heeft om een antwoord te genereren. Zo niet, overweeg het weg te laten om de uitgebreidheid te verminderen. U kunt later altijd beschrijvingen toevoegen als het model moeite heeft om de functie goed te gebruiken.
Naam van de invoegtoepassing: zoals u kunt zien in de geserialiseerde functies, heeft elke functie een
name
eigenschap. Semantic Kernel gebruikt de naam van de invoegtoepassing om de functies een naam te geven. Dit is belangrijk omdat u meerdere invoegtoepassingen met functies van dezelfde naam kunt hebben. U hebt bijvoorbeeld invoegtoepassingen voor meerdere zoekservices, elk met hun eigensearch
functie. Door namen van de functies te maken, kunt u conflicten voorkomen en het model gemakkelijker laten begrijpen welke functie moet worden aangeroepen.Als u dit weet, moet u een naam van de invoegtoepassing kiezen die uniek en beschrijvend is. In het bovenstaande voorbeeld is
OrderPizza
de naam van de invoegtoepassing. Dit maakt duidelijk dat de functies betrekking hebben op het bestellen van pizza.Tip
Wanneer u een naam van de invoegtoepassing kiest, raden we u aan overbodige woorden zoals 'plugin' of 'service' te verwijderen. Dit helpt de uitgebreidheid te verminderen en maakt de naam van de invoegtoepassing gemakkelijker te begrijpen voor het model.
2) De berichten en functies verzenden naar het model
Zodra de functies zijn geserialiseerd, worden ze samen met de huidige chatgeschiedenis naar het model verzonden. Hierdoor kan het model de context van het gesprek en de beschikbare functies begrijpen.
In dit scenario kunnen we ons voorstellen dat de gebruiker de assistent vraagt om een pizza toe te voegen aan zijn winkelwagen:
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!");
Vervolgens kunnen we deze chatgeschiedenis en de geserialiseerde functies naar het model verzenden. Het model gebruikt deze informatie om te bepalen wat de beste manier is om te reageren.
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();
Notitie
In dit voorbeeld wordt het FunctionChoiceBehavior.Auto()
gedrag gebruikt, een van de weinige beschikbare. Raadpleeg het artikel over het gedrag van functiekeuzes voor meer informatie over ander gedrag van functiekeuzes.
3) Model verwerkt de invoer
Met zowel de chatgeschiedenis als de geserialiseerde functies kan het model bepalen wat de beste manier is om te reageren. In dit geval herkent het model dat de gebruiker een pizza wil bestellen. Het model zou waarschijnlijk de add_pizza_to_cart
functie willen aanroepen, maar omdat we de grootte en toppings als vereiste parameters hebben opgegeven, vraagt het model de gebruiker om deze informatie:
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?"
Omdat het model wil dat de gebruiker vervolgens reageert, wordt er een beëindigingssignaal verzonden naar Semantische kernel om automatische functieaanroepen te stoppen totdat de gebruiker reageert.
Op dit moment kan de gebruiker reageren met de grootte en toppings van de pizza die ze willen bestellen:
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 het model over de benodigde informatie beschikt, kan deze de add_pizza_to_cart
functie nu aanroepen met de invoer van de gebruiker. Achter de schermen wordt een nieuw bericht toegevoegd aan de chatgeschiedenis die er als volgt uitziet:
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "OrderPizzaPlugin-add_pizza_to_cart",
"arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
}
}
]
Tip
Het is goed om te onthouden dat elk argument dat u nodig hebt, moet worden gegenereerd door het model. Dit betekent dat er uitgaventokens worden uitgegeven om het antwoord te genereren. Vermijd argumenten waarvoor veel tokens zijn vereist (zoals een GUID). U ziet bijvoorbeeld dat we een int
voor de pizzaId
. Het vragen van het model om een een-op-tweecijferig getal te verzenden, is veel eenvoudiger dan het vragen om een GUID.
Belangrijk
Deze stap is wat het aanroepen van functies zo krachtig maakt. Voorheen moesten ontwikkelaars van AI-apps afzonderlijke processen maken om intentie- en slotopvullingsfuncties te extraheren. Met functieoproepen kan het model bepalen wanneer een functie moet worden aangeroepen en welke informatie moet worden opgegeven.
4) Het antwoord verwerken
Wanneer Semantic Kernel het antwoord van het model ontvangt, wordt gecontroleerd of het antwoord een functie-aanroep is. Als dat het is, extraheert Semantische Kernel de functienaam en de bijbehorende parameters. In dit geval is OrderPizzaPlugin-add_pizza_to_cart
de functienaam en de argumenten de grootte en toppings van de pizza.
Met deze informatie kan Semantische kernel de invoer in de juiste typen marshalen en deze doorgeven aan de add_pizza_to_cart
functie in de OrderPizzaPlugin
. In dit voorbeeld zijn de argumenten afkomstig als een JSON-tekenreeks, maar worden gedeserialiseerd door Semantische kernel in een PizzaSize
opsomming en een List<PizzaToppings>
.
Notitie
Marshaling van de invoer in de juiste typen is een van de belangrijkste voordelen van het gebruik van Semantische kernel. Alles van het model komt binnen als een JSON-object, maar Semantische kernel kan deze objecten automatisch deserialiseren in de juiste typen voor uw functies.
Na het marshallen van de invoer kan Semantic Kernel ook de functieaanroep toevoegen aan de chatgeschiedenis:
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"]})
)
]
)
)
Semantische kernel voor Java verwerkt functie die anders aanroept dan C# en Python wanneer het gedrag van het automatisch aanroepen van hulpprogramma's onwaar is. U voegt geen functieoproepinhoud toe aan de chatgeschiedenis; in plaats daarvan wordt de toepassing verantwoordelijk gemaakt voor het aanroepen van de functie. Ga naar de volgende sectie, 'De functie aanroepen', voor een voorbeeld van het afhandelen van functieaanroepen in Java wanneer automatisch aanroepen onwaar is.
5) Roep de functie aan
Zodra Semantic Kernel de juiste typen heeft, kan deze de add_pizza_to_cart
functie eindelijk aanroepen. Omdat de invoegtoepassing gebruikmaakt van afhankelijkheidsinjectie, kan de functie communiceren met externe services zoals pizzaService
en userContext
om de pizza toe te voegen aan de winkelwagen van de gebruiker.
Niet alle functies slagen echter wel. Als de functie mislukt, kan Semantische kernel de fout afhandelen en een standaardantwoord op het model opgeven. Hierdoor kan het model begrijpen wat er fout is gegaan en een reactie genereren op de gebruiker.
Tip
Om ervoor te zorgen dat een model zelf corrigerend kan zijn, is het belangrijk dat u foutberichten opgeeft die duidelijk aangeven wat er mis is gegaan en hoe u dit kunt oplossen. Dit kan het model helpen de functie-aanroep opnieuw uit te voeren met de juiste informatie.
Notitie
Semantische kernel roept standaard automatisch functies aan. Als u echter liever handmatig functie-aanroep beheert, kunt u de modus voor handmatig aanroepen van functies inschakelen. Raadpleeg het artikel over het aanroepen van functies voor meer informatie over hoe u dit doet.
6) Het resultaat van de functie retourneren
Nadat de functie is aangeroepen, wordt het functieresultaat teruggestuurd naar het model als onderdeel van de chatgeschiedenis. Hierdoor kan het model de context van het gesprek begrijpen en een volgende reactie genereren.
Achter de schermen voegt Semantische kernel een nieuw bericht toe aan de chatgeschiedenis van de functie die er als volgt uitziet:
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\"] } ] }"
)
]
)
)
Als automatisch aanroepen is uitgeschakeld in het gedrag van de toolaanroep, moet een Java-toepassing de functieaanroepen aanroepen aanroepen en het functieresultaat toevoegen als een AuthorRole.TOOL
bericht aan de chatgeschiedenis.
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()));
});
U ziet dat het resultaat een JSON-tekenreeks is die het model vervolgens moet verwerken. Net als voorheen moet het model tokens uitgeven die deze informatie gebruiken. Daarom is het belangrijk om de retourtypen zo eenvoudig mogelijk te houden. In dit geval bevat de retourzending alleen de nieuwe items die aan de winkelwagen zijn toegevoegd, niet de hele winkelwagen.
Tip
Wees zo beknopt mogelijk met uw terugkeer. Indien mogelijk moet u alleen de informatie retourneren die het model nodig heeft of de informatie samenvatten met behulp van een andere LLM-prompt voordat u deze retourneert.
Herhaal stap 2-6
Nadat het resultaat is geretourneerd naar het model, wordt het proces herhaald. Het model verwerkt de nieuwste chatgeschiedenis en genereert een antwoord. In dit geval kan het model de gebruiker vragen of ze een andere pizza aan hun winkelwagen willen toevoegen of als ze willen uitchecken.
Parallelle functie-aanroepen
In het bovenstaande voorbeeld hebben we gedemonstreerd hoe een LLM één functie kan aanroepen. Dit kan vaak traag zijn als u meerdere functies op volgorde wilt aanroepen. Om het proces te versnellen, ondersteunen verschillende LLM's parallelle functie-aanroepen. Hierdoor kan de LLM meerdere functies tegelijk aanroepen, waardoor het proces wordt versneld.
Als een gebruiker bijvoorbeeld meerdere pizza's wil bestellen, kan de LLM de add_pizza_to_cart
functie voor elke pizza tegelijkertijd aanroepen. Dit kan het aantal retouren naar de LLM aanzienlijk verminderen en het bestelproces versnellen.
Volgende stappen
Nu u begrijpt hoe functieaanroepen werken, kunt u doorgaan met het configureren van verschillende aspecten van functieaanroepen die beter overeenkomen met uw specifieke scenario's door te verwijzen naar het artikel over het gedrag van functiekeuze