Bagikan melalui


Panggilan fungsi dengan penyelesaian obrolan

Fitur penyelesaian obrolan yang paling kuat adalah kemampuan untuk memanggil fungsi dari model. Ini memungkinkan Anda membuat bot obrolan yang dapat berinteraksi dengan kode yang ada, memungkinkan untuk mengotomatiskan proses bisnis, membuat cuplikan kode, dan banyak lagi.

Dengan Semantic Kernel, kami menyederhanakan proses penggunaan panggilan fungsi dengan secara otomatis menjelaskan fungsi Anda dan parameternya ke model lalu menangani komunikasi bolak-balik antara model dan kode Anda.

Namun, saat menggunakan panggilan fungsi, ada baiknya untuk memahami apa yang sebenarnya terjadi di belakang layar sehingga Anda dapat mengoptimalkan kode Anda dan memanfaatkan fitur ini sebaik mungkin.

Cara kerja panggilan fungsi otomatis

Catatan

Bagian berikut menjelaskan cara kerja panggilan fungsi otomatis di Kernel Semantik. Panggilan fungsi otomatis adalah perilaku default di Kernel Semantik, tetapi Anda juga dapat memanggil fungsi secara manual jika mau. Untuk informasi selengkapnya tentang pemanggilan fungsi manual, silakan lihat artikel pemanggilan fungsi.

Saat Anda membuat permintaan ke model dengan panggilan fungsi diaktifkan, Semantic Kernel melakukan langkah-langkah berikut:

# Langkah Deskripsi
1 Menserialisasikan fungsi Semua fungsi yang tersedia (dan parameter inputnya) dalam kernel diserialisasikan menggunakan skema JSON.
2 Mengirim pesan dan fungsi ke model Fungsi berseri (dan riwayat obrolan saat ini) dikirim ke model sebagai bagian dari input.
3 Model memproses input Model memproses input dan menghasilkan respons. Respons dapat berupa pesan obrolan atau satu atau beberapa panggilan fungsi.
4 Mengelola respon Jika respons adalah pesan obrolan, respons akan dikembalikan ke pemanggil. Namun, jika respons adalah panggilan fungsi, Kernel Semantik mengekstrak nama fungsi dan parameternya.
5 Memanggil fungsi Nama fungsi dan parameter yang diekstrak digunakan untuk memanggil fungsi dalam kernel.
6 Mengembalikan hasil fungsi Hasil fungsi kemudian dikirim kembali ke model sebagai bagian dari riwayat obrolan. Langkah 2-6 kemudian diulang hingga model mengembalikan pesan obrolan atau nomor iterasi maks telah tercapai.

Diagram berikut mengilustrasikan proses pemanggilan fungsi:

Pemanggilan fungsi Kernel Semantik

Bagian berikut akan menggunakan contoh konkret untuk mengilustrasikan cara kerja panggilan fungsi dalam praktiknya.

Contoh: Memesan pizza

Mari kita asumsikan Anda memiliki plugin yang memungkinkan pengguna untuk memesan pizza. Plugin memiliki fungsi berikut:

  1. get_pizza_menu: Mengembalikan daftar pizza yang tersedia
  2. add_pizza_to_cart: Menambahkan pizza ke kelir pengguna
  3. remove_pizza_from_cart: Menghapus pizza dari keranjang pengguna
  4. get_pizza_from_cart: Mengembalikan detail spesifik pizza di keranjang pengguna
  5. get_cart: Mengembalikan kelir pengguna saat ini
  6. checkout: Memeriksa keranjang pengguna

Di C#, plugin mungkin terlihat seperti ini:

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);
    }
}

Anda kemudian akan menambahkan plugin ini ke kernel seperti itu:

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();

Catatan

Hanya fungsi dengan atribut yang KernelFunction akan diserialisasikan dan dikirim ke model. Ini memungkinkan Anda memiliki fungsi pembantu yang tidak terekspos ke model.

