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

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

איך לשלוח הודעות עדכון מ Tools ב Vercel AI SDK

22/07/2025

בדוגמה מהאתר של Vercel AI SDK הם מראים איך לקרוא לכלים מתוך הסוכן עם הקוד הבא:

import { z } from 'zod';
import { generateText, tool } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      parameters: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  prompt: 'What is the weather in San Francisco?',
});

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

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

  1. צריך ליצור את ה Stream בצורה יזומה לפני שמפעילים generateText, ואז לחבר את הפלט של generateText ל Stream שיצרנו.

  2. צריך לשים לב ש Vercel AI SDK המציאה פרוטוקול כדי להעביר metadata בשיחה. בכל הודעה יש תחילית של סוג ההודעה ואחריה נקודותיים ואז ההודעה עצמה בתוך מרכאות. התחילית לטקסט היא פשוט 0. לכן היינו יכולים לכתוב משהו כמו:

writer.write('0:"hello"');

אבל יותר קל להשתמש בפונקציה מובנית שלהם שנקראת formatDataStreamPart.

התוצאה נראית כך:

import { openai } from '@ai-sdk/openai';
import { generateId, createDataStreamResponse, streamText, formatDataStreamPart } from 'ai';

import tools from '@/lib/tools';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages, data } = await req.json();

  // immediately start streaming (solves RAG issues with status, etc.)
  return createDataStreamResponse({
    execute: dataStream => {
      dataStream.write(formatDataStreamPart('text', 'Warming up!ֿ'));
      dataStream.writeMessageAnnotation({ chunk: 'processing' });

      const result = streamText({
        model: openai('gpt-4-turbo'),
        maxSteps: 5,
        tools: tools(jwtToken, dataStream),
        messages,
        onFinish() {
          // message annotation:
          dataStream.writeMessageAnnotation({
            id: generateId(), // e.g. id from saved DB record
            other: 'information',
          });

          // call annotation:
          dataStream.writeData('call completed');
        },
      });

      result.mergeIntoDataStream(dataStream);
    },
    onError: error => {
      // Error messages are masked by default for security reasons.
      // If you want to expose the error message to the client, you can do so here:
      return error instanceof Error ? error.message : String(error);
    },
  });
}

עכשיו מתוך כל tool אפשר לקרוא ל dataStream.write כדי להוסיף הודעות לשיחה, או לבנות Workflows יותר מורכבים עם מספר סוכנים שכל אחד יוסיף הודעות לשיחה מתוך generateText שלו.

קודבייס ידידותי ל AI

21/07/2025

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

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

  1. שלוש דרכים לעשות X, אבל רק אחת עובדת.

  2. שתי טבלאות ב DB עם שמות דומים שמחזיקות מידע שקשור לדברים אחרים לגמרי.

  3. סקריפט שמריץ את הפרויקט אבל לא את כל רכיבי הפרויקט (כי יש רכיב שאף אחד לא צריך ומי שצריך יודע להריץ אותו לבד).

  4. התקנה שדורשת שינוי ידני ב DB באמצע הדרך.

  5. שפה פנימית ולא מתועדת של צוות הפיתוח.

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

בן האדם שבלופ

20/07/2025

שלוש שיחות עם קלוד-

  1. כתוב קוד פייתון שמתחבר לגיטהאב ומאפשר לי ליצור Issue חדש, להציג את ה Issues הקיימים ולסגור Issue.

קלוד עונה עם קלאס בשם GitHubIssuesManager

  1. למה בחרת לכתוב קלאס? הצג יתרונות וחסרונות של יצירת קלאס מול כתיבת פונקציות והמלץ על ארכיטקטורה.

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

def example_usage():
    """Example of how to use the functions programmatically."""
    config = create_config("your_token", "owner", "repo")

    # Open an issue
    issue = open_issue(config, "Bug found", "Description here", labels=["bug"])

    # List all open issues
    issues = list_issues(config, state="open")

    # Close an issue
    if issues:
        close_issue(config, issues[0]["number"], "Fixed!")
  1. ואיזה משתי הגישות תהיה קלה יותר עבור AI לקרוא ולתחזק?

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

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

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

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

