다음을 통해 공유


채팅 완료를 사용하여 함수 호출

채팅 완성의 가장 강력한 기능은 모델에서 함수를 호출하는 기능입니다. 이를 통해 기존 코드와 상호 작용할 수 있는 챗봇을 만들 수 있으므로 비즈니스 프로세스를 자동화하고 코드 조각을 만드는 등의 작업을 수행할 수 있습니다.

의미 체계 커널을 사용하면 함수 및 해당 매개 변수를 모델에 자동으로 설명한 다음 모델과 코드 간의 앞뒤로 통신을 처리하여 함수 호출을 사용하는 프로세스를 간소화합니다.

그러나 함수 호출을 사용하는 경우 코드를 최적화하고 이 기능을 최대한 활용할 수 있도록 백그라운드에서 실제로 발생하는 상황을 이해하는 것이 좋습니다.

함수 호출의 작동 방식

함수 호출이 사용하도록 설정된 모델에 대한 요청을 수행하면 의미 체계 커널은 다음 단계를 수행합니다.

Step Description
1 함수 직렬화 커널에서 사용 가능한 모든 함수(및 해당 입력 매개 변수)는 JSON 스키마를 사용하여 직렬화됩니다.
2 모델에 메시지 및 함수 보내기 직렬화된 함수(및 현재 채팅 기록)는 입력의 일부로 모델에 전송됩니다.
3 모델은 입력을 처리합니다. 모델은 입력을 처리하고 응답을 생성합니다. 응답은 채팅 메시지 또는 함수 호출일 수 있습니다.
4 응답 처리 응답이 채팅 메시지인 경우 화면에 응답을 인쇄하기 위해 개발자에게 반환됩니다. 그러나 응답이 함수 호출인 경우 의미 체계 커널은 함수 이름과 해당 매개 변수를 추출합니다.
5 함수 호출 추출된 함수 이름 및 매개 변수는 커널에서 함수를 호출하는 데 사용됩니다.
6 함수 결과 반환 그런 다음, 함수의 결과가 채팅 기록의 일부로 모델로 다시 전송됩니다. 그런 다음 모델에서 종료 신호를 보낼 때까지 2-6단계가 반복됩니다.

다음 다이어그램에서는 함수 호출 프로세스를 보여 줍니다.

의미 체계 커널 함수 호출

다음 섹션에서는 함수 호출이 실제로 작동하는 방식을 설명하기 위해 구체적인 예제를 사용합니다.

예: 피자 주문

사용자가 피자를 주문할 수 있는 플러그 인이 있다고 가정해 보겠습니다. 플러그 인에는 다음과 같은 함수가 있습니다.

  1. get_pizza_menu: 사용 가능한 피자 목록을 반환합니다.
  2. add_pizza_to_cart: 사용자의 카트에 피자 추가
  3. remove_pizza_from_cart: 사용자의 카트에서 피자를 제거합니다.
  4. get_pizza_from_cart: 사용자의 카트에 있는 피자의 특정 세부 정보를 반환합니다.
  5. get_cart: 사용자의 현재 카트를 반환합니다.
  6. checkout: 사용자의 카트 체크 아웃

C#에서 플러그 인은 다음과 같이 표시될 수 있습니다.

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

그런 다음 다음과 같이 커널에 이 플러그 인을 추가합니다.

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

Python에서 플러그 인은 다음과 같이 표시될 수 있습니다.

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)

그런 다음 다음과 같이 커널에 이 플러그 인을 추가합니다.

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

Java에서 플러그 인은 다음과 같이 표시될 수 있습니다.

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

그런 다음 다음과 같이 커널에 이 플러그 인을 추가합니다.

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) 함수 직렬화

커널 OrderPizzaPlugin을 사용하여 만들 때 커널은 함수와 해당 매개 변수를 자동으로 직렬화합니다. 모델이 함수 및 해당 입력을 이해할 수 있도록 이 작업이 필요합니다.

위의 플러그 인의 경우 직렬화된 함수는 다음과 같습니다.

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

