מצב היועץ (/advisor)
היועץ עונה לגולשים, ובעיקר למי שעוד לא משתמש ב-CodeKeeper, אם המוצר מתאים לאופן שבו הם עובדים עם סוכני AI, או שפתרון פשוט יותר יספיק להם. הוא עונה מתוך מסמך ידע אחד, services/advisor_knowledge/codekeeper.md, שנכנס כולו לפרומפט בכל שאלה, בלי RAG. הוא עבר לכאן מהבוט הנפרד codekeeper_advisor_bot. המדריך המלא, עם הנימוק לכל החלטה בבנייה, נשאר בריפו ההוא (docs/codekeeper_advisor_bot_guide.md) וב-CodeKeeper. העמוד הזה מתאר את ההשתלבות בבוט.
כניסה ויציאה
כניסה: הפקודה
/advisor, או הקישורhttps://t.me/my_code_keeper_bot?start=advisor, שמגיע לבוט כ-/start advisor. גולש שמעולם לא פתח את הבוט ונכנס מהקישור מקבל את פתיחת היועץ, ולא את הפתיחה הרגילה. הכניסה אינה רושמת אותו כמשתמש, ואת זה עושה/startהרגיל, שהודעת היציאה מפנה אליו. אין ליועץ כפתור בתפריט הראשי.יציאה: כפתור ”יציאה מהיועץ“ שמופיע בכל תשובה, או הפקודה
/cancel. לחיצה על כפתור יציאה בהודעה ישנה, אחרי שהגולש כבר יצא, עונה שהוא כבר בחוץ ואיך חוזרים. אין יציאה אוטומטית אחרי זמן בלי פעילות: גולש שחוזר אחרי שעה ממשיך את השיחה מאיפה שעצר. המחיר הוא שגולש ששכח שהוא ביועץ ושולח קוד מקבל תשובה מהמודל ולא שמירה, עד שהוא יוצא.כל פעולה אחרת מוציאה מהיועץ: פקודה, כפתור של המקלדת הראשית, קובץ, או לחיצה על כפתור של תפריט אחר. הגולש מקבל הודעה קצרה שהיועץ נסגר, והפעולה ממשיכה לבוט הרגיל כאילו לא היה שם. היועץ עונה על טקסט בלבד. עריכה של הודעה בזמן שהיועץ פתוח נבלעת: היא אינה שאלה חדשה, וגם אינה קוד לשמירה.
הניתוב
מחוץ ליועץ, כל הודעת טקסט שלא שייכת לזרימה אחרת היא קוד לשמירה (handle_text_message ב-main.py). לכן היועץ הוא ConversationHandler בקבוצה משלו, ADVISOR_HANDLER_GROUP ב-handlers/advisor.py: אחרי שער מגבלת הקצב, ולפני כל handler טקסט אחר של הבוט. הודעה שהיועץ לוקח עוצרת שם ב-ApplicationHandlerStop, ולא מגיעה לשאר הקבוצות.
הבוט מעבד עדכון אחד בכל פעם, וקריאה למודל נמשכת שניות. לכן ה-handlers של היועץ רק מחליטים לאן ההודעה הולכת ומחזירים מיד, והעבודה עצמה (הפתיחה, הקריאה למודל, השליחה והשמירה) רצה במשימה של application.create_task. את ההחלטה אי אפשר להעביר לרקע: ב-handler שרץ עם block=False, ApplicationHandlerStop לא עוצר דבר (python-telegram-bot 22.5, telegram/ext/_application.py), וההודעה הייתה ממשיכה גם לשמירת קוד. כל המשימות של גולש אחד עוברות דרך asyncio.Lock אחד, ולכן שאלה ששולחים מיד אחרי הכניסה רואה את הפתיחה בזיכרון השיחה, ושתי שאלות מהירות נענות לפי הסדר.
def build_advisor_conversation() -> ConversationHandler:
private_message = filters.ChatType.PRIVATE & filters.UpdateType.MESSAGE
question = private_message & filters.TEXT & ~filters.COMMAND & ~filters.Text(_main_menu_labels())
return ConversationHandler(
entry_points=[
CommandHandler("advisor", _enter, filters=private_message),
CommandHandler(
"start", _enter, filters=private_message & filters.Regex(r"^/start(?:@\w+)?\s+advisor\s*$")
),
CallbackQueryHandler(_on_follow_up, pattern=_FOLLOW_UP_PATTERN),
],
states={
ADVISOR_CHAT: [
MessageHandler(question, _on_text),
CommandHandler("cancel", _exit, filters=private_message),
CallbackQueryHandler(_exit, pattern=_EXIT_PATTERN),
MessageHandler(filters.UpdateType.EDITED_MESSAGE, _ignore_edit),
],
},
fallbacks=[
MessageHandler(filters.UpdateType.MESSAGE, _leave),
CallbackQueryHandler(_leave),
],
allow_reentry=True,
)
מה לא שורד ריסטארט: מצב השיחה חי בזיכרון, כמו בכל שאר הזרימות של הבוט, שאין לו persistence. אחרי פריסה הגולש כבר מחוץ ליועץ, וההודעה הבאה שלו מגיעה לבוט הרגיל. זיכרון השיחה והמכסה היומית שמורים במסד, ולכן הם נשמרים.
מה נשמר במסד
advisor_history— זיכרון השיחה. מסמך אחד לכל זוג שאלה-תשובה, כך ש-insert_oneאחד שומר את שתיהן או אף אחת. נשמרות רק תשובות מוצלחות, והפתיחה נשמרת כזוג הראשון, כי היא שואלת את הגולש שאלות.ADVISOR_CONTEXT_EXCHANGESהזוגות האחרונים נכנסים לפרומפט (ראו בהמשך, ”הגדרות והקריאה למודל“). המסמכים נמחקים באינדקס TTL אחריHISTORY_RETENTION_DAYSימים, וזו הבטחה שהפתיחה נותנת לגולש. השאילתה מסננת לפי גיל גם בעצמה, כי מחיקת ה-TTL אינה מיידית.advisor_daily_usage— המכסה היומית. מגבלת הקצב של הבוט סופרת הודעות בדקה, כלומר עוצרת הצפה ולא עלות. לכן לכל גולש יש תקרה שלDAILY_QUESTION_LIMITשאלות ליום, לפי היום בשעון ישראל, ולחיצה על שאלת המשך נספרת כשאלה. הבדיקה והרישום הם פעולה אטומית אחת (take_daily_question), שעומדת גם בבקשות מקבילות. בלי מסד היועץ אינו עונה: אי אפשר לספור, ולכן גם לא לאשר.
שמות האוספים, HISTORY_RETENTION_DAYS ומפרט האינדקסים יושבים ב-advisor_collections.py, מודול שורש בלי pymongo, כמו mcp_uploads.py: database/manager.py מייבא אותו, וחייב לעלות גם כשהייבוא של pymongo נכשל. חלון השמירה קבוע בקוד ולא משתנה סביבה, כי כל שירות שמייבא את database מצהיר על אינדקס ה-TTL בעלייה, וערך שמוגדר רק בבוט היה מתהפך בכל עלייה של שירות אחר. DAILY_QUESTION_LIMIT והקריאה והכתיבה עצמן ב-services/advisor_store.py. האינדקסים נוצרים בכל עלייה ב-DatabaseManager._create_advisor_indexes, וכשל של אינדקס TTL יוצא כאירוע db_advisor_index_missing ברמת error (קטלוג אירועים קנוניים).
הגדרות והקריאה למודל
חמישה משתני סביבה בשירות של הבוט (משתני סביבה - רפרנס): ADVISOR_API_KEY, ADVISOR_BASE_URL ו-ADVISOR_MODEL הם חובה. ADVISOR_CONTACT_URL הוא הקישור לכפתור ”לדבר עם המפתח“. ADVISOR_CONTEXT_EXCHANGES הוא אורך הזיכרון, בזוגות של שאלה ותשובה, ובלעדיו DEFAULT_CONTEXT_EXCHANGES. הוא נספר בזוגות ולא בהודעות כמו CONTEXT_WINDOW_SIZE בבוט הנפרד, כי כל מסמך בזיכרון השיחה הוא זוג. כל זוג נשלח שוב בכל שאלה שאחריו, ולכן MAX_CONTEXT_EXCHANGES חוסם ערך שהוקלד בטעות. כשאחד מהשלושה חסר או פגום, היועץ כבוי: /advisor עונה שהוא לא זמין, /start advisor מגיע ל-/start הרגיל, ולחיצה על כפתור בהודעה ישנה של היועץ, מתקופה שבה היה פעיל, עונה שהוא לא זמין (ה-handler הכללי של הלחיצות לא מכיר את הכפתורים האלה ולא עונה עליהם). הסיבה נרשמת בלוג בעלייה, והבוט עצמו עולה כרגיל.
הקריאה למודל עוברת דרך openai.AsyncOpenAI מול כל API שתואם OpenAI, למשל Gemini. base_url והמפתח מועברים ללקוח תמיד במפורש, כי בלעדיהם ה-SDK קורא בעצמו את OPENAI_BASE_URL ואת OPENAI_API_KEY מהסביבה. לקריאה כולה, כולל הניסיון החוזר של ה-SDK, יש תקרת זמן אחת, ANSWER_DEADLINE_SECONDS. כשל של הספק או חריגה מהתקרה מחזירים לגולש הודעת תקלה כללית, והיא לא נכנסת לזיכרון השיחה. ההגדרות והקבועים ב-services/advisor_service.py.
בדיקות
tests/test_advisor_routing_real_ptb.py— הניתוב באפליקציה האמיתית של הבוט, מול python-telegram-bot האמיתי, בתת-תהליך: גולש חדש מהקישור, יציאה בכל הדרכים, הודעות שמגיעות לשמירת קוד אחרי היציאה, והמכסה המלאה.tests/test_advisor_store_mongo.py— המכסה תחת בקשות מקבילות, הזיכרון והאינדקסים, מול מונגו אמיתי (ראו Testing Guide, הסעיף על בדיקות מול מונגו אמיתי).tests/test_advisor_pipeline.py,tests/test_advisor_service.py,tests/test_advisor_store.pyו-tests/test_advisor_text.py— הצינור, הקריאה למודל (כולל בקשה אמיתית של ה-SDK מול שרת מקומי), והעיצוב לטלגרם.