인터럽트를 사용하여 생성 일시중지

중단은 LLM 생성 및 도구 호출 루프를 일시중지하여 제어를 다시 사용자에게 반환할 수 있는 특수한 종류의 도구입니다. 준비가 되면 LLM이 추가 생성을 위해 처리하는 답장을 전송하여 생성을 재개할 수 있습니다.

인터럽트의 가장 일반적인 용도는 다음과 같은 몇 가지 카테고리로 나뉩니다.

  • 인간 참여: 대화형 AI의 사용자가 필요한 정보를 명확히 하거나 LLM의 작업이 완료되기 전에 확인하여 어느 정도의 안전성과 신뢰성을 제공할 수 있습니다.
  • 비동기 처리: 사람 검토자에게 승인 알림을 보내거나 장기 실행 백그라운드 프로세스를 시작하는 등 비대역으로만 완료할 수 있는 비동기 작업을 시작합니다.
  • 자율 작업 종료: 긴 일련의 도구 호출을 반복할 수 있는 워크플로에서 작업을 완료로 표시하는 방법을 모델에 제공합니다.

시작하기 전에

여기에 설명된 모든 예에서는 Genkit 종속 항목이 설치된 프로젝트를 이미 설정했다고 가정합니다. 이 페이지의 코드 예시를 실행하려면 먼저 시작하기 가이드의 단계를 완료하세요.

너무 깊이 들어가기 전에 다음 개념도 숙지해야 합니다.

인터럽트 개요

대략적으로 LLM과 상호작용할 때 인터럽트는 다음과 같이 표시됩니다.

  1. 호출 애플리케이션은 요청을 포함하여 LLM에 메시지를 표시합니다. 프롬프트에는 LLM이 응답을 생성하는 데 사용할 수 있는 인터럽트용 도구를 하나 이상 포함한 도구 목록이 포함됩니다.
  2. LLM은 특정 형식의 전체 응답 또는 도구 호출 요청을 생성합니다. LLM에 인터럽트 호출은 다른 도구 호출과 마찬가지로 보입니다.
  3. LLM이 인터럽트 도구를 호출하면 Genkit 라이브러리는 추가 처리를 위해 응답을 즉시 모델에 다시 전달하는 대신 생성을 자동으로 일시중지합니다.
  4. 개발자는 인터럽트 호출이 이루어지는지 확인하고 인터럽트 응답에 필요한 정보를 수집하는 데 필요한 모든 작업을 실행합니다.
  5. 개발자는 인터럽트 응답을 모델에 전달하여 생성을 재개합니다. 이 작업을 실행하면 2단계로 돌아갑니다.

수동 응답 인터럽트 정의

가장 일반적인 종류의 인터럽트를 사용하면 LLM이 객관식 질문을 하는 등 사용자에게 명확한 설명을 요청할 수 있습니다.

이 사용 사례에서는 Genkit 인스턴스의 defineInterrupt() 메서드를 사용합니다.

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은 인터럽트 도구 호출을 수신하면 즉시 응답을 반환합니다.

인터럽트에 응답

generate 호출에 하나 이상의 인터럽트를 전달한 경우 인터럽트를 처리할 수 있도록 인터럽트에 대한 응답을 확인해야 합니다.

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

인터럽트에 응답하는 작업은 후속 generate 호출에서 resume 옵션을 사용하여 수행되며, 기존 기록을 전달해야 합니다. 각 도구에는 응답을 구성하는 데 도움이 되는 .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);