Formazione
Modulo
Fornire competenze dell'agente basato su intelligenza artificiale - Training
Informazioni sul modo in cui le funzioni native possono essere combinate con le richieste per eseguire attività personalizzate con Semantic Kernel SDK.
Questo browser non è più supportato.
Esegui l'aggiornamento a Microsoft Edge per sfruttare i vantaggi di funzionalità più recenti, aggiornamenti della sicurezza e supporto tecnico.
La funzionalità più potente di completamento della chat è la possibilità di chiamare funzioni dal modello. In questo modo è possibile creare un chatbot in grado di interagire con il codice esistente, rendendo possibile automatizzare i processi aziendali, creare frammenti di codice e altro ancora.
Con il kernel semantico si semplifica il processo di utilizzo della chiamata di funzione descrivendo automaticamente le funzioni e i relativi parametri al modello e quindi gestendo la comunicazione avanti e indietro tra il modello e il codice.
Quando si usa la chiamata a funzioni, tuttavia, è consigliabile comprendere cosa accade in realtà dietro le quinte in modo da poter ottimizzare il codice e sfruttare al meglio questa funzionalità.
Nota
La sezione seguente descrive il funzionamento delle chiamate di funzione automatica in Semantic Kernel. La chiamata automatica della funzione è il comportamento predefinito nel kernel semantico, ma è anche possibile richiamare manualmente le funzioni se si preferisce. Per ulteriori informazioni sull'invocazione manuale delle funzioni, fare riferimento all'articolo sull'invocazione delle funzioni .
Quando si effettua una richiesta a un modello con chiamata di funzione abilitata, il kernel semantico esegue i passaggi seguenti:
# | Procedi | Description |
---|---|---|
1 | Serializzare le funzioni | Tutte le funzioni disponibili (e i relativi parametri di input) nel kernel vengono serializzate usando lo schema JSON. |
2 | Inviare messaggi e funzioni al modello | Le funzioni serializzate (e la cronologia della chat corrente) vengono inviate al modello come parte dell'input. |
3 | Il modello elabora l'input | Il modello elabora l'input e genera una risposta. La risposta può essere un messaggio di chat o una o più chiamate di funzione. |
4 | Gestire la risposta | Se la risposta è un messaggio di chat, viene restituita al chiamante. Se la risposta è una chiamata di funzione, tuttavia, Semantic Kernel estrae il nome della funzione e i relativi parametri. |
5 | Richiamare la funzione | Il nome e i parametri della funzione estratti vengono usati per richiamare la funzione nel kernel. |
6 | Restituire il risultato della funzione | Il risultato della funzione viene quindi inviato di nuovo al modello come parte della cronologia delle chat. I passaggi da 2 a 6 vengono quindi ripetuti fino a quando il modello non restituisce un messaggio di chat o viene raggiunto il numero massimo di iterazione. |
Il diagramma seguente illustra il processo di chiamata di funzione:
La sezione seguente userà un esempio concreto per illustrare il funzionamento delle chiamate di funzione in pratica.
Si supponga di avere un plug-in che consente a un utente di ordinare una pizza. Il plug-in ha le funzioni seguenti:
get_pizza_menu
: restituisce un elenco di pizze disponibiliadd_pizza_to_cart
: aggiunge una pizza al carrello dell'utenteremove_pizza_from_cart
: rimuove una pizza dal carrello dell'utenteget_pizza_from_cart
: restituisce i dettagli specifici di una pizza nel carrello dell'utenteget_cart
: restituisce il carrello corrente dell'utentecheckout
: estrae il carrello dell'utenteIn C# il plug-in potrebbe essere simile al seguente:
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);
}
}
Si aggiungerà quindi questo plug-in al kernel come segue:
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 le funzioni con l'attributo KernelFunction
verranno serializzate e inviate al modello. In questo modo è possibile avere funzioni helper che non sono esposte al modello.
In Python il plug-in potrebbe essere simile al seguente:
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)
Si aggiungerà quindi questo plug-in al kernel come segue:
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 le funzioni con la kernel_function
decorator verranno serializzate e inviate al modello. In questo modo è possibile avere funzioni helper che non sono esposte al modello.
Quando si usa la chiamata automatica di funzioni in KernelFunctions, alcuni nomi di parametro vengono riservati e ricevono una gestione speciale. Questi nomi riservati consentono di accedere automaticamente agli oggetti chiave necessari per l'esecuzione della funzione.
I nomi dei parametri seguenti sono riservati:
kernel
service
execution_settings
arguments
Durante la chiamata di funzione, il metodo gather_function_parameters
controlla ogni parametro. Se il nome del parametro corrisponde a uno dei nomi riservati, viene popolato con oggetti specifici:
kernel
: Iniettato con l'oggetto kernel.service
: popolato con il servizio di intelligenza artificiale selezionato in base agli argomenti forniti.execution_settings
: contiene le impostazioni pertinenti all'esecuzione della funzione.arguments
: riceve l'intero set di argomenti del kernel passati durante la chiamata.Questa progettazione garantisce che questi parametri vengano gestiti automaticamente, eliminando la necessità di estrazione o assegnazione manuale.
Si consideri l'esempio seguente:
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!"
In Java il plug-in potrebbe essere simile al seguente:
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));
}
}
Si aggiungerà quindi questo plug-in al kernel come segue:
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 le funzioni con l'annotazione DefineKernelFunction
verranno serializzate e inviate al modello. In questo modo è possibile avere funzioni helper che non sono esposte al modello.
Quando si crea un kernel con , OrderPizzaPlugin
il kernel serializzerà automaticamente le funzioni e i relativi parametri. Questa operazione è necessaria in modo che il modello possa comprendere le funzioni e i relativi input.
Per il plug-in precedente, le funzioni serializzate sono simili alle seguenti:
[
{
"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": []
}
}
}
]
Ecco alcuni aspetti da notare che possono influire sulle prestazioni e sulla qualità del completamento della chat:
Dettaglio dello schema delle funzioni: la serializzazione delle funzioni da usare per il modello non è gratuita. Più dettagliato è lo schema, maggiore è il numero di token che il modello deve elaborare, che può rallentare il tempo di risposta e aumentare i costi.
Suggerimento
Mantenere le funzioni il più semplice possibile. Nell'esempio precedente si noterà che non tutte le funzioni hanno descrizioni in cui il nome della funzione è autoesplicativo. Questo è intenzionale per ridurre il numero di token. I parametri sono anche mantenuti semplici; qualsiasi elemento che il modello non deve conoscere (ad esempio cartId
o paymentId
) viene mantenuto nascosto. Queste informazioni vengono invece fornite dai servizi interni.
Nota
L'unica cosa che non è necessario preoccuparsi è la complessità dei tipi restituiti. Si noterà che i tipi restituiti non vengono serializzati nello schema. Ciò è dovuto al fatto che il modello non deve conoscere il tipo restituito per generare una risposta. Nel passaggio 6, tuttavia, vedremo come i tipi di ritorno eccessivamente dettagliati possano influire sulla qualità del completamento della chat.
Tipi di parametri: con lo schema è possibile specificare il tipo di ogni parametro. Questo aspetto è importante per il modello per comprendere l'input previsto. Nell'esempio precedente il size
parametro è un'enumerazione e il toppings
parametro è una matrice di enumerazioni. Questo consente al modello di generare risposte più accurate.
Suggerimento
Evitare, se possibile, di usare string
come tipo di parametro. Il modello non può dedurre il tipo di stringa, che può causare risposte ambigue. Usare invece enumerazioni o altri tipi (ad esempio, int
, float
e tipi complessi) laddove possibile.
Parametri obbligatori: è anche possibile specificare i parametri necessari. Questo è importante per il modello per comprendere quali parametri sono effettivamente necessari per il funzionamento della funzione. Più avanti nel passaggio 3, il modello userà queste informazioni per fornire le informazioni minime necessarie per chiamare la funzione.
Suggerimento
Contrassegnare solo i parametri necessari se sono effettivamente necessari. Ciò consente alle funzioni di chiamare il modello in modo più rapido e accurato.
Descrizioni delle funzioni: le descrizioni delle funzioni sono facoltative, ma consentono al modello di generare risposte più accurate. In particolare, le descrizioni possono indicare al modello cosa aspettarsi dalla risposta poiché il tipo restituito non viene serializzato nello schema. Se il modello usa funzioni in modo non corretto, è anche possibile aggiungere descrizioni per fornire esempi e indicazioni.
Ad esempio, nella funzione la get_pizza_from_cart
descrizione indica all'utente di usare questa funzione invece di basarsi sui messaggi precedenti. Questo è importante perché il carrello potrebbe essere cambiato dopo l'ultimo messaggio.
Suggerimento
Prima di aggiungere una descrizione, chiedere se il modello necessita di queste informazioni per generare una risposta. In caso contrario, è consigliabile evitare di ridurre il livello di dettaglio. È sempre possibile aggiungere descrizioni in un secondo momento se il modello ha difficoltà a usare correttamente la funzione.
Nome del plug-in: come si può vedere nelle funzioni serializzate, ogni funzione ha una name
proprietà . Il kernel semantico usa il nome del plug-in per spazi dei nomi delle funzioni. Questo è importante perché consente di avere più plug-in con funzioni con lo stesso nome. Ad esempio, si potrebbero avere plug-in per più servizi di ricerca, ognuno con la propria search
funzione. Assegnando nomi alle funzioni, è possibile evitare conflitti e semplificare la comprensione della funzione da chiamare dal modello.
Sapendo questo, è necessario scegliere un nome di plug-in univoco e descrittivo. Nell'esempio precedente il nome del plug-in è OrderPizza
. Ciò rende chiaro che le funzioni sono correlate all'ordinamento della pizza.
Suggerimento
Quando si sceglie un nome di plug-in, è consigliabile rimuovere parole superflue, ad esempio "plug-in" o "service". In questo modo è possibile ridurre il livello di dettaglio e semplificare la comprensione del nome del plug-in per il modello.
Nota
Per impostazione predefinita, il delimitatore per il nome della funzione è -
. Anche se funziona per la maggior parte dei modelli, alcuni di essi possono avere requisiti diversi, ad esempio Gemini. Questa operazione viene presa in considerazione automaticamente dal kernel, tuttavia è possibile che vengano visualizzati nomi di funzione leggermente diversi nelle funzioni serializzate.
Dopo aver serializzato le funzioni, vengono inviate al modello insieme alla cronologia della chat corrente. In questo modo il modello può comprendere il contesto della conversazione e le funzioni disponibili.
In questo scenario, è possibile immaginare l'utente che chiede all'assistente di aggiungere una pizza al carrello:
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!");
È quindi possibile inviare la cronologia delle chat e le funzioni serializzate al modello. Il modello userà queste informazioni per determinare il modo migliore per rispondere.
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_content(
chat_history=history,
settings=execution_settings,
kernel=kernel,
)
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
Il kernel deve essere passato al servizio per usare la chiamata di funzione. Questo perché i plug-in sono registrati con il kernel e il servizio deve sapere quali plug-in sono disponibili.
Con la cronologia delle chat e le funzioni serializzate, il modello può determinare il modo migliore per rispondere. In questo caso, il modello riconosce che l'utente vuole ordinare una pizza. È probabile che il modello voglia chiamare la add_pizza_to_cart
funzione, ma poiché sono stati specificati le dimensioni e i condimenti come parametri obbligatori, il modello chiederà all'utente queste informazioni:
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?"
Poiché il modello vuole che l'utente risponda successivamente, il kernel semantico interromperà la chiamata automatica delle funzioni e restituirà il controllo all'utente. A questo punto, l'utente può rispondere con le dimensioni e i condimenti della pizza che vogliono ordinare:
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();
Ora che il modello ha le informazioni necessarie, ora può chiamare la add_pizza_to_cart
funzione con l'input dell'utente. Dietro le quinte, aggiunge un nuovo messaggio alla cronologia delle chat simile al seguente:
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "OrderPizzaPlugin-add_pizza_to_cart",
"arguments": "{\n\"size\": \"Medium\",\n\"toppings\": [\"Cheese\", \"Pepperoni\"]\n}"
}
}
]
Suggerimento
È consigliabile ricordare che ogni argomento necessario deve essere generato dal modello. Ciò significa che i token di spesa per generare la risposta. Evitare argomenti che richiedono molti token (ad esempio un GUID). Si noti, ad esempio, che per int
.pizzaId
Chiedere al modello di inviare un numero da uno a due cifre è molto più semplice rispetto alla richiesta di un GUID.
Importante
Questo passaggio rende la chiamata di funzione così potente. In precedenza, gli sviluppatori di app di intelligenza artificiale dovevano creare processi separati per estrarre funzioni di riempimento finalità e slot. Con la chiamata di funzione, il modello può decidere quando chiamare una funzione e quali informazioni fornire.
Quando il kernel semantico riceve la risposta dal modello, verifica se la risposta è una chiamata di funzione. In caso affermativo, il kernel semantico estrae il nome della funzione e i relativi parametri. In questo caso, il nome della funzione è OrderPizzaPlugin-add_pizza_to_cart
e gli argomenti sono le dimensioni e i condimenti della pizza.
Con queste informazioni, il kernel semantico può effettuare il marshalling degli input nei tipi appropriati e passarli alla add_pizza_to_cart
funzione in OrderPizzaPlugin
. In questo esempio gli argomenti hanno origine come stringa JSON, ma vengono deserializzati dal kernel semantico in un'enumerazione PizzaSize
e in un oggetto List<PizzaToppings>
.
Nota
Il marshalling degli input nei tipi corretti è uno dei vantaggi principali dell'uso del kernel semantico. Tutto il contenuto del modello viene fornito come oggetto JSON, ma il kernel semantico può deserializzare automaticamente questi oggetti nei tipi corretti per le funzioni.
Dopo il marshalling degli input, il Semantic Kernel aggiungerà anche la chiamata di funzione alla cronologia delle 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"]})
)
]
)
)
Il kernel semantico per Java gestisce la funzione che chiama in modo diverso da C# e Python quando il comportamento di chiamata dello strumento di richiamo automatico è false. Non si aggiunge il contenuto della chiamata di funzione alla cronologia delle chat; piuttosto, l'applicazione viene resa responsabile della chiamata delle chiamate di funzione. Passare alla sezione successiva , "Invoke the function", per un esempio di gestione delle chiamate di funzione in Java quando il richiamo automatico è false.
Una volta che il kernel semantico ha i tipi corretti, può infine richiamare la add_pizza_to_cart
funzione. Poiché il plug-in usa l'inserimento delle dipendenze, la funzione può interagire con servizi esterni come pizzaService
e userContext
per aggiungere la pizza al carrello dell'utente.
Non tutte le funzioni avranno esito positivo, tuttavia. Se la funzione ha esito negativo, il kernel semantico può gestire l'errore e fornire una risposta predefinita al modello. In questo modo il modello può comprendere cosa è andato storto e decidere di riprovare o generare una risposta all'utente.
Suggerimento
Per assicurarsi che un modello possa essere corretto in modo automatico, è importante fornire messaggi di errore che comunicano chiaramente cosa è andato storto e come risolverlo. Ciò consente al modello di ripetere la chiamata di funzione con le informazioni corrette.
Nota
Il kernel semantico richiama automaticamente le funzioni per impostazione predefinita. Tuttavia, se si preferisce gestire manualmente la chiamata di funzione, è possibile abilitare la modalità di chiamata manuale della funzione. Per altre informazioni su come eseguire questa operazione, vedere l'articolo relativo alla chiamata di funzione.
Dopo aver richiamato la funzione, il risultato della funzione viene restituito al modello come parte della cronologia delle chat. In questo modo il modello può comprendere il contesto della conversazione e generare una risposta successiva.
Dietro le quinte, Semantic Kernel aggiunge un nuovo messaggio alla cronologia delle chat dal ruolo dello strumento simile al seguente:
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\"] } ] }"
)
]
)
)
Se il richiamo automatico è disabilitato nel comportamento di chiamata dello strumento, un'applicazione Java deve richiamare le chiamate di funzione e aggiungere il risultato della funzione come AuthorRole.TOOL
messaggio alla cronologia delle 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()));
});
Si noti che il risultato è una stringa JSON che il modello deve quindi elaborare. Come in precedenza, il modello dovrà spendere i token che usano queste informazioni. Questo è il motivo per cui è importante mantenere i tipi restituiti il più semplice possibile. In questo caso, il ritorno include solo i nuovi articoli aggiunti al carrello, non l'intero carrello.
Suggerimento
Essere il più conciso possibile con i tuoi ritorni. Se possibile, restituire solo le informazioni necessarie al modello o riepilogare le informazioni usando un altro prompt LLM prima di restituirlo.
Dopo che il risultato viene restituito al modello, il processo viene ripetuto. Il modello elabora la cronologia delle chat più recente e genera una risposta. In questo caso, il modello potrebbe chiedere all'utente se vuole aggiungere un'altra pizza al carrello o se vuole eseguire il check-out.
Nell'esempio precedente è stato illustrato come un LLM può chiamare una singola funzione. Spesso questo può essere lento se è necessario chiamare più funzioni in sequenza. Per velocizzare il processo, diversi LLM supportano chiamate di funzione parallele. In questo modo l'LLM può chiamare più funzioni contemporaneamente, velocizzando il processo.
Ad esempio, se un utente vuole ordinare più pizze, l'LLM può chiamare la add_pizza_to_cart
funzione per ogni pizza contemporaneamente. Ciò può ridurre significativamente il numero di round trip al LLM e velocizzare il processo di ordinamento.
Ora che si è appreso come funziona la chiamata di funzioni, è possibile procedere con le informazioni su come configurare vari aspetti della chiamata di funzione che corrispondono meglio agli scenari specifici passando al passaggio successivo:
Formazione
Modulo
Fornire competenze dell'agente basato su intelligenza artificiale - Training
Informazioni sul modo in cui le funzioni native possono essere combinate con le richieste per eseguire attività personalizzate con Semantic Kernel SDK.