אני אמור לקרוא עכשיו מזהי קומיט?

19/07/2025

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

כן. כשדברים נשברים.

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

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

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

ואז זה עבד

18/07/2025

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

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

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

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

מסתבר שלפחות עבור DHH, ואני בטוח שעבור רבים נוספים, הכיף לא קלקל את הרצון ללמוד אלא להיפך - סביבה לימודית שבה כל דבר עובד יוצרת את הרצון להבין. ה HTML שגרם ל DHH לרצות לכתוב קוד הוא ה Vibe Coding של היום, ואני די בטוח שככל שנחשוף ילדים מגיל צעיר יותר לכלי בניית מוצרים מבוססי AI כך נשפר את הביטחון העצמי והמוטיבציה שלהם.

ספירת שורות בקובץ בפייתון

17/07/2025

הסיפור היום מתחיל כשביקשתי מ ChatGPT להדפיס פונקציה שסופרת כמה שורות יש בקובץ:

def count_lines(filepath):
    with open(filepath, 'r') as f:
        return sum(1 for _ in f)

אבל - איך זה עובד? בואו נפרק את הקוד יחד.

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

>>> open('/etc/passwd', 'r')
<_io.TextIOWrapper name='/etc/passwd' mode='r' encoding='UTF-8'>

פקודת open מחזירה TextIOWrapper. שתי פונקציות העטיפה שלו הן:

>>> f.__enter__
<built-in method __enter__ of _io.TextIOWrapper object at 0x100e879f0>

ו:

>>> f.__exit__
<built-in method __exit__ of _io.TextIOWrapper object at 0x100e879f0>

פקודת ה __exit__ אחראית על סגירת הקובץ והיא מופעלת אוטומטית על ידי פקודת ה with בסיום הבלוק. אני יכול להפעיל אותה ידנית כדי להשתכנע שהקובץ נסגר:

>>> f=open('/etc/passwd', 'r')
>>> f.__exit__()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

גם השורה השלישית בקוד מסתירה הרבה התנהגות מובנית של השפה. נתחיל עם:

>>> (1 for _ in f)
<generator object <genexpr> at 0x101013ed0>

אם אמרנו ש f הוא TextIOWrapper, אז פקודת for ... in f היא פקודת יצירת גנרטור. היא עדיין לא מפעילה שום לולאה על הקובץ אלא רק מחזירה אוביקט שיודע "לעשות משהו" עם השורות שבקובץ:

>>> f=open('/etc/passwd', 'r')
>>> g=(1 for _ in f)
>>> next(g)
1
>>> next(g)
1
>>> next(g)
1

כל קריאה ל next על הגנרטור מבצעת איטרציה בודדת של האוביקט. במקרה של TextIOWrapper, איטרציה בודדת היא קריאה של מידע מהקובץ עד לגודל החוצץ המקסימלי (מוגדר ב io.DEFAULT_BUFFER_SIZE), חיפוש השורה הבאה והחזרת אותה השורה. הגנרטור עוטף את ההתנהגות הזאת ומחזיר משהו אחר במקום השורה. נתבונן רגע בגנרטור הפשוט יותר:

>>> g=(line for line in f)
>>> next(g)
'# Note that this file is consulted directly only when the system is running\n'
>>> next(g)
'# in single-user mode.  At other times this information is provided by\n'
>>> next(g)
'# Open Directory.\n'

הגנרטור (line for line in f) מחזיר את ערך המשתנה line בכל איטרציה. באותו אופן הגנרטור (1 for line in f) מחזיר את הערך 1 בכל איטרציה ומתעלם מהמשתנה line. ובגלל שאנחנו לא מתיחסים למשתנה הלולאה הרבה אנשים מעדיפים לקרוא לו בשם _, פשוט כדי שנדע שאנחנו לא משתמשים בערך שלו.

השורה המלאה:

return sum(1 for _ in f)

מפעילה את sum על גנרטור. זה מעניין! אנחנו רגילים להפעיל sum על רשימות:

