ToCode
1.43K subscribers
3.95K links
טיפים קצרים למתכנתים מאת ינון פרק
Download Telegram
📌 תהליך הפיתוח משולב AI ב 37 Signals

בפרק האחרון של rework דייויד הנסן מדבר על פיתוח משולב AI ב 37 Signals.

https://37signals.com/podcast/ai-challenges-in-software/

אלה הדברים המרכזיים שאני לוקח מהפרק:

1. רוב הפיתוחים החדשים אצלם מתחילים בפרומפט. כך גם אני עובד ואני שומע את זה מיותר ויותר חברים בתעשייה. כשבאים לבנות פיצ'ר חדש נקודת ההתחלה היא לא עורך הטקסט אלא ממשק השיחה.
2. כח העל של AI עבורם היה בהקמת Preview Pipeline למעצבים - כלומר מעצבים היום כותבים פרומפטים, ה AI מעדכן את הקוד ל PR, המערכת מעלה אוטומטית גרסה של הקוד החדש על מכונת בדיקה ומחזירה למעצב לינק למכונת הבדיקה. זה Pipeline שמאוד קל להקים היום ויש לו המון כח, כי מעצבים יכולים לנסות כך המון רעיונות לפני שבכלל מגיעים לדבר עם המפתחים.
3. כשמגיעים כבר לדבר עם המפתחים הם מרגישים מאוד בנוח לזרוק את כל הקוד ש AI כתב. זו נקודה קריטית ב Pipeline שהרבה אנשים מפספסים. כשמעצב או מתכנת ג'וניור כותב פרומפטים התוצאה מבחינת הקוד היא די גרועה, גם אם על המסך המוצר נראה יפה. המפתחים הטובים שלהם קוראים קוד של AI בשביל לפסול אותו. הם לא עושים Code Review בשביל להבין מה AI עשה ובשביל לתקן דברים קטנים, אלא מזהים קודם כל אם הקוד בכלל מתחבר לעולם שלהם, קריא וקל להבנה. הם שמחים לזרוק קוד ש AI יצר ולממש את הפיצ'ר בדרך אחרת כשהקוד לא בדיוק מה שהם רצו.
4. אם תתנו ל AI לכתוב קוד ללא השגחה התוצאה תהיה בלאגן. או במילים של DHH:

The problem is if you just keep doing that, if you keep asking the monkeypaw for more wishes, you’re going to get a distorted code base.


5. יש בעיה אמיתית שסוכני קידוד לא אומרים "לא", אבל מוצאים דרכים עקומות לפתור בעיות. DHH נתן פה כדוגמה מישהו שמקבל שלוש משאלות ומבקש סכום כסף גדול ואז למחרת קרוב משפחה שאהב נפטר והוא נשאר עם הביטוח חיים ... זאת לא היתה הכוונה.
6. יותר פיצ'רים לא מביאים יותר מכירות. זאת נקודה שגם Dax מאופןקוד העלה. האתגר שלנו הוא לא לבנות כמה שיותר פיצ'רים אלא לבנות מוצר טוב ומלוטש. הרבה פעמים שני הדברים האלה לא הולכים יחד ואפילו סותרים. ככל שנשים יותר פיצ'רים רק נפגע בחווית המשתמשים.
👍1
📌 מה שלא סיפרו ב ynet על הפיטורים בהייטק

הכתבים של טמקא מודאגים. בעקבות ה AI נרשם זינוק דרמטי במספר דורשי העבודה בהייטק. הכתבה סוקרת דוח חדש של שירות התעסוקה שמסביר שיש היום כ 16 הייטקיסטים המחפשים עבודה.

את הדוח עצמו אפשר למצוא כאן:
https://www.taasuka.gov.il/media/oauod4pb/hitech_report.pdf

הוא מרתק ומלא בנתונים אבל הנתון היחיד שעניין אותי לא היה שם. מצאתי אותו בסקירה הזו של דה מרקר (דורש מנוי לקריאה):

