Funktionsaufrufe mit Chatabschluss
Das leistungsstärkste Feature des Chatabschlusses ist die Möglichkeit, Funktionen aus dem Modell aufzurufen. Auf diese Weise können Sie einen Chat-Bot erstellen, der mit Ihrem vorhandenen Code interagieren kann, sodass Geschäftsprozesse automatisiert, Codeausschnitte erstellt und vieles mehr möglich sind.
Mit dem semantischen Kernel vereinfachen wir den Prozess der Verwendung von Funktionsaufrufen, indem Sie Ihre Funktionen und deren Parameter automatisch für das Modell beschreiben und dann die Rück- und Rückkommunikation zwischen dem Modell und Ihrem Code behandeln.
Beim Verwenden von Funktionsaufrufen ist es jedoch gut zu verstehen, was tatsächlich hinter den Kulissen passiert, damit Sie Ihren Code optimieren und dieses Feature optimal nutzen können.
Funktionsweise von Funktionsaufrufen
Wenn Sie eine Anforderung an ein Modell mit aktiviertem Funktionsaufruf stellen, führt semantischer Kernel die folgenden Schritte aus:
Schritt | Beschreibung | |
---|---|---|
1 | Serialisieren von Funktionen | Alle verfügbaren Funktionen (und ihre Eingabeparameter) im Kernel werden mithilfe des JSON-Schemas serialisiert. |
2 | Senden der Nachrichten und Funktionen an das Modell | Die serialisierten Funktionen (und der aktuelle Chatverlauf) werden als Teil der Eingabe an das Modell gesendet. |
3 | Modell verarbeitet die Eingabe | Das Modell verarbeitet die Eingabe und generiert eine Antwort. Die Antwort kann entweder eine Chatnachricht oder ein Funktionsaufruf sein. |
4 | Behandeln der Antwort | Wenn es sich bei der Antwort um eine Chatnachricht handelt, wird sie an den Entwickler zurückgegeben, um die Antwort auf den Bildschirm zu drucken. Wenn es sich bei der Antwort jedoch um einen Funktionsaufruf handelt, extrahiert der semantische Kernel den Funktionsnamen und dessen Parameter. |
5 | Aufrufen der Funktion | Der extrahierte Funktionsname und die extrahierten Parameter werden verwendet, um die Funktion im Kernel aufzurufen. |
6 | Zurückgeben des Funktionsergebnisses | Das Ergebnis der Funktion wird dann als Teil des Chatverlaufs an das Modell zurückgesendet. Die Schritte 2-6 werden dann wiederholt, bis das Modell ein Beendigungssignal sendet. |
Das folgende Diagramm veranschaulicht den Prozess des Funktionsaufrufs:
Im folgenden Abschnitt wird ein konkretes Beispiel verwendet, um zu veranschaulichen, wie Funktionsaufrufe in der Praxis funktionieren.
Beispiel: Bestellen einer Pizza
Nehmen wir an, Sie haben ein Plug-In, mit dem ein Benutzer eine Pizza bestellen kann. Das Plug-In hat die folgenden Funktionen:
get_pizza_menu
: Gibt eine Liste der verfügbaren Pizza zurück.add_pizza_to_cart
: Fügt dem Einkaufswagen des Benutzers eine Pizza hinzu.remove_pizza_from_cart
: Entfernt eine Pizza aus dem Einkaufswagen des Benutzers.get_pizza_from_cart
: Gibt die spezifischen Details einer Pizza im Warenkorb des Benutzers zurück.get_cart
: Gibt den aktuellen Warenkorb des Benutzers zurück.checkout
: Checkt den Warenkorb des Benutzers aus
In C# sieht das Plug-In möglicherweise wie folgt aus:
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);
}
}
Anschließend fügen Sie dieses Plug-In dem Kernel wie folgt hinzu:
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 sieht das Plug-In möglicherweise wie folgt aus:
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)
Anschließend fügen Sie dieses Plug-In dem Kernel wie folgt hinzu:
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 sieht das Plug-In möglicherweise wie folgt aus:
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));
}
}
Anschließend fügen Sie dieses Plug-In dem Kernel wie folgt hinzu:
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) Serialisieren der Funktionen
Wenn Sie einen Kernel mit dem OrderPizzaPlugin
Kernel erstellen, serialisiert der Kernel automatisch die Funktionen und deren Parameter. Dies ist erforderlich, damit das Modell die Funktionen und ihre Eingaben verstehen kann.
Für das obige Plug-In würden die serialisierten Funktionen wie folgt aussehen:
[
{
"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": []
}
}
}
]
Beachten Sie hier einige Punkte, die sich sowohl auf die Leistung als auch auf die Qualität des Chatabschlusses auswirken können:
Verbosität des Funktionsschemas – Serialisieren von Funktionen für das zu verwendende Modell ist nicht kostenlos. Je ausführlicher das Schema ist, desto mehr Token muss das Modell verarbeiten, was die Reaktionszeit verlangsamen und die Kosten erhöhen kann.
Tipp
Halten Sie Ihre Funktionen so einfach wie möglich. Im obigen Beispiel werden Sie feststellen, dass nicht alle Funktionen Beschreibungen haben, bei denen der Funktionsname selbsterklärend ist. Dies ist beabsichtigt, die Anzahl der Token zu reduzieren. Die Parameter werden auch einfach gehalten; alles, was das Modell nicht wissen muss (z. B. oder
cartId
paymentId
) werden ausgeblendet. Diese Informationen werden stattdessen von internen Diensten bereitgestellt.Hinweis
Das, was Sie sich keine Sorgen machen müssen, ist die Komplexität der Rückgabetypen. Sie werden feststellen, dass die Rückgabetypen nicht im Schema serialisiert werden. Dies liegt daran, dass das Modell nicht den Rückgabetyp kennen muss, um eine Antwort zu generieren. In Schritt 6 sehen wir jedoch, wie ausführliche Rückgabetypen sich auf die Qualität des Chatabschlusses auswirken können.
Parametertypen – Mit dem Schema können Sie den Typ der einzelnen Parameter angeben. Dies ist wichtig für das Modell, um die erwartete Eingabe zu verstehen. Im obigen Beispiel ist der
size
Parameter eine Enumeration, und dertoppings
Parameter ist ein Array von Enumerationen. Dies hilft dem Modell, genauere Antworten zu generieren.Tipp
Vermeiden Sie nach Möglichkeit die Verwendung
string
als Parametertyp. Das Modell kann den Typ der Zeichenfolge nicht ableiten, was zu mehrdeutigen Antworten führen kann. Verwenden Sie stattdessen Enums oder andere Typen (z. Bint
. , ,float
und komplexe Typen), sofern möglich.Erforderliche Parameter – Sie können auch angeben, welche Parameter erforderlich sind. Dies ist wichtig für das Modell, um zu verstehen, welche Parameter tatsächlich erforderlich sind, damit die Funktion funktioniert. Später in Schritt 3 verwendet das Modell diese Informationen, um möglichst minimale Informationen bereitzustellen, um die Funktion aufzurufen.
Tipp
Markieren Sie Parameter nur als erforderlich, wenn sie tatsächlich erforderlich sind. Dies hilft dem Modell, Funktionen schneller und genauer aufzurufen.
Funktionsbeschreibungen – Funktionsbeschreibungen sind optional, können aber dazu beitragen, dass das Modell genauere Antworten generiert. Insbesondere können Beschreibungen dem Modell mitteilen, was von der Antwort erwartet werden soll, da der Rückgabetyp nicht im Schema serialisiert ist. Wenn das Modell Funktionen nicht ordnungsgemäß verwendet, können Sie auch Beschreibungen hinzufügen, um Beispiele und Anleitungen bereitzustellen.
In der Funktion weist die
get_pizza_from_cart
Beschreibung beispielsweise dem Benutzer an, diese Funktion zu verwenden, anstatt sich auf vorherige Nachrichten zu verlassen. Dies ist wichtig, da sich der Warenkorb seit der letzten Nachricht möglicherweise geändert hat.Tipp
Fragen Sie sich vor dem Hinzufügen einer Beschreibung, ob das Modell diese Informationen benötigt , um eine Antwort zu generieren. Wenn nicht, sollten Sie es verlassen, um die Ausführlichkeit zu reduzieren. Sie können später immer Beschreibungen hinzufügen, wenn das Modell problemet, die Funktion ordnungsgemäß zu verwenden.
Plug-In-Name – Wie Sie in den serialisierten Funktionen sehen können, verfügt jede Funktion über eine
name
Eigenschaft. Der semantische Kernel verwendet den Plug-In-Namen zum Namespace der Funktionen. Dies ist wichtig, da es Ihnen ermöglicht, mehrere Plug-Ins mit Funktionen desselben Namens zu haben. Beispielsweise können Sie Plug-Ins für mehrere Suchdienste haben, jede mit ihrer eigenensearch
Funktion. Indem Sie die Funktionen benennen, können Sie Konflikte vermeiden und das Modell einfacher verstehen, welche Funktion aufgerufen werden soll.Wenn Sie dies wissen, sollten Sie einen Plug-In-Namen auswählen, der eindeutig und beschreibend ist. Im obigen Beispiel lautet
OrderPizza
der Plug-In-Name . Dies macht deutlich, dass die Funktionen im Zusammenhang mit der Bestellung von Pizza stehen.Tipp
Beim Auswählen eines Plug-In-Namens wird empfohlen, überflüssige Wörter wie "Plugin" oder "Dienst" zu entfernen. Dadurch wird die Ausführlichkeit reduziert und der Plug-In-Name für das Modell leichter verständlich.
2) Senden der Nachrichten und Funktionen an das Modell
Sobald die Funktionen serialisiert wurden, werden sie zusammen mit dem aktuellen Chatverlauf an das Modell gesendet. Auf diese Weise kann das Modell den Kontext der Unterhaltung und der verfügbaren Funktionen verstehen.
In diesem Szenario können wir uns vorstellen, dass der Benutzer den Assistenten auffordern, dem Warenkorb eine Pizza hinzuzufügen:
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!");
Anschließend können wir diesen Chatverlauf und die serialisierten Funktionen an das Modell senden. Das Modell verwendet diese Informationen, um die beste Antwort zu ermitteln.
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();
Hinweis
In diesem Beispiel wird das FunctionChoiceBehavior.Auto()
Verhalten verwendet, eines der wenigen verfügbaren. Weitere Informationen zu anderen Funktionsauswahlverhalten finden Sie im Artikel "Funktionsauswahlverhalten".
3) Modell verarbeitet die Eingabe
Sowohl mit dem Chatverlauf als auch mit den serialisierten Funktionen kann das Modell die beste Reaktionsweise bestimmen. In diesem Fall erkennt das Modell, dass der Benutzer eine Pizza bestellen möchte. Das Modell möchte die add_pizza_to_cart
Funktion wahrscheinlich aufrufen, aber da wir die Größe und Ppings als erforderliche Parameter angegeben haben, fragt das Modell den Benutzer nach diesen 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?"
Da das Modell möchte, dass der Benutzer als Nächstes antwortet, wird ein Beendigungssignal an den semantischen Kernel gesendet, um den automatischen Funktionsaufruf zu beenden, bis der Benutzer antwortet.
An diesem Punkt kann der Benutzer mit der Größe und den Deckeln der Pizza reagieren, die er bestellen möchte:
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();
Nachdem das Modell nun über die erforderlichen Informationen verfügt, kann es nun die add_pizza_to_cart
Funktion mit der Eingabe des Benutzers aufrufen. Hinter den Kulissen wird dem Chatverlauf eine neue Nachricht hinzugefügt, die wie folgt aussieht:
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "OrderPizzaPlugin-add_pizza_to_cart",
"arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
}
}
]
Tipp
Beachten Sie, dass jedes erforderliche Argument vom Modell generiert werden muss. Dies bedeutet, dass Token ausgegeben werden, um die Antwort zu generieren. Vermeiden Sie Argumente, die viele Token erfordern (z. B. eine GUID). Beachten Sie beispielsweise, dass wir eine int
für die pizzaId
. Das Modell zum Senden einer einstelligen bis zweistelligen Nummer ist viel einfacher als die Frage nach einer GUID.
Wichtig
Dieser Schritt macht Funktionsaufrufe so leistungsfähig. Zuvor mussten KI-App-Entwickler separate Prozesse erstellen, um Intent- und Slot-Füllfunktionen zu extrahieren. Beim Aufrufen von Funktionen kann das Modell entscheiden , wann eine Funktion aufgerufen werden soll und welche Informationen bereitgestellt werden sollen.
4) Behandeln der Antwort
Wenn der semantische Kernel die Antwort vom Modell empfängt, überprüft er, ob es sich bei der Antwort um einen Funktionsaufruf handelt. Wenn dies der Name der Funktion ist, extrahiert der semantische Kernel den Funktionsnamen und die zugehörigen Parameter. In diesem Fall lautet OrderPizzaPlugin-add_pizza_to_cart
der Funktionsname , und die Argumente sind die Größe und Toppings der Pizza.
Mit diesen Informationen kann der semantische Kernel die Eingaben in die entsprechenden Typen marshallen und an die Funktion in der add_pizza_to_cart
.OrderPizzaPlugin
In diesem Beispiel stammen die Argumente als JSON-Zeichenfolge, werden jedoch durch semantischen Kernel in eine PizzaSize
Enumeration und eine List<PizzaToppings>
deserialisiert.
Hinweis
Das Marshallen der Eingaben in die richtigen Typen ist einer der wichtigsten Vorteile der Verwendung des semantischen Kernels. Alles aus dem Modell stammt als JSON-Objekt, aber Der semantische Kernel kann diese Objekte automatisch in die richtigen Typen für Ihre Funktionen deserialisieren.
Nach dem Marshalling der Eingaben kann der semantische Kernel auch den Funktionsaufruf zum Chatverlauf hinzufügen:
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"]})
)
]
)
)
Der semantische Kernel für Java behandelt Funktionsaufrufe anders als C# und Python, wenn das Verhalten des Aufrufs des automatisch aufgerufenen Tools falsch ist. Sie fügen dem Chatverlauf keinen Funktionsaufrufinhalt hinzu; Stattdessen wird die Anwendung für das Aufrufen der Funktionsaufrufe verantwortlich gemacht. Fahren Sie mit dem nächsten Abschnitt "Aufrufen der Funktion" fort, um beispielsweise Funktionsaufrufe in Java zu behandeln, wenn der automatische Aufruf falsch ist.
5) Aufrufen der Funktion
Sobald der semantische Kernel über die richtigen Typen verfügt, kann er schließlich die add_pizza_to_cart
Funktion aufrufen. Da das Plug-In Abhängigkeitsinjektion verwendet, kann die Funktion mit externen Diensten interagieren und pizzaService
userContext
die Pizza zum Einkaufswagen des Benutzers hinzufügen.
Nicht alle Funktionen werden jedoch erfolgreich ausgeführt. Wenn die Funktion fehlschlägt, kann der semantische Kernel den Fehler behandeln und eine Standardantwort für das Modell bereitstellen. Auf diese Weise kann das Modell verstehen, was schief gelaufen ist, und eine Antwort auf den Benutzer generieren.
Tipp
Um sicherzustellen, dass sich ein Modell selbst korrigieren kann, ist es wichtig, Fehlermeldungen bereitzustellen, die deutlich kommunizieren, was schief gegangen ist und wie sie behoben werden kann. Dies kann dazu beitragen, dass das Modell den Funktionsaufruf mit den richtigen Informationen wiederholen kann.
Hinweis
Der semantische Kernel ruft standardmäßig Automatisch Funktionen auf. Wenn Sie den Funktionsaufruf jedoch lieber manuell verwalten möchten, können Sie den Modus für manuelle Funktionsaufrufe aktivieren. Weitere Informationen dazu finden Sie im Funktionsaufrufartikel.
6) Zurückgeben des Funktionsergebnisses
Nachdem die Funktion aufgerufen wurde, wird das Funktionsergebnis als Teil des Chatverlaufs an das Modell zurückgesendet. Auf diese Weise kann das Modell den Kontext der Unterhaltung verstehen und eine nachfolgende Antwort generieren.
Hinter den Kulissen fügt Der semantische Kernel dem Chatverlauf eine neue Nachricht aus der Toolrolle hinzu, die wie folgt aussieht:
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\"] } ] }"
)
]
)
)
Wenn der automatische Aufruf im Toolaufrufverhalten deaktiviert ist, muss eine Java-Anwendung die Funktionsaufrufe aufrufen und das Funktionsergebnis als AuthorRole.TOOL
Nachricht zum Chatverlauf hinzufügen.
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()));
});
Beachten Sie, dass das Ergebnis eine JSON-Zeichenfolge ist, die das Modell dann verarbeiten muss. Wie zuvor muss das Modell Token ausgeben, die diese Informationen verwenden. Deshalb ist es wichtig, die Rückgabetypen so einfach wie möglich zu halten. In diesem Fall enthält die Rücksendung nur die neuen Artikel, die dem Warenkorb hinzugefügt wurden, nicht den gesamten Warenkorb.
Tipp
Seien Sie mit Ihren Rückgaben so prägnant wie möglich. Wenn möglich, geben Sie nur die Informationen zurück, die das Modell benötigt, oder fassen Sie die Informationen mithilfe einer anderen LLM-Eingabeaufforderung zusammen, bevor Sie sie zurückgeben.
Wiederholen Sie die Schritte 2 bis 6.
Nachdem das Ergebnis an das Modell zurückgegeben wurde, wiederholt sich der Prozess. Das Modell verarbeitet den neuesten Chatverlauf und generiert eine Antwort. In diesem Fall fragt das Modell den Benutzer möglicherweise, ob er dem Warenkorb eine weitere Pizza hinzufügen möchte oder ob er auschecken möchte.
Parallele Funktionsaufrufe
Im obigen Beispiel haben wir gezeigt, wie ein LLM eine einzelne Funktion aufrufen kann. Dies kann häufig langsam sein, wenn Sie mehrere Funktionen in Sequenz aufrufen müssen. Um den Prozess zu beschleunigen, unterstützen mehrere LLMs parallele Funktionsaufrufe. Dadurch kann der LLM mehrere Funktionen gleichzeitig aufrufen und den Prozess beschleunigen.
Wenn ein Benutzer beispielsweise mehrere Pizzas bestellen möchte, kann die LLM die add_pizza_to_cart
Funktion für jede Pizza gleichzeitig aufrufen. Dies kann die Anzahl der Roundtrips zum LLM erheblich reduzieren und den Bestellvorgang beschleunigen.
Nächste Schritte
Nachdem Sie nun verstehen, wie Funktionsaufrufe funktionieren, erfahren Sie, wie Sie verschiedene Aspekte des Funktionsaufrufs konfigurieren, die Ihren spezifischen Szenarien besser entsprechen, indem Sie sich auf den Artikel zum Funktionsauswahlverhalten beziehen.