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

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

אין עקומת לימוד

11/08/2025

זה מרגיש ככה עם AI לפעמים, לא?

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

עברנו הרבה מאז.

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

לא פלא שאנשים חכמים כותבים:

Learning how to use LLMs in a coding workflow is trivial

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

אבל אז הפוסט מסתיים עם התלונה:

Every time I tried using an LLM for core features of applications I develop at work, the implementations were questionable and I spent at least as much time rewriting the code than I would have spent writing it from scratch.

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

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

כדאי לזכור:

  1. גם ל AI יש עקומת לימוד.

  2. אף אחד לא יכול להחליף מתכנתים ב AI.

  3. היכרות עם היכולות, המגבלות ואופן העבודה של AI משפרת משמעותית את התוצאה שמקבלים מהכלי.

ייצוא קובץ HAR לטקסט

10/08/2025

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

הבעיה היחידה - דפדפנים לא מייצאים Session שלם מטאב network בכלי הפיתוח לקובץ טקסט. איזה מזל שאנחנו יודעים פייתון.

זה הסקריפט:

import re
import json
from haralyzer import HarParser, HarPage
with open('demo.har', 'r', encoding='utf8') as f:
    har_parser = HarParser(json.loads(f.read()))

data = har_parser.har_data

for page in har_parser.pages:
    print(f"Page URL: {page.url}")

    for entry in page.entries:
        print("---\n\n")
        print("Request URL: ")
        print(entry.request.url)
        print("Request method: ")
        print(entry.request.method)
        print("Request Headers: ")
        print(entry.request.headers)
        print("")
        print("Response Headers: ")
        print(entry.response.headers)
        print("")
        if 'content' in entry.response:
            print("Response Body: ")
            print(entry.response['content'].get('text'))

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

טיפ למתקדמים - כדאי להוסיף בתחילת הלולאה דילוגים על url-ים לא רלוונטים למשל:

if entry.request.url.endswith('.jpg'): continue

סדר פריטים ב SQL

09/08/2025

כשאני מושך מבסיס נתונים את כל השירים במערכת:

select * from songs;

לפי איזה סדר הם מגיעים? האם אני מצפה לסדר? ואיך לוודא שהם יגיעו בסדר שאני צריך?

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

  2. אם יש הרבה שירים ולא אכפת לכם מהסדר, להוסיף order by יאט את השאילתה וידרוש יותר זיכרון.

ועכשיו לשאלה - האם כדאי לכתוב order by בכל שאילתה ראשית במערכת (כלומר לא במצב שה select בתוך where)?

אני מודה שאחרי שביליתי השבוע כמה שעות בשביל להבין שגיאה שנבעה משאילתה בלי order אני מתחיל לחשוב בחיוב על האופציה. מה דעתכם?

איך לקרוא את החלוקה לרמות של תומאס דומקה

08/08/2025

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

  1. סקפטיים - משתמשים ב AI למשימות קטנות או השלמת קוד (Tab Completion)

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

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

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

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

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

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

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

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

מתי "לכתוב את זה לבד" הופך לחוב טכני?

07/08/2025

נתון קוד HTML/JavaScript שמציג תיבת קלט ואת מספר התווים בתיבה:

<input type="text" id="input" />
<p id="count">0</p>
input.addEventListener('input', (e) => {
  count.textContent = e.target.value.length;
})

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

let timeout = null;
input.addEventListener('input', (e) => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
      count.textContent = e.target.value.length;
  }, 500);
})

ויש לנו פיתרון מקומי שקצת מלכלך את הקוד אבל אני יכול לחיות איתו.

אבל אז יגיע מישהו שיחשוב שאולי יש פה בעיה כללית יותר וש timeout הוא משתנה גלובאלי ויהיה עדיף בהרבה לסגור את כל הלכלוך הזה בפונקציה:

input.addEventListener('input', debounce((e) => {
  count.textContent = e.target.value.length;
}, 500));

function debounce(f, ms) {
  let timeout = null;

  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
        f(...args);
    }, ms);
  }
}

והנה החוב הטכני שלנו.

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

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

נ.ב. היום בעשר וובינר מודלים חופשיים ומקומיים. אם יש לכם שעה פנויה שווה לקפוץ ולגלות על יתרונות הפרטיות והמחיר של מודלים כאלה. אם אין לכם עדיין את הלינק לזום אפשר להירשם כאן: https://www.tocode.co.il/talking_ai

אני לא מאמין ש PHP קיבלו את האופרטור הזה לפנינו

06/08/2025

כבר הרבה זמן שיש הצעה על השולחן לאופרטור Pipe ב JavaScript. זה יראה כך:

value |> foo(%)

או בשירשור ארוך יותר:

[1, 2, 3]
|> %.filter(x => x % 2 === 0)
|> %.map(x => x * 4)
|> %.join(' ')
|> console.log(%)

או אפילו:

return links
  |> Object.keys(%).map(function (rel) {
    return '<' + links[rel] + '>; rel="' + rel + '"';
  })
  |> link + %.join(', ')
  |> this.set('Link', %);

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

$result = "Hello World"
    |> strtoupper(...)
    |> str_shuffle(...)
    |> trim(...);

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

