📌 הפסיקו לקרוא לזה Spec
בעולם החדש כשאנחנו כותבים דף הוראות ארוך לסוכן קידוד אנחנו קוראים לו Spec. מפתחים מספרים שעכשיו נהיינו כולנו מאפיינים וכותבים Spec-ים כל היום. יש אפילו גישות פיתוח חדשות שזכו לשם Spec Driven Development. אבל למרות שהמילה Spec או מסמך איפיון נשארה, המשמעות של מסמכי איפיון היום והמבנה שלהם מאוד שונה ממסמכי האיפיון שכתבו ארכיטקטים עד לפני מספר שנים.
מסמכי איפיון קלאסיים נכתבו לבני אדם. הם היו תיאוריים והסבירו את ה"מה" וה"למה" כדי שבני אדם יבינו את ההגיון של הפיתוח. בני אדם יודעים לקרוא בין השורות ולכן אפשר היה להסתדר עם טקסט דו משמעי או לא מספיק מדויק ולפחות בתיאוריה מסמכי האיפיון היו אמורים להישמר עדכניים לאורך כל חיי המערכת.
מסמכי Spec שאנחנו כותבים לסוכני AI חייבים להיות מדויקים. הם מתארים את הפיצ'ר אבל הרבה יותר טכניים ומסבירים לסוכן איך לממש ומה לא לעשות. המילים שאנחנו בוחרים חייבות לתאר בצורה מדויקת מה אנחנו רוצים והרבה פעמים ניסוח קצת אחר הוא ההבדל בין הצלחה לכשלון. שינוי במודל או בסוכן הקידוד כמעט תמיד מחייב עדכון של ה Spec כי הוראות שעבדו עם סוכן קידוד ומודל מסוים יעבדו פחות טוב בהפעלה עם סוכן אחר.
בעולם הישן Spec היה תבנית. תבנית שארכיטקטים נותנים למפתחים כדי שיוסיפו את התובנות שלהם ויבנו תוך קבלת החלטות ולקיחת אחריות. התבנית היתה בעלת ערך לאורך זמן בלי לפגוע בערך של הקוד עצמו. בעולם החדש Spec הוא דף הוראות, הוא סקריפט שמסביר איך להביא את הקוד מנקודה א לנקודה ב. הוא ספציפי לקוד ולסוכן, הוא מדויק ומאחר והקוד עצמו נוצר על ידי מכונה אותו Spec מתחרה עם הקוד על מי מקור האמת.
אין ספק שאנחנו צריכים לשמור את הפרומפטים ולתעד את הכוונה בעבודה עם AI. אבל השם "מסמך איפיון" לא תורם להבנה.
בעולם החדש כשאנחנו כותבים דף הוראות ארוך לסוכן קידוד אנחנו קוראים לו Spec. מפתחים מספרים שעכשיו נהיינו כולנו מאפיינים וכותבים Spec-ים כל היום. יש אפילו גישות פיתוח חדשות שזכו לשם Spec Driven Development. אבל למרות שהמילה Spec או מסמך איפיון נשארה, המשמעות של מסמכי איפיון היום והמבנה שלהם מאוד שונה ממסמכי האיפיון שכתבו ארכיטקטים עד לפני מספר שנים.
מסמכי איפיון קלאסיים נכתבו לבני אדם. הם היו תיאוריים והסבירו את ה"מה" וה"למה" כדי שבני אדם יבינו את ההגיון של הפיתוח. בני אדם יודעים לקרוא בין השורות ולכן אפשר היה להסתדר עם טקסט דו משמעי או לא מספיק מדויק ולפחות בתיאוריה מסמכי האיפיון היו אמורים להישמר עדכניים לאורך כל חיי המערכת.
מסמכי Spec שאנחנו כותבים לסוכני AI חייבים להיות מדויקים. הם מתארים את הפיצ'ר אבל הרבה יותר טכניים ומסבירים לסוכן איך לממש ומה לא לעשות. המילים שאנחנו בוחרים חייבות לתאר בצורה מדויקת מה אנחנו רוצים והרבה פעמים ניסוח קצת אחר הוא ההבדל בין הצלחה לכשלון. שינוי במודל או בסוכן הקידוד כמעט תמיד מחייב עדכון של ה Spec כי הוראות שעבדו עם סוכן קידוד ומודל מסוים יעבדו פחות טוב בהפעלה עם סוכן אחר.
בעולם הישן Spec היה תבנית. תבנית שארכיטקטים נותנים למפתחים כדי שיוסיפו את התובנות שלהם ויבנו תוך קבלת החלטות ולקיחת אחריות. התבנית היתה בעלת ערך לאורך זמן בלי לפגוע בערך של הקוד עצמו. בעולם החדש Spec הוא דף הוראות, הוא סקריפט שמסביר איך להביא את הקוד מנקודה א לנקודה ב. הוא ספציפי לקוד ולסוכן, הוא מדויק ומאחר והקוד עצמו נוצר על ידי מכונה אותו Spec מתחרה עם הקוד על מי מקור האמת.
אין ספק שאנחנו צריכים לשמור את הפרומפטים ולתעד את הכוונה בעבודה עם AI. אבל השם "מסמך איפיון" לא תורם להבנה.
📌 איך עובדת הזדהות OAuth ב MCP של Langlets
בוובינר היום ראינו איך לחבר סוכני AI למערכות שלנו ודיברנו על הדוגמה של Langlets ומימוש ה MCP שם. מפאת קוצר זמן דילגתי על הירידה לפרטים התחביריים של הקוד. בפוסט זה אני אתחיל לסגור את הפער ואציג את הפרטים הטכניים סביב הזדהות OAuth.
✏ רישום האפליקציה
בשלב הראשון בתהליך ההזדהות סוכן ה AI צריך להירשם בתור "אפליקציה מורשית גישה". בדרך כלל ב OAuth אנחנו רושמים אפליקציות צד שלישי שיוכלו להתחבר למערכות שלנו בצורה ידנית, לדוגמה אם אתם רוצים לכתוב אפליקציה שתיגש בשם המשתמש לגיטהאב עליכם להיכנס למסך הניהול של גיטהאב ולרשום שם "אפליקציית גיטהאב חדשה" בחשבון שלכם. אתם תקבלו מזהה של האפליקציה בדמות Client Id ו Client Secret ותשתמשו במזהה זה כשתשלחו את המשתמשים להזדהות מול גיטהאב עבור האפליקציה שלכם.
בעולם של MCP סוכני AI לא יכולים לרשום את עצמם מראש מול האפליקציה שלי ולכן OAuth מאפשר מנגנון של רישום אוטומטי. ב langlets מנגנון זה ממומש בקבצים:
https://github.com/ynonp/langlets-rails/blob/main/app/controllers/well_known_controller.rb
https://github.com/ynonp/langlets-rails/blob/main/app/controllers/oauth/registrations_controller.rb
סוכן ה AI מתחיל עם הנתיב
רק אחרי יצירת האפליקציה אפשר לשלוח את המשתמש לתהליך ההזדהות ולקבל טוקן עבורו.
✏ הזדהות בשם המשתמש ובקשת קוד גישה
אחרי שסוכן ה AI נרשם בתור אפליקציה הוא מקבל תשובה במבנה הבא (עדיין מתוך הקובץ
אנחנו מזהים שם את מזהה האפליקציה
קריאה זו מטופלת בצד של langlets על ידי ספריית Doorkeeper בזכות השורות האלה בקובץ
דורקיפר מטפלת בפרמטרים שהתקבלו ומציגה טופס הזדהות שמוגדר בקובץ:
https://github.com/ynonp/langlets-rails/blob/main/app/views/doorkeeper/authorizations/new.html.erb
בבסיס הנתונים דורקיפר שומרת את פרמטר ה Code Challenge בטבלת
בוובינר היום ראינו איך לחבר סוכני AI למערכות שלנו ודיברנו על הדוגמה של Langlets ומימוש ה MCP שם. מפאת קוצר זמן דילגתי על הירידה לפרטים התחביריים של הקוד. בפוסט זה אני אתחיל לסגור את הפער ואציג את הפרטים הטכניים סביב הזדהות OAuth.
✏ רישום האפליקציה
בשלב הראשון בתהליך ההזדהות סוכן ה AI צריך להירשם בתור "אפליקציה מורשית גישה". בדרך כלל ב OAuth אנחנו רושמים אפליקציות צד שלישי שיוכלו להתחבר למערכות שלנו בצורה ידנית, לדוגמה אם אתם רוצים לכתוב אפליקציה שתיגש בשם המשתמש לגיטהאב עליכם להיכנס למסך הניהול של גיטהאב ולרשום שם "אפליקציית גיטהאב חדשה" בחשבון שלכם. אתם תקבלו מזהה של האפליקציה בדמות Client Id ו Client Secret ותשתמשו במזהה זה כשתשלחו את המשתמשים להזדהות מול גיטהאב עבור האפליקציה שלכם.
בעולם של MCP סוכני AI לא יכולים לרשום את עצמם מראש מול האפליקציה שלי ולכן OAuth מאפשר מנגנון של רישום אוטומטי. ב langlets מנגנון זה ממומש בקבצים:
https://github.com/ynonp/langlets-rails/blob/main/app/controllers/well_known_controller.rb
https://github.com/ynonp/langlets-rails/blob/main/app/controllers/oauth/registrations_controller.rb
סוכן ה AI מתחיל עם הנתיב
oauth_authorization_server ודרכו מקבל את השמות של כל הנתיבים שהוא צריך כדי לרשום אפליקציה. לאחר מכן הוא ממשיך לנתיב ההרשמה ושם מגיע לפונקציה RegistrationsController#create. פונקציה זו יוצרת אפליקציה חדשה ב Doorkeeper שזו הספריה ברובי שאחראית על הזדהות באמצעות טוקנים. זה הקוד הרלוונטי ממנה:application = Doorkeeper::Application.new(
name: params[:client_name].presence || "Unnamed agent client",
redirect_uri: redirect_uris.join("\n"),
scopes: allowed_scopes(params[:scope]),
confidential: params[:token_endpoint_auth_method] != "none",
dynamically_registered: true
)
unless application.save
return rfc_error("invalid_client_metadata", application.errors.full_messages.join("; "))
end
render status: :created, json: registration_response(application, redirect_uris)
רק אחרי יצירת האפליקציה אפשר לשלוח את המשתמש לתהליך ההזדהות ולקבל טוקן עבורו.
✏ הזדהות בשם המשתמש ובקשת קוד גישה
אחרי שסוכן ה AI נרשם בתור אפליקציה הוא מקבל תשובה במבנה הבא (עדיין מתוך הקובץ
registrations_controller.rb): response = {
client_id: application.uid,
client_id_issued_at: application.created_at.to_i,
client_name: application.name,
redirect_uris: redirect_uris,
grant_types: %w[authorization_code refresh_token],
response_types: %w[code],
token_endpoint_auth_method: application.confidential? ? "client_secret_basic" : "none",
scope: application.scopes.to_s
}אנחנו מזהים שם את מזהה האפליקציה
client_id. בשלב הבא סוכן ה AI שולח את הגולש באמצעות הפניית הדפדפן לנתיב הזדהות על Langlets:Open /oauth/authorize?client_id=...&code_challenge=...&code_challenge_method=S256
קריאה זו מטופלת בצד של langlets על ידי ספריית Doorkeeper בזכות השורות האלה בקובץ
config/routes.rb:use_doorkeeper do
skip_controllers :authorized_applications
end
דורקיפר מטפלת בפרמטרים שהתקבלו ומציגה טופס הזדהות שמוגדר בקובץ:
https://github.com/ynonp/langlets-rails/blob/main/app/views/doorkeeper/authorizations/new.html.erb
בבסיס הנתונים דורקיפר שומרת את פרמטר ה Code Challenge בטבלת
oauth_access_grants. זו הגדרת הטבלה:create_table "oauth_access_grants", force: :cascade do |t|
t.bigint "resource_owner_id", null: false
t.bigint "application_id", null: false
t.string "token", null: false
t.integer "expires_in", null: false
t.text "redirect_uri", null: false
t.string "scopes", default: "", null: false
t.datetime "created_at", null: false
t.datetime "revoked_at"
t.string "code_challenge"
t.string "code_challenge_method"
t.index ["application_id"], name: "index_oauth_access_grants_on_application_id"
t.index ["resource_owner_id"], name: "index_oauth_access_grants_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
GitHub
langlets-rails/app/controllers/well_known_controller.rb at main · ynonp/langlets-rails
Contribute to ynonp/langlets-rails development by creating an account on GitHub.
אנחנו עוד נצטרך את הערך שלו. אחרי שהמשתמש מאשר את הגישה דורקיפר שולחת את המשתמש חזרה לסוכן ה AI עם פרמטר בשם code.
✏ החלפת קוד הגישה בטוקן
סוכן ה AI קיבל קוד גישה אבל עדיין אי אפשר לבצע פעולות במערכת עם קוד זה, ל OAuth יש עוד שלב אחד - החלפת קוד הגישה בטוקן. בשביל ההחלפה סוכן ה AI פונה לנתיב:
ומצרף את הקוד שקיבל. דורקיפר בצד השרת מבצעת:
1. מוצאת את השורה הרלוונטית מ
2. מחשבת Hash של
3. במידה והכל תקין מחזירה Access Token ו Refresh Token. ה Access Token תקף לשעתיים וה Refresh Token לחודש. בעזרת ה Refresh Token אפשר לקבל Access Tokens נוספים בהמשך.
✏ ביצוע בקשות מול ה MCP
עכשיו יש לסוכן ה AI אסימון גישה והוא יכול להתחיל לבצע פעולות בשם המשתמש. פניה ראשונה ל
https://github.com/ynonp/langlets-rails/blob/main/app/controllers/mcp_controller.rb
שם אנחנו פוגשים את הפונקציה handle שמגדירה את הכלים הזמינים ומפעילה את הכלי המתאים:
הכלים עצמם מוגדרים בקבצים בתיקיה:
https://github.com/ynonp/langlets-rails/tree/main/app/mcp/tools
ובתוך כל כלי המשתנה
אומנם לנגלטס כתוב ב Ruby אבל העקרונות וזרימת המידע שתוארה כאן יעבדו בכל מערכת שתרצו לחבר לסוכני AI או אפילו באופן כללי יותר למערכות צד שלישי באמצעות OAuth.
✏ החלפת קוד הגישה בטוקן
סוכן ה AI קיבל קוד גישה אבל עדיין אי אפשר לבצע פעולות במערכת עם קוד זה, ל OAuth יש עוד שלב אחד - החלפת קוד הגישה בטוקן. בשביל ההחלפה סוכן ה AI פונה לנתיב:
POST /oauth/token {grant_type, code, code_verifier, client_id}ומצרף את הקוד שקיבל. דורקיפר בצד השרת מבצעת:
1. מוצאת את השורה הרלוונטית מ
oauth_access_grants בה עמודת token שווה לקוד שהתקבל כפרמטר.2. מחשבת Hash של
code_verifier ומוודאת שהוא תואם ל code_challenge ששמור בטבלה.3. במידה והכל תקין מחזירה Access Token ו Refresh Token. ה Access Token תקף לשעתיים וה Refresh Token לחודש. בעזרת ה Refresh Token אפשר לקבל Access Tokens נוספים בהמשך.
✏ ביצוע בקשות מול ה MCP
עכשיו יש לסוכן ה AI אסימון גישה והוא יכול להתחיל לבצע פעולות בשם המשתמש. פניה ראשונה ל
/mcp מגיעה לקובץ:https://github.com/ynonp/langlets-rails/blob/main/app/controllers/mcp_controller.rb
שם אנחנו פוגשים את הפונקציה handle שמגדירה את הכלים הזמינים ומפעילה את הכלי המתאים:
def handle
server = MCP::Server.new(
name: "langlets",
version: "1.0.0",
tools: [ Tools::GetVocabulary, Tools::GetCourses ],
server_context: { user: @current_user, scopes: @token_scopes, base_url: request.base_url }
)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(
server, stateless: true, enable_json_response: true
)
status, transport_headers, body = transport.handle_request(request)
transport_headers.each { |key, value| response.set_header(key, value) }
render body: body.join, status: status, content_type: transport_headers["Content-Type"] || "application/json"
end
הכלים עצמם מוגדרים בקבצים בתיקיה:
https://github.com/ynonp/langlets-rails/tree/main/app/mcp/tools
ובתוך כל כלי המשתנה
server_context[:user] מחזיק את המשתמש המחובר.אומנם לנגלטס כתוב ב Ruby אבל העקרונות וזרימת המידע שתוארה כאן יעבדו בכל מערכת שתרצו לחבר לסוכני AI או אפילו באופן כללי יותר למערכות צד שלישי באמצעות OAuth.
GitHub
langlets-rails/app/controllers/mcp_controller.rb at main · ynonp/langlets-rails
Contribute to ynonp/langlets-rails development by creating an account on GitHub.
📌 בעידן ה AI באמת אין דבר כזה חודשים?
לקוח אמר לי את זה השבוע ואני חייב להודות שגם אני שמתי לב לשינוי: כל רעיון שיש לנו אפשר להעלות לאוויר דמו של הפיצ'ר תוך שעה-שעתיים.
אפילו אם מדובר בפיצ'ר גדול ועם כל האתגר של ביצועים ומעבר לפרודקשן, אם הפידבק חיובי תוך כמה שעות או ימים אנחנו עם זה באוויר.
השבוע חבר שכתב פיצ'ר במערכת. במקור המפתח שבנה את הפיצ'ר ישב על זה חודשים, אולי חצי שנה. החבר העלה גרסה חדשה מאפס וטובה יותר של הפיצ'ר תוך יומיים. הזמנים האלה התקצרו.
כשבוריס צ'רני מדבר על Coding Is Solved אני לא חושב שהכוונה שלא יהיו אנשי תוכנה, אלא שהחלק הספציפי בעבודה של אנשי תוכנה של לקחת רעיון ולהמיר אותו לקוד התקצר משמעותית. השאלה היא כבר לא האם אפשר לבנות את זה או כמה זמן זה ייקח אלא איך אני רוצה שזה יהיה בנוי. זה שינוי גדול.
מצד שני עדיין יש מנגנונים שייקח לנו הרבה זמן לבנות, אולי אפילו חודשים. לדוגמה:
1. פיצ'ר שאנחנו לא בטוחים איך יראה, הרבה פעמים אני מזהה שאני מעלה דמו ללקוח, מקבל משוב, מעלה עוד דמו וכך ממשיכים להתלבט ולנסות עד שמגיעים למשהו שעובד מספיק טוב. דיאלוג כזה יכול לקחת שבועות ואפילו חודשים.
2. פיצ'ר שדורש תיאום עם לקוחות או עם בעלי עניין אחרים. אם צריך מיגרציה של מידע בין מערכות וכדי להריץ את המיגרציה אני צריך ששני צוותים יפסיקו את העבודה השוטפת על המערכות עד שאני מסיים להעביר את הנתונים התיאום הזה יכול לקחת זמן.
ככל שהזמנים לכתיבת קוד מתקצרים אנחנו לומדים לראות את החלקים האחרים של העבודה שלנו ואת החשיבות שלהם: תקשורת, קבלת החלטות מתוך הבנה טכנית מעמיקה, היכרות עם הארגון, יכולת לצפות את העתיד, הבנת המשתמשים. האם מפתחים הופכים יותר לאנשי פרודקט? או שאנשי פרודקט הופכים יותר דומים למפתחים? עדיין מוקדם לדעת. מה שבטוח הוא שהיכולת לדחוף מהר מוצרים עובדים ללקוחות מביאה שינוי עצום לתעשייה.
לקוח אמר לי את זה השבוע ואני חייב להודות שגם אני שמתי לב לשינוי: כל רעיון שיש לנו אפשר להעלות לאוויר דמו של הפיצ'ר תוך שעה-שעתיים.
אפילו אם מדובר בפיצ'ר גדול ועם כל האתגר של ביצועים ומעבר לפרודקשן, אם הפידבק חיובי תוך כמה שעות או ימים אנחנו עם זה באוויר.
השבוע חבר שכתב פיצ'ר במערכת. במקור המפתח שבנה את הפיצ'ר ישב על זה חודשים, אולי חצי שנה. החבר העלה גרסה חדשה מאפס וטובה יותר של הפיצ'ר תוך יומיים. הזמנים האלה התקצרו.
כשבוריס צ'רני מדבר על Coding Is Solved אני לא חושב שהכוונה שלא יהיו אנשי תוכנה, אלא שהחלק הספציפי בעבודה של אנשי תוכנה של לקחת רעיון ולהמיר אותו לקוד התקצר משמעותית. השאלה היא כבר לא האם אפשר לבנות את זה או כמה זמן זה ייקח אלא איך אני רוצה שזה יהיה בנוי. זה שינוי גדול.
מצד שני עדיין יש מנגנונים שייקח לנו הרבה זמן לבנות, אולי אפילו חודשים. לדוגמה:
1. פיצ'ר שאנחנו לא בטוחים איך יראה, הרבה פעמים אני מזהה שאני מעלה דמו ללקוח, מקבל משוב, מעלה עוד דמו וכך ממשיכים להתלבט ולנסות עד שמגיעים למשהו שעובד מספיק טוב. דיאלוג כזה יכול לקחת שבועות ואפילו חודשים.
2. פיצ'ר שדורש תיאום עם לקוחות או עם בעלי עניין אחרים. אם צריך מיגרציה של מידע בין מערכות וכדי להריץ את המיגרציה אני צריך ששני צוותים יפסיקו את העבודה השוטפת על המערכות עד שאני מסיים להעביר את הנתונים התיאום הזה יכול לקחת זמן.
ככל שהזמנים לכתיבת קוד מתקצרים אנחנו לומדים לראות את החלקים האחרים של העבודה שלנו ואת החשיבות שלהם: תקשורת, קבלת החלטות מתוך הבנה טכנית מעמיקה, היכרות עם הארגון, יכולת לצפות את העתיד, הבנת המשתמשים. האם מפתחים הופכים יותר לאנשי פרודקט? או שאנשי פרודקט הופכים יותר דומים למפתחים? עדיין מוקדם לדעת. מה שבטוח הוא שהיכולת לדחוף מהר מוצרים עובדים ללקוחות מביאה שינוי עצום לתעשייה.
👍1
📌 בסוף גם קלוד הסכים - המלחמה היא על המעטפת
היתה לי שיחה מוצלחת עם קלוד על האסטרטגיה של אנטרופיק, איפה התחרות ואיפה הם מנסים למנוע אותה. אם יש לכם כח אפשר לקרוא כאן:
https://claude.ai/share/87cbabb1-608f-4375-b37c-b6f659b9bba4
אלה הנקודות המרכזיות שאני העליתי:
1. אנטרופיק באופן עקבי משתמשת בקלוד קוד בתור חומה. יש קושי מכוון לעבור מקלוד קוד לכלים אחרים כי במעבר אנחנו מאבדים את הסקילים, פלאגינים, שרתי MCP שהתקנו, היסטוריית שיחות.
2. גם MCP שהוא לכאורה סטנדרט פתוח היה עובד הרבה יותר טוב בתור הרחבה של תקן Open API. הבחירה להמציא משהו חדש ולאלץ את כל התעשייה להתיישר לא תרמה לתחרות.
3. כשאנטרופיק משתמשים בקלוד קוד בתור חומה ומקשים עלינו לעבור לכלים מתחרים הם אומרים משהו על איכות המודל ועל איפה התחרות האמיתית. אם המודל היה כל כך טוב הם היו שמחים לאפשר לנו לנסות כלים אחרים ומודלים אחרים. במציאות הנוכחית גם אנטרופיק מבינים שהייתרון של אופוס או פייבל על GPT או אפילו דיפסיק לא מספיק בולט ולא מספיק משכנע.
בסוף השיחה קלוד הסביר את זה מאוד יפה:
1. המודלים מדלגים אחד מעל השני בכל גרסה. גם אם היום יש לך מודל ממש טוב אחרי כמה שבועות מתחרה יגיע עם מודל טוב יותר. היתרון ברמת ה Harness הוא הדרך ההגיונית להתחרות כשאתה מבין את מחזור החיים של המודל.
2. הרבה מהיכולות ממומשות ברמת ה Harness ולא ברמת המודל. קלוד קוד הוא חלק מהמוצר שלנו.
3. אף אחד לא טוען שהיתרון היחסי של אנטרופיק הוא ברמת המודל. המוצר שלנו מורכב מכל החלקים של האקוסיסטם.
היתה לי שיחה מוצלחת עם קלוד על האסטרטגיה של אנטרופיק, איפה התחרות ואיפה הם מנסים למנוע אותה. אם יש לכם כח אפשר לקרוא כאן:
https://claude.ai/share/87cbabb1-608f-4375-b37c-b6f659b9bba4
אלה הנקודות המרכזיות שאני העליתי:
1. אנטרופיק באופן עקבי משתמשת בקלוד קוד בתור חומה. יש קושי מכוון לעבור מקלוד קוד לכלים אחרים כי במעבר אנחנו מאבדים את הסקילים, פלאגינים, שרתי MCP שהתקנו, היסטוריית שיחות.
2. גם MCP שהוא לכאורה סטנדרט פתוח היה עובד הרבה יותר טוב בתור הרחבה של תקן Open API. הבחירה להמציא משהו חדש ולאלץ את כל התעשייה להתיישר לא תרמה לתחרות.
3. כשאנטרופיק משתמשים בקלוד קוד בתור חומה ומקשים עלינו לעבור לכלים מתחרים הם אומרים משהו על איכות המודל ועל איפה התחרות האמיתית. אם המודל היה כל כך טוב הם היו שמחים לאפשר לנו לנסות כלים אחרים ומודלים אחרים. במציאות הנוכחית גם אנטרופיק מבינים שהייתרון של אופוס או פייבל על GPT או אפילו דיפסיק לא מספיק בולט ולא מספיק משכנע.
בסוף השיחה קלוד הסביר את זה מאוד יפה:
1. המודלים מדלגים אחד מעל השני בכל גרסה. גם אם היום יש לך מודל ממש טוב אחרי כמה שבועות מתחרה יגיע עם מודל טוב יותר. היתרון ברמת ה Harness הוא הדרך ההגיונית להתחרות כשאתה מבין את מחזור החיים של המודל.
2. הרבה מהיכולות ממומשות ברמת ה Harness ולא ברמת המודל. קלוד קוד הוא חלק מהמוצר שלנו.
3. אף אחד לא טוען שהיתרון היחסי של אנטרופיק הוא ברמת המודל. המוצר שלנו מורכב מכל החלקים של האקוסיסטם.
Claude
Using GPT models in Claude Code
Shared via Claude, an AI assistant from Anthropic
🔥2
📌 ניסוי Mojo: פייתון רק על טורבו
מוג'ו היא שפת התכנות החדשה של קריס לאטנר, היוצר של swift ושל LLVM. היא שפה מקומפלת עם טיפוסים חזקים ותחביר שמאוד מזכיר את פייתון. יש לה גם ממשק חיבור מובנה לפייתון. מטרתה המוצהרת היא להיות שפת התכנות לאפליקציות AI מאחר והיא גם מאפשרת חיבור מובנה לכל האקוסיסטם של פייתון אבל גם מספקת ביצועים של שפה מקומפלת.
אני לא מומחה ביישומי AI אבל גם בלי לכתוב יישומי AI קל להתחיל לכתוב ב Mojo ולהאיץ יישומי פייתון כמעט בלי מאמץ. בואו נראה איך זה עובד, אם זה באמת כל כך פשוט וכמה ביצועים נקבל ממעבר ל Mojo.
✏ הניסוי: ספירת מספרים ראשוניים בפייתון
תוכנית הניסוי שבחרתי לא מסובכת. זה הקוד:
התוכנית סופרת כמה מספרים ראשוניים יש עד מיליון ובודקת כמה זמן זה לקח. האלגוריתם לא יעיל בכוונה וריצה על המחשב שלי הסתיימה אחרי שניה וקצת:
✏ איך זה נראה ב Mojo
לפני שנלך לשלב את מוג'ו עם פייתון בואו נראה את אותו הקוד כתוכנית Mojo רגילה. את מוג'ו התקנתי עם uv לפי ההוראות מהאתר שלהם עם הפקודה:
ומיד לאחר מכן התקנתי את ה Agent Skill המתאים עם הפקודה:
ביקשתי מקלוד לתרגם את הקוד ל mojo וזו התוצאה:
ההבדל בפונקציה
זמני הריצה לעומת זאת זה סיפור אחר לגמרי:
במקום קצת יותר משניה אנחנו על 0.02 שניות. אבל שוב זה לא ממש כוחות כי אי אפשר להשוות שפת Interpreter כמו פייתון לשפה מקומפלת.
✏ כח העל של מוג'ו: חיבור עם פייתון
אז איפה מתחיל הכיף? כח העל של Mojo הוא החיבור המובנה לפייתון. אפשר לכתוב קוד ב Mojo ולטעון אותו מפייתון, אפשר לטעון קוד פייתון מתוך קוד מוג'ו. בדוגמה של המספרים הראשוניים הניסוי השלישי להיום יהיה לקחת רק את הפונקציה שבודקת אם מספר הוא ראשוני ולכתוב אותה ב Mojo, תוך שאני שומר על הפונקציה main בפייתון, עם כל הרכבת הרשימות וה sum שאהבתי.
בשביל זה עליי לפצל את הקוד לשני קבצים. הקובץ
קובץ זה מייבא את הפונקציה
מוג'ו היא שפת התכנות החדשה של קריס לאטנר, היוצר של swift ושל LLVM. היא שפה מקומפלת עם טיפוסים חזקים ותחביר שמאוד מזכיר את פייתון. יש לה גם ממשק חיבור מובנה לפייתון. מטרתה המוצהרת היא להיות שפת התכנות לאפליקציות AI מאחר והיא גם מאפשרת חיבור מובנה לכל האקוסיסטם של פייתון אבל גם מספקת ביצועים של שפה מקומפלת.
אני לא מומחה ביישומי AI אבל גם בלי לכתוב יישומי AI קל להתחיל לכתוב ב Mojo ולהאיץ יישומי פייתון כמעט בלי מאמץ. בואו נראה איך זה עובד, אם זה באמת כל כך פשוט וכמה ביצועים נקבל ממעבר ל Mojo.
✏ הניסוי: ספירת מספרים ראשוניים בפייתון
תוכנית הניסוי שבחרתי לא מסובכת. זה הקוד:
import time
LIMIT = 1_000_000
def is_prime(n: int) -> bool:
if n < 2:
return False
if n < 4:
return True
if n % 2 == 0:
return False
d = 3
while d * d <= n:
if n % d == 0:
return False
d += 2
return True
def main():
start = time.perf_counter()
count = sum(is_prime(n) for n in range(1, LIMIT + 1))
elapsed = time.perf_counter() - start
print(f"Primes between 1 and {LIMIT:,}: {count:,}")
print(f"Took {elapsed:.3f} s in pure Python")
if __name__ == "__main__":
main()
התוכנית סופרת כמה מספרים ראשוניים יש עד מיליון ובודקת כמה זמן זה לקח. האלגוריתם לא יעיל בכוונה וריצה על המחשב שלי הסתיימה אחרי שניה וקצת:
ynonp@ynons-MacBook-Air ~/tmp/blog/mojodemo (main) $ uv run main_python.py
Primes between 1 and 1,000,000: 78,498
Took 1.341 s in pure Python
✏ איך זה נראה ב Mojo
לפני שנלך לשלב את מוג'ו עם פייתון בואו נראה את אותו הקוד כתוכנית Mojo רגילה. את מוג'ו התקנתי עם uv לפי ההוראות מהאתר שלהם עם הפקודה:
uv add mojo --prerelease allow
ומיד לאחר מכן התקנתי את ה Agent Skill המתאים עם הפקודה:
npx skills add modular/skills
ביקשתי מקלוד לתרגם את הקוד ל mojo וזו התוצאה:
from std.time import perf_counter_ns
comptime LIMIT = 1_000_000
def is_prime(n: Int) -> Bool:
if n < 2:
return False
if n < 4:
return True
if n % 2 == 0:
return False
var d = 3
while d * d <= n:
if n % d == 0:
return False
d += 2
return True
def main():
var start = perf_counter_ns()
var count = 0
for n in range(1, LIMIT + 1):
if is_prime(n):
count += 1
var elapsed = Float64(perf_counter_ns() - start) / 1e9
print(t"Primes between 1 and {LIMIT}: {count}")
print(t"Took {elapsed} s in pure Mojo")
ההבדל בפונקציה
is_prime כמעט ולא מורגש - טיפוסי המשתנים ב mojo כתובים עם אות ראשונה גדולה והוספנו var בהצהרה על משתנים. פונקציית ה main של mojo פחות יפה מזו של פייתון, אין שם את ה sum וה List Comprehension ולפני מחרוזת עם משתנים הם רושמים t במקום f. לא משהו לקחת ללב במיוחד כשממילא יש סוכני AI שכותבים את זה.זמני הריצה לעומת זאת זה סיפור אחר לגמרי:
Primes between 1 and 1000000: 78498
Took 0.02463 s in pure Mojo
במקום קצת יותר משניה אנחנו על 0.02 שניות. אבל שוב זה לא ממש כוחות כי אי אפשר להשוות שפת Interpreter כמו פייתון לשפה מקומפלת.
✏ כח העל של מוג'ו: חיבור עם פייתון
אז איפה מתחיל הכיף? כח העל של Mojo הוא החיבור המובנה לפייתון. אפשר לכתוב קוד ב Mojo ולטעון אותו מפייתון, אפשר לטעון קוד פייתון מתוך קוד מוג'ו. בדוגמה של המספרים הראשוניים הניסוי השלישי להיום יהיה לקחת רק את הפונקציה שבודקת אם מספר הוא ראשוני ולכתוב אותה ב Mojo, תוך שאני שומר על הפונקציה main בפייתון, עם כל הרכבת הרשימות וה sum שאהבתי.
בשביל זה עליי לפצל את הקוד לשני קבצים. הקובץ
main_mojo.py מכיל את פונקציית ה main:import time
import mojo.importer # noqa: F401 (enables importing .mojo modules)
from primes import is_prime
LIMIT = 1_000_000
def main():
start = time.perf_counter()
count = sum(is_prime(n) for n in range(1, LIMIT + 1))
elapsed = time.perf_counter() - start
print(f"Primes between 1 and {LIMIT:,}: {count:,}")
print(f"Took {elapsed:.3f} s with Mojo is_prime")
if __name__ == "__main__":
main()
קובץ זה מייבא את הפונקציה
is_prime מהמודול primes - ופה ההפתעה הראשונה, במקום שיהיה לי קובץ primes.py יצרתי קובץ primes.mojo. זה תוכנו:from std.os import abort
from std.python import PythonObject
from std.python.bindings import PythonModuleBuilder
@export
def PyInit_primes() abi("C") -> PythonObject:
try:
var m = PythonModuleBuilder("primes")
m.def_function[is_prime]("is_prime")
return m.finalize()
except e:
abort(String("failed to create module: ", e))
def is_prime(n_obj: PythonObject) raises -> PythonObject:
var n = Int(py=n_obj)
if n < 2:
return PythonObject(False)
if n < 4:
return PythonObject(True)
if n % 2 == 0:
return PythonObject(False)
var d = 3
while d * d <= n:
if n % d == 0:
return PythonObject(False)
d += 2
return PythonObject(True)
יש פה את הפונקציה
is_prime שאנחנו זוכרים מקודם אבל הפעם החתימה שלה שונה וגם המימוש. הפונקציה נקראת מתוך קוד פייתון ולכן הפרמטר שלה הוא מסוג PythonObject. היא גם עלולה לזרוק Exception וב Mojo עלינו להצהיר במפורש כשפונקציה עלולה לזרוק Exception, ובמקום להחזיר בוליאני היא מחזירה PythonObject כי אותו ערך יחזור לפייתון.הקוד עצמו לוקח את המספר הפייתונאי והופך אותו למספר Int של Mojo, וגם בכל return הקוד עוטף את הערך הבוליאני של Mojo ב PythonObject כדי שפייתון תוכל להשתמש בערך.
החלק השני של הקוד הוא הגדרת המודול. ב Mojo עליי לאתחל את המודול ולשמור בו את כל הפונקציות שאני צריך. שוב קצת כאב ראש אבל לא משהו יותר מדי מאתגר, במיוחד כשממילא קלוד כותב את הקוד.
התוצאה כצפוי יושבת באמצע, יותר איטית ממוג'ו לבד אבל עדיין הרבה יותר מהירה מפייתון לבד:
ynonp@ynons-MacBook-Air ~/tmp/blog/mojodemo (main) $ uv run main_mojo.py
Primes between 1 and 1,000,000: 78,498
Took 0.071 s with Mojo is_prime
סך הכל אם כבר יש לכם יישומי פייתון ואתם מחפשים דרך קלה לשפר ביצועים באמצעות קומפילציה של פונקציות בודדות או חלקים מהתוכנית מוג'ו כבר יכולה לתת לכם כיוון טוב לשיפור. אם אתם יודעים פייתון ומחפשים שפת תכנות מעניינת ללמוד אז Mojo נראית מלהיבה, ואם אתם רוצים לבנות קוד שיבצע פעולות וקטוריות על GPU אז Mojo בכלל נראית כמו אופציה טובה בזכות השילוב של כל האקוסיסטם של פייתון יחד עם ביצועים של שפה מקומפלת.
העליתי לגיטהאב את הקוד של שלושת התוכניות עם כל קבצי הגדרת הפרויקט. מוזמנים להסתכל ולנסות להריץ אצלכם זה הקישור:
https://github.com/ynonp/mojodemo
GitHub
GitHub - ynonp/mojodemo: trying out mojolang
trying out mojolang. Contribute to ynonp/mojodemo development by creating an account on GitHub.
🔥2
📌 השלכות של פיתוח עם AI על פרויקטים
בפרויקט אחד מצאתי שני API Clients שונים שמחברים לאותו ספק צד-שלישי. ביצירת הפיצול סוכן ה AI כתב הערה ארוכה שמסבירה למה חלק מהפונקציות צריכות לעבור דרך מנגנון אחד ופונקציות אחרות דרך מנגנון שני. משם אף סוכן AI לא העלה בדעתו לבדוק מחדש ולאחד את המנגנונים.
בפרויקט אחר מצאתי שני קבצי CSS סותרים - העיצוב החדש והעיצוב הישן. שניהם הופעלו על העמוד אבל רק העיצוב החדש הופיע והוא דרס את כל הכללים של העיצוב הישן.
במקום אחר אותה פונקציה הופיעה כמה פעמים בשינויים קלים. רוב הזמן זה לא הפריע לאף אחד כי כל חלק בקוד קרא למימוש הספציפי שהתאים לו. עד שמישהו שינה את הפורמט ו-5 מקומות לא קשורים נשברו.
בדוגמה רביעית קיבלתי פונקציה עם הערה שלא תאמה את מה שמבוצע בפונקציה. קלוד שינה את הפונקציה אחרי הכתיבה בלי לעדכן את ההערה. המשך שינויים לפעמים בוצעו לפי קוד הפונקציה ולפעמים לפי ההערה, מה שרק הרחיב את אי התאימות.
בפרויקט חמישי ה AI פיתח מאפס מנגנון שקורא HTML ומוציא ממנו את החלקים החשובים בטקסט במקום להשתמש בספריה קיימת כמו readability של מוזילה או trafilatura.
פרגמנטציה בקוד המיוצר על ידי AI היא עדיין בעיה מרכזית בכל פרויקט. הסוכנים לא עושים מספיק בשביל לאחד מנגנונים או אפילו בשביל לאתר מנגנונים קיימים במערכת לפני שרצים לממש מחדש. שימו לב בעת קריאת קוד שנוצר על ידי AI לזהות כאלה פיצולים, לצמצם אותם וגם להכניס לקבצי AGENTS.md הסבר על המנגנון הקיים, דרישה להשתמש בו ודרישה שכשמשנים מנגנון לא להשאיר את השיטה הישנה.
בפרויקט אחד מצאתי שני API Clients שונים שמחברים לאותו ספק צד-שלישי. ביצירת הפיצול סוכן ה AI כתב הערה ארוכה שמסבירה למה חלק מהפונקציות צריכות לעבור דרך מנגנון אחד ופונקציות אחרות דרך מנגנון שני. משם אף סוכן AI לא העלה בדעתו לבדוק מחדש ולאחד את המנגנונים.
בפרויקט אחר מצאתי שני קבצי CSS סותרים - העיצוב החדש והעיצוב הישן. שניהם הופעלו על העמוד אבל רק העיצוב החדש הופיע והוא דרס את כל הכללים של העיצוב הישן.
במקום אחר אותה פונקציה הופיעה כמה פעמים בשינויים קלים. רוב הזמן זה לא הפריע לאף אחד כי כל חלק בקוד קרא למימוש הספציפי שהתאים לו. עד שמישהו שינה את הפורמט ו-5 מקומות לא קשורים נשברו.
בדוגמה רביעית קיבלתי פונקציה עם הערה שלא תאמה את מה שמבוצע בפונקציה. קלוד שינה את הפונקציה אחרי הכתיבה בלי לעדכן את ההערה. המשך שינויים לפעמים בוצעו לפי קוד הפונקציה ולפעמים לפי ההערה, מה שרק הרחיב את אי התאימות.
בפרויקט חמישי ה AI פיתח מאפס מנגנון שקורא HTML ומוציא ממנו את החלקים החשובים בטקסט במקום להשתמש בספריה קיימת כמו readability של מוזילה או trafilatura.
פרגמנטציה בקוד המיוצר על ידי AI היא עדיין בעיה מרכזית בכל פרויקט. הסוכנים לא עושים מספיק בשביל לאחד מנגנונים או אפילו בשביל לאתר מנגנונים קיימים במערכת לפני שרצים לממש מחדש. שימו לב בעת קריאת קוד שנוצר על ידי AI לזהות כאלה פיצולים, לצמצם אותם וגם להכניס לקבצי AGENTS.md הסבר על המנגנון הקיים, דרישה להשתמש בו ודרישה שכשמשנים מנגנון לא להשאיר את השיטה הישנה.
👍2
📌 איך בכל זאת התחברתי לשרת ה MCP של ספוטיפיי
יש שני סוגים של שרתי MCP, שרתי MCP בתקשורת stdio שרצים אצלכם על המחשב ושרתי MCP שרצים על מכונה מרוחקת. אנטרופיק החליטו לבלבל את התעשייה עם סטנדרט חדש ושמות חדשים אבל האמת היא שמאחורי השמות מסתתרים אותם רכיבים שאנחנו מכירים:
1. שרת MCP מסוג stdio הוא בסך הכל כלי משורת הפקודה או סקריפט.
2. שרת MCP מסוג http הוא בסך הכל API.
במילים אחרות, אם הורדתם שרת MCP מסוג stdio וחיברתם אותו לקלוד או ל VS Code אתם ממש לא חייבים לעבוד איתו רק דרך קלוד או VS Code ואפשר לעבוד איתו רגיל משורת הפקודה עם כלי תרגום כמו mcp2cli. זה באמת בסך הכל סקריפט שמקבל פרמטרים בצורה קצת יותר מסורבלת.
מה שקצת מבלבל אנשים הוא ההזדהות - לקוחות MCP מנהלים בשבילנו את ההזדהות ולכן כשקלוד או VS Code מתחברים לשרת MCP הם שולחים אותנו לאינטרנט כדי להזדהות מול השירות שמתחברים אליו, לוקחים אליהם את הטוקן שחזר ושומרים אותו כדי להפעיל את הכלים. הפעלת כלים בשרת MCP בענן היא בסך הכל קריאת API.
לפעמים שרתי MCP מנסים להרחיק אותנו וליצור מגבלות מלאכותיות. לדוגמה שרת ה MCP של ספוטיפיי עובד מצוין מתוך קלוד אבל כשניסיתי להתחבר אליו מקוד או מ mcp2cli הוא זרק אותי ואמר שהוא מוכן להתחבר רק ללקוחות מורשים. מעבר לקונקטור בקלוד או ChatGPT גם לא מצאתי אזכור רשמי לאותו שרת MCP של ספוטיפיי. מה עושים? קצת מחקר:
1. שאלתי את קלוד מה הכתובת של שרת ה MCP של ספוטיפיי אליה הוא מחובר. את זה לא היתה לו בעיה לספר לי.
2. שמתי לב שכשאני מתחבר לשרת ה MCP של ספוטיפיי הדפדפן מראה כתובת ארוכה שכוללת משתנים כמו redirect url ו client id. רשמתי אותם בצד.
3. שמתי לב שאחרי אישור הגישה לספוטיפיי הדפדפן שלי עובר בבקשת POST לקלוד שלוקח את הקוד שהתקבל ומשתמש בו כדי לקבל אסימון גישה, כל זה מאחורי הקלעים. אז חסמתי את הגישה ל claude באמצעות קובץ
עכשיו כבר היה לי את כל מה שהייתי צריך כדי להתחזות לקלוד ולייצר אסימון גישה ל MCP של ספוטיפיי. ביקשתי מקלוד לכתוב סקריפט פייתון שמייצר פרמטרים למסך ההרשאות של ספוטיפיי ומחכה שאני אדביק את כתובת החזרה כדי לקחת משם את הפרמטרים של התשובה, מחבר את הכל לאסימון ואז מחפש קצת מוזיקה בספרדית. זאת התוצאה:
יש שני סוגים של שרתי MCP, שרתי MCP בתקשורת stdio שרצים אצלכם על המחשב ושרתי MCP שרצים על מכונה מרוחקת. אנטרופיק החליטו לבלבל את התעשייה עם סטנדרט חדש ושמות חדשים אבל האמת היא שמאחורי השמות מסתתרים אותם רכיבים שאנחנו מכירים:
1. שרת MCP מסוג stdio הוא בסך הכל כלי משורת הפקודה או סקריפט.
2. שרת MCP מסוג http הוא בסך הכל API.
במילים אחרות, אם הורדתם שרת MCP מסוג stdio וחיברתם אותו לקלוד או ל VS Code אתם ממש לא חייבים לעבוד איתו רק דרך קלוד או VS Code ואפשר לעבוד איתו רגיל משורת הפקודה עם כלי תרגום כמו mcp2cli. זה באמת בסך הכל סקריפט שמקבל פרמטרים בצורה קצת יותר מסורבלת.
מה שקצת מבלבל אנשים הוא ההזדהות - לקוחות MCP מנהלים בשבילנו את ההזדהות ולכן כשקלוד או VS Code מתחברים לשרת MCP הם שולחים אותנו לאינטרנט כדי להזדהות מול השירות שמתחברים אליו, לוקחים אליהם את הטוקן שחזר ושומרים אותו כדי להפעיל את הכלים. הפעלת כלים בשרת MCP בענן היא בסך הכל קריאת API.
לפעמים שרתי MCP מנסים להרחיק אותנו וליצור מגבלות מלאכותיות. לדוגמה שרת ה MCP של ספוטיפיי עובד מצוין מתוך קלוד אבל כשניסיתי להתחבר אליו מקוד או מ mcp2cli הוא זרק אותי ואמר שהוא מוכן להתחבר רק ללקוחות מורשים. מעבר לקונקטור בקלוד או ChatGPT גם לא מצאתי אזכור רשמי לאותו שרת MCP של ספוטיפיי. מה עושים? קצת מחקר:
1. שאלתי את קלוד מה הכתובת של שרת ה MCP של ספוטיפיי אליה הוא מחובר. את זה לא היתה לו בעיה לספר לי.
2. שמתי לב שכשאני מתחבר לשרת ה MCP של ספוטיפיי הדפדפן מראה כתובת ארוכה שכוללת משתנים כמו redirect url ו client id. רשמתי אותם בצד.
3. שמתי לב שאחרי אישור הגישה לספוטיפיי הדפדפן שלי עובר בבקשת POST לקלוד שלוקח את הקוד שהתקבל ומשתמש בו כדי לקבל אסימון גישה, כל זה מאחורי הקלעים. אז חסמתי את הגישה ל claude באמצעות קובץ
/etc/hosts כדי שלא יקבל את האסימון ולקחתי את הכתובת הזאת אליי, גם אותה רשמתי בצד.עכשיו כבר היה לי את כל מה שהייתי צריך כדי להתחזות לקלוד ולייצר אסימון גישה ל MCP של ספוטיפיי. ביקשתי מקלוד לכתוב סקריפט פייתון שמייצר פרמטרים למסך ההרשאות של ספוטיפיי ומחכה שאני אדביק את כתובת החזרה כדי לקחת משם את הפרמטרים של התשובה, מחבר את הכל לאסימון ואז מחפש קצת מוזיקה בספרדית. זאת התוצאה:
import asyncio
import os
from urllib.parse import parse_qs, parse_qsl, urlencode, urlparse, urlunparse
from dotenv import load_dotenv
from fastmcp.client.auth import OAuth
from pydantic import AnyHttpUrl
from pydantic_ai.mcp import MCPToolset
MCP_URL = "https://mcp-gateway-external-pilot.spotify.net/mcp"
REDIRECT_URI = "https://claude.ai/api/mcp/auth_callback"
class ConsoleOAuth(OAuth):
def __init__(self, client_id: str) -> None:
super().__init__(
mcp_url=MCP_URL,
client_id=client_id,
callback_port=1,
callback_timeout=600,
)
redirect_uris = [AnyHttpUrl(REDIRECT_URI)]
self.context.client_metadata.redirect_uris = redirect_uris
if self._static_client_info is not None:
self._static_client_info.redirect_uris = redirect_uris
async def redirect_handler(self, authorization_url: str) -> None:
parsed = urlparse(authorization_url)
query = dict(parse_qsl(parsed.query))
query["show_dialog"] = "true"
print(urlunparse(parsed._replace(query=urlencode(query))))
async def callback_handler(self) -> tuple[str, str | None]:
callback_url = await asyncio.to_thread(input, "Paste callback URL: ")
query = parse_qs(urlparse(callback_url).query)
code = query.get("code", [None])[0]
state = query.get("state", [None])[0]
if not code:
raise RuntimeError("Callback URL does not contain an OAuth code")
return code, state
async def main() -> None:
load_dotenv()
client_id = "a0894f3786c34e64b0fa0e77f8ea83db"
toolset = MCPToolset(
MCP_URL,
auth=ConsoleOAuth(client_id),
init_timeout=600,
)
async with toolset:
tools = await toolset.list_tools()
search = next(tool for tool in tools if tool.name == "search")
field = "query" if "query" in search.inputSchema["properties"] else "prompt"
GitHub
GitHub - knowsuchagency/mcp2cli: Turn any MCP, OpenAPI, or GraphQL server into a CLI — at runtime, with zero codegen
Turn any MCP, OpenAPI, or GraphQL server into a CLI — at runtime, with zero codegen - knowsuchagency/mcp2cli
result = await toolset.client.call_tool(
"search",
{field: "nice Spanish music", "language": "en"},
)
print(result.model_dump_json(indent=2, exclude_none=True))
if __name__ == "__main__":
asyncio.run(main())
שרת MCP הוא בסך הכל API. הוא כבר נותן לכם את כל מה שצריך בשביל לבנות אוטומציות וחיבורים למערכת מתוך קוד. ה AI הוא רק בונוס.
📌 קלוד קוד ותכנות הגנתי
אחת הנטיות המובנות של מודלי שפה ביצירת קוד היא ליצור קוד מתגונן, כלומר קוד שבודק מספר אפשרויות לפני שמתייאש. קוד מתגונן היא תבנית טובה כשהקלט מגיע ממקור חיצוני שאין לנו שליטה עליו אבל הוא מיותר ומבלבל כשהמידע מגיע ממקום שאנחנו יצרנו. שימו לב לדוגמה הבאה בפייתון, קיצונית בכוונה:
ברור שה if לא משנה בכלום את ריצת התוכנית אבל הוא מפריע לקרוא את הקוד ומעוות את הכוונה. האם כותבי הקוד התכוונו שאולי בהמשך המילון הזה יגיע מבחוץ? האם הקוד שמדפיס את x צריך לטפל בעוד מילונים? אנחנו לא יודעים.
באופן כללי נרצה להשתמש בתכנות הגנתי כשיש ממה להתגונן ונרצה להיות ברורים לגבי המידע שיש לנו כשאנחנו יודעים בדיוק איזה מידע אמור להגיע לפונקציה. סוכני AI די גרועים בזה.
ניקח דוגמה יותר מעניינת מפרויקט maigret. בקובץ report.py של הפרויקט אני מוצא שתי פונקציות:
בקוד יש באג - אם מחפשים משתמשים ששמם מכיל נקודה ואז רווח הדוח נשבר אחרי הרווח, כלומר משתמש כמו
בגלל שבתוך המילון ש
כדי להדפיס את הדוח וזה גורם לשבירת השורה באמצע.
הפיתרון קל ואפילו קלוד יודע מה לעשות כאן, צריך לשנות את
זה מיותר כיוון שאנחנו יוצרים את המילון וברור שאם יש בו את brief יהיה בו גם את המפתח החדש
הניסיון הראשון שלי לתקן את זה היה עם התוספת הבאה לקובץ CLAUDE.md:
זה לא עבד.
ניסיון שני היה להחליף את ה dict בחתימת הפונקציה ב TypedDict ולרשום ממש את כל סוגי המפתחות שיש בו. גם זה לא עבד.
בניסיון השלישי הוספתי את הטקסט הבא ל CLAUDE.md ונראה שזה עובד:
וכן זה מאוד ספציפי למילונים. אבל אולי פה הקסם.
בכל מקרה השורה התחתונה כאן מעבר לתכנות הדפנסיבי היא הגישה. בעבודה עם AI אין טעם רק לנסות לפתור בעיה בקוד אלא צריך להבין למה הבעיה נוצרה והכי טוב אם מצליחים לעדכן את הפרויקט כדי שהבעיה לא תיווצר במקרים דומים בעתיד.
אחת הנטיות המובנות של מודלי שפה ביצירת קוד היא ליצור קוד מתגונן, כלומר קוד שבודק מספר אפשרויות לפני שמתייאש. קוד מתגונן היא תבנית טובה כשהקלט מגיע ממקור חיצוני שאין לנו שליטה עליו אבל הוא מיותר ומבלבל כשהמידע מגיע ממקום שאנחנו יצרנו. שימו לב לדוגמה הבאה בפייתון, קיצונית בכוונה:
data = {"x": 10}
if "x" in data:
print(data["x"])ברור שה if לא משנה בכלום את ריצת התוכנית אבל הוא מפריע לקרוא את הקוד ומעוות את הכוונה. האם כותבי הקוד התכוונו שאולי בהמשך המילון הזה יגיע מבחוץ? האם הקוד שמדפיס את x צריך לטפל בעוד מילונים? אנחנו לא יודעים.
באופן כללי נרצה להשתמש בתכנות הגנתי כשיש ממה להתגונן ונרצה להיות ברורים לגבי המידע שיש לנו כשאנחנו יודעים בדיוק איזה מידע אמור להגיע לפונקציה. סוכני AI די גרועים בזה.
ניקח דוגמה יותר מעניינת מפרויקט maigret. בקובץ report.py של הפרויקט אני מוצא שתי פונקציות:
get_plaintext_report ו generate_report_context. הפונקציה generate_report_context מייצרת מילון עם נתונים והפונקציה get_plaintext_report לוקחת את הנתונים ובונה מהם דוח.בקוד יש באג - אם מחפשים משתמשים ששמם מכיל נקודה ואז רווח הדוח נשבר אחרי הרווח, כלומר משתמש כמו
Dr. Evil יגרום בדוח להצגת הטקסט:Search by username Dr.
Evil returned 0 accounts.
Extended info extracted from 0 accounts.
בגלל שבתוך המילון ש
generate_report_context מייצרת יש בלוק של טקסט בו כל שורה מופרדת בנקודה ואז רווח ואז פונקציית יצירת הדוח מפעילה:output = (context['brief'] + " ").replace('. ', '.\n')כדי להדפיס את הדוח וזה גורם לשבירת השורה באמצע.
הפיתרון קל ואפילו קלוד יודע מה לעשות כאן, צריך לשנות את
generate_report_context כדי שתחזיר את השורות המקוריות של הדוח ואז לא נצטרך לשבור לשורות לפי סימן הנקודה. הבעיה שכשאני מבקש מקלוד לעשות את השינוי הוא מוסיף מפתח brief_lines וכותב את הפקודה ההגנתית:if "brief_lines" in context:
output = "\n".join(context['brief_lines'])
else:
output = (context['brief'] + " ").replace('. ', '.\n')
זה מיותר כיוון שאנחנו יוצרים את המילון וברור שאם יש בו את brief יהיה בו גם את המפתח החדש
brief_lines.הניסיון הראשון שלי לתקן את זה היה עם התוספת הבאה לקובץ CLAUDE.md:
Project Philosophy
- When we replace a mechanism in code don't keep the old one for record or backward compatibility
- Implement minimalistic solutions
- Consider what data is actually in a dictionary and use the correct key, don't cover too much ground
זה לא עבד.
ניסיון שני היה להחליף את ה dict בחתימת הפונקציה ב TypedDict ולרשום ממש את כל סוגי המפתחות שיש בו. גם זה לא עבד.
בניסיון השלישי הוספתי את הטקסט הבא ל CLAUDE.md ונראה שזה עובד:
Dictionary Defensive Programming
- When you read a key from a dict/object we construct ourselves, access it directly never .get() with a fallback. Fallbacks and defensive access are only for data crossing a trust boundary we don't control: user input, network responses, parsed files
וכן זה מאוד ספציפי למילונים. אבל אולי פה הקסם.
בכל מקרה השורה התחתונה כאן מעבר לתכנות הדפנסיבי היא הגישה. בעבודה עם AI אין טעם רק לנסות לפתור בעיה בקוד אלא צריך להבין למה הבעיה נוצרה והכי טוב אם מצליחים לעדכן את הפרויקט כדי שהבעיה לא תיווצר במקרים דומים בעתיד.
GitHub
GitHub - soxoj/maigret: 🕵️♂️ Collect a dossier on a person by username from 3000+ sites
🕵️♂️ Collect a dossier on a person by username from 3000+ sites - soxoj/maigret
🏆1
📌 הדרך פנימה
הבן שלי בנה בעזרת AI משחק סיימון. כזה עם כפתורים שמגריל רצף של צבעים וצלילים וצריך ללחוץ על הכפתורים ולחזור על הרצף. כיף.
הוא הציע לי לשחק ואני בתמורה הצעתי שנסתכל על הקוד. ואז התחלתי באתגרים - בוא נשנה את הצבעים, בוא נוסיף עוד כפתורים, בוא נקצר או נאריך את הרצף, בוא נמדוד זמן.
בקונטקסט לימודי כל אתגר הוא עוד דרך פנימה לתוך הקוד. עוד הצצה. כל אתגר הוא הזדמנות להבין לא רק איך כותבים קוד אלא גם מה זה קוד טוב ולמה זה חשוב. אם צריך לשנות דברים ב-4 מקומות שונים רק בשביל להוסיף כפתורים זה מלמד אותנו משהו על מנגנון יצירת הכפתורים, ופותח דלת לשאלה הבאה ל AI - "תגיד אפשר לשכתב את הקוד של הכפתורים כדי שאוכל יותר בקלות להוסיף כפתור?"
רק לעתים נדירות ובמערכות מאוד קטנות הדרך פנימה לקוד עוברת בקריאה מלמעלה למטה של כל הקבצים. רוב הזמן, אפילו הרבה לפני ה AI, הדרך לתוך קוד היתה אותם אתגרים או שינויים קטנים. וכן דווקא מימוש שלהם ידנית עוזר לנו לראות מה קל ומה קשה לשנות, בין אם מדובר על מפתח אנושי או AI.
הבן שלי בנה בעזרת AI משחק סיימון. כזה עם כפתורים שמגריל רצף של צבעים וצלילים וצריך ללחוץ על הכפתורים ולחזור על הרצף. כיף.
הוא הציע לי לשחק ואני בתמורה הצעתי שנסתכל על הקוד. ואז התחלתי באתגרים - בוא נשנה את הצבעים, בוא נוסיף עוד כפתורים, בוא נקצר או נאריך את הרצף, בוא נמדוד זמן.
בקונטקסט לימודי כל אתגר הוא עוד דרך פנימה לתוך הקוד. עוד הצצה. כל אתגר הוא הזדמנות להבין לא רק איך כותבים קוד אלא גם מה זה קוד טוב ולמה זה חשוב. אם צריך לשנות דברים ב-4 מקומות שונים רק בשביל להוסיף כפתורים זה מלמד אותנו משהו על מנגנון יצירת הכפתורים, ופותח דלת לשאלה הבאה ל AI - "תגיד אפשר לשכתב את הקוד של הכפתורים כדי שאוכל יותר בקלות להוסיף כפתור?"
רק לעתים נדירות ובמערכות מאוד קטנות הדרך פנימה לקוד עוברת בקריאה מלמעלה למטה של כל הקבצים. רוב הזמן, אפילו הרבה לפני ה AI, הדרך לתוך קוד היתה אותם אתגרים או שינויים קטנים. וכן דווקא מימוש שלהם ידנית עוזר לנו לראות מה קל ומה קשה לשנות, בין אם מדובר על מפתח אנושי או AI.
👍3🔥1