여기서는 채팅 완료의 성능과 품질 모두에 영향을 미칠 수 있는 몇 가지 사항에 유의해야 합니다.

  1. 함수 스키마 의 세부 정보 – 사용할 모델에 대한 함수 직렬화는 무료로 제공되지 않습니다. 스키마의 자세한 정보가 많을수록 모델에서 처리해야 하는 토큰이 많을수록 응답 시간이 느려지고 비용이 증가할 수 있습니다.

    함수를 가능한 한 간단하게 유지합니다. 위의 예제에서는 모든 함수에 함수 이름이 설명이 있는 설명이 있는 것은 아닙니다. 이는 토큰 수를 줄이기 위한 의도입니다. 매개 변수도 단순하게 유지됩니다. 모델에서 알 필요가 없는 모든 항목(예: cartId or paymentId)은 숨겨집니다. 이 정보는 내부 서비스에서 대신 제공됩니다.

    참고 항목

    걱정할 필요가 없는 한 가지는 반환 형식의 복잡성입니다. 반환 형식은 스키마에서 serialize되지 않습니다. 이는 모델이 응답을 생성하기 위해 반환 형식을 알 필요가 없기 때문입니다. 그러나 6단계에서는 지나치게 자세한 반환 형식이 채팅 완료의 품질에 얼마나 영향을 줄 수 있는지 살펴보겠습니다.

  2. 매개 변수 형식 – 스키마를 사용하여 각 매개 변수의 형식을 지정할 수 있습니다. 이는 모델이 예상된 입력을 이해하는 데 중요합니다. 위의 예제 size 에서 매개 변수는 열거형이고 toppings 매개 변수는 열거형 배열입니다. 이렇게 하면 모델이 보다 정확한 응답을 생성할 수 있습니다.

    가능한 경우 매개 변수 형식으로 사용하지 string 마세요. 모델은 문자열 형식을 유추할 수 없으므로 모호한 응답이 발생할 수 있습니다. 대신 가능한 경우 열거형 또는 다른 형식(예: int복합 float형식)을 사용합니다.

  3. 필수 매개 변수 - 필요한 매개 변수를 지정할 수도 있습니다. 이는 모델이 함수가 실제로 작동하는 데 필요한 매개 변수를 이해하는 데 중요합니다. 3단계의 뒷부분에서 모델은 이 정보를 사용하여 함수를 호출하는 데 필요한 최소한의 정보를 제공합니다.

    매개 변수가 실제로 필요한 경우에만 필수로 표시합니다. 이렇게 하면 모델 호출 함수가 더 빠르고 정확하게 작동할 수 있습니다.

  4. 함수 설명 – 함수 설명은 선택 사항이지만 모델이 보다 정확한 응답을 생성하는 데 도움이 될 수 있습니다. 특히 설명은 반환 형식이 스키마에서 serialize되지 않으므로 응답에서 무엇을 기대해야 하는지 모델에 알릴 수 있습니다. 모델이 함수를 잘못 사용하는 경우 설명을 추가하여 예제 및 지침을 제공할 수도 있습니다.

    예를 들어 함수에서 get_pizza_from_cart 설명은 이전 메시지를 사용하는 대신 이 함수를 사용하도록 사용자에게 지시합니다. 이는 마지막 메시지 이후 카트가 변경되었을 수 있기 때문에 중요합니다.

    설명을 추가하기 전에 모델에 응답을 생성하기 위해 이 정보가 필요한 지 자문해 보세요. 그렇지 않은 경우 자세한 정보를 줄이기 위해 그대로 두는 것이 좋습니다. 모델이 함수를 제대로 사용하는 데 어려움을 겪는 경우 나중에 언제든지 설명을 추가할 수 있습니다.

  5. 플러그 인 이름 – 직렬화된 함수에서 볼 수 있듯이 각 함수에는 속성이 있습니다 name . 의미 체계 커널은 플러그 인 이름을 사용하여 함수의 네임스페이스를 지정합니다. 이는 동일한 이름의 함수를 가진 여러 플러그 인을 가질 수 있기 때문에 중요합니다. 예를 들어 각각 고유한 search 기능을 사용하는 여러 검색 서비스에 대한 플러그 인이 있을 수 있습니다. 함수의 이름을 지정하면 충돌을 방지하고 모델에서 호출할 함수를 더 쉽게 이해할 수 있습니다.

    이를 알고 있으면 고유하고 설명이 포함된 플러그 인 이름을 선택해야 합니다. 위의 예제에서 플러그 인 이름은 .입니다 OrderPizza. 이렇게 하면 함수가 피자 주문과 관련이 있음을 분명히 합니다.

    플러그 인 이름을 선택할 때 "플러그 인" 또는 "서비스"와 같은 불필요한 단어를 제거하는 것이 좋습니다. 이렇게 하면 세부 정보를 줄이고 플러그 인 이름을 모델에 대해 더 쉽게 이해할 수 있습니다.