Di Python, plugin mungkin terlihat seperti ini:

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)

Anda kemudian akan menambahkan plugin ini ke kernel seperti itu:

from semantic_kernel import Kernel

kernel = Kernel()

# 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")

Catatan

Hanya fungsi dengan dekorator kernel_function yang akan diserialisasikan dan dikirim ke model. Ini memungkinkan Anda memiliki fungsi pembantu yang tidak terekspos ke model.

Nama Parameter yang Dicadangkan untuk Panggilan Fungsi Otomatis

Saat menggunakan panggilan fungsi otomatis di KernelFunctions, nama parameter tertentu dicadangkan dan menerima penanganan khusus. Nama yang dipesan ini memungkinkan Anda untuk secara otomatis mengakses objek kunci yang diperlukan untuk eksekusi fungsi.

Nama yang Dicadangkan

Nama parameter berikut dicadangkan:

  • kernel
  • service
  • execution_settings
  • arguments

Cara kerjanya

Selama pemanggilan fungsi, metode gather_function_parameters memeriksa setiap parameter. Jika nama parameter cocok dengan salah satu nama yang dipesan, nama tersebut diisi dengan objek tertentu:

  • kernel: Disuntikkan dengan objek kernel.
  • service: Diisi dengan layanan AI yang dipilih berdasarkan argumen yang disediakan.
  • execution_settings: Berisi pengaturan yang berkaitan dengan eksekusi fungsi.
  • arguments: Menerima seluruh set argumen kernel yang diteruskan selama pemanggilan.

Desain ini memastikan bahwa parameter ini dikelola secara otomatis, menghilangkan kebutuhan akan ekstraksi atau penugasan manual.

Contoh Penggunaan

Pertimbangkan contoh berikut:

class SimplePlugin:
    @kernel_function(name="GetWeather", description="Get the weather for a location.")
    async def get_the_weather(self, location: str, arguments: KernelArguments) -> str:
        # The 'arguments' parameter is reserved and automatically populated with KernelArguments.
        return f"Received user input: {location}, the weather is nice!"

Nama Parameter yang Dicadangkan Secara Khusus untuk Pemanggilan Fungsi Otomatis

Anda juga dapat menyesuaikan perilaku ini. Untuk melakukan itu, Anda perlu membuat anotasi parameter yang ingin Anda kecualikan dari definisi panggilan fungsi, seperti ini:

class SimplePlugin:
    @kernel_function(name="GetWeather", description="Get the weather for a location.")
    async def get_the_weather(self, location: str, special_arg: Annotated[str, {"include_in_function_choices": False}]) -> str:
        # The 'special_arg' parameter is reserved and you need to ensure it either has a default value or gets passed.

Saat memanggil fungsi ini, pastikan untuk meneruskan special_arg parameter, jika tidak, itu akan menimbulkan kesalahan.

response = await kernel.invoke_async(
    plugin_name=...,
    function_name="GetWeather",
    location="Seattle",
    special_arg="This is a special argument"
)

Atau tambahkan ke KernelArguments objek untuk menggunakannya dengan panggilan fungsi otomatis pada agen seperti ini:

arguments = KernelArguments(special_arg="This is a special argument")
response = await agent.get_response(
    messages="what's the weather in Seattle?"
    arguments=arguments)

Di Java, plugin mungkin terlihat seperti ini:

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));
    }
}

Anda kemudian akan menambahkan plugin ini ke kernel seperti itu:

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();

Catatan

Hanya fungsi dengan DefineKernelFunction anotasi yang akan diserialisasikan dan dikirim ke model. Ini memungkinkan Anda memiliki fungsi pembantu yang tidak terekspos ke model.

1) Menserialisasikan fungsi

Ketika Anda membuat kernel dengan OrderPizzaPlugin, kernel akan secara otomatis menserialisasikan fungsi dan parameternya. Ini diperlukan agar model dapat memahami fungsi dan inputnya.

