Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La característica más eficaz de finalización del chat es la capacidad de llamar a funciones desde el modelo. Esto le permite crear un bot de chat que pueda interactuar con el código existente, lo que permite automatizar procesos empresariales, crear fragmentos de código, etc.
Con el kernel semántico, simplificamos el proceso de uso de llamadas de función mediante la descripción automática de las funciones y sus parámetros en el modelo y, a continuación, se controla la comunicación de ida y vuelta entre el modelo y el código.
Sin embargo, cuando se usa la llamada a funciones, es bueno comprender lo que sucede realmente en segundo plano para que pueda optimizar el código y aprovechar la mayor parte de esta característica.
Llamada automática de funciones: cómo funciona
Nota:
En la sección siguiente se describe cómo funciona la llamada a funciones automáticas en kernel semántico. La llamada automática a funciones es el comportamiento predeterminado en kernel semántico, pero también puede invocar manualmente funciones si lo prefiere. Para obtener más información sobre la invocación manual de funciones, consulte el artículo de invocación de funciones.
Al realizar una solicitud a un modelo con una llamada de función habilitada, el kernel semántico realiza los pasos siguientes:
| # | Paso | Descripción |
|---|---|---|
| 1 | Serializar funciones | Todas las funciones disponibles (y sus parámetros de entrada) en el kernel se serializan mediante el esquema JSON. |
| 2 | Envío de mensajes y funciones al modelo | Las funciones serializadas (y el historial de chat actual) se envían al modelo como parte de la entrada. |
| 3 | El modelo procesa la entrada | El modelo procesa la entrada y genera una respuesta. La respuesta puede ser un mensaje de chat o una o varias llamadas de función. |
| 4 | Control de la respuesta | Si la respuesta es un mensaje de chat, se devuelve al autor de la llamada. Sin embargo, si la respuesta es una llamada de función, el kernel semántico extrae el nombre de la función y sus parámetros. |
| 5 | Invocación de la función | El nombre de la función extraída y los parámetros se usan para invocar la función en el kernel. |
| 6 | Devolver el resultado de la función | El resultado de la función se devuelve al modelo como parte del historial de chat. Los pasos 2-6 se repiten hasta que el modelo devuelva un mensaje de chat o se haya alcanzado el número máximo de iteración. |
En el diagrama siguiente se muestra el proceso de llamada a funciones:
En la sección siguiente se usará un ejemplo concreto para ilustrar cómo funciona la llamada de funciones en la práctica.
Ejemplo: Pedir una pizza
Supongamos que tiene un complemento que permite al usuario pedir una pizza. El complemento tiene las siguientes funciones:
-
get_pizza_menu: devuelve una lista de pizzas disponibles. -
add_pizza_to_cart: agrega una pizza al carro del usuario. -
remove_pizza_from_cart: quita una pizza del carro del usuario. -
get_pizza_from_cart: devuelve los detalles específicos de una pizza en el carro del usuario. -
get_cart: devuelve el carro actual del usuario. -
checkout: verifica el carrito del usuario
En C#, el complemento podría tener este aspecto:
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);
}
}
A continuación, agregaría este complemento al kernel de la siguiente manera:
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();
Nota:
Solo se serializarán las funciones con el KernelFunction atributo y se enviarán al modelo. Esto le permite tener funciones auxiliares que no están expuestas al modelo.
En Python, el complemento podría tener este aspecto:
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)
A continuación, agregaría este complemento al kernel de la siguiente manera:
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")
Nota:
Solo se serializarán las funciones con el kernel_function decorador y se enviarán al modelo. Esto le permite tener funciones auxiliares que no están expuestas al modelo.
Nombres de parámetros reservados para llamadas a funciones automáticas
Al usar llamadas automáticas a funciones en KernelFunctions, determinados nombres de parámetros están reservados y reciben un control especial. Estos nombres reservados permiten acceder automáticamente a los objetos de clave necesarios para la ejecución de funciones.
Nombres reservados
Los siguientes nombres de parámetro están reservados:
kernelserviceexecution_settingsarguments
Cómo funcionan
Durante la invocación de función, el método gather_function_parameters inspecciona cada parámetro. Si el nombre del parámetro coincide con uno de los nombres reservados, se rellena con objetos específicos:
-
kernel: se inserta con el objeto kernel. -
service: se rellena con el servicio de IA seleccionado en función de los argumentos proporcionados. -
execution_settings: contiene la configuración pertinente para la ejecución de la función. -
arguments: recibe todo el conjunto de argumentos de kernel pasados durante la invocación.
Este diseño garantiza que estos parámetros se administren automáticamente, lo que elimina la necesidad de extracción o asignación manual.
Ejemplo de uso
Considere el ejemplo siguiente:
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!"
Nombres de parámetros reservados personalizados para llamadas a funciones automáticas
También puede personalizar este comportamiento. Para ello, debe anotar el parámetro que desea excluir de la definición de llamada de función, de la siguiente manera:
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.
Al llamar a esta función, asegúrese de pasar el special_arg parámetro; de lo contrario, generará un error.
response = await kernel.invoke_async(
plugin_name=...,
function_name="GetWeather",
location="Seattle",
special_arg="This is a special argument"
)
O agréguelo al KernelArguments objeto para usarlo para el llamado automático de funciones en un agente como este:
arguments = KernelArguments(special_arg="This is a special argument")
response = await agent.get_response(
messages="what's the weather in Seattle?"
arguments=arguments)
En Java, el complemento podría tener este aspecto:
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));
}
}
A continuación, agregaría este complemento al kernel de la siguiente manera:
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();
Nota:
Solo se serializarán las funciones con la DefineKernelFunction anotación y se enviarán al modelo. Esto le permite tener funciones auxiliares que no están expuestas al modelo.
1) Serialización de las funciones
Al crear un kernel con OrderPizzaPlugin, el kernel serializará automáticamente las funciones y sus parámetros. Esto es necesario para que el modelo pueda comprender las funciones y sus entradas.
Para el complemento anterior, las funciones serializadas tendría el siguiente aspecto:
[
{
"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": []
}
}
}
]
Hay algunas cosas que hay que tener en cuenta aquí que pueden afectar tanto al rendimiento como a la calidad de la finalización del chat:
Verborrea del esquema de función: la serialización de funciones para que las utilice el modelo no es gratuita. Cuanto más detallado sea el esquema, más tokens tiene que procesar el modelo, lo que puede ralentizar el tiempo de respuesta y aumentar los costos.
Sugerencia
Mantenga las funciones lo más sencillas posible. En el ejemplo anterior, observará que no todas las funciones tienen descripciones en las que el nombre de la función se explica automáticamente. Esto es intencional para reducir el número de tokens. Los parámetros también se mantienen sencillos; todo lo que el modelo no debe tener que saber (como o
cartIdpaymentId) se mantienen ocultos. En su lugar, los servicios internos proporcionan esta información.Nota:
Lo único de lo que no tienes que preocuparte es la complejidad de los tipos devueltos. Observará que los tipos devueltos no se serializan en el esquema. Esto se debe a que el modelo no necesita conocer el tipo de valor devuelto para generar una respuesta. Sin embargo, en el paso 6, veremos cómo los tipos de valor devuelto excesivamente detallados pueden afectar a la calidad de la finalización del chat.
Tipos de parámetros : con el esquema, puede especificar el tipo de cada parámetro. Esto es importante para que el modelo comprenda la entrada esperada. En el ejemplo anterior, el
sizeparámetro es una enumeración y eltoppingsparámetro es una matriz de enumeraciones. Esto ayuda al modelo a generar respuestas más precisas.Sugerencia
Evite, siempre que sea posible, el uso
stringcomo tipo de parámetro. El modelo no puede deducir el tipo de cadena, lo que puede provocar respuestas ambiguas. En su lugar, use enumeraciones u otros tipos (por ejemplo,int,floaty tipos complejos) siempre que sea posible.Parámetros obligatorios : también puede especificar qué parámetros son necesarios. Esto es importante para que el modelo comprenda qué parámetros son realmente necesarios para que la función funcione. Luego, en el paso 3, el modelo usará esta información para proporcionar la menor cantidad de información posible necesaria para llamar a la función.
Sugerencia
Solo marque los parámetros según sea necesario si realmente son necesarios. Esto ayuda al modelo a llamar funciones de manera más rápida y precisa.
Descripciones de funciones: las descripciones de funciones son opcionales, pero pueden ayudar al modelo a generar respuestas más precisas. En concreto, las descripciones pueden indicar al modelo qué esperar de la respuesta, ya que el tipo de valor devuelto no se serializa en el esquema. Si el modelo usa funciones de forma incorrecta, también puede agregar descripciones para proporcionar ejemplos e instrucciones.
Por ejemplo, en la
get_pizza_from_cartfunción , la descripción indica al usuario que use esta función en lugar de confiar en mensajes anteriores. Esto es importante porque el carrito puede haber cambiado desde el último mensaje.Sugerencia
Antes de agregar una descripción, pregúntese si el modelo necesita esta información para generar una respuesta. Si no es así, considerar omitirla para reducir la verborrea. Siempre puede agregar descripciones más adelante si el modelo tiene dificultades para usar la función correctamente.
Nombre del complemento: como puede ver en las funciones serializadas, cada función tiene una
namepropiedad . El kernel semántico usa el nombre del complemento para el espacio de nombres de las funciones. Esto es importante porque permite tener varios complementos con funciones del mismo nombre. Por ejemplo, puede tener complementos para varios servicios de búsqueda, cada uno con su propiasearchfunción. Al organizar las funciones por espacios de nombres, puede evitar conflictos y facilitar la comprensión de cuál función debe ser llamada por el modelo.Sabiendo esto, debe elegir un nombre de complemento que sea único y descriptivo. En el ejemplo anterior, el nombre del complemento es
OrderPizza. Esto hace evidente que las funciones están relacionadas con el pedido de pizza.Sugerencia
Al elegir un nombre de complemento, se recomienda quitar palabras superfluas como "plugin" o "service". Esto ayuda a reducir la verbosidad y hace que el nombre del complemento sea más fácil de entender para el modelo.
Nota:
De forma predeterminada, el delimitador del nombre de la función es
-. Aunque esto funciona para la mayoría de los modelos, algunos de ellos pueden tener requisitos diferentes, como Géminis. El kernel se encarga automáticamente de esto, sin embargo, puede que vea nombres de función ligeramente diferentes en las funciones que han sido serializadas.
2) Envío de mensajes y funciones al modelo
Una vez serializadas las funciones, se envían al modelo junto con el historial de chat actual. Esto permite al modelo comprender el contexto de la conversación y las funciones disponibles.
En este escenario, podemos imaginar al usuario que pide al asistente que agregue una pizza a su carro:
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!");
A continuación, podemos enviar este historial de chat y las funciones serializadas al modelo. El modelo usará esta información para determinar la mejor manera de responder.
IChatCompletionService chatCompletion = kernel.GetRequiredService<IChatCompletionService>();
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
ChatResponse response = await chatCompletion.GetChatMessageContentAsync(
chatHistory,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel)
Nota:
En este ejemplo se usa el FunctionChoiceBehavior.Auto() comportamiento, uno de los pocos disponibles. Para obtener más información sobre otros comportamientos de elección de función, consulte el artículo Comportamientos de elección de funciones.
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,
)
Nota:
En este ejemplo se usa el comportamiento FunctionChoiceBehavior.Auto(), uno de los pocos disponibles. Para obtener más información sobre otros comportamientos de elección de función, consulte el artículo Comportamientos de elección de funciones.
ChatCompletionService chatCompletion = kernel.getService(I)ChatCompletionService.class);
InvocationContext invocationContext = InvocationContext.builder()
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false));
List<ChatResponse> responses = chatCompletion.getChatMessageContentsAsync(
chatHistory,
kernel,
invocationContext).block();
Importante
El kernel debe pasarse al servicio para poder usar llamadas a funciones. Esto se debe a que los complementos están registrados con el kernel y el servicio debe saber qué complementos están disponibles.
3) El modelo procesa la entrada
Con el historial de chat y las funciones serializadas, el modelo puede determinar la mejor manera de responder. En este caso, el modelo reconoce que el usuario quiere pedir una pizza. Es probable que el modelo quiera llamar a la add_pizza_to_cart función, pero dado que especificamos el tamaño y los ingredientes como parámetros necesarios, el modelo pedirá al usuario esta información:
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?"
Dado que el modelo quiere que el usuario responda a continuación, el kernel semántico detendrá la llamada automática de funciones y devolverá el control al usuario. En este momento, el usuario puede responder con el tamaño y los ingredientes de la pizza que desea pedir:
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();
Ahora que el modelo tiene la información necesaria, puede llamar a la función con la entrada del usuario add_pizza_to_cart. En segundo plano, agrega un nuevo mensaje al historial de chat que tiene este aspecto:
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "OrderPizzaPlugin-add_pizza_to_cart",
"arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
}
}
]
Sugerencia
Es bueno recordar que el modelo debe generar todos los argumentos que necesite. Esto significa gastar tokens para generar la respuesta. Evite argumentos que requieran muchos tokens (como un GUID). Por ejemplo, observe que usamos un int para el pizzaId. Pedir al modelo que envíe un número de uno a dos dígitos es mucho más fácil que pedir un GUID.
Importante
Este paso es lo que hace que la llamada de función sea tan poderosa. Anteriormente, los desarrolladores de aplicaciones de IA tenían que crear procesos independientes para extraer funciones de intención y llenado de espacios. Con las llamadas a funciones, el modelo puede decidir cuándo llamar a una función y qué información proporcionar.
4) Controlar la respuesta
Cuando el kernel semántico recibe la respuesta del modelo, comprueba si la respuesta es una llamada de función. Si es así, el kernel semántico extrae el nombre de la función y sus parámetros. En este caso, el nombre de la función es OrderPizzaPlugin-add_pizza_to_carty los argumentos son el tamaño y los ingredientes de la pizza.
Con esta información, el Kernel Semántico puede organizar las entradas en los tipos adecuados y pasarlas a la función en el add_pizza_to_cart de OrderPizzaPlugin. En este ejemplo, los argumentos se originan como una cadena JSON, pero se deserializan mediante el Kernel Semántico en una PizzaSize enumeración y un List<PizzaToppings>.
Nota:
Organizar las entradas en los tipos correctos es una de las principales ventajas de usar el Kernel semántico. Todo el contenido del modelo viene como un objeto JSON, pero el kernel semántico puede deserializar automáticamente estos objetos en los tipos correctos para las funciones.
Después de coordinar las entradas, el Kernel Semántico también incluirá la llamada de función en el historial de chat.
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"]})
)
]
)
)
El kernel semántico para Java maneja la llamada de funciones de manera diferente de cómo lo hacen C# y Python cuando el comportamiento de llamada a la herramienta de invocación automática es falso. No se debe agregar contenido de llamadas de función al historial de chats; en su lugar, es responsabilidad de la aplicación invocar las llamadas de función. Vaya a la sección siguiente, "Invocar la función", para obtener un ejemplo de control de llamadas de función en Java cuando la invocación automática es false.
5) Invocar la función
Una vez que el kernel semántico tiene los tipos correctos, finalmente puede invocar la add_pizza_to_cart función . Dado que el complemento usa la inserción de dependencias, la función puede interactuar con servicios externos como pizzaService y userContext agregar la pizza al carro del usuario.
Sin embargo, no todas las funciones se realizarán correctamente. Si se produce un error en la función, el kernel semántico puede controlar el error y proporcionar una respuesta predeterminada al modelo. Esto permite que el modelo comprenda lo que ha ido mal y decida reintentar o generar una respuesta al usuario.
Sugerencia
Para asegurarse de que un modelo puede corregirse automáticamente, es importante proporcionar mensajes de error que comuniquen claramente lo que salió mal y cómo corregirlo. Esto puede ayudar al modelo a reintentar la llamada de función con la información correcta.
Nota:
El kernel semántico invoca automáticamente las funciones de forma predeterminada. Sin embargo, si prefiere administrar manualmente la invocación de funciones, puede habilitar el modo de invocación de función manual. Para obtener más información sobre cómo hacerlo, consulte el artículo de invocación de funciones.
6) Devolver el resultado de la función
Una vez invocada la función, el resultado de la función se devuelve al modelo como parte del historial de chat. Esto permite al modelo comprender el contexto de la conversación y generar una respuesta posterior.
En segundo plano, el Kernel Semántico agrega un nuevo mensaje al historial de chat del rol de herramienta que se ve así:
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\"] } ] }"
)
]
)
)
Si la invocación automática está deshabilitada en el comportamiento de la llamada a la herramienta, una aplicación Java debe invocar las llamadas de función y agregar el resultado de la función como mensaje AuthorRole.TOOL al historial de chat.
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()));
});
Observe que el resultado es una cadena JSON que el modelo necesita procesar. Como antes, el modelo tendrá que gastar tokens al consumir esta información. Este es el motivo por el que es importante mantener los tipos de retorno lo más sencillos posible. En este caso, la devolución solo incluye los nuevos artículos agregados al carro, no todo el carro.
Sugerencia
Sea lo más conciso posible con sus devoluciones. Siempre que sea posible, devuelva solo la información que necesita el modelo o resuma la información utilizando otra indicación de LLM antes de devolverla.
Repita los pasos del 2 al 6
Una vez devuelto el resultado al modelo, el proceso se repite. El modelo procesa el historial de chat más reciente y genera una respuesta. En este caso, el modelo podría preguntar al usuario si desea agregar otra pizza al carrito o si desea realizar la compra.
Llamadas a funciones paralelas
En el ejemplo anterior, se mostró cómo un LLM puede llamar a una sola función. A menudo, esto puede ser lento si necesita llamar a varias funciones en secuencia. Para acelerar el proceso, varias LLM admiten llamadas de función paralelas. Esto permite que el LLM llame a varias funciones a la vez, lo que acelera el proceso.
Por ejemplo, si un usuario quiere pedir varias pizzas, LLM puede llamar a la add_pizza_to_cart función para cada pizza al mismo tiempo. Esto puede reducir significativamente el número de recorridos de ida y vuelta al LLM y acelerar el proceso de ordenación.
Pasos siguientes
Ahora que comprende cómo funciona la llamada a funciones, puede continuar para aprender a configurar varios aspectos de la llamada a funciones que mejor se correspondan con sus escenarios específicos; para ello, vaya al paso siguiente: