Le interruzioni sono un tipo speciale di strumento che può mettere in pausa il loop di generazione e chiamata dello strumento LLM per restituirti il controllo. Quando è tutto pronto, puoi riprendere la generazione inviando risposte che l'LLM elabora per un'ulteriore generazione.
Gli utilizzi più comuni delle interruzioni rientrano in alcune categorie:
- Human-in-the-Loop: consente all'utente di un'AI interattiva di chiarire le informazioni necessarie o confermare l'azione dell'LLM prima del completamento, fornendo una misura di sicurezza e affidabilità.
- Elaborazione asincrona:avvio di un'attività asincrona che può essere completata solo out-of-band, ad esempio l'invio di una notifica di approvazione a un revisore umano o l'avvio di un processo in background di lunga durata.
- Esci da un'attività autonoma: fornisci al modello un modo per contrassegnare un'attività come completata, in un flusso di lavoro che potrebbe eseguire l'iterazione di una lunga serie di chiamate allo strumento.
Prima di iniziare
Tutti gli esempi descritti qui presuppongono che tu abbia già configurato un progetto con le dipendenze Genkit installate. Se vuoi eseguire i esempi di codice in questa pagina, completa prima i passaggi della guida Introduzione.
Prima di addentrarti troppo nel dettaglio, devi anche conoscere i seguenti concetti:
- Generare contenuti con modelli di IA.
- Il sistema di Genkit per la definizione degli schemi di input e output.
- Metodi generali di chiamata degli strumenti.
Panoramica delle interruzioni
In linea di massima, un'interruzione durante l'interazione con un modello LLM ha il seguente aspetto:
- L'applicazione chiamante invia una richiesta all'LLM. Il prompt include un elenco di strumenti, tra cui almeno uno per un'interruzione che l'LLM può utilizzare per generare una risposta.
- L'LLM genera una risposta completa o una richiesta di chiamata allo strumento in un formato specifico. Per l'LLM, una chiamata di interruzione è simile a qualsiasi altra chiamata dello strumento.
- Se l'LLM chiama uno strumento di interruzione, la libreria Genkit mette automaticamente in pausa la generazione anziché riemettere immediatamente le risposte al modello per un'ulteriore elaborazione.
- Lo sviluppatore controlla se viene effettuata una chiamata di interruzione ed esegue qualsiasi attività necessaria per raccogliere le informazioni necessarie per la risposta all'interruzione.
- Lo sviluppatore riprende la generazione passando una risposta di interruzione al modello. Questa azione attiva il ritorno al passaggio 2.
Definire le interruzioni con risposta manuale
Il tipo di interruzione più comune consente all'LLM di richiedere chiarimenti all'utente, ad esempio ponendo una domanda a scelta multipla.
Per questo caso d'uso, utilizza il metodo defineInterrupt()
dell'istanza Genkit:
import { genkit, z } from 'genkit';
import { googleAI, gemini15Flash } from '@genkitai/google-ai';
const ai = genkit({
plugins: [googleAI()],
model: gemini15Flash,
});
const askQuestion = ai.defineInterrupt({
name: 'askQuestion',
description: 'use this to ask the user a clarifying question',
inputSchema: z.object({
choices: z.array(z.string()).describe('the choices to display to the user'),
allowOther: z.boolean().optional().describe('when true, allow write-ins')
}),
outputSchema: z.string()
});
Tieni presente che outputSchema
di un'interruzione corrisponde ai dati di risposta
che fornirai, anziché a un valore che verrà compilato automaticamente
da una funzione dello strumento.
Utilizzare le interruzioni
Le interruzioni vengono passate all'array tools
durante la generazione di contenuti, come accade per altri tipi di strumenti. Puoi passare sia gli strumenti normali che le interruzioni alla stessa chiamata generate
:
genera
const response = await ai.generate({
prompt: 'Ask me a movie trivia question.',
tools: [askQuestion],
});
definePrompt
const triviaPrompt = ai.definePrompt(
{
name: 'triviaPrompt',
tools: [askQuestion],
input: {
schema: z.object({subject: z.string()})
},
prompt: 'Ask me a trivia question about {{subject}}
.',
}
);
const response = await triviaPrompt({ subject: 'computer history' });
File dei prompt
---
tools: [askQuestion]
input:
schema:
partyType: string
---
{{role "system"}}
Use the askQuestion tool if you need to clarify something.
{{role "user"}}
Help me plan a {{partyType}} party next week.
Poi puoi eseguire il prompt nel codice come segue:
```ts
// assuming prompt file is named partyPlanner.prompt
const partyPlanner = ai.prompt('partyPlanner');
const response = await partyPlanner({ partyType: 'birthday' });
```
Chat
const chat = ai.chat({
system: 'Use the askQuestion tool if you need to clarify something.',
tools: [askQuestion],
});
const response = await chat.send('make a plan for my birthday party');
Genkit restituisce immediatamente una risposta alla ricezione di una chiamata dello strumento di interruzione.
Rispondere alle interruzioni
Se hai passato una o più interruzioni alla chiamata generate, devi controllare la risposta per rilevare le interruzioni in modo da poterle gestire:
// you can check the 'finishReason' of the response
response.finishReason === 'interrupted'
// or you can check to see if any interrupt requests are on the response
response.interrupts.length > 0
Per rispondere a un'interruzione, utilizza l'opzione resume
in una chiamata generate
successiva, assicurandoti di passare la cronologia esistente. Ogni strumento ha un metodo .respond()
per aiutarti a costruire la risposta.
Una volta ripreso, il modello rientra nel ciclo di generazione, inclusa l'esecuzione dello strumento, fino al completamento o all'attivazione di un'altra interruzione:
let response = await ai.generate({
tools: [askQuestion],
system: 'ask clarifying questions until you have a complete solution',
prompt: 'help me plan a backyard BBQ',
});
while (response.interrupts.length) {
const answers = [];
// multiple interrupts can be called at once, so we handle them all
for (const question in response.interrupts) {
answers.push(
// use the `respond` method on our tool to populate answers
askQuestion.respond(
question,
// send the tool request input to the user to respond
await askUser(question.toolRequest.input)
)
);
}
response = await ai.generate({
tools: [askQuestion],
messages: response.messages,
resume: {
respond: answers
}
})
}
// no more interrupts, we can see the final response
console.log(response.text);
Strumenti con interruzioni riavviabili
Un altro pattern comune per le interruzioni è la necessità di confermare un'azione suggerita dall'LLM prima di eseguirla effettivamente. Ad esempio, un'app di pagamenti potrebbe chiedere all'utente di confermare determinati tipi di trasferimenti.
Per questo caso d'uso, puoi utilizzare il metodo defineTool
standard per aggiungere logica personalizzata su quando attivare un'interruzione e cosa fare quando un'interruzione viene riavviata con metadati aggiuntivi.
Definire uno strumento riavviabile
Ogni strumento ha accesso a due helper speciali nel secondo argomento della sua definizione di implementazione:
interrupt
: quando viene chiamato, questo metodo genera un tipo speciale di eccezione che viene rilevata per mettere in pausa il ciclo di generazione. Puoi fornire metadati aggiuntivi come oggetto.resumed
: quando una richiesta di una generazione interrotta viene riavviata utilizzando l'opzione{resume: {restart: ...}}
(vedi di seguito), questo helper contiene i metadati forniti al riavvio.
Ad esempio, se stai creando un'app per i pagamenti, potresti voler confermare con l'utente prima di effettuare un trasferimento che superi un determinato importo:
const transferMoney = ai.defineTool({
name: 'transferMoney',
description: 'Transfers money between accounts.',
inputSchema: z.object({
toAccountId: z.string().describe('the account id of the transfer destination'),
amount: z.number().describe('the amount in integer cents (100 = $1.00)'),
}),
outputSchema: z.object({
status: z.string().describe('the outcome of the transfer'),
message: z.string().optional(),
})
}, async (input, {context, interrupt, resumed})) {
// if the user rejected the transaction
if (resumed?.status === "REJECTED") {
return {status: 'REJECTED', message: 'The user rejected the transaction.'};
}
// trigger an interrupt to confirm if amount > $100
if (resumed?.status !== "APPROVED" && input.amount > 10000) {
interrupt({
message: "Please confirm sending an amount > $100.",
});
}
// complete the transaction if not interrupted
return doTransfer(input);
}
In questo esempio, alla prima esecuzione (quando resumed
non è definito), lo strumento controlla se l'importo supera 100 $e, in questo caso, attiva un'interruzione. Alla seconda esecuzione, cerca uno stato nei nuovi metadati forniti ed esegue il trasferimento o restituisce una risposta di rifiuto, a seconda che sia approvato o rifiutato.
Riavviare gli strumenti dopo un'interruzione
Gli strumenti di interruzione ti consentono di avere il pieno controllo su:
- Quando una richiesta iniziale dello strumento deve attivare un'interruzione.
- Quando e se riprendere il ciclo di generazione.
- Quali informazioni aggiuntive fornire allo strumento al momento della ripresa.
Nell'esempio mostrato nella sezione precedente, l'applicazione potrebbe chiedere all'utente di confermare la richiesta interrotta per assicurarsi che l'importo del trasferimento sia corretto:
let response = await ai.generate({
tools: [transferMoney],
prompt: "Transfer $1000 to account ABC123",
});
while (response.interrupts.length) {
const confirmations = [];
// multiple interrupts can be called at once, so we handle them all
for (const interrupt in response.interrupts) {
confirmations.push(
// use the 'restart' method on our tool to provide `resumed` metadata
transferMoney.restart(
interrupt,
// send the tool request input to the user to respond. assume that this
// returns `{status: "APPROVED"}` or `{status: "REJECTED"}`
await requestConfirmation(interrupt.toolRequest.input);
)
);
}
response = await ai.generate({
tools: [transferMoney],
messages: response.messages,
resume: {
restart: confirmations,
}
})
}
// no more interrupts, we can see the final response
console.log(response.text);