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

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

שני אתגרים חדשים בכתיבת סוכני AI

31/08/2025

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

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

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

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

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

מה זה Byte Pair Encoder

30/08/2025

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

היחידה הבסיסית איתה מודל שפה גדול עובד נקראת טוקן ובפוסט זה אסביר מה זה Byte Pair Encoder וגם נכתוב אחד ב Ruby כדי להבין איך LLM רואה את העולם.

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

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

רעיון שני שלא עובד כל כך טוב הוא האפשרות ההפוכה - נחליף כל אות במספר (למשל ערך ה ASCII שלה) ונאמן את המודל לנחש את האות הבאה.

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

  1. נותנים לכל אות מספר, למשל ערך ה ASCII שלה.

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

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

  4. חוזרים על התהליך עד שאוצר המילים מגיע לגודל הרצוי.

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

aa aa bb aabb

אז באיטרציה הראשונה אנחנו מחליפים כל אות במספר. בשביל הדוגמה אני מחליף את a ב-1, את b ב-2 ואת רווח ב-0:

[1, 1, 0, 1, 1, 0, 2, 2, 0, 1, 1, 2, 2]

עכשיו אני מזהה את הרצף שמופיע הכי הרבה פעמים זה יהיה 1,1 שמופיע 3 פעמים ולכן אני יוצר מספר חדש, המספר 4 שמחליף את 1,1:

[4, 0, 4, 0, 2, 2, 0, 4, 2, 2]

בסיבוב הבא יש לי כמה אפשרויות אני יכול לבחור את 4,0 שמופיע פעמיים, את 0,4 שמופיע פעמיים או את 2,2 שמופיע פעמיים. בואו ניקח את 4,0 וניתן לו את הערך 5:

[5, 5, 2, 2, 0, 4, 2, 2]

עכשיו אוצר המילים שלנו כבר מורכב מ:

" " - 0
a - 1
b - 2
aa - 4
"aa " - 5

שימו לב שהמספר 5 הוא טוקן שמייצג טקסט באורך 3 תווים. הזוג הבא הכי נפוץ הוא 2,2 שמופיע פעמיים אז נקרא לו 6 ונקבל:

[5, 5, 6, 0, 4, 6]

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

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

https://www.bpe-visualizer.com

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

module Tokenizer
  def self.encode(text, vocab)
    remaining = text
    encoded = []
    until remaining.empty?
      token, remaining = vocab.encode_next_token(remaining)
      encoded << token
    end
    encoded
  end


  def self.decode(encoded, vocab)
    inv = vocab.inverted
    encoded.map { |token| inv[token] }.join
  end

  class BytePairHash
    attr_accessor :vocab, :maxlen

    def initialize
      @maxlen = 1
      @vocab = (0..255).map(&:chr).each_with_index.to_a.to_h
    end

    def size
      vocab.size
    end

    def inverted
      vocab.invert
    end

    def encode_next_token(text)
      maxlen
        .downto(0)
        .find { |l| vocab.key?(text[...l]) }
        .then { |l| text[...l] }
        .then { |token| [vocab[token], text[token.length..]] }
    end

    def add_pair(pair)
      reverse_lookup = @vocab.invert
      new_token = reverse_lookup[pair[0]] + reverse_lookup[pair[1]]

      @maxlen = [@maxlen, new_token.length].max
      @vocab[new_token] = @vocab.values.max + 1
      [pair, @vocab[new_token]]
    end
  end

  class BPEHashBuilder
    attr_reader :bpe_hash

    def initialize(bpe_hash)
      @bpe_hash = bpe_hash
    end

    def extend_vocabulary(tokens)
      pair = tokens
             .each_cons(2)
             .tally
             .max_by { |(_, c)| c }
             .then { |(v, c)| c > 1 ? v : nil }

      bpe_hash.add_pair(pair) unless pair.nil?
    end
  end
end

def replace_all(tokens, new_pair, new_token)
  loop do
    index = tokens.each_cons(2).find_index(new_pair)
    break unless index

    tokens[index..index+1] = new_token
  end
end

vocab = Tokenizer::BytePairHash.new
builder = Tokenizer::BPEHashBuilder.new(vocab)
text = File.read('the-verdict.txt')
tokens = Tokenizer.encode(text, vocab)

loop do
  new_pair, new_token = builder.extend_vocabulary(tokens)
  break if new_pair.nil?

  replace_all(tokens, new_pair, new_token)
end

encoded = Tokenizer.encode('hello world', vocab)

pp encoded
pp Tokenizer.decode(encoded, vocab)

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

שינויים קטנים שמצטברים

29/08/2025

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

