Wstrzymywanie generowania za pomocą przerw

Przerwania to specjalny rodzaj narzędzia, które może wstrzymać pętlę generowania i wywołania narzędzia w LLM, aby przekazać kontrolę z powrotem użytkownikowi. Gdy będziesz gotowy, możesz wznowić generowanie, wysyłając odpowiedzi, które LLM przetworzy dalej.

Najczęstsze zastosowania przerw można podzielić na kilka kategorii:

  • Człowiek w pętli: użytkownik interakcji z AI może wyjaśnić potrzebne informacje lub potwierdzić działanie LLM przed jego zakończeniem, zapewniając w ten sposób bezpieczeństwo i zaufanie.
  • Przetwarzanie asynchroniczne: rozpoczęcie zadania asynchronicznego, które można wykonać tylko poza kanałem, na przykład wysłanie powiadomienia o zatwierdzeniu do ludzkiego weryfikatora lub uruchomienie długotrwałego procesu w tle.
  • Wyjście z zadania autonomicznego: udostępnienie modelowi sposobu oznaczania zadania jako ukończonego w ramach przepływu pracy, który może przechodzić przez długą serię wywołań narzędzi.

Zanim zaczniesz

Wszystkie opisane tutaj przykłady zakładają, że masz już skonfigurowany projekt z zainstalowanymi zależnościami Genkit. Jeśli chcesz uruchomić przykłady kodu na tej stronie, najpierw wykonaj czynności opisane w przewodniku Początki.

Zanim zaczniesz się zagłębiać, zapoznaj się z tymi pojęciami:

Omówienie przerw

Ogólnie rzecz biorąc, przerwanie podczas interakcji z modelem LLM wygląda tak:

  1. Aplikacja wywołująca wysyła żądanie do LLM. Prompt zawiera listę narzędzi, w tym co najmniej 1 na potrzeby przerwania, którego LLM może użyć do wygenerowania odpowiedzi.
  2. Model LLM generuje pełną odpowiedź lub żądanie wywołania narzędzia w określonym formacie. Dla LLM wywołanie przerwania wygląda jak każde inne wywołanie narzędzia.
  3. Jeśli duży model językowy wywoła narzędzie do przerywania, biblioteka Genkit automatycznie wstrzyma generowanie, zamiast natychmiast przekazywać odpowiedzi z powrotem do modelu w celu dodatkowego przetwarzania.
  4. Programista sprawdza, czy wywołanie przerwania zostało wykonane, i wykonuje odpowiednie czynności, aby zebrać informacje potrzebne do odpowiedzi na przerwanie.
  5. Deweloper wznawia generowanie, przekazując modelowi odpowiedź na przerwanie. To działanie powoduje powrót do kroku 2.

Definiowanie przerw w reakcji ręcznej

Najczęstszy rodzaj przerwania pozwala LLM poprosić użytkownika o doprecyzowanie odpowiedzi, na przykład poprzez zadanie pytania wielokrotnego wyboru.

W tym przypadku użyj metody defineInterrupt() instancji 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()
});

Pamiętaj, że outputSchema przerwania odpowiada podanym przez Ciebie danym odpowiedzi, a nie wartościom automatycznie wypełnianym przez funkcję narzędzia.

Używanie przerw

Przerwy są przekazywane do tablicy tools podczas generowania treści, tak jak w przypadku innych typów narzędzi. Do tego samego generate wywołania możesz przekazywać zarówno zwykłe narzędzia, jak i przerwy:

wygeneruj

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

Plik z promptami

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

Następnie możesz uruchomić prompt w kodzie w ten sposób:

```ts
// assuming prompt file is named partyPlanner.prompt
const partyPlanner = ai.prompt('partyPlanner');

const response = await partyPlanner({ partyType: 'birthday' });
```

Czat

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 natychmiast zwraca odpowiedź po otrzymaniu wywołania narzędzia przerwania.

Odpowiadanie na przerwy

Jeśli do generowania wywołania zostało przekazane co najmniej 1 przerwanie, musisz sprawdzić odpowiedź na przerwanie, aby móc je obsłużyć:

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

Odpowiedź na przerwanie jest wykonywana za pomocą opcji resume w kolejne generate wywołanie, z uwzględnieniem istniejącej historii. Każde narzędzie ma metodę .respond(), która pomaga tworzyć odpowiedź.

Po wznowieniu model ponownie przechodzi przez pętlę generowania, w tym wykonywanie narzędzia, aż do zakończenia lub uruchomienia innego przerwania:

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

Narzędzia z możliwością ponownego przerwania

Innym typowym wzorcem przerwania jest konieczność potwierdzenia działania, które sugeruje LLM, przed jego wykonaniem. Aplikacja do płatności może na przykład wymagać od użytkownika potwierdzenia niektórych rodzajów przelewów.

W tym przypadku możesz użyć standardowej metody defineTool, aby dodać niestandardową logikę dotyczącą tego, kiedy ma nastąpić przerwanie i co zrobić, gdy zostanie ono włączone ponownie z dodatkowymi metadanymi.

Definiowanie narzędzia do ponownego uruchamiania

Każde narzędzie ma dostęp do 2 specjalnych narzędzi pomocniczych w 2 argumencie definicji implementacji:

  • interrupt: po wywołaniu ta metoda zgłasza specjalny rodzaj wyjątku, który jest przechwycony, aby wstrzymać pętlę generowania. Możesz podać dodatkowe metadane jako obiekt.
  • resumed: gdy żądanie z przerwanego generowania zostanie wznowione za pomocą opcji {resume: {restart: ...}} (patrz poniżej), ten pomocnik zawiera metadane podane podczas wznawiania.

Jeśli na przykład tworzysz aplikację do płatności, możesz poprosić użytkownika o potwierdzenie przed przeniesieniem kwoty przekraczającej określony limit:

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

W tym przykładzie podczas pierwszego uruchomienia (gdy parametr resumed jest nieokreślony) narzędzie sprawdza, czy kwota przekracza 100 USD, i w takim przypadku powoduje przerwanie. Podczas drugiego uruchomienia sprawdza stan w dostarczonych nowych metadanych i przeprowadza przelew lub zwraca odpowiedź odrzucenia, w zależności od tego, czy stan to „zatwierdzono” czy „odrzucono”.

Ponowne uruchamianie narzędzi po przerwie

Narzędzia do przerywania zapewniają pełną kontrolę nad:

  1. Kiedy początkowe żądanie narzędzia powinno wywołać przerwanie.
  2. Kiedy i czy wznowić pętlę generowania.
  3. jakie dodatkowe informacje należy podać narzędziu podczas wznawiania procesu;

W przykładzie przedstawionym w poprzedniej sekcji aplikacja może poprosić użytkownika o potwierdzenie przerwanego żądania, aby upewnić się, że kwota przelewu jest prawidłowa:

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