Untuk plugin di atas, fungsi berseri akan terlihat seperti ini:

[
  {
    "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": []
      }
    }
  }
]

Ada beberapa hal yang perlu diperhatikan di sini yang dapat memengaruhi performa dan kualitas penyelesaian obrolan:

  1. Verbositas skema fungsi – Fungsi serialisasi untuk digunakan model tidak datang secara gratis. Semakin verbose skema, semakin banyak token yang harus diproses model, yang dapat memperlambat waktu respons dan meningkatkan biaya.

    Tips

    Jaga fungsi Anda sesering mungkin. Dalam contoh di atas, Anda akan melihat bahwa tidak semua fungsi memiliki deskripsi di mana nama fungsinya jelas. Ini disengaja untuk mengurangi jumlah token. Parameternya juga tetap sederhana; apa pun yang tidak perlu diketahui model (seperti cartId atau paymentId) disembunyikan. Informasi ini sebagai gantinya disediakan oleh layanan internal.

    Catatan

    Satu hal yang tidak perlu Anda khawatirkan adalah kompleksitas jenis pengembalian. Anda akan melihat bahwa jenis pengembalian tidak diserialisasikan dalam skema. Ini karena model tidak perlu mengetahui jenis pengembalian untuk menghasilkan respons. Namun, di Langkah 6, kita akan melihat seberapa besar jenis pengembalian yang terlalu rinci dapat memengaruhi kualitas hasil akhir obrolan.

  2. Jenis parameter – Dengan skema, Anda dapat menentukan jenis setiap parameter. Ini penting bagi model untuk memahami input yang diharapkan. Dalam contoh di atas, size parameter adalah enum, dan toppings parameternya adalah array enum. Ini membantu model menghasilkan respons yang lebih akurat.

    Petunjuk

    Hindari, jika memungkinkan, menggunakan string sebagai jenis parameter. Model tidak dapat menyimpulkan jenis string, yang dapat menyebabkan respons ambigu. Sebagai gantinya, gunakan enum atau jenis lain (misalnya, , int, floatdan jenis kompleks) jika memungkinkan.

  3. Parameter yang diperlukan - Anda juga dapat menentukan parameter mana yang diperlukan. Ini penting bagi model untuk memahami parameter mana yang sebenarnya diperlukan agar fungsi berfungsi. Nantinya di Langkah 3, model akan menggunakan informasi ini untuk memberikan informasi minimal seperlunya untuk memanggil fungsi.

    Tip

    Hanya tandai parameter sebagai diperlukan jika memang benar-benar diperlukan. Ini membantu model untuk memanggil fungsi lebih cepat dan lebih akurat.

  4. Deskripsi fungsi – Deskripsi fungsi bersifat opsional tetapi dapat membantu model menghasilkan respons yang lebih akurat. Secara khusus, deskripsi dapat memberi tahu model apa yang diharapkan dari respons karena jenis pengembalian tidak diserialisasikan dalam skema. Jika model menggunakan fungsi dengan tidak benar, Anda juga dapat menambahkan deskripsi untuk memberikan contoh dan panduan.

    Misalnya, dalam get_pizza_from_cart fungsi , deskripsi memberi tahu pengguna untuk menggunakan fungsi ini alih-alih mengandalkan pesan sebelumnya. Ini penting karena keranjang mungkin telah berubah sejak pesan terakhir.

    Petunjuk

    Sebelum menambahkan deskripsi, tanyakan kepada diri Anda apakah model memerlukan informasi ini untuk menghasilkan respons. Jika tidak, pertimbangkan untuk menghilangkannya untuk mengurangi kelebihan kata. Anda selalu dapat menambahkan deskripsi nanti jika model berjuang untuk menggunakan fungsi dengan benar.

  5. Nama Plugin — Seperti yang Anda lihat dalam fungsi yang diserialkan, setiap fungsi memiliki name properti. Semantic Kernel menggunakan nama plugin untuk namespace fungsi. Ini penting karena memungkinkan Anda memiliki beberapa plugin dengan fungsi dengan nama yang sama. Misalnya, Anda mungkin memiliki plugin untuk beberapa layanan pencarian, masing-masing dengan fungsinya sendiri search . Dengan memberi nama fungsi, Anda dapat menghindari konflik dan mempermudah model untuk memahami fungsi mana yang akan dipanggil.

    Mengetahui hal ini, Anda harus memilih nama plugin yang unik dan deskriptif. Dalam contoh di atas, nama plugin adalah OrderPizza. Ini memperjelas bahwa fungsi terkait dengan pemesanan pizza.

    Tips

    Saat memilih nama plugin, sebaiknya hapus kata-kata berlebihan seperti "plugin" atau "layanan". Ini membantu mengurangi verbositas dan membuat nama plugin lebih mudah dipahami untuk model.

    Catatan

    Secara default, pemisah untuk nama fungsi adalah -. Meskipun ini berfungsi untuk sebagian besar model, beberapa di antaranya mungkin memiliki persyaratan yang berbeda, seperti Gemini. Ini diurus oleh kernel secara otomatis namun Anda mungkin melihat nama fungsi yang sedikit berbeda dalam fungsi berseri.

