使用中斷暫停產生

中斷是一種特殊的工具,可暫停 LLM 生成和工具呼叫迴圈,將控制權交還給您。準備就緒後,您可以傳送 LLM 處理的回覆,藉此恢復產生作業。

中斷最常見的用途可分為以下幾類:

  • 人機協作:讓互動式 AI 使用者在 LLM 完成前,先確認所需資訊或確認 LLM 的動作,以提供一定程度的安全性和信心。
  • 非同步處理:啟動只能在非頻寬範圍內完成的非同步工作,例如向人工審查人員傳送核准通知,或啟動長時間執行的背景程序。
  • 退出自動工作:在可能會重複執行大量工具呼叫的工作流程中,為模型提供一種標示工作已完成的方式。

事前準備

本文所述的所有範例都假設您已設定安裝 Genkit 依附元件的專案。如果您想執行本頁的程式碼範例,請先完成「開始使用」指南中的步驟。

在深入探討之前,您也應熟悉下列概念:

中斷總覽

大致來說,以下是與 LLM 互動時中斷的情況:

  1. 呼叫應用程式會向 LLM 提示要求。提示包含工具清單,其中至少包含一個中斷工具,可供 LLM 用來產生回應。
  2. 大型語言模型會以特定格式產生完整回應或工具呼叫要求。對 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 會在收到中斷工具呼叫時立即傳回回應。

回應中斷

如果您已將一或多個中斷傳遞至產生呼叫,就必須檢查中斷的回應,以便處理中斷:

// 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,並在金額超過 $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);