פיצ'ר AI יכול להיראות תקין בדמו, ואז להיכשל מיד כשהוא פוגש לקוחות אמיתיים: תשובה נשלפת ממקור לא נכון, ציטוט חסר, משתמש מקבל מידע של חשבון אחר, או שזמן התגובה קופץ בלי הסבר. במקרים רבים, הבעיה אינה רק במודל או ב-RAG. היא מתחילה בשכבת החיבור בין המוצר, הנתונים והשירותים. לכן השאלה איך מתכננים ארכיטקטורת API גמישה היא שאלה תפעולית: איך מאפשרים למערכת להשתנות בלי לשבור לקוחות, הרשאות או יכולת לאתר תקלות.
API גמיש אינו API עם עשרות endpoints או עם payload כללי מסוג `data`. הוא חוזה ברור בין רכיבים, כזה שמאפשר לצוות לשנות מימוש פנימי, להחליף ספק, לפצל שירות או להוסיף יכולת, בלי לגרום לשינוי בלתי צפוי בצד השני. במוצר SaaS חי, הגמישות הזאת חייבת להתקיים לצד משמעת: בעלות ברורה על נתונים, הרשאות עקביות, גרסאות, תצפיות ובדיקות.
התחילו מגבולות המערכת, לא מרשימת endpoints
הטעות הנפוצה היא לפתוח את מסמך ה-API ולשאול אילו פעולות המסך צריך לבצע. זו שאלה שימושית, אבל היא מאוחרת מדי. קודם צריך להבין מי מחזיק בכל החלטה עסקית ובכל נתון.
לדוגמה, אם יש במוצר שיחה מבוססת RAG, כדאי להפריד בין זהות המשתמש והחשבון, מסמכי המקור, אינדוקס וחיפוש, ניהול השיחה, והפקת התשובה. אין הכרח שכל חלק יהיה microservice נפרד. לעיתים מודולרי מונולית הוא הפתרון הנכון יותר: פשוט יותר לפריסה, לדיבוג ולעקביות נתונים. ההפרדה החשובה היא לוגית - מי רשאי לקרוא, לשנות או לחשוף כל סוג מידע.
כאשר endpoint של chat מקבל מזהה מסמך מהדפדפן וסומך עליו, נוצר פתח לדליפת מידע בין accounts. במקום זאת, השירות צריך לקבל את זהות המשתמש ואת ההקשר המאומת שלו, ולחשב בעצמו אילו מקורות מותרים לחיפוש. הלקוח יכול לבקש פעולה, אבל הוא לא צריך להכתיב את גבולות ההרשאה.
הכלל המעשי הוא פשוט: כל API צריך לבטא יכולת עסקית והקשר הרשאה. `POST /conversations/{id}/messages` ברור יותר מ-endpoint כללי שמבצע פעולות שונות לפי שדה `action`. הראשון מאפשר להגדיר מי רשאי לשלוח הודעה, לאיזו שיחה, ובאיזה tenant. השני בדרך כלל אוסף חריגים עד שאי אפשר לדעת מה באמת מותר לו לעשות.
איך מתכננים ארכיטקטורת API גמישה בלי להפוך אותה למופשטת מדי
גמישות אינה אומרת לבנות שכבת abstraction לכל אפשרות עתידית. API שתוכנן עבור חמישה ספקי מודלים, שלושה מנועי חיפוש ושמונה סוגי לקוחות לפני שיש צורך ממשי, יהיה קשה להבנה וקשה לבדיקה. עדיף לתכנן נקודות החלפה סביב החלטות שכבר צפויות להשתנות.
במערכת AI, ספק המודל יכול להשתנות. גם מנגנון retrieval, פורמט הציטוטים, מדיניות ה-cache ומגבלות העלות עשויים להשתנות. לכן כדאי לחשוף למוצר חוזה יציב, למשל שירות שמחזיר תשובה, מקורות, סטטוס ותיעוד של מגבלות. מאחורי החוזה הזה אפשר להחליף provider או לשנות prompt. לעומת זאת, אין סיבה שה-frontend יכיר את מבנה התגובה הגולמי של כל ספק AI.
אותו עיקרון חל על אינטגרציות. אם מערכת חיצונית מחזירה סטטוסים לא עקביים, אל תאפשרו לסטטוסים האלה לזלוג לכל המוצר. צרו שכבת התאמה שמתרגמת את השפה החיצונית למונחים של הדומיין שלכם. כך החלפת ספק, שינוי API חיצוני או כשל זמני נשארים תחומים במקום אחד.
הגדירו חוזים קטנים, מפורשים וניתנים לאימות
חוזה API טוב אינו רק תיעוד. הוא כולל schema לבקשה ולתגובה, משמעות של כל שדה, קודי שגיאה צפויים, כללי pagination, ומגבלות כמו rate limit או גודל payload. חשוב במיוחד להבדיל בין שדה חסר, `null` וערך ריק. במערכות ייצור, ההבדל הזה הופך לא פעם לבאג עסקי.
תנו שמות שמתארים את משמעות הנתון, ולא את דרך המימוש שלו. לדוגמה, `citation` עדיף על `vectorSearchResult`, אם מה שהלקוח צריך הוא מקור לציטוט ולא פרטי מנוע החיפוש. אם בעתיד תעברו מחיפוש וקטורי למנגנון היברידי, החוזה יכול להישאר יציב.
כדאי לייצר בדיקות חוזה בין שירותים. בדיקה כזו מאמתת שהתגובה בפועל עדיין עומדת ב-schema שהצרכן מצפה לו. היא אינה מחליפה בדיקות end-to-end, אבל היא תופסת שינויים שקטים, כמו שינוי טיפוס של שדה או הסרת ערך שהלקוח תלוי בו.
גרסאות: לא שוברים לקוחות כדי לנקות קוד
גרסאות API נדרשות כאשר שינוי משנה משמעות קיימת, לא בכל refactor פנימי. הוספת שדה אופציונלי בדרך כלל אינה דורשת גרסה חדשה. שינוי של משמעות שדה, הסרת שדה, או החלפת פורמט תשובה כן עשויים לדרוש זאת.
הבחירה בין גרסה בנתיב, בכותרת בקשה או במנגנון אחר פחות חשובה מהמדיניות. צריך לדעת מי משתמש באיזו גרסה, מה תאריך ה-deprecation, אילו התראות נשלחות, ואיך מודדים שימוש בגרסה הישנה. בלי נתוני שימוש, צוותים נוטים להשאיר קוד ישן לנצח או להסיר אותו מוקדם מדי.
במוצר פנימי אפשר לעיתים לתאם שינוי רחב יותר. ב-API לצרכנים חיצוניים או לאפליקציות מובייל, עליכם להניח שלקוחות ישדרגו באיחור. לכן תאימות לאחור היא נכס תפעולי, לא נימוס.
הרשאות חייבות להיות חלק מהחוזה
ב-SaaS רב-חשבונות, tenant isolation אינו שכבת middleware שמוסיפים בסוף. הוא צריך להיכנס לכל מסלול נתונים: קריאה, כתיבה, חיפוש, cache, קבצים, logs ותורים אסינכרוניים.
במיוחד ב-RAG, מסנן הרשאות שמופעל רק אחרי retrieval הוא מאוחר מדי. אם מנוע החיפוש החזיר קטע ממסמך של account אחר, כבר ביצעתם פעולה שאסור היה לבצע. סינון ה-tenant, המשתמש וההרשאות צריך להיות חלק מה-query עצמו, ובמידת האפשר גם חלק ממבנה האינדקס והמטא-דאטה.
ה-API צריך להחזיר שגיאות שלא חושפות מידע מיותר. לפעמים נכון להחזיר `404` במקום `403`, כדי לא לאשר שמשאב קיים. זו אינה החלטה אוניברסלית: בממשק ניהול פנימי, `403` מפורש יכול להועיל לתמיכה. ההחלטה תלויה במודל האיום ובחוויית המשתמש, אבל היא צריכה להיות עקבית ומתועדת.
תכננו כשל, זמן ועלות כבר בממשק
קריאות למודל, חיפוש חיצוני או אינטגרציה אינן תמיד מהירות ואינן תמיד זמינות. API גמיש חייב להבהיר מה קורה כשפעולה אורכת זמן, נכשלת חלקית או מתבצעת מחדש.
לפעולה קצרה, בקשה סינכרונית מתאימה. לפעולת אינדוקס, ייצוא או ניתוח קבצים, עדיף לרוב להחזיר מזהה עבודה ולהציע endpoint סטטוס. אל תסתירו עבודה ארוכה מאחורי timeout של HTTP. זה יוצר retries כפולים, עומס מיותר וחוויית משתמש לא צפויה.
בפעולות כתיבה, השתמשו ב-idempotency key כשיש סיכוי שהלקוח ישלח את אותה בקשה שוב. כך retry בעקבות ניתוק רשת לא ייצור שתי שיחות, שתי חשבוניות או שתי משימות עיבוד. עבור פעולות אסינכרוניות, הגדירו במפורש האם המערכת מבטיחה עיבוד לפחות פעם אחת, לכל היותר פעם אחת, או שיש צורך בטיפול בכפילויות.
גם עלות היא חלק מהתכנון. endpoint שמאפשר לכל משתמש לבצע retrieval רחב ועיבוד מודל יקר ללא מגבלה עלול לעבוד היטב בהתחלה, ואז להפוך לבעיה תקציבית. מגבלות שימוש, caching, הגבלת גודל הקשר ומדידת צריכת tokens צריכים להיות החלטות מוצר ומערכת, לא תיקון חירום אחרי העלייה לאוויר.
תצפיות ו-evals הופכים API למערכת שניתן לנהל
אם אי אפשר לקשור בקשת משתמש לקריאת API, לחיפוש המקורות, לגרסת ה-prompt ולתשובת המודל, קשה מאוד לחקור תשובה שגויה. כל בקשה צריכה לקבל correlation ID שעובר בין השירותים. לצד זה, יש לתעד באופן בטוח את ה-tenant, סוג הפעולה, זמני רכיבים, קודי שגיאה, עלות משוערת ומקורות שנבחרו. לא מתעדים מידע רגיש רק כדי שיהיה קל לדבג.
בפיצ'ר RAG, תצפיות לבדן אינן מספיקות. צריך evals - מערך בדיקות שמודד איכות על שאלות ותשובות מייצגות. אפשר לבדוק למשל אם התשובה נתמכת במקורות שהוחזרו, אם הציטוטים נכונים, ואם המערכת מסרבת כשאין בסיס מספק לתשובה. כאשר משנים endpoint, מנגנון retrieval או prompt, מריצים את ה-evals לפני שחרור ולא מסתמכים רק על תחושת בטן.
ב-DevCo Solutions אנו רואים שוב ושוב שאיכות AI בייצור תלויה לא רק בבחירת מודל, אלא בגבולות API, בהרשאות וביכולת למדוד את כל השרשרת. אלה החלקים שמאפשרים לצוות לדעת אם שינוי שיפר את המוצר או רק הזיז את התקלה למקום אחר.
מתי לעצור ולפשט
לא כל מערכת צריכה event bus, gateway נפרד, CQRS או עשרות שירותים. אם יש צוות קטן, דומיין שעדיין משתנה במהירות ומסד נתונים אחד, מודולרי מונולית עם חוזים פנימיים ברורים עשוי להיות הבחירה הבטוחה ביותר. פיצול מוקדם מוסיף פריסות, ניטור, כשלי רשת ועקביות מורכבת.
מצד שני, אם רכיב AI יקר משתנה בקצב אחר מהליבה, אם קיימים צרכנים חיצוניים, או אם דרישות הרשאה שונות באופן מהותי, הפרדה יכולה להפחית סיכון. השאלה אינה איזה תרשים ארכיטקטורה נראה מתקדם יותר, אלא איזה גבול מאפשר לצוות לשנות דבר אחד בלי לפגוע בדברים שאינם קשורים.
הצעד הבא אינו שכתוב. בחרו endpoint אחד שמייצר היום הכי הרבה תקלות או אי-ודאות, ומפו עבורו את החוזה, ההרשאות, הכשלים והמדדים שחסרים. אם אתם צריכים עין ארכיטקטונית נוספת על מערכת קיימת, אפשר לקבוע שיחת סקירה קצרה דרך טופס יצירת הקשר באתר.