2) 모델에 메시지 및 함수 보내기

함수가 직렬화되면 현재 채팅 기록과 함께 모델로 전송됩니다. 이를 통해 모델은 대화의 컨텍스트와 사용 가능한 함수를 이해할 수 있습니다.

이 시나리오에서는 사용자가 도우미에게 피자를 카트에 추가하도록 요청하는 것을 상상할 수 있습니다.

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

그런 다음, 이 채팅 기록 및 직렬화된 함수를 모델로 보낼 수 있습니다. 모델은 이 정보를 사용하여 응답하는 가장 좋은 방법을 결정합니다.

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

참고 항목

이 예제에서는 사용 가능한 몇 가지 동작 중 하나인 동작을 사용합니다 FunctionChoiceBehavior.Auto() . 다른 함수 선택 동작에 대한 자세한 내용은 함수 선택 동작 문서를 확인하세요.

3) 모델이 입력을 처리합니다.

채팅 기록과 직렬화된 함수를 모두 사용하여 모델은 응답하는 가장 좋은 방법을 결정할 수 있습니다. 이 경우 모델은 사용자가 피자를 주문하려고 한다는 것을 인식합니다. 모델은 함수를 호출 add_pizza_to_cart 하려고 할 수 있지만 크기 및 토핑을 필수 매개 변수로 지정했기 때문에 모델은 사용자에게 다음 정보를 요청합니다.

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

모델은 사용자가 다음에 응답하기를 원하므로 종료 신호가 의미 체계 커널로 전송되어 사용자가 응답할 때까지 자동 함수 호출을 중지합니다.

이 시점에서 사용자는 주문하려는 피자의 크기와 토핑으로 응답할 수 있습니다.

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

이제 모델에 필요한 정보가 있으므로 이제 사용자의 입력으로 add_pizza_to_cart 함수를 호출할 수 있습니다. 백그라운드에서 다음과 같은 새 메시지를 채팅 기록에 추가합니다.

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

필요한 모든 인수는 모델에서 생성해야 합니다. 즉, 응답을 생성하기 위해 토큰을 지출합니다. 많은 토큰(예: GUID)이 필요한 인수는 사용하지 마세요. 예를 들어 .int pizzaId 모델에 1~2자리 숫자를 보내도록 요청하는 것은 GUID를 요청하는 것보다 훨씬 쉽습니다.

Important

이 단계는 함수 호출을 매우 강력하게 만드는 단계입니다. 이전에는 AI 앱 개발자가 의도 및 슬롯 채우기 함수를 추출하기 위해 별도의 프로세스를 만들어야 했습니다. 함수 호출을 사용하면 모델에서 함수를 호출할 시기 제공할 정보를 결정할 수 있습니다.

4) 응답 처리

의미 체계 커널이 모델에서 응답을 받으면 응답이 함수 호출인지 확인합니다. 이 경우 의미 체계 커널은 함수 이름과 해당 매개 변수를 추출합니다. 이 경우 함수 이름은 OrderPizzaPlugin-add_pizza_to_cart피자의 크기 및 토핑이며 인수는 피자의 크기 및 토핑입니다.

이 정보를 사용하면 의미 체계 커널이 입력을 적절한 형식으로 마샬링하고 해당 형식의 add_pizza_to_cart 함수에 OrderPizzaPlugin전달할 수 있습니다. 이 예제에서 인수는 JSON 문자열로 시작되지만 의미 체계 커널에 의해 열거형 및 List<PizzaToppings>1 PizzaSize 로 역직렬화됩니다.

참고 항목

입력을 올바른 형식으로 마샬링하는 것은 의미 체계 커널을 사용할 때의 주요 이점 중 하나입니다. 모델의 모든 항목은 JSON 개체로 제공되지만 의미 체계 커널은 이러한 개체를 함수에 대한 올바른 형식으로 자동으로 역직렬화할 수 있습니다.

입력을 마샬링한 후 의미 체계 커널은 채팅 기록에 함수 호출을 추가할 수도 있습니다.

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