https://www.themarker.com/career/2026-07-07/ty-article/.premium/0000019f-377d-d07c-af9f-ff7dcc840000?fromLogin=success

זה המשפט:

לפי נתוני הלשכה המרכזית לסטטיסטיקה, כיום מוצעות 13.8 אלף משרות בענף ההייטק, ועוד כ־4,000 משרות "שירותי הייטק", שהן משרות טכנולוגיות בענפים לא-טכנולוגיים.


או בעברית יש היום יותר משרות טק פנויות מאשר דורשי עבודה.

אז מה קורה כאן? למה כל כך הרבה אנשים מחפשים עבודה? למה יש כל כך הרבה משרות פנויות? ולמה המספרים האלה רק יגדלו בתקופה הקרובה? ב 2016 כתבתי בהקשר של שינוי טכנולוגי אחר:

אבל במובן אחר יש כאן בעיה: והיא שרעידת האדמה תגיע. נוקיה הפסיקה לייצר טלפונים, סוגי בסיסי נתונים חדשים מופיעים, וכמובן בעולם הווב עליו אנחנו מדברים: בקבון החליפה את jQuery, אנגולר החליפה את Backbone, וכיום לא ברור אם אנגולר2 או ריאקט יחליפו את אנגולר, אבל מה שבטוח שאם תקחו מתכנת PHP ו jQuery היום ותשלחו אותו לחפש עבודה, קל זה לא יהיה.


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


אנשים טובים, חכמים, מנוסים, רואים את הידע והמיומנות שלהם נמחקים כי טכנולוגיה חדשה משנה הכל וקופאים. אנחנו רוצים ללמוד את הדבר החדש אבל לא מצליחים להיפרד מההרגלים הישנים ואפילו לא ממש רוצים. גם אני המשכתי לכתוב קוד לנוקיה הרבה אחרי שהיה ברור שאף אחד לא יקנה יותר טלפון נוקיה. גם אני המשכתי ללמד perl אחרי שהיה ברור שכולם משכתבים פרויקטים מ perl ל Python. גם אני התעקשתי על Backbone כשכל העולם עבר לאנגולר. גם אני הייתי דורש עבודה בעולם שהיה צריך מפתחים אחרים.

אין לנו איך להשפיע על הסטטיסטיקה אבל יש לנו איך להשפיע על עצמנו והגישה שלנו. הדרך קדימה מתחילה ב"אני לא יודע". אני לא יודע איך לעבוד הכי יעיל ונכון עם AI. אני לא יודע איך לכתוב קוד כפול עשר יותר מהר. אני לא יודע איך להקים צבא של סוכנים שיקודדו בזמן שאני ישן. אני לא יודע איך לשמור על רמת הקוד של הפרויקט בעולם שאני לא זה שכותב את הקוד. אני לא יודע איך ללמוד שפת תכנות חדשה בעולם שאני לא זה שמקליד.

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

בעולם החדש כשאנחנו כותבים דף הוראות ארוך לסוכן קידוד אנחנו קוראים לו 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 מתחיל עם הנתיב 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
אנחנו עוד נצטרך את הערך שלו. אחרי שהמשתמש מאשר את הגישה דורקיפר שולחת את המשתמש חזרה לסוכן ה AI עם פרמטר בשם code.

החלפת קוד הגישה בטוקן

סוכן ה 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.
📌 בעידן ה AI באמת אין דבר כזה חודשים?

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

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

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

כשבוריס צ'רני מדבר על 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. אף אחד לא טוען שהיתרון היחסי של אנטרופיק הוא ברמת המודל. המוצר שלנו מורכב מכל החלקים של האקוסיסטם.
🔥2
📌 ניסוי Mojo: פייתון רק על טורבו

מוג'ו היא שפת התכנות החדשה של קריס לאטנר, היוצר של 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
🔥2
📌 השלכות של פיתוח עם AI על פרויקטים

בפרויקט אחד מצאתי שני 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 באמצעות קובץ /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"
        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 הוא רק בונוס.