Генерация паузы с использованием прерываний

Прерывания — это особый вид инструмента , который может приостановить цикл генерации и вызова инструмента LLM, чтобы вернуть вам управление. Когда вы будете готовы, вы можете возобновить генерацию, отправив ответы , которые LLM обрабатывает для дальнейшей генерации.

Наиболее распространенные варианты использования прерываний делятся на несколько категорий:

  • Человек в цикле: позволяет пользователю интерактивного ИИ уточнять необходимую информацию или подтверждать действие LLM до его завершения, обеспечивая определенную степень безопасности и уверенности.
  • Асинхронная обработка: запуск асинхронной задачи, которую можно выполнить только вне канала, например отправка уведомления об утверждении проверяющему-человеку или запуск длительного фонового процесса.
  • Выход из автономной задачи: предоставление модели возможности пометить задачу как выполненную в рабочем процессе, который может выполнять длинную серию вызовов инструментов.

Прежде чем начать

Во всех примерах, описанных здесь, предполагается, что вы уже настроили проект с установленными зависимостями Genkit. Если вы хотите запустить примеры кода на этой странице, сначала выполните действия, описанные в руководстве по началу работы .

Прежде чем погружаться слишком глубоко, вы также должны быть знакомы со следующими понятиями:

Обзор прерываний

На высоком уровне так выглядит прерывание при взаимодействии с LLM:

  1. Вызывающее приложение отправляет LLM запрос. Приглашение включает список инструментов, в том числе по крайней мере один для прерывания, который LLM может использовать для генерации ответа.
  2. LLM генерирует либо полный ответ, либо запрос вызова инструмента в определенном формате. Для LLM вызов прерывания выглядит как вызов любого другого инструмента.
  3. Если LLM вызывает инструмент прерывания, библиотека Genkit автоматически приостанавливает генерацию, а не немедленно передает ответы обратно в модель для дополнительной обработки.
  4. Разработчик проверяет, выполнен ли вызов прерывания, и выполняет любую задачу, необходимую для сбора информации, необходимой для ответа на прерывание.
  5. Разработчик возобновляет генерацию, передавая модели ответ на прерывание. Это действие вызывает возврат к шагу 2.

Определите прерывания, реагирующие вручную

Самый распространенный вид прерывания позволяет LLM запросить разъяснения у пользователя, например, задав вопрос с несколькими вариантами ответов.

В этом случае используйте метод defineInterrupt() экземпляра 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()
});

Обратите внимание, что outputSchema прерывания соответствует данным ответа, которые вы предоставите, а не тому, что будет автоматически заполнено функцией инструмента.

Используйте прерывания

Прерывания передаются в массив tools при создании контента, как и другие типы инструментов. Вы можете передавать как обычные инструменты, так и прерывания в один и тот же вызов generate :

генерировать

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

Подскажите файл

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

Затем вы можете выполнить приглашение в своем коде следующим образом:

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

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

Чат

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 немедленно возвращает ответ при получении вызова инструмента прерывания.

Реагировать на прерывания

Если вы передали одно или несколько прерываний в вызов генерации, вам необходимо проверить ответ на прерывания, чтобы можно было их обработать:

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

Реакция на прерывание осуществляется с использованием опции resume при последующем вызове generate с обязательной передачей существующей истории. В каждом инструменте есть метод .respond() который помогает сконструировать ответ.

После возобновления модель повторно входит в цикл генерации, включая выполнение инструмента, до тех пор, пока он не завершится или не будет вызвано другое прерывание:

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

Инструменты с перезапускаемыми прерываниями

Другой распространенной моделью прерываний является необходимость подтвердить действие, предлагаемое LLM, прежде чем фактически его выполнить. Например, платежное приложение может потребовать, чтобы пользователь подтверждал определенные виды переводов.

В этом случае вы можете использовать стандартный метод defineTool чтобы добавить пользовательскую логику, определяющую, когда запускать прерывание и что делать, когда прерывание перезапускается с дополнительными метаданными.

Определите перезапускаемый инструмент

Каждый инструмент имеет доступ к двум специальным помощникам во втором аргументе определения реализации:

  • interrupt : при вызове этот метод генерирует исключение особого типа, которое перехватывается для приостановки цикла генерации. Вы можете предоставить дополнительные метаданные в виде объекта.
  • resumed : когда запрос прерванной генерации перезапускается с использованием опции {resume: {restart: ...}} (см. ниже), этот помощник содержит метаданные, предоставленные при перезапуске.

Например, если вы создаете платежное приложение, вам может потребоваться подтвердить это у пользователя, прежде чем совершать перевод, превышающий определенную сумму:

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

В этом примере при первом выполнении (когда resumed не определено) инструмент проверяет, превышает ли сумма 100 долларов США, и в случае положительного результата вызывает прерывание. При втором выполнении он ищет статус в предоставленных новых метаданных и выполняет передачу или возвращает ответ об отклонении, в зависимости от того, одобрен он или отклонен.

Перезапустить инструменты после прерывания

Инструменты прерываний дают вам полный контроль над:

  1. Когда первоначальный запрос инструмента должен вызвать прерывание.
  2. Когда и стоит ли возобновлять цикл генерации.
  3. Какую дополнительную информацию предоставить инструменту при возобновлении работы.

В примере, показанном в предыдущем разделе, приложение может попросить пользователя подтвердить прерванный запрос, чтобы убедиться, что сумма перевода в порядке:

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