Generierung mithilfe von Unterbrechungen pausieren

Unterbrechungen sind eine spezielle Art von Tool, mit dem die LLM-Generierungs- und Tool-Aufrufschleife pausiert werden kann, um die Kontrolle wieder an Sie zurückzugeben. Wenn Sie bereit sind, können Sie die Generierung fortsetzen, indem Sie Antworten senden, die das LLM für die weitere Generierung verarbeitet.

Die häufigsten Verwendungen von Unterbrechungen lassen sich in einige Kategorien unterteilen:

  • Human-in-the-Loop: Der Nutzer einer interaktiven KI kann erforderliche Informationen erläutern oder die Aktion der LLM bestätigen, bevor sie abgeschlossen wird. So wird für ein gewisses Maß an Sicherheit und Vertrauen gesorgt.
  • Asynchrone Verarbeitung:Starten einer asynchronen Aufgabe, die nur außerhalb des Bandes abgeschlossen werden kann, z. B. das Senden einer Genehmigungsbenachrichtigung an einen menschlichen Prüfer oder das Starten eines langwierigen Hintergrundprozesses.
  • Aus einer autonomen Aufgabe aussteigen:Dem Modell wird eine Möglichkeit gegeben, eine Aufgabe in einem Workflow als erledigt zu markieren, der eine lange Reihe von Toolaufrufen durchlaufen kann.

Hinweis

Bei allen hier dokumentierten Beispielen wird davon ausgegangen, dass Sie bereits ein Projekt mit installierten Genkit-Abhängigkeiten eingerichtet haben. Wenn Sie die Codebeispiele auf dieser Seite ausführen möchten, führen Sie zuerst die Schritte in der Anleitung Erste Schritte aus.

Bevor Sie sich näher mit dem Thema befassen, sollten Sie sich mit den folgenden Konzepten vertraut machen:

Unterbrechungen – Übersicht

Im Folgenden wird ein Interrupt bei der Interaktion mit einem LLM allgemein beschrieben:

  1. Die aufrufende Anwendung sendet eine Anfrage an das LLM. Der Prompt enthält eine Liste von Tools, darunter mindestens eines für eine Unterbrechung, mit der das LLM eine Antwort generieren kann.
  2. Das LLM generiert entweder eine vollständige Antwort oder eine Tool-Aufrufanfrage in einem bestimmten Format. Für den LLM sieht ein Interruptaufruf wie jeder andere Toolaufruf aus.
  3. Wenn das LLM ein Unterbrechungstool aufruft, hält die Genkit-Bibliothek die Generierung automatisch an, anstatt die Antworten sofort zur weiteren Verarbeitung an das Modell zurückzugeben.
  4. Der Entwickler prüft, ob ein Interruptaufruf erfolgt, und führt alle erforderlichen Aufgaben aus, um die für die Interruptantwort erforderlichen Informationen zu erfassen.
  5. Der Entwickler setzt die Generierung fort, indem er dem Modell eine Unterbrechungsantwort übergibt. Dadurch werden Sie zu Schritt 2 zurückgeleitet.

Unterbrechungen für manuelle Antworten definieren

Bei der häufigsten Art der Unterbrechung kann der LLM vom Nutzer eine Klarstellung anfordern, z. B. durch eine Multiple-Choice-Frage.

Verwenden Sie für diesen Anwendungsfall die Methode defineInterrupt() der Genkit-Instanz:

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

Die outputSchema eines Interrupts entspricht den von Ihnen bereitgestellten Antwortdaten und nicht einem Wert, der automatisch von einer Toolfunktion eingefügt wird.

Unterbrechungen verwenden

Unterbrechungen werden beim Generieren von Inhalten wie bei anderen Tools an das tools-Array übergeben. Du kannst demselben generate-Aufruf sowohl normale Tools als auch Unterbrechungen übergeben:

generieren

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

Prompt-Datei

---
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.

Anschließend können Sie den Prompt in Ihrem Code so ausführen:

```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 gibt sofort eine Antwort zurück, wenn ein Interrupt-Tool-Aufruf empfangen wird.

Auf Unterbrechungen reagieren

Wenn Sie Ihrem generate-Aufruf eine oder mehrere Unterbrechungen übergeben haben, müssen Sie die Antwort auf Unterbrechungen prüfen, damit Sie sie verarbeiten können:

// 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

Um auf eine Unterbrechung zu reagieren, verwenden Sie die Option resume bei einem nachfolgenden generate-Aufruf. Dabei muss der vorhandene Verlauf übergeben werden. Jedes Tool hat eine .respond()-Methode, die beim Erstellen der Antwort hilft.

Nach der Wiederaufnahme kehrt das Modell in die Generierungsschleife zurück, einschließlich der Toolausführung, bis der Vorgang abgeschlossen ist oder eine weitere Unterbrechung ausgelöst wird:

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

Tools mit neustartfähigen Unterbrechungen

Ein weiteres häufiges Muster für Unterbrechungen ist die Notwendigkeit, eine vom LLM vorgeschlagene Aktion zu bestätigen, bevor sie ausgeführt wird. In einer Zahlungs-App kann es beispielsweise erforderlich sein, dass der Nutzer bestimmte Arten von Überweisungen bestätigt.

Für diesen Anwendungsfall können Sie die Standardmethode defineTool verwenden, um benutzerdefinierte Logik hinzuzufügen, wann eine Unterbrechung ausgelöst werden soll und was passiert, wenn eine Unterbrechung mit zusätzlichen Metadaten erneut gestartet wird.

Neustartbares Tool definieren

Jedes Tool hat im zweiten Argument seiner Implementierungsdefinition Zugriff auf zwei spezielle Helfer:

  • interrupt: Wenn diese Methode aufgerufen wird, wird eine spezielle Art von Ausnahme ausgelöst, die abgefangen wird, um die Generierungsschleife anzuhalten. Sie können zusätzliche Metadaten als Objekt angeben.
  • resumed: Wenn eine Anfrage aus einer unterbrochenen Generierung mit der Option {resume: {restart: ...}} (siehe unten) fortgesetzt wird, enthält dieser Helfer die beim Neustart bereitgestellten Metadaten.

Wenn Sie beispielsweise eine Zahlungs-App entwickeln, sollten Sie den Nutzer vor einer Überweisung über einen bestimmten Betrag informieren:

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 diesem Beispiel prüft das Tool bei der ersten Ausführung (wenn resumed nicht definiert ist), ob der Betrag 100 $überschreitet, und löst bei Überschreitung eine Unterbrechung aus. Bei der zweiten Ausführung sucht es in den neuen bereitgestellten Metadaten nach einem Status und führt die Übertragung aus oder gibt eine Ablehnungsantwort zurück, je nachdem, ob die Transaktion genehmigt oder abgelehnt wurde.

Tools nach Unterbrechung neu starten

Mit Unterbrechungstools haben Sie die vollständige Kontrolle über Folgendes:

  1. Gibt an, wann eine erste Toolanfrage eine Unterbrechung auslösen soll.
  2. Wann und ob der Generierungs-Loop fortgesetzt werden soll.
  3. Welche zusätzlichen Informationen müssen Sie dem Tool bei der Wiederaufnahme zur Verfügung stellen?

Im Beispiel im vorherigen Abschnitt könnte die Anwendung den Nutzer auffordern, die unterbrochene Anfrage zu bestätigen, um sicherzustellen, dass der Überweisungsbetrag korrekt ist:

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