Delen via


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:

Semantische kernelfunctie aanroepen

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:

  1. get_pizza_menu: retourneert een lijst met beschikbare pizza's
  2. add_pizza_to_cart: Voegt een pizza toe aan de winkelwagen van de gebruiker
  3. remove_pizza_from_cart: Verwijdert een pizza uit de winkelwagen van de gebruiker
  4. get_pizza_from_cart: retourneert de specifieke details van een pizza in de winkelwagen van de gebruiker
  5. get_cart: retourneert de huidige winkelwagen van de gebruiker
  6. checkout: 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 OrderPizzaPluginkernel, 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:

  1. 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 of paymentId) 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.

  2. 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 de toppings 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 (bijvoorbeeld int, floaten complexe typen).

  3. 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.

  4. 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.

  5. 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 eigen search 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 OrderPizzade 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_cartde 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