class UserSelectionsController < ApplicationController
  before_action :require_login
  before_action :find_chat_and_message

  def create
    # Handle initial selection creation (if needed in the future)
    # For now, redirect to chat
    redirect_to chat_path(@chat)
  end

  def update
    # Use the service to handle the selection
    service = UserSelectionService.new(
      chat: @chat,
      message: @message,
      user: current_user
    )

    if params[:text_input].present?
      service.handle_selection(text_input: params[:text_input].strip)
    elsif params[:option_id].present?
      service.handle_selection(option_id: params[:option_id])
    else
      redirect_to chat_path(@chat), alert: "Invalid selection"
      return
    end

    redirect_to chat_path(@chat)
  rescue => e
    Rails.logger.error "UserSelection error: #{e.message}"
    Rails.logger.error e.backtrace.join("\n")
    redirect_to chat_path(@chat), alert: "Something went wrong"
  end

  private

  def find_chat_and_message
    @chat = current_user.chats.find(params[:chat_id])
    @message = @chat.messages.find(params[:message_id])
  rescue ActiveRecord::RecordNotFound
    redirect_to new_chat_path, alert: t('chat.not_found')
  end
end

לא צריך להכיר רובי בשביל לראות את הבעיות:

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

  2. הפונקציה השניה update מסתיימת בפקודת rescue שכותבת ללוג את השגיאה ומחזירה למשתמש הודעה Something went wrong. פונקציית find_chat_and_messag תופסת שגיאה ומחזירה למשתמש הודעה מתוך קובץ ההודעות המתורגמות. בן אדם היה בוחר דרך אחת ומשתמש בה בשתי הפונקציות. כש AI כותב קוד הוא לא יודע מה הוא כתב קודם.

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

  4. הפונקציה update כוללת שני מנגנונים לטיפול בשגיאות, יש שם if else שמטפל במצב בו לא עבר פרמטר שהפונקציה ציפתה לקבל וגם rescue שמטפל בבעיות אחרות. בן אדם היה מוודא שקוד הטיפול בשגיאה, כלומר הקריאה ל redirect_to תופיע רק פעם אחת.

  5. בפונקציה update קוד הטיפול בשגיאות מטפל בכל Exception. ב find_chat_and_message יש טיפול רק ב ActiveRecord::RecordNotFound.

  6. הטיפול בשגיאות הוא תמיד מקומי. כש AI כותב את קוד הטיפול בשגיאות הוא לא יכול לשים לב שאפשר לכתוב קוד טיפול בשגיאות במקום יותר "גבוה" בשרשרת הקריאות כדי לכסות יותר מקרים.

  7. הפונקציה update כוללת את הבלוק הזה:

    if params[:text_input].present?
      service.handle_selection(text_input: params[:text_input].strip)
    elsif params[:option_id].present?
      service.handle_selection(option_id: params[:option_id])
    ...

הפונקציה handle_selection שמופעלת מתוך הבלוק כוללת את הבלוק הזה:

 def handle_selection(option_id: nil, text_input: nil)
    if text_input.present?
      handle_text_selection(text_input)
    elsif option_id.present?
      handle_option_selection(option_id)
    else
      raise ArgumentError, "Either option_id or text_input must be provided"
    end
  end

קיבלנו את אותה בדיקה בשני מקומות עם שני מנגנוני זריקת שגיאה שונים. בן אדם היה כותב ב update:

service.handle_selection(params)

ונותן ל if הפנימי לבדוק מה עבר, או מפצל את הקוד ב service לשתי פונקציות ומוותר על ה if שם. AI לא יודע מה הוא כבר כתב אז הוא משכפל.

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

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

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

היום למדתי: פונקציית random ב CSS

28/08/2025

אני מודה כששמעתי על הפיצ'ר random ב CSS השאלה הראשונה שלי היתה - מספרים אקראיים ב CSS? למה??

אבל אז הורדתי את Safari Technology Preview החדש וניסיתי למשל את הדמו של הכוכבים:

body {
  background: black;
}
.star {
   background-color: white;
   border-radius: 50%;
   aspect-ratio: 1/1; 
   width: random(2px, 10px, 1px);
   position: fixed;
   top: random(0%, 100%);
   left: random(0%, 100%);
 }

לינק לקודפן: https://codepen.io/ynonp/pen/XJmYaae

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

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

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

https://webkit.org/blog/17285/rolling-the-dice-with-css-random/

עד אז נמשיך להגריל ערכים באמצעות JavaScript.

מה לגבי keyword arguments?

27/08/2025

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

def demo(a, b):
    print(f"{a=}, {b=}")

אני יכול להפעיל אותה בכל הדרכים האלה בלי בעיה:

demo(2, 4)
demo(a=2, b=4)
demo(b=4, a=2)
demo(2, b=4)

רובי, JavaScript ושפות רבות אחרות דורשות יותר טקס בשביל משחק דומה. לדוגמה ב JavaScript אני יכול לכתוב:

function demo(a, b) {
  console.log(`a = ${a}, b = ${b}`);
}

demo(2, 4);

או אם אני מעדיף keywords אני אצטרך לכתוב:

function demo({a, b}) {
  console.log(`a = ${a}, b = ${b}`);
}

demo({a: 2, b: 4});

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

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

with open('/etc/passwd', 'r', -1, 'utf8', None, None, True, None) as f:
    print(f.read())

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

