הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

לא שוב, קלוד

22/09/2025

אני מודה אני אשם. אני נתתי לקלוד לכתוב את הקוד הזה. אני לא קראתי אותו מספיק. אני לא לקחתי ברצינות את ה include שהוא כתב שם. אני חשבתי שזה חמוד שקלוד הצליח למשוך את כל הנתונים בשאילתה אחת. זה מה שהוא כתב שם:

@lessons = @course.lessons
          .includes(:activities, :lesson_users, activities: :activity_users)
          .with_progress_data(current_user)
          .order(:order)

קצר, קסום ומהר מאוד גומר את הזיכרון של המכונה.

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

עם או בלי היסטוריה

21/09/2025

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

קוד ראשון מ OpenAI Agents SDK שולח שאלה לסוכן:

from agents import Agent, Runner

agent = Agent(name="Assistant", instructions="You are a helpful assistant")

result = Runner.run_sync(agent, "Write a haiku about recursion in programming.")
print(result.final_output)

השאלה הבאה שתישלח לאותו סוכן לא תכיל את היסטוריית ההודעות, לא את בקשת ההייקו ולא את ההייקו שנוצר. כל הפעלה של Runner.run היא עצמאית. בשביל לשמור את היסטוריית ההודעות יש להם מבנה אחר שנקרא Session שצריך ליצור בנפרד.

והנה דוגמה שניה מ RubyLLM:

chat = RubyLLM.chat
chat.ask "What's the best way to learn Ruby?"
chat.ask "Send me some resources"

פה כל היסטוריית ההודעות נשמרת בתוך אוביקט ה chat אוטומטית ונשלחת למודל עם כל שאלה חדשה. כך המודל יכול לענות על השאלה השניה.

ואיך זה נראה כשמתבלבלים? הנה באג שמצאתי היום:

chat = TracedChat.new(span_name: "add_token_translations", model: 'gpt-5-mini')

long_text.lines.each_slice(12) do |block|
  chat.with_instructions(instructions).add_message role: :user, content: block.join

  response = chat.complete
  data["result"] += response.content.strip + "\n\n"
  save!
end

הקוד לוקח טקסט ארוך, שובר אותו לבלוקים של 12 שורות ושולח כל בלוק ל AI. החלוקה לבלוקים עוזרת לקבל תוצאות טובות יותר מה AI, אבל שימו לב לטעות - אוביקט chat נוצר לפני הלולאה. לכן כל פניה עבור בלוק מכילה גם את כל ההודעות הקודמות (כל הבלוקים הקודמים והתוצאות שלהם). התוצאה היא ירידה באיכות התוצאות ככל שהטקסט מתארך.

מרגע שראינו את זה התיקון פשוט - צריך רק להעביר את יצירת ה chat לתוך הלולאה:

long_text.lines.each_slice(12) do |block|
  chat = TracedChat.new(span_name: "add_token_translations", model: 'gpt-5-mini')
  chat.with_instructions(instructions).add_message role: :user, content: block.join

  response = chat.complete
  data["result"] += response.content.strip + "\n\n"
  save!
end

האתגר בעבודה עם LLM-ים הוא לשים לב שבכלל יש בעיה. בגלל זה חשוב תמיד לעקוב אחרי הלוגים ולוודא שהשיחות שלכם עם ה AI נראות כמו שאתם מצפים.

מצב לא חוקי

20/09/2025

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

בפיתוח מערכת שווה לשים לב לשני תהליכים חשובים שצריך לבצע במקביל:

  1. עלינו למנוע ממשתמשים להכניס את המערכת למצב לא חוקי.

  2. עלינו לצמצם את המצבים הלא חוקיים של המערכות שאנחנו בונים.

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

"אני רק אוסיף פה וולידציה ב UI" אולי ישמח את אנשי הפרודקט. מפתחים טובים ידאגו להוסיף גם וולידציה בצד שרת ובבסיס הנתונים.

מה שלא היה באסיפת הורים

19/09/2025

הייתי באסיפת הורים בבית ספר השבוע. לא יודע איך המצב אצלכם אבל על כל השאלות האלה לא דיברו בכלל:

  1. מה המטרה של בית הספר?

  2. למה התלמידים הולכים לשם? איזה תהליכים לימודיים, חינוכיים ומחשבתיים הם יעברו השנה?

  3. איזה מיומנויות חדשות התלמידים יקבלו השנה?

  4. מה הרמה של התלמידים היום בכיתה? מה הם יודעים? מה מעניין אותם? מה הם רוצים לדעת?

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