2) Mengirim pesan dan fungsi ke model

Setelah fungsi diserialisasikan, fungsi tersebut dikirim ke model bersama dengan riwayat obrolan saat ini. Ini memungkinkan model untuk memahami konteks percakapan dan fungsi yang tersedia.

Dalam skenario ini, kita dapat membayangkan pengguna meminta asisten untuk menambahkan pizza ke keranjang mereka.

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!");

Kita kemudian dapat mengirim riwayat obrolan ini dan fungsi berseri ke model. Model akan menggunakan informasi ini untuk menentukan cara terbaik untuk merespons.

IChatCompletionService chatCompletion = kernel.GetRequiredService<IChatCompletionService>();

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() 
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

ChatResponse response = await chatCompletion.GetChatMessageContentAsync(
    chatHistory,
    executionSettings: openAIPromptExecutionSettings,
    kernel: kernel)

Catatan

Contoh ini menggunakan FunctionChoiceBehavior.Auto() perilaku, salah satu dari beberapa yang tersedia. Untuk informasi selengkapnya tentang perilaku pilihan fungsi lainnya, lihat artikel perilaku pilihan fungsi.

chat_completion = kernel.get_service(type=ChatCompletionClientBase)

execution_settings = AzureChatPromptExecutionSettings()
execution_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

response = await chat_completion.get_chat_message_content(
    chat_history=history,
    settings=execution_settings,
    kernel=kernel,
)

Catatan

Contoh ini menggunakan FunctionChoiceBehavior.Auto() perilaku, salah satu dari sedikit pilihan yang tersedia. Untuk informasi selengkapnya tentang perilaku pilihan fungsi lainnya, lihat artikel perilaku pilihan fungsi.

ChatCompletionService chatCompletion = kernel.getService(I)ChatCompletionService.class);

InvocationContext invocationContext = InvocationContext.builder()
    .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false));

List<ChatResponse> responses = chatCompletion.getChatMessageContentsAsync(
    chatHistory,
    kernel,
    invocationContext).block();

Penting

Kernel harus dikirimkan ke layanan agar dapat menggunakan fungsi panggilan. Ini karena plugin terdaftar dengan kernel, dan layanan perlu mengetahui plugin mana yang tersedia.

3) Model komputer memproses input

Dengan riwayat obrolan dan fungsi berseri, model dapat menentukan cara terbaik untuk merespons. Dalam hal ini, model mengenali bahwa pengguna ingin memesan pizza. Model mungkin ingin memanggil fungsi add_pizza_to_cart, tetapi karena kami menentukan ukuran dan topping sebagai parameter yang wajib, model akan meminta informasi ini dari pengguna.

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?"

