Middleware

Middleware הוא המימוש הקונקרטי של ה-Harness

בפרק הראשון בנושא הזה קבענו ש-createAgent בונה בפועל את אותו "הארנס" שנלמד בנושא AI Agents — model, tools, ו-middleware. middleware הוא בדיוק החלק השלישי הזה: המנגנון שמאפשר להתערב בלולאה האגנטית עצמה, לא רק לספק לה כלים או מודל.

כל מה שראינו בפרק ה-Harness (הרשאות, sandboxing, תקציבים, לוגים) הוא דוגמה קונקרטית למה ש-middleware יכול לממש בקוד LangChain אמיתי.

ה-hook lifecycle: איפה middleware מתערב

middleware נתלה בנקודות קבועות לאורך הלולאה האגנטית: לפני שקוראים למודל, אחרי שהמודל מחזיר תשובה, לפני שמפעילים כלי, ואחרי שהכלי מחזיר תוצאה. בכל נקודה כזו middleware יכול לקרוא את המצב הנוכחי, לשנות אותו, או — כפי שנראה בהמשך — לעצור את הפעולה לגמרי.

שמות ה-hooks המדויקים והחתימה שלהם תלויים בגרסת LangChain שבה אתם משתמשים — כדאי לבדוק את התיעוד הרשמי לפרטים העדכניים; הדוגמאות בפרק הזה ממחישות את העיקרון, לא מצטטות API קפוא בזמן.

Middleware מובנה מראש

LangChain מגיע עם קטגוריות שלמות של middleware מוכן לשימוש, בלי לכתוב אותו בעצמכם: guardrails לזיהוי מידע רגיש (PII) וסינון תוכן, fault tolerance עם retries ו-fallbacks כשקריאה למודל נכשלת, וsteering ל-human-in-the-loop — עצירה לאישור אנושי לפני פעולה מסוימת, בדיוק כמו שראינו ברמת העיקרון בפרק ה-Harness.

היתרון של middleware מובנה מראש הוא שאין צורך לממש מחדש לוגיקה גנרית כמו retry-with-backoff או זיהוי PII בכל פרויקט — מוסיפים אותו לרשימת ה-middleware ומקבלים את ההתנהגות מוכנה.

כתיבת middleware מותאם אישית

כשצריך התנהגות ספציפית לפרויקט — לוגים בפורמט מסוים, מדדים, או כלל עסקי ייחודי — כותבים middleware משלכם: אובייקט עם פונקציות ל-hooks הרלוונטיים, שמועבר לרשימת middleware ב-createAgent.

TypeScript
const loggingMiddleware = {
  beforeModel: async ({ messages }: { messages: Message[] }) => {
    console.log(`שולח ${messages.length} הודעות למודל`);
  },
  afterToolCall: async ({ toolName, result }: { toolName: string; result: unknown }) => {
    console.log(`כלי ${toolName} החזיר:`, result);
  },
};

const agent = createAgent({
  model: "anthropic:claude-sonnet-5",
  tools: [getWeather],
  middleware: [loggingMiddleware],
});

דוגמה מלאה: חסימת כלי מסוכן בלי אישור

בפרק ה-Harness בנושא AI Agents ראינו דוגמת JSON של agent עם הרשאות מוגבלות (code-review-worker עם scope שאוסר כתיבה ורשת). middleware הוא המקום שבו אכיפה כזו קורית בפועל בקוד: לפני שכלי רץ, ה-middleware יכול לבדוק את הפרמטרים שלו ולחסום את הקריאה אם היא חורגת מהמדיניות שהוגדרה — בלי לסמוך על כך שהמודל 'יבחר' לא לקרוא לכלי.

בדוגמה הבאה beforeToolCall בודק בקשת זיכוי גדולה מדי, וזורק שגיאה במקום לתת לכלי לרוץ — בדיוק אותו עקרון של least privilege ו-approval gates שכבר נלמד, רק הפעם אכוף ב-middleware אמיתי.

TypeScript
const approvalMiddleware = {
  beforeToolCall: async ({
    toolName,
    args,
  }: {
    toolName: string;
    args: Record<string, unknown>;
  }) => {
    if (toolName === "issue_refund" && Number(args.amount) > 1000) {
      throw new Error("זיכוי מעל 1000 ש\"ח דורש אישור אנושי לפני ביצוע");
    }
  },
};

const agent = createAgent({
  model: "anthropic:claude-sonnet-5",
  tools: [getWeather, issueRefundTool],
  middleware: [approvalMiddleware],
});

שאלות נפוצות

באיזה סדר רצים כמה middleware?

לפי הסדר ברשימה שמעבירים ל-createAgent, אבל הכיוון יכול להיות שונה בין hooks שלפני פעולה לבין hooks שאחריה: בדרך כלל ה"לפני" רצים לפי סדר הרשימה וה"אחרי" בסדר הפוך, כמו שכבות שעוטפות זו את זו. הפרטים המדויקים תלויים בגרסה, ולכן כשהסדר חשוב (למשל סינון PII לפני לוגים) כדאי לבדוק בתיעוד.

מה ההבדל בין חסימה ב-middleware לבין בדיקה בתוך הכלי עצמו?

שתיהן עובדות, אבל middleware מתאים למדיניות שחלה על כמה כלים או על כל ה-agent — PII, תקציב, אישור אנושי — ומרכז אותה במקום אחד. בדיקה בתוך הכלי מתאימה לחוקים שהם חלק מהלוגיקה העסקית שלו. בכלים רגישים כדאי את שתיהן: גם אם middleware יוסר בטעות, הכלי עדיין לא יבצע פעולה אסורה.

האם middleware מאט את ה-agent?

middleware שעושה בדיקה מקומית, כמו השוואת סכום, מוסיף זמן זניח. middleware שקורא למודל או לשירות חיצוני — למשל זיהוי תוכן עם מודל נוסף או סיכום היסטוריה — מוסיף קריאה בכל סיבוב, ולכן כדאי למדוד את ההשפעה שלו ב-traces (פרק 10).