בלי הבנה ברורה של נקודת המוצא, היעד והמסלול קשה לראות את הערך שאותה חוויה לימודית מספקת.

שי חלוד

18/09/2025

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

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

ומצד שני אי אפשר בכלל להשוות את הפופולריות של npm מול rubygems. לא מפתיע ששחקנים זדוניים לא מנסים להשתלט על ריילס במתקפות נגד שרשרת האספקה באותה עוצמה וכמות כמו שתוקפים את npm. גם אליקסיר, php, קלוז'ר וכלים לא פופולריים רבים נוספים "נהנים" מהיותם מטרה פחות מלהיבה. קצת כמו ש Desktop Linux די חסין לוירוסים, פשוט בגלל שהוא לא מטרה מספיק מלהיבה.

לקחים מעשיים מהסיפור? כמו במקרים דומים כדאי לשים לב ל Best Practices של פיתוח מאובטח:

  1. לא להתקין גירסאות חדשות של ספריות חיצוניות מיד כשעולות לאוויר.

  2. להוריד הרשאה על מפתחות גישה, לא לערבב בין מפתחות גישה של פיתוח ושל פרודקשן.

  3. להפריד בין רכיבים במערכת ובין מערכות שונות, כולל משתמשים שונים לכל מערכת.

  4. לנטר בקשות שיוצאות מהמחשב שלכם (כן גם ממחשב הפיתוח, כמובן שמסביבת סטייג'ינג ובמיוחד ממחשבי הפרודקשן).

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

  6. להתקין פחות ספריות JavaScript. הרבה דברים אפשר לפתור היום לבד או עם מימוש קטן של AI.

תקציר דוגמאות הוובינר מחר

17/09/2025

הי חברים מה קורה? השבוע במפגש של מדברים AI אני אציג את הספריה OpenAI Agents ואראה איך להתקין אותה, מה הקשר שלה לספריות סוכנים מתחרות או מקבילות ומה המנגנונים והעקרונות המרכזיים של כתיבת סוכנים. אלה 5 דוגמאות שאני מתכנן להציג.

המשך קריאה

רק AI היה יכול לחשוב על זה, חלק 2

16/09/2025

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

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

items = [
    { id: 'a', name: 'one', next: 'b' },
    { id: 'b', name: 'two', prev: 'a', next: 'c' },
    { id: 'c', name: 'three', prev: 'b', next: 'd' },
    { id: 'd', name: 'four', prev: 'c' }
]

וביקשתי מ AI לכתוב בדיקות ללוגיקה.

קיבלתי קובץ של כמה מאות שורות כל בדיקה בנויה באותו סגנון:

expected = [{id: 'a', name: 'one'}]
assert_equal expected, build_list

רואים כבר את הבעיה? תכף היא תהיה ברורה.

הפיצ'ר הבא למימוש היה שינוי שהמזהים יהיו אקראיים, כך שעכשיו הפונקציה build_list עדיין מחזירה מבנה נתונים בדיוק באותו מבנה אבל במקום a, b, c ו d היא משתמשת ב uuid. מבחינת הקוד זה היה שינוי של דקה אבל הבדיקה (כלומר כמה מאות שורות של בדיקות) לא שרדה.

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

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

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

רק AI היה יכול לחשוב על זה

15/09/2025

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

item
  .select { |item| !item["id"] }
  .each_with_index do |item, index|
    item["id"] = "item_#{index + 1}"
  end

רואים את הכשל? בגלל שהלולאה רצה רק על הפריטים שאין להם id, האינדקס לא תואם למספר הפריטים. כשאני מוסיף פריט חדש ויש ברשימה 5 פריטים הוא יקבל את המזהה item_0 כי יש רק פריט אחד ללא id. וכך גם בפריט הבא וזה שאחריו.

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

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

היום למדתי: המחלקה StringScanner ב Ruby

14/09/2025

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

המשך קריאה

הפיתרונות של אתמול

13/09/2025

הכפתור הכי חשוב במשחק Spelling Bee הוא "הפיתרונות של אתמול".

לא כי בפיתרונות של אתמול יש רמזים לחידה של היום (ממילא כל יום יש אותיות אחרות).

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