הפסקות הן סוג מיוחד של כלי שיכול להשהות את הלולאה של יצירת ה-LLM וקריאת הכלי כדי להחזיר לכם את השליטה. כשתהיו מוכנים, תוכלו להמשיך את היצירה על ידי שליחת תשובות ש-LLM מעבד ליצירה נוספת.
השימושים הנפוצים ביותר להפסקות נחלקים לכמה קטגוריות:
- אדם בתהליך: האפשרות של משתמש ב-AI אינטראקטיבי להבהיר את המידע הנדרש או לאשר את הפעולה של ה-LLM לפני שהיא תושלם, כדי לספק מידה מסוימת של בטיחות וביטחון.
- עיבוד אסינכרוני: הפעלת משימה אסינכרונית שאפשר להשלים רק מחוץ למסגרת, למשל שליחת התראה על אישור לבודק אנושי או הפעלת תהליך ארוך טווח ברקע.
- יציאה ממשימה אוטונומית: מתן אפשרות למודל לסמן משימה כ'הושלמה' בתהליך עבודה שעשוי לעבור על סדרה ארוכה של קריאות לכלי.
לפני שמתחילים
כל הדוגמאות שמפורטות כאן מבוססות על ההנחה שכבר הגדרתם פרויקט עם יחסי התלות של Genkit. כדי להריץ את דוגמאות הקוד בדף הזה, צריך לבצע קודם את השלבים שמפורטים במדריך תחילת העבודה.
לפני שמתעמקים יותר מדי, כדאי להכיר גם את המושגים הבאים:
- יצירת תוכן באמצעות מודלים של AI.
- המערכת של Genkit להגדרת סכימות קלט ופלט.
- שיטות כלליות לקריאה לכלי.
סקירה כללית על הפסקות
ברמת העל, כך נראה הפסקה זמנית (interrupt) במהלך אינטראקציה עם LLM:
- אפליקציית הקריאה שולחת בקשה ל-LLM. ההנחיה כוללת רשימה של כלים, כולל לפחות אחד להפרעה, ש-LLM יכול להשתמש בהם כדי ליצור תשובה.
- ה-LLM יוצר תשובה מלאה או בקשה לקריאה לכלי בפורמט ספציפי. בעיני ה-LLM, קריאה להפסקה נראית כמו כל קריאה אחרת לכלי.
- אם ה-LLM קורא לכלי להפסקה, ספריית Genkit משהיית את היצירה באופן אוטומטי במקום להעביר מיד את התשובות חזרה למודל לעיבוד נוסף.
- המפתח בודק אם התקבלה קריאה להפרעה, ומבצע את כל המשימה הנדרשת כדי לאסוף את המידע הנדרש לתשובה להפרעה.
- המפתח ממשיך את היצירה על ידי העברת תגובה להפסקה למודל. הפעולה הזו תגרום לחזרה לשלב 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
לא מוגדר), הכלי בודק אם הסכום חורג מ-400 ש"ח, ומפעיל הפסקה אם כן. בהפעלה השנייה, הוא מחפש סטטוס במטא-נתונים החדשים שסופקו ומבצע את ההעברה או מחזיר תשובה של דחייה, בהתאם לאישור או לדחייה.
הפעלה מחדש של כלים לאחר הפרעה
בעזרת הכלים להפסקות יש לכם שליטה מלאה על:
- מתי בקשה ראשונית לכלי צריכה להפעיל הפסקה.
- מתי ומתי לא להמשיך את לולאת היצירה.
- איזה מידע נוסף צריך לספק לכלי כשממשיכים.
בדוגמה שמוצגת בקטע הקודם, יכול להיות שהאפליקציה תבקש מהמשתמש לאשר את הבקשה שהופסק תהליך ההעברה שלה, כדי לוודא שסכום ההעברה תקין:
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);