def demo(*, a, b):
    print(f"{a=}, {b=}")

# ok
demo(a=2, b=4)

# doesn't work
demo(2, 4)

מעבר לזה ברירת המחדל של פונקציות פייתון מכריחה אותי לבחור את השמות הנכונים לפרמטרים מההתחלה, כי באופן אוטומטי אנשים שקוראים לפונקציה יכולים להשתמש בשמות שבחרתי. בפונקציה demo אם אני רוצה להחליף את השמות מ a ו b לשמות אחרים אני לא יכול להיות בטוח שאף אחד לא השתמש בשמות המקוריים.

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

def demo(a:, b:)
  puts "a = #{a}, b = #{b}"
end

demo(a: 2, b: 4)

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

def demo(a, b)
  puts "a = #{a}, b = #{b}"
end

# ok
demo(2, 4)

# doesn't work - passing a single dictionary
demo(a: 2, b: 4)

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

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

def demo(a, b:, c:)
end

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

def demo(a, /, b, c):
    ...

ואם אני רוצה לחייב ש b ו c יתקבלו בתור keywords כדי לקבל את אותו אפקט של רובי אני כבר צריך לחבר לזה גם את הכוכבית:

def demo(a, /, *, b, c):
    ...

בפיתוח תוכנה כל דבר הוא Trade Off. כשאנחנו רואים פיצ'ר מדליק חשוב לשאול - איפה אני משלם על זה ומה המחיר.

ריאקט הוא לא jQuery טוב יותר

26/08/2025

מה אפשר ללמוד על אבסטרקציות מההבדל בין ריאקט ל jQuery?

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

const counter = document.getElementById("counter");
const button = document.getElementById("clickBtn");

// Initialize count
let count = 0;

// Add click event listener
button.addEventListener("click", () => {
  count++;
  counter.textContent = count;
});

וזה קוד מקביל ב JavaScript עם jQuery:

let count = 0;

$('#clickBtn').on('click', function() {
  count++;
  $('#counter').text(count);
});

מי שמכירים JavaScript ללא jQuery יבינו מהר מאוד איך לעבוד עם jQuery וכל פיצ'ר של jQuery שאנחנו לומדים "מתלבש" על מיומנות קיימת. במקום לכתוב getElementById כותבים $. קל וקצר.

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

function ClickCounter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div style={styles.container}>
      <h1>Click Counter (React)</h1>
      <div>{count}</div>
      <button onClick={handleClick}>Click Me!</button>
    </div>
  );
}

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

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

נשים לב - בשני המקרים חשוב להכיר את הדבר שהאבסטרקציה בנויה עליו. מתכנתי ריאקט שמכירים JavaScript כותבים קוד ריאקט טוב יותר. אבל יש טוויסט:

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

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

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

איך ללמוד ריאקט (או כל דבר) עם AI ולמה לשים לב

25/08/2025

פרומפט ראשון:

teach me React from scratch write 10 exercises to try react out for complete beginners

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

פרומפט שני:

create a solution for each in a tutorial style blog post. Explain all the basic concepts and theory as we advance from basic to more complex

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

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

expand each blog post to include full beginner friendly explanation

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

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

import { useEffect, useState } from "react";

function Users() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => setUsers(data));
  }, []); // run only once

  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

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

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

איפה הסוכן רץ ותהליך העבודה העתידי

24/08/2025

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

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

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

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

מיקרו אופטימיזציות

23/08/2025

שימו לב לפעולת ||= בקוד של טובי כאן:

    $token_map ||= {
      # semantic foreground styles
      # '{text}' => "\e[39m",
      '{text}' => "\e[39m",
      '{dim_text}' => "\e[90m",
      '{h1}' => "\e[1;33m",
      '{h2}' => "\e[1;36m",
      '{highlight}' => "\e[1;33m",
      # resets/util
      '{reset}' => "\e[0m", '{reset_bg}' => "\e[49m",
      # screen/cursor
      '{clear_screen}' => "\e[2J", '{clear_line}' => "\e[2K", '{home}' => "\e[H",
      '{hide_cursor}' => "\e[?25l", '{show_cursor}' => "\e[?25h",
      # Selection background: faint
      '{start_selected}' => "\e[6m",
      '{end_selected}' => "\e[0m"
    }

זה מגיע מפרויקט צד בשם try:

https://github.com/tobi/try/blob/main/try.rb

אני די בטוח שהוא לא השווה סטטיסטיקות של זמני ריצה או ניסה לטפל בתלונה ממשתמשים. ההבדל בין השמה רגילה ל ||= הוא שולי בהקשר הזה.

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

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

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

כזה ניסיתי: Tidewave

22/08/2025

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

https://tidewave.ai/

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

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

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

  1. במקום להתחיל פיצ'ר מקובץ HTML אני מתחיל את השיחה מהדפדפן כשאני מסתכל על אלמנט ספציפי ואף מסמן אותו בממשק של טיידוייב.

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

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

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

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

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