중단은 LLM 생성 및 도구 호출 루프를 일시중지하여 제어를 다시 사용자에게 반환할 수 있는 특수한 종류의 도구입니다. 준비가 되면 LLM이 추가 생성을 위해 처리하는 답장을 전송하여 생성을 재개할 수 있습니다.
인터럽트의 가장 일반적인 용도는 다음과 같은 몇 가지 카테고리로 나뉩니다.
- 인간 참여: 대화형 AI의 사용자가 필요한 정보를 명확히 하거나 LLM의 작업이 완료되기 전에 확인하여 어느 정도의 안전성과 신뢰성을 제공할 수 있습니다.
- 비동기 처리: 사람 검토자에게 승인 알림을 보내거나 장기 실행 백그라운드 프로세스를 시작하는 등 비대역으로만 완료할 수 있는 비동기 작업을 시작합니다.
- 자율 작업 종료: 긴 일련의 도구 호출을 반복할 수 있는 워크플로에서 작업을 완료로 표시하는 방법을 모델에 제공합니다.
시작하기 전에
여기에 설명된 모든 예에서는 Genkit 종속 항목이 설치된 프로젝트를 이미 설정했다고 가정합니다. 이 페이지의 코드 예시를 실행하려면 먼저 시작하기 가이드의 단계를 완료하세요.
너무 깊이 들어가기 전에 다음 개념도 숙지해야 합니다.
- AI 모델로 콘텐츠 생성
- 입력 및 출력 스키마를 정의하는 Genkit의 시스템입니다.
- 도구 호출의 일반적인 메서드입니다.
인터럽트 개요
대략적으로 LLM과 상호작용할 때 인터럽트는 다음과 같이 표시됩니다.
- 호출 애플리케이션은 요청을 포함하여 LLM에 메시지를 표시합니다. 프롬프트에는 LLM이 응답을 생성하는 데 사용할 수 있는 인터럽트용 도구를 하나 이상 포함한 도구 목록이 포함됩니다.
- LLM은 특정 형식의 전체 응답 또는 도구 호출 요청을 생성합니다. LLM에 인터럽트 호출은 다른 도구 호출과 마찬가지로 보입니다.
- LLM이 인터럽트 도구를 호출하면 Genkit 라이브러리는 추가 처리를 위해 응답을 즉시 모델에 다시 전달하는 대신 생성을 자동으로 일시중지합니다.
- 개발자는 인터럽트 호출이 이루어지는지 확인하고 인터럽트 응답에 필요한 정보를 수집하는 데 필요한 모든 작업을 실행합니다.
- 개발자는 인터럽트 응답을 모델에 전달하여 생성을 재개합니다. 이 작업을 실행하면 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달러를 초과하는지 확인하고 초과하는 경우 인터럽트를 트리거합니다. 두 번째 실행에서는 제공된 새 메타데이터에서 상태를 찾고 승인 또는 거부 여부에 따라 이전을 실행하거나 거부 응답을 반환합니다.
중단 후 도구 다시 시작
인터럽트 도구를 사용하면 다음을 완전히 제어할 수 있습니다.
- 초기 도구 요청이 인터럽트를 트리거해야 하는 경우
- 생성 루프를 재개할 시점 및 여부입니다.
- 재개 시 도구에 제공할 추가 정보입니다.
이전 섹션에 나온 예시에서 애플리케이션은 사용자에게 이전 금액이 적절한지 확인하기 위해 중단된 요청을 확인해 달라고 요청할 수 있습니다.
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);