>>> sum([10, 20, 30, 40])
100

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

>>> f=open('/etc/passwd', 'r')
>>> g=(1 for line in f)
>>> line_count = 0
>>> for value in g:
...     line_count += 1
...
>>> line_count
140

למעשה אם היינו כותבים לולאה כנראה שלא היינו מגדירים את הגנרטור ומראש כותבים לולאה כזאת:

def count_lines(filepath):
    with open(filepath, 'r') as f:
        line_count = 0
        for line in f:
            line_count += 1
        return line_count

הגנרטור אפשר לארגן מחדש את המבנה בצורה שיותר מתאימה ל sum וכך הצלחנו לכתוב:

def count_lines(filepath):
    with open(filepath, 'r') as f:
        return sum(1 for _ in f)

כי האיטרציה מבוצעת בתוך הפונקציה sum.

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

מעבר לקופיילוט - מה זה אומר להטמיע AI בארגון פיתוח?

16/07/2025

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

  1. איך משלבים כל כך הרבה PR-ים לגירסה? מתי מעלים גירסה? מה צריכות להיות דרישות הקבלה ומה התכולה?

  2. מיומנות גיט - איך מנווטים בין כל ה PR-ים? איך מבטלים PR אחרי ששילבתי אותו? איך מוציאים תיקון לגירסה ישנה?

  3. מיומנויות תקשורת - איך פרודקט ופיתוח משתפים פעולה בניסוח Issue שסוכן קידוד יוכל לממש? איך לתקשר למנהלים שלמרות שיש מימוש עובד אנחנו לא יכולים לשלב אותו כי הוא יסבך אותנו בעתיד?

  4. מיומנות תשתית - איך לארגן את הקוד כדי שסוכן קוד יוכל לעבוד עליו בענן?

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

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

יבוא מושהה בטייפסקריפט 5.9 הוא אחד השיפורים המלהיבים בשפה

15/07/2025

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

  1. עם השנים קוד JavaScript רק מתארך.

  2. לא רק שקשה להוריד קוד JavaScript ארוך מהשרת (כי הקובץ יותר גדול) אלא שהרצה ופיענוח של קוד כזה מכבידים על הדפדפן.

  3. אחרי שכתבת את הקוד קשה להחליף מ import רגיל ל import דינמי, בגלל שזה יכריח אותך לשנות את כל שרשרת הקריאות לקריאות עם await.

וכאן נכנס החבר שלנו import defer. פקודה זאת טוענת מודול אבל לא מבצעת אותו וכך חוסכת לדפדפן את זמן הריצה של קובץ ה JavaScript הגדול. זאת הדוגמה מדף התיעוד - תחילה קובץ המודול:

Copy
// ./some-feature.ts
initializationWithSideEffects();

function initializationWithSideEffects() {
  // ...
  specialConstant = 42;

  console.log("Side effects have occurred!");
}

export let specialConstant: number;

והקוד שמשתמש בו:

import defer * as feature from "./some-feature.js";

// No side effects have occurred yet

// ...

// As soon as `specialConstant` is accessed, the contents of the `feature`
// module are run and side effects have taken place.
console.log(feature.specialConstant); // 42

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

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

דף הפרסום המלא של טייפסקריפט 5.9 בטא זמין כאן לסקרנים: https://devblogs.microsoft.com/typescript/announcing-typescript-5-9-beta/

מימוש משחק סנייק עם AI (טייק 2)

14/07/2025

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

המשך קריאה

ואולי AI בכלל מאט אותך?

13/07/2025

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

התוצאות מעניינות ובמספר מימדים:

  1. גם לפני וגם אחרי המחקר המפתחים הרגישו שהם עבדו יותר מהר עם AI.

  2. בממוצע מפתחים מנוסים שהשתמשו ב AI על פרויקטים שאותם הם מכירים עבדו לאט יותר ב 19% בהשוואה למשימות שביצעו בלי AI. כן שמעתם נכון - לאט יותר מאשר בלי AI.

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

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

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

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