ניסוי OpenRouter מ Python

05/08/2025

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

אפשר להירשם כאן: https://openrouter.ai/

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

import asyncio
from agents import Agent, Runner
from agents.extensions.models.litellm_model import LitellmModel
import os

async def main():
    models = [
        "openrouter/moonshotai/kimi-k2",
        "openrouter/z-ai/glm-4.5",
        "openrouter/cognitivecomputations/dolphin-mistral-24b-venice-edition:free",
        "openrouter/inception/mercury"
    ]

    for model in models:
        agent = Agent(
            name="Assistant",
            model=LitellmModel(model=model),
            instructions="You only respond in haikus.",
        )

        print(f"Model: {model}")
        result = await Runner.run(agent, "Tell me about recursion in programming.")
        print(result.final_output)
        print("---")

if __name__ == "__main__":
    asyncio.run(main())

הקוד משתמש בספריית OpenAI Agents SDK אותה אני מאוד מחבב ומבקש מ-4 מודלים להסביר בשיר Haiku מהי רקורסיה. אלה התשובות שקיבלתי:

Model: openrouter/moonshotai/kimi-k2
Function calls itself
Each step unpeels a layer
Base case stops the fall
---

Model: openrouter/z-ai/glm-4.5
Function calls itself
To solve a smaller problem
Base case stops the loop

Code within the code
Stack grows deeper with each call
Until base is found

Divide and conquer
Breaking problems into parts
Solve the simplest
---

Model: openrouter/cognitivecomputations/dolphin-mistral-24b-venice-edition:free
A function calls itself,
Looping back in endless dance,
Chasing its own tail.

Base case breaks the spell,
Else infinite descent,
Stack overflow lovers.

Elegant solutions,
Complex problems simplified,
Beauty in repetition.

But beware the trap,
Unchecked, it can drive you mad,
Like rabbits in a field.

Fibonacci, trees,
Recursion's grace is seen,
In patterns endless woven.

So with care and caution,
Wield this tool, and watch it grow,
A tapestry of code.
---

Model: openrouter/inception/mercury
Function calls itself,
In layers it goes UNCer hands depthHappyLe Finding to end.

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

איזה מודלי קוד פתוח אתם הכי אוהבים? ואיך אתם ניגשים אליהם?

חיבור Rails ל Langfuse

04/08/2025

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

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

def ask_ai
  Langfuse.trace("extract_lyrics", attributes: {
     "gen_ai.request.model" => "gemini-2.5-pro-preview-06-05",
     "gen_ai.system" => "Gemini"
  }) do |tracer|
    chat = RubyLLM.chat(model: 'gemini-2.5-pro-preview-06-05')
    response = chat.ask("hello")
    tracer.trace(response)
  end
end

ביקשתי גם להשתמש בממשק ה OpenTelemetry של Langfuse עבור המימוש וסיפקתי קישור לדף התיעוד שלהם על מאפייני ה OpenTelemetry הדרושים כדי לתעד שיחה. התוצאה היתה קוד לא נורא שגם עבר בכל המקרים שבדקתי:

require 'opentelemetry-api'

module Langfuse
  class TracerWrapper
    def initialize(span)
      @span = span
    end

    def trace(llm_response)
      # Customize based on RubyLLM / OpenAI / Gemini format
      if llm_response.respond_to?(:model)
        @span.set_attribute("gen_ai.response.model", llm_response.model)
      end

      if llm_response.respond_to?(:content)
        content = llm_response.content

        # Set the completion content according to GenAI semantic conventions
        @span.set_attribute("gen_ai.completion.0.role", "assistant")

        if content.is_a?(Hash)
          # For structured responses, store as JSON string
          @span.set_attribute("gen_ai.completion.0.content", content.to_json)
        elsif content.is_a?(String)
          @span.set_attribute("gen_ai.completion.0.content", content)
        end
      end

      if llm_response.respond_to?(:usage)
        usage = llm_response.usage
        @span.set_attribute("gen_ai.usage.prompt_tokens", usage.prompt_tokens)
        @span.set_attribute("gen_ai.usage.completion_tokens", usage.completion_tokens)
        @span.set_attribute("gen_ai.usage.total_tokens", usage.total_tokens)
      end
    end
  end

  def self.trace(name, attributes: {}, &block)
    tracer = OpenTelemetry.tracer_provider.tracer('langfuse)
    tracer.in_span(name, attributes: default_attributes.merge(attributes)) do |span|
      yield TracerWrapper.new(span)
    end
  end

  def self.default_attributes
    {
    }
  end
end

וקובץ האיתחול config/initializers/opentelemetry.rb:

require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'base64'

public_key = Rails.application.credentials.langfuse[:pk]
secret_key = Rails.application.credentials.langfuse[:secret]

auth_token = Base64.strict_encode64("#{public_key}:#{secret_key}")

exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
  endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces",
  headers: { "Authorization" => "Basic #{auth_token}" },
)

span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)

OpenTelemetry::SDK.configure do |c|
  c.add_span_processor(span_processor)
end

לא משתמש בפריימוורק

03/08/2025

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

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

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

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

02/08/2025

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

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

המשך קריאה