Karena model ingin pengguna merespons berikutnya, Semantic Kernel akan menghentikan panggilan fungsi otomatis dan mengembalikan kontrol kepada pengguna. Pada titik ini, pengguna dapat merespons dengan ukuran dan topping pizza yang ingin mereka pesan:

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_content(
    chat_history=history,
    settings=execution_settings,
    kernel=kernel,
)
chatHistory.addUserMessage("I'd like a medium pizza with cheese and pepperoni, please.");

responses = chatCompletion.GetChatMessageContentAsync(
    chatHistory,
    kernel,
    null).block();

Setelah model memiliki informasi yang diperlukan, model dapat memanggil fungsi add_pizza_to_cart dengan input pengguna. Di balik layar, ini menambahkan pesan baru ke riwayat obrolan yang terlihat seperti ini:

"tool_calls": [
    {
        "id": "call_abc123",
        "type": "function",
        "function": {
            "name": "OrderPizzaPlugin-add_pizza_to_cart",
            "arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
        }
    }
]

Tip

Ada baiknya untuk mengingat bahwa setiap argumen yang Anda butuhkan harus dihasilkan oleh model. Ini berarti membelanjakan token untuk menghasilkan respons. Hindari argumen yang memerlukan banyak token (seperti GUID). Misalnya, perhatikan bahwa kita menggunakan int untuk pizzaId. Meminta model untuk mengirim angka satu hingga dua digit jauh lebih mudah daripada meminta GUID.

Penting

Langkah inilah yang membuat panggilan fungsi menjadi sangat efektif. Sebelumnya, pengembang aplikasi AI harus membuat proses terpisah untuk mengekstrak niat dan fungsi pengisian slot. Dengan panggilan fungsi, model dapat memutuskan kapan harus memanggil fungsi dan informasi apa yang akan diberikan.

4) Menangani respons

Ketika Kernel Semantik menerima respons dari model, ia memeriksa apakah responsnya adalah panggilan fungsi. Jika ya, Kernel Semantik mengekstrak nama fungsi dan parameternya. Dalam hal ini, nama fungsinya adalah OrderPizzaPlugin-add_pizza_to_cart, dan argumennya adalah ukuran dan topping pizza.

Dengan informasi ini, Kernel Semantik dapat melakukan marshal input ke dalam jenis yang sesuai dan meneruskannya ke add_pizza_to_cart fungsi dalam OrderPizzaPlugin. Dalam contoh ini, argumen berasal sebagai string JSON tetapi dideserialisasi oleh Semantic Kernel menjadi PizzaSize enum dan List<PizzaToppings>.

Catatan

Marshaling input ke dalam jenis yang benar adalah salah satu manfaat utama menggunakan Semantic Kernel. Segala sesuatu dari model hadir sebagai objek JSON, tetapi Kernel Semantik dapat secara otomatis mendeserialisasi objek ini ke dalam jenis yang benar untuk fungsi Anda.

Setelah melakukan marsekal input, Kernel Semantik juga akan menambahkan panggilan fungsi ke riwayat obrolan:

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"]})
            )
        ]
    )
)

Kernel Semantik untuk Java menangani fungsi yang memanggil secara berbeda dari C# dan Python ketika perilaku panggilan alat pemanggilan otomatis salah. Anda tidak menambahkan konten panggilan fungsi ke riwayat obrolan; sebaliknya, aplikasi dibuat bertanggung jawab untuk memanggil panggilan fungsi. Lewati ke bagian berikutnya, "Panggil fungsi", untuk contoh penanganan panggilan fungsi di Java saat pemanggilan otomatis salah.

5) Memanggil fungsi

