Skip to content

Latest commit

 

History

History
657 lines (523 loc) · 76.4 KB

GUARDRAILS.md

File metadata and controls

657 lines (523 loc) · 76.4 KB

Guardrails (עברית)

🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW


מקור האמת: src/lib/guardrails/ עדכון אחרון: 2026-08-29 — v3.8.51 (מקור התמליל של Video Bridge מוצהר על ידי הקורא, ועדיין אינו מאומת על ידי השרת — הובהר בהתאם ל-#11661)

מנגנוני הגנה אוכפים בטיחות, מדיניות והתמרות תוכן בגבול שבין OmniRoute לספקים במעלה הזרם. כל מנגנון הגנה יכול לבדוק (ובאופן אופציונלי לדחות, להתמיר או להוסיף הערות אל) מטעני בקשות (preCall) ותגובות במעלה הזרם (postCall).

המערכת פועלת בגישת fail-open: אם מנגנון הגנה זורק חריגה במהלך הביצוע, הרישום מתעד את השגיאה וממשיך למנגנון ההגנה הבא במקום להכשיל את הבקשה. חסימה היא החלטה מפורשת (block: true), ולעולם אינה מתרחשת בטעות.

מנגנוני הגנה מובנים

הרישום טוען אוטומטית שישה מנגנוני הגנה לפי סדר עדיפות בעת הייבוא (ראה registry.ts → registerDefaultGuardrails()):

Priority Name Stage(s) File
5 vision-bridge preCall visionBridge.ts
6 audio-bridge preCall audioBridge.ts
7 video-bridge preCall videoBridge.ts
10 pii-masker pre + post piiMasker.ts
20 prompt-injection preCall promptInjection.ts
95 credential-masker pre + post credentialMasker.ts

מספרי עדיפות נמוכים יותר מופעלים ראשונים.

גשר ראייה (visionBridge.ts) — גשר מודאליות PR-1

מיירט בקשות המכילות תמונות המיועדות למודלים שאינם מודלי ראייה ומנתב מחדש את הבקשה כולה למודל בעל יכולת ראייה, או מחליף את חלקי התמונה בתיאורי טקסט המופקים על ידי מודל ראייה הניתן להגדרה, לפני הקריאה במעלה הזרם. זה מאפשר לספקים מבוססי טקסט בלבד לטפל באופן שקוף במטענים רב-מודאליים.

זרימה:

  1. דלג אם מודל היעד כבר תומך בראייה (אלא אם כן הוא מופיע ברשימת הגשרים הכפויים isVisionBridgeForcedModel).
  2. חלץ חלקי תמונה באמצעות extractImageParts(messages) (visionBridgeHelpers.ts), המאציל סמכות למזהה המדיה המאוחד detectMediaParts() ב-open-sse/utils/mediaParts.ts — מקור האמת היחיד המשותף עם מסנן התאימות המשולב. החילוץ מותר רק לחלקים ברמה העליונה של הצורות ש-replaceImageParts יכול להחזיר (חוזה החילוץ↔החלפה): OpenAI image_url, Anthropic base64 source.type:"base64", Anthropic URL source.type:"url", ו-Responses API input_image. התאמות מקוננות וצורות המציינות בלבד הן חומר למסנן המשולב ולעולם אינן נחלצות. דלג אם לא נמצאו.
  3. פתור תצורת זמן ריצה באמצעות resolveVisionBridgeRuntimeSettings() (src/shared/constants/modalityBridgeDefaults.ts): מפתחות הגדרות modalityBridge* חדשים מנצחים; מפתחות visionBridge* מדור קודם נשארים כגיבוי חד-מחזורי (חלון חזרה לאחור). דלג לפני כל מעבר מדיה כאשר הגשר מושבת.
  4. בורר מצב (modalityBridgeVisionMode, ראה טבלה למטה) מחליט בין ניתוב מחדש לתיאור. ניתוב מחדש מחזיר modifiedPayload כאשר רק ה-model הוחלף, בתוספת מטא { rerouted, fromModel, toModel, imagesKept }.
  5. נתיב תיאור: הגבל תמונות ב-maxImages, הרכב את ההנחיה המודעת למשימה, התייעץ עם מטמון התיאורים, קרא למודל הראייה במקביל (Promise.allSettled), והזרק חלקי טקסט [Image N]: <description> במקומם. תיאור כושל מחזיר null וחלק התמונה המקורי נשמר (#4012) — למעט בנתיב התיאור המשולב כאשר כל התיאורים נכשלו, שם מקור עליון שאינו ראייה מקבל במקום זאת קטע (unavailable — no vision-capable provider connected) (#8430).
  6. החזר modifiedPayload + מטא (imagesProcessed, descriptions, processingTimeMs, visionModel).

בורר מצב (modalityBridgeVisionMode)

Mode Default Behavior
auto ✔ היוריסטיקה מדור קודם, ללא שינוי (#6640/#7204): מודלים שאינם משולבים/auto/ מנותבים מחדש למודל הראייה הטוב ביותר אלא אם למודל המקורי כבר יש אישורים שמישים (אז מתארים); יעדי קומבו תמיד מתארים.
describe תמיד תאר — בלוק הניתוב מחדש מדלג לחלוטין; המודל שנבחר על ידי המשתמש תמיד עונה.
reroute כפה ניתוב מחדש: מנגנון ההגנה של שמירת מודל עם אישורים עוקף. מנגנון ההגנה של אישורי יעד הניתוב מחדש עדיין חל — כאשר אין יעד ראייה שמיש, הבקשה עוברת לתיאור כך שתמונות גולמיות לעולם לא יגיעו לקצה עורפי מבוסס טקסט בלבד (#8430).

מצבים כפויים מבצעים קיצור דרך לפני שההיוריסטיקה האוטומטית פועלת; התנהגות auto זהה בבתים למנגנון ההגנה שלפני PR-1.

הנחיית תיאור מודעת למשימה (modalityBridgeVisionTaskAware)

ברירת מחדל נכון. composeVisionPrompt() (visionBridgeHelpers.ts) מצרף את הטקסט של הודעת המשתמש האחרונה (קטומה ל-500 תווים) להנחיית התיאור הבסיסית, ומכוון את התיאור למה שהמשתמש באמת שאל (תבנית codex-vision-proxy) ומבקש ממודל הראייה לתמלל טקסט גלוי. כאשר הדגל כבוי — או ללא טקסט משתמש — ההנחיה הבסיסית משמשת ללא שינוי.

הבקשה התואמת ל-OpenAI של לולאת התיאור העצמית (callVisionModelSingle() ב-visionBridgeHelpers.ts) תמיד מבקשת image_url.detail: "high" — ללא תנאי, עבור כל קורא/ספק, ולא מוגבלת על ידי כל אות לקוח. דגימה בפירוט נמוך פוגעת בדיוק ה-OCR עבור משימת תמלול הטקסט בדיוק זו שההנחיה הזו מבקשת, ולכן קריאת התיאור עצמה תמיד מבקשת פירוט גבוה ללא קשר לרמת הפירוט שבה השתמשה הבקשה הנכנסת המקורית. זה משפיע רק על גוף בקשת התיאור הפנימית; זה לא משנה איך OmniRoute מעביר את ה-image_url.detail של הקורא בבקשה הראשית — ברירת מחדל זו מיושמת בנפרד, ורק עבור לקוחות OpenCode שזוהו, ב-defaultImageDetail() (open-sse/handlers/chatCore/upstreamBody.ts). לענף פורמט ה-Anthropic wire של לולאת התיאור העצמית אין שדה detail והוא אינו מושפע מאף אחת מברירות המחדל.

הגבלת פלט תיאור (modalityBridgeVisionMaxChars)

מפתח ברירת מחדל טווח
modalityBridgeVisionMaxChars 0 0 או 100–50000

0 (ברירת מחדל) פירושו ללא הגבלה — התיאור המוחזר על ידי callVisionModel() מועבר ללא שינוי, ושומר על ההתנהגות הקיימת. כל ערך בטווח 100–50000 קוטע את התיאור עם סיומת … לפני שהוא משולב בחזרה כ-[Image N]: <description> (VisionBridgeGuardrail.preCall() ב-src/lib/guardrails/visionBridge.ts). הגדל זאת עבור משימות OCR עתירות פרטים שבהן המודל במורד הזרם זקוק לתמלול המלא; הקטן זאת כדי להגביל את השימוש באסימונים במודלי ראייה "פטפטניים". שדה לוח המחוונים נמצא בלוח המתקדם של לשונית ה-Vision (modality-bridge-max-chars ב-ModalityBridgeVisionTab.tsx) ומגביל כל ערך בין 1 ל-99 עד לרצפה של 100 תוך השארת 0 מפורש ללא שינוי — 0 הוא ערך Zod חוקי בזכות עצמו (z.union([z.literal(0), z.number().int().min(100).max(50000)])), לא רק ברירת המחדל "לא מוגדר".

מטמון תיאור (modalityBridge/bridgeCache.ts)

מטמון LRU + TTL בזיכרון עבור פלטי תיאור, משותף לכל התהליך. מפתח = sha256(imageRef + composedPrompt + configuredBridgeModel) עם מסגור באורך-קידומת (ללא התנגשויות גבולות שדות). רכיב המודל הוא המודל המגשר המוגדר, לא המודל שענה בפועל — callVisionModel עשוי לחזור פנימית, וקישור לפי ניסיון יפצל את המטמון. תיאורים כושלים לעולם אינם נשמרים במטמון. הגדרות:

מפתח ברירת מחדל טווח
modalityBridgeCacheEnabled true —
modalityBridgeCacheTtlMinutes 60 1–1440
modalityBridgeCacheMaxEntries 200 10–5000

נורמליזציה של תמונות מרוחקות (תיאור לולאה עצמית/אחזור base64)

כאשר הגשר מאחזר תמונה מרוחקת בעצמו — קריאת ה-Anthropic describe העצמית והמרת ה-base64 בפורמט claude-wire-format (ensureBase64ImagesForClaudeWire), שתיהן דרך fetchRemoteImageAsDataUri() ב-visionBridgeHelpers.ts — ה-URI של הנתונים המתקבל עובר דרך normalizeDataUri() (open-sse/utils/imageNormalize.ts) לפני שהוא מוטמע בבקשת מודל הראייה. תמונות גדולות מדי מוקטנות לקצה ארוך של 2048px (תואם את מגבלת שינוי הגודל ש-OpenAI/Anthropic כבר מיישמים בצד השרת), מה שמפחית את בייטי ההעלאה/השהיה מבלי לשנות את מה שמודל הראייה רואה. שינוי גודל משתמש ב-sharp, שנטען באמצעות ייבוא דינמי: בפלטפורמה שבה הקובץ הבינארי המקורי שלו נכשל בטעינה, normalizeDataUri() לעולם אינו זורק שגיאה — הוא חוזר למעבר ישיר של הבייטים המקוריים, כך שנתיב התיאור/המרת base64 תמיד ממשיך לעבוד. בייטים שאינם תמונה (אחזור שלא החזיר תמונה ניתנת לפענוח) גם הם מועברים ללא שינוי. נורמליזציה זו מוגבלת לתמונות שהגשר מאחזר עבור הקריאה העצמית שלו — היא לעולם אינה מיושמת על מטען ה-passthrough הגולמי של הקורא, בהתאם לעיקרון המוטציה של "הצטרפות בלבד" (כלל קשיח מס' 20).

סכימת הגדרות + מיגרציה

מפתחות ה-modalityBridge* החדשים מאומתים ב-Zod ב-updateSettingsSchema (src/shared/validation/settingsSchemas.ts): modalityBridgeVisionEnabled, modalityBridgeVisionMode, modalityBridgeVisionModel, modalityBridgeVisionTaskAware, modalityBridgeVisionPrompt, modalityBridgeVisionTimeout, modalityBridgeVisionMaxImages, modalityBridgeVisionMaxChars, שלישיית ה-modalityBridgeCache*, וקבוצת ה-modalityBridgeAudio* המשמשת את גשר האודיו. מיגרציה 141_modality_bridge_settings.sql מעתיקה ערכי visionBridge* קיימים למפתחות החדשים התואמים (אידמפוטנטית, לעולם אינה דורסת ערך modalityBridge* שהוגדר על ידי מפעיל); המפתחות הישנים נשארים מקובלים כגיבוי קריאה למחזור שחרור אחד.

כותרת שקיפות + סטטיסטיקות

תגובות שעברו טרנספורמציה של תיאור נושאות x-omniroute-modality-bridge: image->text;model=<visionModel>;parts=<n> (נבנה על ידי buildModalityBridgeHeader() ב-modalityBridge/bridgeStats.ts, מוטבע על ידי withModalityBridgeHeader() ב-src/sse/handlers/chatHelpers.ts). בקשות שהופנו מחדש אינן מקבלות כותרת — המטען לא שונה והחלפת המודל כבר גלויה בשדה model בגוף התגובה.

GET /api/modality-bridge/stats (אימות ניהול, באותה רמה כמו GET /api/settings) מחזיר את המונים בזיכרון לכל מודליות { attempts, successes, bridged, cacheHits, failures, totalLatencyMs, latencySamples, averageLatencyMs, lastUsedAt } עבור vision, audio, ו-video. averageLatencyMs משתמש ב-latencySamples, לא בכל הניסיונות, כמכנה שלו; פעולה ללא תזמון אינה מייצרת דגימה של אפס מילישניות. bridged נשאר הכינוי התואם לאחור עבור המרות מוצלחות; ניסיונות כושלים אינם מגדילים אותו. המונים מתאפסים עם הפעלה מחדש של התהליך לפי תכנון (טלמטריה, לא חשבונאות).

תצורת לוח מחוונים

דף לוח המחוונים הייעודי הוא /dashboard/settings/modality-bridge. לשוניות ה-Vision, Audio ו-Video הניתנות לגישה באמצעות URL שומרות על פרמטרי שאילתה בעת החלפת ערך ה-tab. לשונית ה-Vision חושפת הפעלה, מצב, בחירת מודל (כולל ברירת המחדל האוטומטית), הנחיה מודעת משימה, מגבלות מתקדמות של פסק זמן/תמונה/אורך תיאור/מטמון, מונים בזמן ריצה, ובקשת דוגמה מוגנת. לשונית ה-Audio גם פעילה: היא חושפת הפעלה, בורר מודלים STT-בלבד עם Auto, מגבלות פסק זמן/מקסימום קליפים, מונים של אודיו, ובדיקת דוגמה input_audio. לשונית ה-Video פונקציונלית: היא מדווחת על מצב הריצה של FFmpeg/ffprobe — אחד מארבעה מצבי ממשק משתמש מפורשים (unknown בזמן שהבדיקה מתבצעת או לא הצליחה להסתיים, restricted במארח לוח מחוונים שאינו לולאה חוזרת שבו הבדיקה מדלגת בצד הלקוח, unavailable לאחר בדיקה ואישור חסר, או available עם גרסאות FFmpeg/ffprobe) — שומרת על מגבלות הפעלה/מודל/פריים/וידאו/פסק זמן, מסננת את בורר המודלים למודלים בעלי יכולת ראייה, וחושפת מונים של וידאו.

כרטיס ה-Vision Bridge הקודם תחת הגדרות AI הוא קישור תאימות לדף החדש; הוא כבר לא מחזיק עותק שני של הטופס. ספקי מדיה מקשרים גם זרימות עבודה של Image-to-Text ו-Speech-to-Text ללשוניות ה-Modality Bridge המתאימות מבלי להסיר את סביבת ה-Speech-to-Text הקיימת.

עקיפת אישור לולאה עצמית: כאשר קריאת התיאור מנותבת דרך לולאה עצמית /v1 של OmniRoute (מודל ספק לא סטנדרטי), הבקשה המשנית שולחת x-omniroute-admission-bypass: internal ומאומתת באמצעות אישור הלולאה העצמית שנפתר — ה-sk_omniroute המקומי במצב מקומי, או מפתח הסביבה OMNIROUTE_API_KEY / ROUTER_API_KEY שהוגדר על ידי המפעיל (#1350) כך שפריסות REQUIRE_API_KEY=true עדיין יכולות להריץ את קריאת התיאור. העקיפה מכובדת רק עבור אישורים מדויקים אלה, כך שלקוחות חיצוניים אינם יכולים להשתמש בכותרת כדי לדלג על אישור.

ברירות המחדל הישנות נמצאות ב-src/shared/constants/visionBridgeDefaults.ts; ברירות המחדל החדשות של מצב/מודעות משימה/מטמון ורזולבר ההגדרות נמצאים ב- src/shared/constants/modalityBridgeDefaults.ts. מנגנון ההגנה חושף אפשרות בנאי deps כך שבדיקות יכולות להזריק יישומי getSettings ו-callVisionModel מדומים.

גשר אודיו (audioBridge.ts) — גשר מודליות PR-3

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

זרימה:

  1. פתור את supportsAudio באמצעות getResolvedModelCapabilities(). מטא-נתונים מפורשים של רישום ספקים מנצחים, ואז מטא-נתונים סטטיים של מודל, ואז modalities_input מסונכרנים. רשימת קלט מוצהרת ללא audio היא false; אין עדות ליכולת נשארת null. גם false וגם null מפעילים את הגשר השמרני, בעוד ש-true עוקף אותו.
  2. פתור את הגדרות modalityBridgeAudio* וחלץ חלקי אודיו ברמה העליונה הניתנים לחיבור מכל הודעה באמצעות מזהה detectMediaParts() המשותף. צורות חוט נתמכות הן OpenAI input_audio, audio_url, ו-source.media_type: "audio/*". אודיו מקונן מזוהה לצורך ניתוב אך אינו מוסר על ידי נתיב החיבור. העבודה מוגבלת על ידי modalityBridgeAudioMaxClips; חלקים מאוחרים יותר נשארים ללא שינוי.
  3. כבד provider/model מוגדר, או תן ל-selectAudioBridgeModel() לעבור על AUDIO_TRANSCRIPTION_PROVIDERS בסדר קטלוג יציב ולבחור את המודל הראשון עם אישור ספק פעיל שמיש.
  4. callAudioTranscription() ממיר אודיו base64/data-URI ל-file מרובה חלקים, או מוריד audio_url מרוחק דרך מנגנון ההגנה היוצא הציבורי בלבד עם הצמדת DNS ומגבלה של 25 מגה-בייט. לאחר מכן הוא שולח POST את הקובץ והמודל הנבחר ללולאה העצמית המקומית /v1/audio/transcriptions, מאומת באמצעות resolveSelfLoopBearer(). נתיב התמלול הקיים מבצע בדיקת אישורים רגילה, טיפול בהשהיה/מגבלת קצב, ושיגור ספק.
  5. קריאות מוצלחות מחליפות את חלקיהן ב-[Audio N]: <transcript>. קריאות רצות עם Promise.allSettled: כשל פרטני שומר על חלק האודיו המקורי (#4012 חוזה). אם כל הקריאות נכשלות והיעד מוכח כ-supportsAudio === false, החלקים הופכים ל-[Audio N]: (unavailable — no STT provider connected) (#8430 חוזה). עבור יעד לא ידוע (null), תוצאה של כל הכשלים נשארת ללא שינוי. יעד מוכח טקסט בלבד ללא אישור STT שמיש מקבל את אותו קטע קוד מפורש מבלי לבצע קריאת רשת.

תמלילים מוצלחים משתמשים במטמון LRU/TTL של Modality Bridge ברמת התהליך. המפתח משלב את הפניית האודיו, תווית הפעולה היציבה audio-transcription, ומודל STT נבחר; כשלים לעולם אינם נשמרים במטמון. ניסיונות אודיו מעדכנים את המונים המשותפים bridged, cacheHits, failures, ו-lastUsedAt. תגובות שעברו טרנספורמציה נושאות x-omniroute-modality-bridge: audio->text;model=<sttModel>;parts=<n>; בקשות שלא נגעו בהן אינן מקבלות מקטע Audio Bridge.

הגדרות זמן ריצה מגובות במסד נתונים ומאומתות על ידי Zod:

מפתח ברירת מחדל טווח
modalityBridgeAudioEnabled true —
modalityBridgeAudioModel "" Auto או STT ID
modalityBridgeAudioTimeout 60000 1000–300000
modalityBridgeAudioMaxClips 3 1–10

המטמון המשותף נשאר נשלט על ידי modalityBridgeCacheEnabled, modalityBridgeCacheTtlMinutes, ו-modalityBridgeCacheMaxEntries.

גשר וידאו (videoBridge.ts, videoBridgePipeline.ts)

מיירט חלקי וידאו ברמה העליונה ב-Chat Completions messages ו-Responses API input לפני שנקרא יעד ללא תמיכה ידועה בווידאו מקורי. הצורות הנתמכות הן input_video, video_url, video_source, כתובות HTTPS, ו-data:video/*;base64,... URI של נתונים. שמות קבצים רגילים בטקסט אינם מטופלים כסרטון.

VideoBridgeGuardrail.preCall (videoBridge.ts) אחראי על מעבר הבקשה, בדיקת היכולת/מדיניות, צבירה לכל בקשה, ומטען התגובה. עבודה לכל סרטון — רכישה, מטמון התוצאות המלאות, תיאור רצף פריימים (המשלב כל תמלול שמע שהוצהר על ידי המתקשר), ומדדים/ביטול/ניקוי לכל ניסיון — מוסתרת מאחורי processVideoPart ב-videoBridgePipeline.ts, הנקראת פעם אחת לכל חלק וידאו בתוך הלולאה של preCall. מודול זה גם מגדיר את גבולות הפורט המפורשים VideoMediaBrokerPort (רכישת בתים וחילוץ פריימים מדוגמים), VideoAudioTranscriptionPort (שילוב תמלול שמע שהוצהר על ידי המתקשר עם הכתוביות המדוגמות), ו- VideoDrilldownPort (גבול ההתמדה של פירוט הפריימים; עדיין לא מחובר ל-processVideoPart — רק הנתיב הנפרד /api/modality-bridge/video/drilldown כותב כיום רשומות פירוט).

נתיב הבקשה הציבורי /v1 לעולם אינו מייבא או מפעיל תהליך משנה. סרטונים מרוחקים מורידים תחת מגבלה של 50 MiB; לסרטוני base64 מוטבעים יש מגבלה שמרנית של 36 MiB מפוענחים לכל סרטון, כך שמעטפת המודל/הודעות/מסגור יכולה להישאר בתוך מגבלת הקבלה של בקשת JSON הציבורית של 50 MiB. אורך מוטבע ואומדני גודל מפוענחים נבדקים לפני הקצאה. HTTPS נדרש בכתובת ה-URL המרוחקת הראשונית ובכל הפניה מחדש, באמצעות השומר היוצא הקיים רק לציבור עם הצמדת DNS. הבתים עוברים אז את גבול המתווך הפנימי המדויק POST /api/modality-bridge/video/extract. נתיב זה הוא גם LOCAL_ONLY וגם SPAWN_CAPABLE, מקבל רק בקשה מאומתת לכל תהליך, לולאה מהימנה, ולעולם אינו מקבל URL, נתיב מערכת קבצים, קובץ הפעלה או רשימת ארגומנטים. צינור גודל גוף ה-API וקורא הגוף המצטבר של המטפל אוכפים באופן עצמאי מגבלת קלט מתווך של 50 MiB. התור המוגבל שלו מריץ חילוץ אחד בכל פעם, מאפשר ארבע עבודות ממתינות, ומגביל קלט ממתין ל-100 MiB.

בתוך המתווך, ffprobe קורא קובץ מקומי פרטי; רשימת ההיתרים בפורמט קבוע אינה כוללת פורמטים של רשימות השמעה ומניפסטים. עבור קונטיינרים מותרים ממשפחת MOV, הפניות לנתוני MOV חיצוניים נשארות מושבתות כברירת מחדל, והפקודה הקבועה אינה בוחרת בהן. גם ffprobe וגם ffmpeg משתמשים ברשימת ההיתרים של פרוטוקול file בלבד, בחוט אחד, במערכי ארגומנטים קבועים, ללא מעטפת, ובקבצי הפעלה שנפתרו מ-PATH. זרמי כיסוי תמונה מצורפים אינם מועמדים להפעלה. כל הזרמים הניתנים להפעלה חייבים לקיים את המגבלות, וזרם ברירת מחדל מפורש מועדף לפני החזרה דטרמיניסטית למדד הנמוך ביותר. סרטונים מוגבלים ל-600 שניות, 8,192 פיקסלים לכל מימד, ו-33,554,432 פיקסלים מקור. FFmpeg דוגם 1–16 פריימי JPEG אמצעיים, מקטין את הקצה הארוך לכל היותר 1,024 פיקסלים ללא הגדלת קלטים קטנים יותר, ולעולם אינו מקבל URL. הדגימה היא uniform כברירת מחדל. המדיניות האופציונלית scene_aware והניסיונית segment_aware מבצעות מעבר FFmpeg קבוע נוסף על הזרם המקומי שכבר אומת, בוחרות חותמות זמן של סצנות showinfo מוגבלות, ונופלות באופן דטרמיניסטי לאותן נקודות אמצע אחידות במקרה של כשל גלאי, פסק זמן, פלט לא תקין, או קבוצת מועמדים ריקה. מצב מודע-קטעים מקצה דגימות אמצע באופן פרופורציונלי למרווחי הסצנות המאומתים; עדויות מודעות-קטעים והתנהגות חזרה מפורטות להלן. מגבלת 16 הפריימים הקשיחה מיושמת לאחר הבחירה בכל מדיניות. כאשר לבקשה מודעת-סצנה יש תקציב של פריים אחד בלבד, היא משתמשת בנקודת האמצע האחידה של חלון הווידאו המלא הפעיל או חלון המיקוד ומדווחת policyEffective: uniform: פריים סצנה בודד שנבחר אינו יכול לשמר את שני הקצוות הזמניים. מתקשר יכול לספק באופן אופציונלי חלון מיקוד סופי (שניות start/end); גבולות נצמדים למשך המדיה, חלונות הפוכים או לא סופיים נדחים, וכל מדיניות הדגימה מבוצעת רק בתוך המרווח המנורמל. החלון המתקבל נכלל במטא-נתונים של הדגימה ובקידומת התיאור הלא מהימנה, כך שמודלים במורד הזרם יכולים להבחין בין קטע ממוקד לבין ציר הזמן המלא.

מיקוד כתוביות סמנטי הוא הגדרה נפרדת ומפורשת. מצב הניתוח full כברירת מחדל שומר על הנחיית הפריים הקיימת ולעולם אינו מעביר טקסט בקשה למודל הכתוביות. במצב focused, הגשר קורא רק את ה-text/input_text האחרון שאינו ריק שנכתב על ידי המשתמש מאותו מיכל צ'אט או תגובות, מנרמל אותו ל-NFC, מצמצם תווי בקרה ורווחים, ומגביל אותו ל-500 נקודות קוד יוניקוד. תוצאה ריקה חוזרת להנחיה full המדויקת. רמז שמיש מסודר כ-JSON בבלוק ייעודי של הקשר משתמש לא מהימן ועשוי רק לתעדף פרטים ניתנים לצפייה; הוא אינו יכול לעקוף את האזהרה הנפרדת מפני ביצוע הוראות הנראות או נשמעות במדיה. מיקוד טקסטואלי לעולם אינו מסיק start/end או משנה את דוגם הזמן.

FU-07 עדויות מבניות של קטעים

segment_aware משתמש במעבר ניתוח מקדים מוגבל אחד על זרם הווידאו המקומי שכבר אומת. שרשרת המסננים הקבועה קודם כל משנה קנה מידה לרוחב של 320 פיקסלים לכל היותר, מזהה שינויי סצנה ומרווחי קיפאון, ואז דוגמת ב-1 פריים לשנייה עבור טשטוש, בהירות ממוצעת ומידע מרחבי/זמני. המעבר מוגבל ל-600 דגימות מבניות, חוט FFmpeg/מסנן אחד, אותה רשימת היתרים של פרוטוקול file בלבד וקונטיינרים, מגבלת פלט תהליך של 1 MiB, ולכל היותר 30 שניות בתוך ביטול/מועד אחרון משותף של המתווך. הוא לעולם אינו מקבל פקודה, מסנן, נתיב או URL מהבקשה.

הערכים המבניים הם ראיות דגימה דטרמיניסטיות, לא הבנה סמנטית של וידאו. הם אינם מסיקים נושאים, פעולות, כתוביות, דיבור או כוונת משתמש. גבולות סצנה והקפאה יוצרים מקטעים; כיסוי הקפאה, טשטוש, חשיפה, פרטים מרחביים ושינוי זמני משפיעים רק על אופן הקצאת תקציב 1–16 הפריימים הקיים. מקטע קפוא לחלוטין מוגבל לפריים אחד, בעוד שמקטעים שאינם קפואים מתחרים על התקציב הנותר. כאשר מספר הגבולות עולה על מספר הפריימים, נשמר כיסוי ציר זמן אחיד כך שחתכים מהירים מוקדמים לא יוכלו להסתיר מקטע ארוך ונגרר. גבולות סצנה בתוך רזולוציית ניתוח של שנייה אחת של גבול הקפאה מתמזגים.

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

scripts/perf/video-bridge-fu07-eval.ts מייצר מתקני FFmpeg אמיתיים דטרמיניסטיים עבור חיסכון בכתוביות לאחר דה-דופליקציה, הקצאת תקציב תנועה צפופה, ראיות טשטוש/חשיפה/SI-TI, חיתוכים מהירים עם זנב ארוך, ושגיאות חיוביות כוזבות של דהייה הדרגתית. הוא מתעד זמן קיר לפני ניתוח, ובמקום שבו /usr/bin/time זמין, מעבד ילד ו-RSS שיא. בדיקות האיכות שלו הן אורקלים מבניים בלבד. איכות מודל הכתוביות האמיתית נשארת HOLD מכיוון שלרתמה זו אין נקודת קצה מורשית או שופט קפוא. חיסכון כספי נשאר גם הוא HOLD אלא אם --caption-cost-per-call-usd מספק הערכה חיובית מפורשת לכל שיחה; הסקריפט לעולם אינו מייצר אף אחת מהתוצאות.

כל פריים מוגבל ל-4 MiB, כל הפריימים הגולמיים יחד ל-23 MiB, ותגובת הברוקר המוגדרת ל-32 MiB. ספרייה זמנית פרטית מוסרת ב-finally. OmniRoute אינו כולל FFmpeg ואינו מקבל נתיב הפעלה מותאם אישית. לפני יצירת כתוביות, הגשר מיישם מעבר דה-דופליקציה ויזואלי שמרני: כל JPEG מצטמצם למאגר גווני אפור בגודל 16×16 ומושווה רק לפריים האחרון שנשמר. עבור תקציב כתוביות מבוקש מעל פריים אחד, החילוץ מספק מאגר מועמדים מוגבל של עד פי שניים מהתקציב ולעולם לא יותר מ-16 פריימים. המגבלה המבוקשת מיושמת רק לאחר דה-דופליקציה, כאשר המועמדים הראשונים והאחרונים שנבחרו נשמרים במהלך דילול סופי כאשר התקציב הוא לפחות שניים. מדיניות grayscale-16x16-mean-cells-v2 הגרסאית משתמשת בגדול מבין דלתא לומא ממוצעת ויחס תאי התמונה הממוזערת שדלתא המנורמלת שלהם היא לפחות 0.05. סף הכפילות הוא הקבוע 0.04, שנבחר לצורך חיזוי ולא נחשף כהגדרת זמן ריצה. אות ניגודיות גבוהה משני זה שומר על תנועה קטנה ושינויים בטקסט גלוי שהשוואה ממוצעת בלבד יכולה להסתיר. שגיאות משווה או מפענח נכשלות ונפתחות ושומרות על כיסוי. מטא נתונים של פלט מפרידים בין מועמדים שחולצו, פריימים ששימשו בהצלחה, וכפילויות ויזואליות שנשמטו.

חלק וידאו המסומן במפורש עשוי לבקש גיליון מגע מתויג בזמן. הגשר בונה לכל היותר רשת JPEG של 4 עמודות, 16 פריימים. כל תא בגודל 512 פיקסלים שורף את חותמת הזמן המקורית שלו לרצועה תחתונה בעלת ניגודיות גבוהה, בעוד שאותן חותמות זמן נשארות במטא נתונים טקסטואליים לצורך שיוך וביקורת בהמשך. ה-JPEG המלא נשאר מוגבל ל-32 MiB. אם sharp אינו יכול לפענח או להרכיב את הרשת, הגשר חוזר לפריימי ה-JPEG הבודדים; ביטול לקוח עדיין מתפשט דרך פעולת הגיליון.

ראיות קידום נפרדות בכוונה ממיקרו-בנצ'מרק ההרכבה הסינתטי. scripts/perf/video-bridge-contact-sheet-eval.ts מגדיר רתמת A/B עם גרסת סכימה עבור מודלי ראייה אמיתיים תואמי OpenAI. היא מודדת אסימונים שדווחו על ידי הספק, זמן השהיה מקצה לקצה (כולל הרכבת גיליון), ספירת קריאות מודל, ושמירת עובדות מוגדרות במניפסט. תגובות מודל גולמיות אינן נכתבות לדוח; רק תקצירי SHA-256 ומזהי עובדות תואמים נשמרים. הרתמה אינה מבצעת קריאת רשת או מודל בתשלום אלא אם מועבר --execute-real ומוגדרים --model, OMNIROUTE_BASE_URL ו-OMNIROUTE_API_KEY. ללא הפעלה אמיתית מפורשת זו, פסק הדין הקריא למכונה שלה נשאר HOLD; מדידות מטען/ספירת קריאות סינתטיות בלבד אינן ראיות קידום.

מתקשרים רשאים לצרף מערך transcript.cues אופציונלי לחלק וידאו נתמך כאשר הם כבר מחזיקים בטקסט מיושר. כל רמז חייב לשאת text, מרווח start/end סופי בתוך משך הזמן הנבדק, ו-source ברשימה לבנה (client, embedded, או audio-bridge); confidence מוגדר כברירת מחדל ל-1 וחייב להישאר בין 0 ל-1. רמזים כפולים זהים מתמוטטים. OmniRoute לעולם אינו מתחיל תמלול ממטא נתונים אלה: רמזים מאומתים מועתקים לתוצאה המתוארת עם מקור, ביטחון ומרווח, ומוצגים כתצפיות לא מהימנות לצד כתוביות הפריימים. טקסט לא חוקי, מחוץ לטווח או ללא מקור נדחה במקום להתערבב לזרם הכתוביות. שדה ה-source מוצהר כרגע על ידי המתקשר, לא מאומת על ידי השרת: OmniRoute אוכף שהערך הוא אחד משלושת המחרוזות המותרות, אך עדיין אינו מאשר קריפטוגרפית שתגית embedded או audio-bridge אכן הגיעה מחילוץ בבעלות השרת. התייחס ל-source כרמז לא מהימן עד לאימות זה; אל תבנה עליו החלטות הרשאה.

מתקשר מתקדם רשאי לספק רצועת audioTranscript מאושרת מראש עבור אותו סרטון. תפר האיחוי מריץ תצפיות ויזואליות ושמע תחת מועד אחרון אחד ואות ביטול, מסדר אותן על ציר זמן משותף, מאחד כפילויות מדויקות, ומדווח על תוצאה חלקית כאשר רק צד אחד מצליח. audioTranscript לא חוקי מתדרדר לתוצאה חלקית זו – התיאור הוויזואלי נשמר וענף השמע רושם קוד כשל מטוהר – במקום להכשיל את הסרטון כולו. זמינות לפי ענף, הדגל החלקי, וקודי הכשל המטוהרים נשמרים בתוצאה המתוארת, במטא-דאטה של מנגנון ההגנה (audioFusionRuns/audioFusionPartials/audioFusionFailureCodes), במטא-דאטה של מטמון התוצאות, ובמוני איחוי הגשר. נתיב ה-Video Bridge המוגדר כברירת מחדל אינו מפעיל דיבור לטקסט או מוריד עותק מדיה שני; ללא רצועה מפורשת זו, הוא נשאר וידאו-בלבד.

שמירת תמלול (#12150 P1). זה חל אוטומטית בכל פעם שה-Video Bridge (שבעצמו הוא אופט-אין) מציג רמז תמלול – אין דגל שמירה נפרד. כאשר בקשה מציגה רמז תמלול כלשהו (transcript שהוצהר על ידי המתקשר או audioTranscript מאוחד), מנגנון ההגנה מסמן אותו כ-videoBridgeObserved ומייצר צל מושחר של תיאור הווידאו – עיבוד זהה שבו גוף הטקסט החופשי של כל רמז מוחלף ב-[redacted-video-transcript], הבנוי על ידי החלפת שדה הרמז המובנה לפני הרכבת המחרוזת (לעולם לא על ידי ניתוח הטקסט השטוח, כך ששום תוכן רמז – עוין או רגיל, כולל גופים המכילים ] כגון [inaudible]/[music] – אינו יכול לשרוד). גוף הבקשה המתועד ביומן השיחות מחליף כל חלק טקסט שנגזר מווידאו בצל מושחר זה, בהתאמה לפי שוויון תוכן; עוגן ה-fullText נקרא מחדש ממטען מנגנון ההגנה המוגמר לפני השיחה, כך שההתאמה עדיין מצליחה לאחר שמנגנוני הגנה מאוחרים יותר בשרשרת (מסיכות ה-PII והאישורים, בעדיפויות 10/95) משכתבים את טקסט התיאור במקום ולאחר שהזרקת הנחיה/העברה/זיכרון מערכת מעצבת מחדש את מערך ההודעות. הגוף שנשלח במעלה הזרם למודל נשאר ללא שינוי. בקשה שנצפתה גם אינה מאכלסת זיכרון עמיד (גם חילוץ שנגזר מבקשה וגם חילוץ שנגזר מתגובה מדלגים), כך שהתשובה של המודל עצמו אינה יכולה להדהד טקסט תמלול לתוך הזיכרון.

עותקים נוספים שנשמרו משתמשים באותו אות בקשה נצפית. תמונת המצב הגולמית של בקשת הלקוח לפני מנגנון ההגנה, בקשה ממתינה בזיכרון, ויומן בקשות שנדחו מוקדם מחליפים מבנית שדות תמלול בחלקי וידאו; הנחיות מחרוזת המסונתזות על ידי שלבי צינור העבודה והעברת הקשר מושחרות בכיור גוף הבקשה המתועד. סמן ה-video_content_removed המתועד גורם להמשך previous_response_id להיכשל בסגירה במקום לשחזר טקסט שנזרק בכוונה. אם בקשה נצפית מאבדת את צל ההשחרה שלה לפי חלק לפני התיעוד, או אפילו אחד מכמה צללי וידאו אינו מצליח להתאים לאחר שינויים מאוחרים יותר בבקשה, גוף הבקשה שנשמר מושמט לחלוטין במקום לשמור תמלול מושחר חלקית.

עבור בקשה נצפית, תגובת מודל עשויה לצטט כל חלק מהתמלול ללא גבול רמז מובנה. לכן, ה-responseBody המתועד ביומן השיחות מוחלף בסמן השמטה; חפץ צינור העבודה המפורט (שיכול לכלול גופים במעלה הזרם/לקוח ונתחי זרם) אינו נשמר. מטמוני סמנטיקה, אידמפוטנטיות ושחזור היגיון עוקפים קריאות וכתיבות עבור בקשה זו. בקשת הספק והתגובה הנראית ללקוח נשארות ללא שינוי. בתים ראשוניים של keepalive מנוקזים מהמאגר הזמני כאשר החפץ המפורט מושמט. אזהרת EventStream שגויה של Kiro מדווחת רק על ספירת בתי המטען, לעולם לא על תוכנו או על השגיאה הגולמית של מנתח ה-JSON. זה לא טוען שכל אבחון ספק/תוסף לא קשור נבדק; הסריקה הרחבה יותר של כיור השמירה מתועדת ב-#11658.

מחזור החיים הפנימי של /api/modality-bridge/video/drilldown הוא תשתית מטמון נפרדת, מבוססת לולאה חוזרת/אימות אסימונים. כל פעולה דורשת גם מזהה ישות (principal ID) קנוני אטום. לפני הפעלת מתקשר ייצור, עליו לגזור את המזהה הזה מהדייר המאומת ולעולם אסור לו להעביר ערך שנבחר על ידי הלקוח. מפתחות מטמון קושרים את הישות הזו למזהי סשן ווידאו-הפניה קנוניים, מאחסנים רק את המפתחות שנגזרו מ-SHA-256 שלהם, ומגבילים גם קריאות וגם מחיקה לאותה ישות. המטמון מאחסן לכל היותר 16 פריימי JPEG נגזרים לכל רשומה, פג תוקפם לאחר עשר דקות, ותומך בקריאות start/end מוגבלות או מחיקת סשן מפורשת.

כל ישות מוגבלת ל-16 רשומות ו-64 מגה-בייט של נתוני JPEG קנוניים. מגבלות אלו אינן תלויות בתקרה הגלובלית של 64 רשומות/256 מגה-בייט: לחץ מכסה של ישות מפנה רק את הרשומות שבהן נעשה שימוש לאחרונה באותה ישות לפני שמתבצעת פינוי LRU גלובלי. רשומות שפג תוקפן נסרקות הן מהחשבונאות של הישות והן מהחשבונאות הגלובלית בפעילות המטמון, בעוד שביטול וכשל אימות אינם מבצעים החלפה חלקית.

המטמון דוחה Base64 לא קנוני, ריפוד עודף, מדיה שאינה JPEG, קובצי JPEG פגומים או קטועים, וקובצי JPEG המייצרים אזהרה במהלך פענוח sharp מוגבל של תמונה מלאה. הוא מקודד מחדש כל תמונה מקובלת כ-JPEG קנוני, גוזר רוחב וגובה מהבתים המפוענחים במקום לסמוך על שדות המתקשר, וזורק כל בתים פוליגלוטיים נלווים במקום לשמור אותם. רק המאגר הדחוס הקנוני המוגבל נטען לשתי המכסות. מגבלת ה-JSON בחיבור כוללת תקורה של Base64 עבור תקרת הקלט המפוענח של 32 מגה-בייט. כל נגזרת מאוחסנת רושמת את פורמט/רזולוציית ה-JPEG המאומתים שלה, מדיניות הדגימה, גרסת הנגזרת, זמן היצירה, גיבוב התוכן המחושב על ידי השרת, והפניית אב מגובבת בתוספת גיבוב התוכן של האב מהמתקשר המהימן. ביטול נבדק בין שלבי פענוח/גיבוב אסינכרוניים לפני אישור המטמון האטומי.

חבילה זו עדיין אינה מחברת מפיק ייצור לנתיב ואינה מספקת בחירת וריאנטים מרובי רזולוציות. נתיב הבקשה השקוף של Video Bridge אינו גורר עבודה נוספת, בעוד שגזירת ישויות הקשורות לדיירים ו מחזור החיים המלא של FU-08 מרובה הרזולוציות נשארים עבודת המשך מפורשת במקום להיות מתועדים כהתנהגות שלמה.

הפריימים מתוארים ברצף עם מודל הווידאו המוגדר. הגדרת וידאו ריקה יורשת את הגדרת ה-Vision; אם שניהם ריקים, הנתב האוטומטי של Vision בוחר את המודל היעיל התומך ב-Vision. כיתובים מוצלחים מחליפים את החלק המקורי בקידומת יציבה [Video description: שגם מסמנת את הטקסט כתצפית לא מהימנה שמקורה במדיה ואומרת למודלים במורד הזרם לא לציית להוראות שנמצאו במדיה. מפתחות מטמון כיתובי פריימים כוללים את בייטי ה-JPEG, ההנחיה, חותמת הזמן והמודל היעיל; רק כיתובים מוצלחים נשמרים במטמון. רשומות המטמון שומרות את מודל המפיק המוצלח בפועל, כולל מודל חלופי; הגשר מדווח mixed כאשר פריימים שונים הופקו על ידי מודלים שונים. פגיעת מטמון משתמשת מחדש באותה זהות מפיק במקום לתייג אותה מחדש כתוכנית הניתוב המבוקשת. מטמון התוצאות של הווידאו כולו מקושר לכל קלט שמשנה את הפלט — הנחיה, מודל יעיל, מדיניות דגימה, ספירת פריימים, מצב ניתוח סמנטי, טביעת האצבע SHA-256 של רמז המיקוד המנורמל, חלון מיקוד, transcript, audioTranscript, ודגל גיליון המגע — כך ששינוי של כל אחד מהמימדים הללו הוא החטאת מטמון, לעולם לא שימוש חוזר מיושן. גרסת מדיניות ה-dedup החזותית, הסף, וספירת פריימים מועמדים מוגבלת הם גם מפורשים במפתח מטמון התוצאות ובמטא-נתונים; שינוי מדיניות אינו יכול, לכן, לעשות שימוש חוזר בתיאור וידאו שלם מיושן. מטא-נתוני מטמון תוצאות v4 שומרים את המצב ואת טביעת האצבע, לעולם לא את משימת המשתמש הגולמית. מטא-נתוני Guardrail מדווחים על מצבי הניתוח המבוקשים והיעילים כאחד; מצב focused מבוקש ללא טקסט משתמש שמיש מדווח כיעיל full.

ה-guardrail מחלץ כל חלק וידאו נתמך אך מתאר לא יותר מ-modalityBridgeVideoMaxVideos. עבור יעד שהוכח שיש לו supportsVideo === false, סרטונים שנכשלו או חרגו מהמגבלה הופכים לסמני טקסט בטוחים מפורשים כך שלא יישאר וידאו גולמי. כאשר היכולת אינה ידועה, חלקים אלה נשארים ללא מגע. יעדים עם supportsVideo === true עוקפים את הגשר. אות ביטול בקשת הלקוח מתפשט דרך ההורדה, תור הברוקר, תהליכי משנה וקריאות כיתוב; ביטולים נעצרים בין סרטונים ולעולם אינם נכשלים בפתיחה למדיה גולמית.

הגדרות זמן ריצה מגובות ב-DB ומאומתות באמצעות Zod:

מפתח ברירת מחדל טווח / התנהגות
modalityBridgeVideoEnabled false זמן ריצה אופציונלי, הצטרפות
modalityBridgeVideoAnalysisMode "full" full שומר כיתובים כלליים; focused משתמש בהקשר משתמש אחרון מוגבל ולא מהימן
modalityBridgeVideoModel "" יורש את מודל ה-Vision Bridge
modalityBridgeVideoFrameCount 8 1–16
modalityBridgeVideoSamplingPolicy "uniform" uniform, scene_aware, או segment_aware פרופורציונלי; כשל בזיהוי חוזר ל-uniform
modalityBridgeVideoMaxVideos 1 1–4
modalityBridgeVideoTimeout 120000 1000–120000 אלפיות השנייה

ערכי פסק זמן של וידאו קיימים שנשמרו מעל 120 שניות מוגבלים למועד האחרון של הברוקר; כתיבת הגדרות חדשות מעל מגבלה זו נדחית. GET /api/modality-bridge/video/runtime דורש לוקליות לולאתית חתומה מהימנה לפני אימות או בדיקת זמן ריצה, ולאחר מכן דורש אימות ניהול. הוא מחזיר רק available, גרסאות FFmpeg/ffprobe מנוקות, וסיבה קבועה כאשר זמן הריצה אינו זמין. נקודת הקצה הפנימית לחילוץ אינה API ציבורי להעלאה: רווית תור מחזירה 503 בתוספת Retry-After, ניתוק מתקשר מחזיר 499, והמועד האחרון הקבוע של הברוקר מחזיר 504. תגובות מומרות מוסיפות video->text;model=<visionModel>;parts=<videos> לכותרת המרכזית x-omniroute-modality-bridge מבלי להסיר קטעי Vision או Audio.

מסנן PII (piiMasker.ts)

פועל בשני השלבים.

  • preCall משכפל את המטען, עובר על system, messages, input, ו-prompt (כולל פריטי מחרוזת רגילים), ומחיל את processPII() (מתוך @/shared/utils/inputSanitizer) על שדות content/text מסוג מחרוזת. כאשר PII_REDACTION_ENABLED=true, PII שזוהה מצונזר במטען היוצא. זה בלתי תלוי ב-INPUT_SANITIZER_MODE (ששולט רק במדיניות הזרקת הנחיות). כאשר הצנזורה כבויה, הקריאה רושמת ספירות זיהוי מבלי לשכתב תוכן.
  • postCall משכפל עמוק את התגובה, מריץ את sanitizePIIResponse() בתוספת מסנן צורת ה-API של התגובות (maskResponsesOutput — מכסה את output_text ואת output[].content[].text). אם מתרחשת צנזורה כלשהי, התגובה שהשתנתה מחליפה את המקורית.

ה-guardrail לעולם אינו חוסם; הוא רק מוסיף הערות (meta.detections, meta.redacted) או משכתב.

הזרקת הנחיות (promptInjection.ts)

מזהה מבנים עוינים בתוכן המסופק על ידי המשתמש ואוכף את המדיניות המוגדרת. ההתנהגות מונעת על ידי משתני סביבה ואפשרויות בנאי:

| הגדרה | משתנה סביבה | ברירת מחדל | השפעה
| INPUT_SANITIZER_ENABLED | true | כאשר false, ה-guardrail מבצע קיצור מעגל. | | INJECTION_GUARD_MODE / INPUT_SANITIZER_MODE | warn | מדיניות הזרקה: block, warn, או log. (redact מתקבל עבור תאימות לאחור אך אינו מסיר טקסט הזרקה; בקשת שכתוב PII נשלטת על ידי PII_REDACTION_ENABLED.) | | blockThreshold option / INPUT_SANITIZER_BLOCK_THRESHOLD (alias INJECTION_GUARD_BLOCK_THRESHOLD) | high | רמת חומרה מינימלית הנדרשת לחסימה. בינוני הוא צפייה בלבד כברירת מחדל. |

קדימות מצב (getMode): options.mode של הקורא ← עקיפת דגל תכונה של DB (INJECTION_GUARD_MODE) (לוח מחוונים ← הגדרות ← דגלי תכונות) ← משתנה סביבה INJECTION_GUARD_MODE ← משתנה סביבה INPUT_SANITIZER_MODE ← warn. עקיפת לוח מחוונים גוברת על משתני הסביבה, כך שממשק המשתמש של דגלי התכונות שולט על ה-guard בזמן אמת (ללא הפעלה מחדש). קריאת ה-DB בטוחה מפני כשלים: אם היא נכשלת, ה-guard חוזר להתנהגות מבוססת סביבה, וכאשר לא מוגדרת עקיפה, ההתנהגות זהה לרזולוציה מבוססת סביבה בלבד.

מקורות זיהוי:

  1. sanitizeRequest() מתוך @/shared/utils/inputSanitizer (סט גלאים משותף המשמש במקומות אחרים בצינור).
  2. DEFAULT_GUARD_PATTERNS מובנה (כיום system_override_inline ו- markdown_system_block, שניהם בחומרה high).
  3. customPatterns אופציונלי המועבר דרך אפשרויות הבנאי (מחרוזות, regex, או רשומות { name, pattern, severity }).

כאשר mode === "block" וגם לפחות זיהוי אחד עומד בסף החומרה, preCall מחזיר { block: true, message: "Request rejected: suspicious content detected" }. במצבי warn/log ה-guardrail רושם יומן אך מאפשר את הקריאה. העוזר המשותף evaluatePromptInjection() מיוצא גם עבור קוראים שצריכים להעריך הנחיות מבלי לעבור דרך הרישום.

גבול סריקה (v3.8.20): הגלאי בודק רק את 16KB הראשונים של טקסט ההנחיה המאוחד — MAX_INJECTION_SCAN_BYTES = 16 * 1024 (16,384 בתים) ב- src/shared/utils/inputSanitizer.ts. גם detectInjection() וגם evaluatePromptInjection() מבצעים slice(0, MAX_INJECTION_SCAN_BYTES) לפני הפעלת לולאת התבניות. הנחיות הזרקה נמצאות קרוב לחלק העליון של קלט, כך שזה מגביל את ה-CPU/GC של regex על מטענים של מאות קילובייט מבלי להחליש את הזיהוי (ראה #3932, #4041).

מסנן אישורים (credentialMasker.ts)

פועל בשני השלבים, אחרון בשרשרת ברירת המחדל (עדיפות 95). מסנן דפוסי מפתח API / אסימון סודי ידועים מהמטען היוצא (תוכן הודעה, ארגומנטים של קריאת כלי, תוצאות כלי) וגם מתגובת הספק, כך שאישור שהודבק להנחיה (או הוחזר על ידי תוצאת כלי) לא ידלוף לספק העליון או בחזרה ללקוח.

  • אופט-אין בלבד, אותה מוסכמה כמו סינון PII (כלל קשיח #20-סמוך): מושבת אלא אם settings.credentialRedactionEnabled === true או CREDENTIAL_REDACTION_ENABLED=true. כשהוא כבוי, ה-guardrail הוא פעולה ללא השפעה — הוא לעולם לא חוסם ולעולם לא משכתב.
  • redactCredentials() עובר על עץ המטען/תגובה המלא (walkValue(), בטוח מפני זיהום אבטיפוס, בטוח מפני מחזוריות באמצעות WeakSet) ומחליף התאמות במציין מיקום [REDACTED:<type>], משכפל רק את הענפים שבאמת השתנו.
  • CREDENTIAL_PATTERNS מכסה מפתחות ספק LLM (OpenAI, OpenAI-proj, Anthropic, Google, Hugging Face, Replicate), אסימוני VCS/SaaS (GitHub, Slack, Linear, Notion, npm, Postman, Discord), מפתחות תשלום (Stripe, Square), מפתחות ענן (מפתח גישה של AWS, Twilio, SendGrid, Mailgun), מפתחות פרטיים / JWTs, מחרוזות חיבור נושאות אישורים (mongodb://user:pass@..., וכו'), ודפוס ערך כותרת גנרי Authorization/x-api-key/api-key/apikey. מפתחות בצורת כותרת (authorization, x-api-key, api-key, apikey) מסוננים מבחינה מבנית (ערך בלבד, קידומת סכימה כמו Bearer /Basic נשמרת) ולא באמצעות regex טקסט גנרי.
  • ה-guardrail לעולם אינו חוסם; הוא רק משכתב (modifiedPayload / modifiedResponse) ומציין (meta.credentialsRedacted, meta.count).

שומר רגרסיה: tests/unit/credential-masker-guardrail.test.ts.

חוזה בסיס (base.ts)

class BaseGuardrail {
  enabled: boolean;
  name: string;
  priority: number;

  constructor(name: string, options?: { enabled?: boolean; priority?: number });

  async preCall(payload: unknown, context: GuardrailContext): Promise<GuardrailResult | void>;

  async postCall(response: unknown, context: GuardrailContext): Promise<GuardrailResult | void>;
}

interface GuardrailResult<TValue = unknown> {
  block?: boolean; // true עוצר מיד את השרשרת
  message?: string; // מוצג בעת חסימה
  meta?: Record<string, unknown> | null;
  modifiedPayload?: TValue; // מוחזר על ידי preCall כדי לשכתב את הבקשה
  modifiedResponse?: TValue; // מוחזר על ידי postCall כדי לשכתב את התגובה
}

interface GuardrailContext {
  apiKeyInfo?: Record<string, unknown> | null;
  disabledGuardrails?: string[] | null;
  endpoint?: string | null;
  headers?: Headers | Record<string, unknown> | null;
  log?: GuardrailLog | Console | null;
  method?: string | null;
  model?: string | null;
  provider?: string | null;
  signal?: AbortSignal;
  sourceFormat?: string | null;
  stream?: boolean;
  targetFormat?: string | null;
}

מנגנון הגנה מסמן "ללא שינוי" באמצעות החזרת void, {}, או { block: false }. החזרת modifiedPayload/modifiedResponse מחליפה את הערך שזורם בשרשרת עבור מנגנוני ההגנה שבהמשך. signal?: AbortSignal מעביר אל מנגנוני ההגנה את מחזור החיים של הקורא. ביטול בקשה הוא החרגת fail-open מכוונת: גשרי מדיה מפסיקים את העבודה ומבצעים ניקוי מבלי לשחזר מדיה גולמית ליעד שידוע כי אינו תומך בה.

מרשם (registry.ts)

היחידון guardrailRegistry חושף:

  • register(guardrail) — מוסיף מנגנון הגנה (או מחליף לפי שם מנורמל) וממיין מחדש לפי priority בסדר עולה.
  • clear() / list() — כלי עזר ניהוליים.
  • runPreCallHooks(payload, context) — עובר על מנגנוני ההגנה הפעילים, מעביר את המטען דרך modifiedPayload, ועוצר ב-block: true הראשון.
  • runPostCallHooks(response, context) — אותה זרימה בצד התגובה.
  • resetGuardrailsForTests({ registerDefaults }) — מנקה את המצב, ולפי הצורך רושם מחדש את ברירות המחדל לצורך בידוד נקי של בדיקות.

שני המריצים מחזירים { blocked, payload|response, results, guardrail?, message? } כאשר results הוא מערך של רשומות GuardrailExecutionResult הכוללות את השדות blocked, skipped, modified, error ו-meta לכל מנגנון הגנה, ושימושיות למעקב.

השבתת מנגנוני הגנה לכל בקשה

resolveDisabledGuardrails({ apiKeyInfo, body, headers }) אוסף רשימה ללא כפילויות של שמות מנגנוני הגנה שיש לדלג עליהם עבור הבקשה הנוכחית. המקורות (כולם אופציונליים וכולם ממוזגים):

  • apiKeyInfo.disabledGuardrails
  • disabledGuardrails בגוף הבקשה (ברמה העליונה)
  • metadata.disabledGuardrails בגוף הבקשה
  • הכותרת x-omniroute-disabled-guardrails (או הכותרת הישנה x-disabled-guardrails)

הערכים יכולים להיות מערכי מחרוזות או מחרוזת מופרדת בפסיקים; השמות מנורמלים ל-kebab-case באותיות קטנות (pii_masker → pii-masker). התוצאה מועברת דרך context.disabledGuardrails אל המרשם, שמדלג על מנגנוני הגנה תואמים (skipped: true בתוך results).

סדר הביצוע

עבור כל בקשה שעוברת דרך src/sse/handlers/chat.ts ודרך open-sse/handlers/chatCore.ts:

  1. resolveDisabledGuardrails(...) בונה את רשימת הדילוג ממפתח ה-API, מגוף הבקשה ומהכותרות.
  2. guardrailRegistry.runPreCallHooks(body, ctx) מפעיל את מנגנוני ההגנה לפי סדר עדיפויות עולה:
    • מנגנוני הגנה מושבתים מתועדים כ-skipped.
    • ה-preCall של כל מנגנון הגנה עשוי לשכתב את המטען באמצעות modifiedPayload.
    • המופע הראשון של block: true עוצר את השרשרת, וה-handler מחזיר תגובת דחייה של מנגנון ההגנה.
  3. המטען (שייתכן ששוכתב) מועבר לניתוב משולב ולשליחה לשירות שבמעלה הזרם.
  4. לאחר הרכבת התגובה, guardrailRegistry.runPostCallHooks(...) מפעיל את אותה שרשרת על התגובה. block: true בשלב זה מבטל את התגובה משירות מעלה הזרם.

מנגנוני הגנה שזורקים חריגה מתועדים עם error: <message> ונרשמים ביומן באמצעות logger.warn, אך השרשרת ממשיכה — זהו תכנון מסוג fail-open.

תצורה

משתני סביבה שנקראים על ידי מנגנוני ההגנה המובנים:

משתנה בשימוש על ידי השפעה
INPUT_SANITIZER_ENABLED prompt-injection יש להגדיר false כדי להשבית לחלוטין את הזיהוי.
INPUT_SANITIZER_MODE prompt-injection מדיניות הזרקה: warn,‏ block או log. הערך הישן redact אינו משכתב טקסט של הזרקה.
INJECTION_GUARD_MODE prompt-injection מצב מגן ההזרקה; משמש גם כדגל תכונה במסד הנתונים אשר דורס את משתני הסביבה (DB > ENV).
INPUT_SANITIZER_BLOCK_THRESHOLD prompt-injection רמת החומרה המזערית שאותה MODE=block דוחה: high (ברירת המחדל), medium או low.
INJECTION_GUARD_BLOCK_THRESHOLD prompt-injection כינוי ישן עבור INPUT_SANITIZER_BLOCK_THRESHOLD.
PII_REDACTION_ENABLED pii-masker כאשר הערך הוא true, מידע אישי מזהה בבקשה מושחר (ללא תלות במצב ההזרקה).
PII_RESPONSE_SANITIZATION / _MODE pii-masker (במורד הזרם) שולט בהתנהגות המיסוך בצד התגובה.

מנגנוני ההגנה של Modality Bridge קוראים את תצורת זמן הריצה ממאגר ההגדרות המגובה במסד הנתונים (getSettings()), ולא ממשתני סביבה. המפתחות הראשיים של Vision הם modalityBridgeVisionEnabled,‏ modalityBridgeVisionMode, modalityBridgeVisionModel,‏ modalityBridgeVisionTaskAware, modalityBridgeVisionPrompt,‏ modalityBridgeVisionTimeout, modalityBridgeVisionMaxImages,‏ modalityBridgeVisionMaxChars, modalityBridgeCacheEnabled,‏ modalityBridgeCacheTtlMinutes ו- modalityBridgeCacheMaxEntries. המפתחות הישנים visionBridge* מתקבלים רק כמנגנון הגיבוי המתועד לקריאה במחזור אחד; כתיבות מלוח הבקרה משתמשות במפתחות הראשיים. ברירות המחדל ופותר מנגנון הגיבוי נמצאים ב-src/shared/constants/modalityBridgeDefaults.ts, בעוד שהקבועים הישנים נשמרים ב-src/shared/constants/visionBridgeDefaults.ts.

אודיו משתמש ב-modalityBridgeAudioEnabled,‏ modalityBridgeAudioModel, modalityBridgeAudioTimeout וב-modalityBridgeAudioMaxClips, יחד עם הגדרות modalityBridgeCache* המשותפות. לאודיו אין מנגנון גיבוי למפתחות ישנים, משום שמפתחות אלה הוצגו יחד עם הסכימה של Modality Bridge.

וידאו משתמש ב-modalityBridgeVideoEnabled,‏ modalityBridgeVideoAnalysisMode, modalityBridgeVideoModel, modalityBridgeVideoFrameCount,‏ modalityBridgeVideoSamplingPolicy, modalityBridgeVideoMaxVideos וב- modalityBridgeVideoTimeout, יחד עם הגדרות modalityBridgeCache* המשותפות. הוא מושבת כברירת מחדל משום ש-FFmpeg/ffprobe הם תלויות תפעוליות אופציונליות, והוספת כיתובים לפריימים מגדילה את זמן ההשהיה ואת עלות המודל.

מנגנוני הגנה מותאמים אישית

import { BaseGuardrail, guardrailRegistry } from "@/lib/guardrails";

class BudgetGuardrail extends BaseGuardrail {
  constructor() {
    super("budget", { priority: 50 });
  }

  async preCall(payload, ctx) {
    if (ctx.apiKeyInfo?.budgetExceeded) {
      return { block: true, message: "Daily budget exceeded" };
    }
    return { block: false };
  }
}

guardrailRegistry.register(new BudgetGuardrail());

שלבים:

  1. צרו את src/lib/guardrails/myGuardrail.ts כך שירחיב את BaseGuardrail.
  2. ממשו את preCall ו/או את postCall.
  3. בצעו רישום בזמן הייבוא (הוספה מתוך registerDefaultGuardrails) או קראו ל-guardrailRegistry.register(...) בזמן ריצה — הרישום מחליף כל מנגנון הגנה קודם בעל אותו שם מנורמל.
  4. הוסיפו בדיקות תחת tests/unit/ (דוגמאות קיימות: tests/unit/guardrails-registry.test.ts, tests/unit/prompt-injection-guard.test.ts, tests/unit/guardrails/visionBridge.test.ts).

בדיקות

השתמשו ב-resetGuardrailsForTests() בין הבדיקות כדי להתחיל ממצב ידוע. העבירו { registerDefaults: false } כדי להתחיל עם רישום ריק ולרשום רק את מנגנוני ההגנה הנבדקים. Vision Bridge תומך בהזרקת תלויות (deps.getSettings, deps.callVisionModel); ‏Audio Bridge חושף נקודות חיבור מקבילות עבור הגדרות, יכולות, בחירת מודל STT, בדיקות פרטי גישה ותמלול. לכן הבדיקות יכולות להפעיל את שני התהליכים ללא גישה למסד נתונים או לרשת.

ראו גם

  • src/lib/guardrails/ — מימוש
  • src/shared/utils/inputSanitizer.ts — גלאי משותף שמפעיל זיהוי הזרקת הנחיות ומיסוך PII
  • src/shared/constants/visionBridgeDefaults.ts — ברירות המחדל של Vision Bridge ורשימת המודלים שבהם השימוש בגשר נכפה
  • src/shared/constants/modalityBridgeDefaults.ts — ברירות מחדל משותפות בזמן ריצה עבור Vision/Audio
  • docs/architecture/RESILIENCE_GUIDE.md — שכבה אורתוגונלית (מפסק זרם, תקופות צינון)
  • docs/reference/ENVIRONMENT.md — הפניה מלאה למשתני סביבה

כיסוי נתיבים ובדיקות צוות אדום למנגנון ההגנה מפני הזרקה (שלב 8 · בלוק D)

מנגנון ההגנה מפני הזרקה (createInjectionGuard / withInjectionGuard) מכסה את כל הנתיבים שמקבלים הנחיות משתמש. הוא מכבד את INJECTION_GUARD_MODE (ברירת המחדל warn = רישום בלבד; block = החזרת HTTP 400 עם SECURITY_001).

סוג נתיבים מצב ברירת מחדל
טקסט (קיים) /v1/chat/completions, /v1/completions, /v1/relay/chat/completions warn
יצירתי /v1/messages, /v1/responses, /v1/images/generations, /v1/images/edits, /v1/videos/generations, /v1/music/generations, /v1/audio/speech warn
נתונים /v1/embeddings, /v1/rerank, /v1/search, /v1/moderations warn

חילוץ טקסט (extractMessageContents) מכסה את messages/input/prompt/query+documents/instructions/system.

צוות אדום (מדי לילה, nightly-llm-security.yml): ‏promptfoo מאמת שכל נתיב חוסם את קורפוס OWASP-LLM כאשר INJECTION_GUARD_MODE=block; ‏garak מריץ בדיקות חדירה (ומדלג ללא סוד). moderations נכלל למען העקביות — מפעילים במצב חסימה יכולים להחריג אותו באמצעות resolveDisabledGuardrails.

תהליך העבודה הלילי (.github/workflows/nightly-llm-security.yml, ‏cron + הפעלה ידנית) כולל שתי משימות:

  • promptfoo-guard (חוסמת) — מריצה את promptfoo eval -c promptfooconfig.yaml עם INJECTION_GUARD_MODE=block. כל מקרה עוין (למשל "התעלם מכל ההוראות הקודמות…", פריצות בסגנון DAN) מוודא שהתשובה מכילה error.code === "SECURITY_001", כלומר שמנגנון ההגנה אכן דחה את הבקשה.
  • garak (מייעצת) — מריצה את garak עם --probes promptinject,dan,leakreplay מול מופע OmniRoute מקומי (http://localhost:20128/v1). ההרצה מותנית בסוד של ספק (PROMPTFOO_PROVIDER_KEY); היא מדלגת בצורה תקינה בהיעדרו ומסתיימת בסיומת || true, כך שהיא מדווחת מבלי להכשיל את CI.

הכיסוי של פונקציית העזר של מנגנון ההגנה (createInjectionGuard / withInjectionGuard) משתרע על כל נתיב /v1 שמכיל הנחיה; טקסט ההנחיה נשלף מתוך messages/input/prompt/query+documents/instructions/system באמצעות extractMessageContents() שב-src/shared/utils/inputSanitizer.ts.