Java용 의미 체계 커널은 자동 호출 도구 호출 동작이 false인 경우 C# 및 Python과 다르게 호출하는 함수를 처리합니다. 채팅 기록에 함수 호출 콘텐츠를 추가하지 않습니다. 오히려 애플리케이션이 함수 호출을 담당합니다. 자동 호출이 false일 때 Java에서 함수 호출을 처리하는 예를 들어 다음 섹션인 "함수 호출"으로 건너뜁니다.

5) 함수 호출

의미 체계 커널에 올바른 형식이 있으면 마지막으로 함수를 호출할 add_pizza_to_cart 수 있습니다. 플러그 인은 종속성 주입을 사용하므로 함수는 사용자의 카트에 피자를 추가하는 것과 같은 pizzaService userContext 외부 서비스와 상호 작용할 수 있습니다.

그러나 모든 함수가 성공하는 것은 아닙니다. 함수가 실패하면 의미 체계 커널이 오류를 처리하고 모델에 대한 기본 응답을 제공할 수 있습니다. 이를 통해 모델은 무엇이 잘못되었는지 이해하고 사용자에게 응답을 생성할 수 있습니다.

모델이 스스로 수정할 수 있도록 하려면 무엇이 잘못되었는지와 해결 방법을 명확하게 전달하는 오류 메시지를 제공하는 것이 중요합니다. 이렇게 하면 모델이 올바른 정보를 사용하여 함수 호출을 다시 시도하는 데 도움이 될 수 있습니다.

참고 항목

의미 체계 커널은 기본적으로 함수를 자동으로 호출합니다. 그러나 함수 호출을 수동으로 관리하려는 경우 수동 함수 호출 모드를 사용하도록 설정할 수 있습니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 함수 호출 문서를 참조하세요.

6) 함수 결과 반환

함수가 호출되면 함수 결과가 채팅 기록의 일부로 모델로 다시 전송됩니다. 이를 통해 모델은 대화의 컨텍스트를 이해하고 후속 응답을 생성할 수 있습니다.

이면에서 의미 체계 커널은 다음과 같은 도구 역할의 채팅 기록에 새 메시지를 추가합니다.

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

도구 호출 동작에서 자동 호출을 사용하지 않도록 설정한 경우 Java 애플리케이션은 함수 호출을 호출하고 함수 결과를 채팅 기록에 메시지로 AuthorRole.TOOL 추가해야 합니다.

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

결과는 모델이 처리해야 하는 JSON 문자열입니다. 이전과 마찬가지로 모델은 이 정보를 사용하는 토큰을 사용해야 합니다. 따라서 반환 형식을 가능한 한 단순하게 유지하는 것이 중요합니다. 이 경우 반환에는 전체 카트가 아닌 카트에 추가된 새 항목만 포함됩니다.

반환과 함께 가능한 한 간결합니다. 가능한 경우 모델이 필요로 하는 정보만 반환하거나 반환하기 전에 다른 LLM 프롬프트를 사용하여 정보를 요약합니다.

2-6단계 반복

결과가 모델에 반환되면 프로세스가 반복됩니다. 모델은 최신 채팅 기록을 처리하고 응답을 생성합니다. 이 경우 모델은 카트에 다른 피자를 추가할지 또는 체크 아웃할지 여부를 사용자에게 요청할 수 있습니다.

병렬 함수 호출

위의 예제에서는 LLM이 단일 함수를 호출하는 방법을 설명했습니다. 여러 함수를 순서대로 호출해야 하는 경우 속도가 느려질 수 있습니다. 프로세스 속도를 높이기 위해 여러 LLM이 병렬 함수 호출을 지원합니다. 이를 통해 LLM은 한 번에 여러 함수를 호출하여 프로세스 속도를 높일 수 있습니다.

예를 들어 사용자가 여러 피자를 주문하려는 경우 LLM은 각 피자에 add_pizza_to_cart 대한 함수를 동시에 호출할 수 있습니다. 이렇게 하면 LLM으로의 왕복 횟수를 크게 줄이고 주문 프로세스를 가속화할 수 있습니다.

다음 단계

함수 호출의 작동 방식을 이해했으므로 함수 선택 동작 문서를 참조 하여 특정 시나리오에 더 적합한 함수 호출의 다양한 측면을 구성하는 방법을 알아볼 수 있습니다.