Setelah Semantic Kernel memiliki jenis yang benar, akhirnya dapat memanggil fungsi add_pizza_to_cart. Karena plugin menggunakan injeksi dependensi, fungsi dapat berinteraksi dengan layanan eksternal seperti pizzaService dan userContext untuk menambahkan pizza ke kelir pengguna.

Namun, tidak semua fungsi akan berhasil. Jika fungsi gagal, Kernel Semantik dapat menangani kesalahan dan memberikan respons default ke model. Ini memungkinkan model untuk memahami apa yang salah dan memutuskan untuk mencoba kembali atau menghasilkan respons kepada pengguna.

Tip

Untuk memastikan model dapat memperbaiki diri sendiri, penting untuk memberikan pesan kesalahan yang dengan jelas mengomunikasikan apa yang salah dan cara memperbaikinya. Ini dapat membantu model mencoba kembali panggilan fungsi dengan informasi yang benar.

Catatan

Kernel Semantik secara otomatis memanggil fungsi secara default. Namun, jika Anda lebih suka mengelola pemanggilan fungsi secara manual, Anda dapat mengaktifkan mode pemanggilan fungsi manual. Untuk detail selengkapnya tentang cara melakukan ini, silakan lihat artikel pemanggilan fungsi.

6) Mengembalikan hasil fungsi

Setelah fungsi dipanggil, hasil fungsi dikirim kembali ke model sebagai bagian dari riwayat obrolan. Ini memungkinkan model untuk memahami konteks percakapan dan menghasilkan respons berikutnya.

Di balik layar, Semantic Kernel menambahkan pesan baru ke riwayat obrolan dari peran alat yang terlihat seperti ini:

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\"] } ] }"
            )
        ]
    )
)

Jika pemanggilan otomatis dinonaktifkan dalam perilaku panggilan alat, aplikasi Java harus memanggil panggilan fungsi dan menambahkan hasil fungsi sebagai AuthorRole.TOOL pesan ke riwayat obrolan.

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()));
        });

Perhatikan bahwa hasilnya adalah string JSON yang kemudian perlu diproses oleh model. Seperti sebelumnya, model perlu menggunakan token untuk memproses informasi ini. Inilah sebabnya mengapa penting untuk menjaga jenis pengembalian sesederhana mungkin. Dalam hal ini, pengembalian barang hanya mencakup item baru yang ditambahkan ke keranjang, bukan seluruh keranjang.

Petunjuk

Usahakan sependek mungkin dalam pengembalian Anda. Jika memungkinkan, hanya kembalikan informasi yang dibutuhkan model atau ringkas informasi menggunakan prompt LLM lain sebelum mengembalikannya.

Ulangi langkah 2-6

Setelah hasil dikembalikan ke model, proses akan berulang. Model memproses riwayat obrolan terbaru dan menghasilkan respons. Dalam hal ini, model mungkin bertanya kepada pengguna apakah mereka ingin menambahkan pizza lain ke keranjang mereka atau jika mereka ingin melakukan pembayaran.

Panggilan fungsi paralel

Dalam contoh di atas, kami menunjukkan bagaimana LLM dapat memanggil satu fungsi. Seringkali ini bisa lambat jika Anda perlu memanggil beberapa fungsi secara berurutan. Untuk mempercepat proses, beberapa LLM mendukung panggilan fungsi paralel. Ini memungkinkan LLM untuk memanggil beberapa fungsi sekaligus, mempercepat proses.

Misalnya, jika pengguna ingin memesan beberapa pizza, LLM dapat memanggil add_pizza_to_cart fungsi untuk setiap pizza secara bersamaan. Ini dapat secara signifikan mengurangi jumlah perjalanan pulang pergi ke LLM dan mempercepat proses pemesanan.

Langkah berikutnya

Sekarang setelah Anda memahami cara kerja panggilan fungsi, Anda dapat melanjutkan untuk mempelajari cara mengonfigurasi berbagai aspek panggilan fungsi yang lebih sesuai dengan skenario spesifik Anda dengan masuk ke langkah berikutnya: