ToCode
الذهاب إلى القناة على Telegram
1 420
المشتركون
لا توجد بيانات24 ساعات
+27 أيام
-230 أيام
أرشيف المشاركات
1 420
📌 יותר מהיר לא אומר יותר פשוט
חבר מקשה - "לכתוב קוד היום זה משעמם. אתה רק מבקש משהו מה AI ויש לך את הקוד. עבודה שפעם לקחה זמן וחשיבה היום עושים כלאחר יד. זה לא מעניין".
אז שאלתי בחזרה - "נו אז מה כתבת מעניין עם ה AI"?.
שתיקה.
יותר קל לרכב על אופניים מאשר לנהוג באוטו, אפילו שהאוטו נוסע יותר מהר ואפילו שבאוטו לא מזיעים. כשמשהו מהיר יותר לא אומר שהוא פשוט יותר.
ברור שאפשר לכתוב היום בשעתיים POC שפעם היה דורש כסף זמן ומפתחים.
ברור שמפתחים מנוסים מצליחים לכתוב היום עם AI קוד במהירות גבוהה משמעותית ממה שכתבנו לפני שנה.
אבל שווה להכיר גם ש 50-70% מהעובדים בחברות לא מנצלים את תקציב הטוקנים שלהם. לא משתמשים בסוכני קידוד כדי להתקדם במהירות שהמנהלים שלהם רוצים שהם יתקדמו. הם אולי נוסעים באוטו אבל עדיין במהירות 15 קמ"ש.
הם לא עושים את זה כי הם עצלנים או כי הם לא רוצים לעבוד. נסיעה במהירות גבוהה דורשת מיומנות שונה מנסיעה במהירות נמוכה. מפתחים רבים היום מסתכלים על "מהפכת ה AI" ובאמת לא מבינים מה רוצים מהם. הם משתמשים ב AI כדי לכתוב קוד אבל הקוד לא נכתב כל כך הרבה יותר מהר. כשיוצאים מעולם ה POC הם מגלים שהם משקיעים הרבה יותר זמן ב Code Review, בתיקונים. "הוא התחיל לעשות בלאגן ולעבוד בלופים. מזל שתפסתי אותו בזמן ועצרתי אותו".
האתגר הוא שהרף עולה. חברות היום מצפות מעובדים לייצר קוד במהירות של מכונית. הסיפור הוא לא אם משתמשים ב AI אלא איך משתמשים בו. הרבה מהבעיות של אתמול נפתרו. הרבה בעיות חדשות נוצרו. היום אנחנו צריכים לעלות רמה.
1 420
from repid import Job, Queue
from app_repid import app, QUEUE
async def main():
if len(sys.argv) < 2:
print("Usage: python dispatcher_repid.py <max_posts>")
sys.exit(1)
max_posts = int(sys.argv[1])
pages_needed = math.ceil(max_posts / 10)
print(f"Downloading up to {max_posts} posts across {pages_needed} index page(s)")
async with app.magic(auto_disconnect=True):
# Declare the queue so jobs can be enqueued before any worker starts.
await Queue(QUEUE).declare()
for page_num in range(1, pages_needed + 1):
# Each page has 10 posts; the last page may have fewer
already_allocated = (page_num - 1) * 10
posts_limit = min(10, max_posts - already_allocated)
await Job(
name="download_index_page",
queue=QUEUE,
args={"page_num": page_num, "posts_limit": posts_limit},
retries=3,
).enqueue()
print(f" Dispatched page {page_num} (limit={posts_limit})")
print("All index pages dispatched. Workers will now process the jobs.")
if __name__ == "__main__":
asyncio.run(main())
✏ סיכום - מה מוצלח בדוגמה. מה עוד צריך לשפר.
הקוד מראה איך להוריד במקביל פוסטים מהבלוג. הקוד מקבל מספר פוסטים להורדה, מחשב כמה דפים צריך לשמור ואז מוציא משימות במקביל:
1. משימה לכל דף שצריך להוריד.
2. בזמן שדפי האינדקס יורדים, כל דף שסיים יוצר משימות חדשות להורדת הפוסטים שהופיעו בו.
הכל אסינכרוני והכל מוגבל בעד 10 משימות לפועל ושני פועלים, סך הכל עד 20 הורדות במקביל מהאתר.
הדוגמה לא כוללת Type Safety על המשימות עצמן. לרפיד יש אינטגרציה עם pydantic אבל עדיין לא ניסיתי אותה אז זה יהיה נושא לפוסט המשך.1 420
def to_markdown_url(post_url: str) -> str:
"""Convert a blog post URL to its markdown version by stripping
the query string and appending .md."""
base = post_url.split("?")[0]
return f"{base}.md"
def extract_post_urls(soup: BeautifulSoup, limit: int):
"""
Generator that yields up to `limit` unique blog post URLs
from a blog index page BeautifulSoup parse tree.
Blog post URLs are like /blog/<date-slug>?page=1
Index page is /blog?page=N (no trailing slash) — excluded by startswith.
"""
seen = set()
count = 0
for a_tag in soup.select("a[href]"):
href = a_tag["href"]
if href.startswith("/blog/") and href != "/blog/":
full_url = f"https://www.tocode.co.il{href}"
if full_url not in seen:
seen.add(full_url)
yield full_url
count += 1
if count >= limit:
return
@router.actor(queue=QUEUE)
async def download_index_page(page_num: int, posts_limit: int) -> dict:
"""
Download a blog index page, extract post links, and enqueue a
download_post job for each post found.
posts_limit: how many posts to download from this page (max 10).
"""
url = f"{BASE_URL}?page={page_num}"
resp = await client.get(url)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for post_url in extract_post_urls(soup, posts_limit):
md_url = to_markdown_url(post_url)
# The worker holds the active (magic) connection, so jobs enqueued
# from inside an actor reuse it automatically.
await Job(
name="download_post",
queue=QUEUE,
args={"md_url": md_url},
retries=3,
).enqueue()
return {"page": page_num}
@router.actor(queue=QUEUE)
async def download_post(md_url: str) -> dict:
"""
Download a single blog post markdown file and save it to the data folder.
The URL is already the .md version (converted by the dispatcher).
"""
resp = await client.get(md_url)
resp.raise_for_status()
# Derive filename from the URL slug: /blog/slug.md → slug
slug = md_url.rstrip("/").split("/")[-1].removesuffix(".md")
filename = f"{slug}.md"
filepath = os.path.join(DATA_DIR, filename)
os.makedirs(DATA_DIR, exist_ok=True)
with open(filepath, "w", encoding="utf-8") as f:
f.write(resp.text)
return {"file": filename, "size_bytes": len(resp.text)}
כל פונקציה שמסומת בתור actor מגדירה משימה. אצלנו בתוכנית אלה הפונקציות download_index_page ו download_post. משימות מוגדרות על ה Router וזכרו שכל Worker מקבל ביצירה את ה Router-ים לקחת מהם משימות:
worker = Worker(routers=[router], tasks_limit=10)
המשימות עצמן הן פשוט קוד שמוריד דף אינדקס (רשימה של פוסטים) או פוסט ספציפי ומה שחשוב לגביהן שיהיו פונקציות אסינכרוניות.
מעניין לשים לב בקוד המשימה שמורידה את דף האינדקס איך היא מייצרת משימות חדשות כדי להוריד את הפוסטים:
for post_url in extract_post_urls(soup, posts_limit):
md_url = to_markdown_url(post_url)
# The worker holds the active (magic) connection, so jobs enqueued
# from inside an actor reuse it automatically.
await Job(
name="download_post",
queue=QUEUE,
args={"md_url": md_url},
retries=3,
).enqueue()
return {"page": page_num}
✏ שיגור המשימות
החלק האחרון של המערכת הוא הקונטיינר שמשגר את המשימות. למעשה המשימה היחידה שאנחנו צריכים להפעיל היא הורדת דף האינדקס כי הפונקציה שלו כבר מפעילה את משימת הורדת הפוסטים כמו שקודם ראינו.
זה הקוד של dispatcher_repid.py שמתחיל הורדה של דפי האינדקס במקביל:
"""
Dispatcher (repid version): calculates how many blog index pages are needed
and enqueues download_index_page jobs via repid.
Usage:
uv run python dispatcher_repid.py <max_posts>
Example:
uv run python dispatcher_repid.py 25 # downloads up to 25 posts
"""
import asyncio
import math
import sys1 420
📌 דוגמת Repid - הורדת הפוסטים מהבלוג במקביל
רפיד היא ספריית פייתון להרצת משימות במקביל. היא דומה ל Celery אבל פופולרית פחות ואסינכרונית, ודומה ל dramatiq הפעם אסינכרונית כמוה, חדשה יותר ופחות פופולרית.
התכונות הטובות של Repid כוללות:
1. משימות אסינכרוניות (כמובן, בשביל זה באנו).
2. תמיכה טובה בבדיקות עם רכיב TestClient.
3. תיעוד ידידותי לסוכנים עם קבצי llms.txt ו llms-full.txt.
4. חיבור מובנה ל pydantic בשביל הגדרת טיפוסים.
דוגמה? בטח בואו נכתוב תוכנית פייתון שתשמור פוסטים מהבלוג הזה לקריאה אופליין.
✏ מבנה המערכת docker-compose.yml
תחנה ראשונה היא מבנה המערכת. מאחר ורפיד הוא מערכת להרצת משימות בצורה מבוזרת אשתמש ב docker-compose.yml כדי לייצר מספר קונטיינרים. זה הקובץ:
services:
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
worker-1:
image: ghcr.io/astral-sh/uv:python3.14-bookworm
working_dir: /app
depends_on:
redis:
condition: service_healthy
command: sh -c "uv sync --frozen --no-dev && uv run python worker_repid.py"
volumes:
- .:/app
- ./data:/app/data
- /app/.venv
environment:
- REPID_REDIS_URL=redis://redis:6379/0
worker-2:
image: ghcr.io/astral-sh/uv:python3.14-bookworm
working_dir: /app
depends_on:
redis:
condition: service_healthy
command: sh -c "uv sync --frozen --no-dev && uv run python worker_repid.py"
volumes:
- .:/app
- ./data:/app/data
- /app/.venv
environment:
- REPID_REDIS_URL=redis://redis:6379/0
dispatcher:
image: ghcr.io/astral-sh/uv:python3.14-bookworm
working_dir: /app
depends_on:
redis:
condition: service_healthy
command: sh -c "uv sync --frozen --no-dev && uv run python dispatcher_repid.py ${MAX_POSTS:-10}"
volumes:
- .:/app
- ./data:/app/data
- /app/.venv
environment:
- REPID_REDIS_URL=redis://redis:6379/0
profiles:
- dispatch
מתאם העבודה הוא רדיס בקונטיינר הראשון. קונטיינרים 2 ו 3 הם הפועלים וקונטיינר רביעי שולח את המשימות המתוזמנות.
✏ קוד הפועלים
הסקריפט של הפועלים ממש פשוט:
import asyncio
from repid import Worker
from app_repid import app, router, QUEUE
async def run_worker():
async with app.magic(auto_disconnect=True):
# auto_declare=True (default) makes the worker declare the queue
# before it starts consuming.
# tasks_limit caps how many actor coroutines run concurrently on this
# worker's event loop -> at most 10 concurrent downloads per worker.
worker = Worker(routers=[router], tasks_limit=10)
await worker.run()
if __name__ == "__main__":
asyncio.run(run_worker())
הוא מריץ פונקציה של רפיד ומגדיר הגבלה של 10 משימות במקביל. קוד המשימה עצמה מוגדר בקובץ app_repid.py באותה תיקיה. זה תוכן הקובץ:
"""
Repid application: defines the broker connection, the router and the
two actors (download_index_page, download_post).
This module is imported by both the worker (worker_repid.py) and the
dispatcher (dispatcher_repid.py). The actors are the repid equivalent of
the Celery tasks in tasks.py.
"""
import os
import httpx
from bs4 import BeautifulSoup
from repid import Repid, Connection, RedisMessageBroker, Job, Router
BROKER_URL = os.environ.get("REPID_REDIS_URL", "redis://localhost:6379/0")
# A single Repid app wraps the connection to the message broker (Redis).
# Entering `app.magic()` makes this connection the implicit one used by
# Job().enqueue(), Queue().declare() and the Worker.
app = Repid(Connection(RedisMessageBroker(BROKER_URL)))
# Shared async HTTP client. Because the actors `await` it, many downloads can
# be in flight on the worker's event loop at once (up to the worker's
# tasks_limit). follow_redirects mirrors requests' default behavior.
client = httpx.AsyncClient(timeout=30, follow_redirects=True)
router = Router()
QUEUE = "crawler"
BASE_URL = "https://www.tocode.co.il/blog"
DATA_DIR = "/app/data"1 420
📌 מותר להתחרט? כלי הפיתוח שאנחנו צריכים אתמול כדי לשלב Agents בצוות
שילוב AI Agents בצוות פיתוח מוביל יחד עם השיפור במהירות הפיתוח גם אתגרים חדשים ובמיוחד הרבה מאוד עבודת Review ו Refactor. נכון להיום אני מקבל קוד שכתב AI Agent רק בניסיון השני או השלישי למימוש. הבעיה היא שלא תמיד אנחנו מצליחים לעצור את רצף הפיתוח בשביל Code Review. במיוחד כשיש עוד אנשים שמעורבים בפיתוח ובראייה לעתיד מה שהולך לקרות זה שאנשי מוצר או אנשים טכניים יכתבו איפיונים ומפתחים יצטרכו להגיע לקוד בהמשך כדי לבצע Code Review ולשפר. ולתהליך הזה אנחנו לא ערוכים. הנה כמה בעיות ורעיונות לכלים עתידיים מבוססי AI שאולי יעזרו:
1. גיט - גיט לא אוהב שאנחנו מכניסים שינוי אחרי שיש קומיטים חדשים שהתבססו על קוד שכבר נדחף לריפו. בעולם משולב AI אנחנו נרצה כלי ניהול גרסאות שמאפשר לי לשנות את ההיסטוריה ונעזר ב AI כדי לייצר מחדש את כל הקוד שמגיע אחרי.
2. בסיסי נתונים - מפתחות זרים ו Sequence-ים בבסיסי נתונים מבינים למצב שקשה לי מאוד לשנות היסטוריה אחרי שהיא קרתה. בעתיד אנחנו נצטרך למצוא דרך להוציא "ענף" מגרסה ישנה של בסיס הנתונים ולהלביש את הנתונים החדשים על השינוי שנבצע בהיסטוריה.
3. תשתיות - שינוי חיבורי תשתית עם כלים כמו אנסיבל או טרפורם היום מכריח אותנו לבצע מיגרציה ידנית של הנתונים. אם סוכן מחליט להשתמש במונגו ואני מגלה את זה אחרי שבסיס הנתונים החדש באוויר אני צריך להיות מסוגל לשנות את ההחלטה וסוכן אחרי ידע להעביר את הנתונים שכבר נוצרו לארכיטקטורה החדשה.
המשמעות של להוציא את המפתחים מהלופ ולוותר על צוואר הבקבוק של Code Reviews היא היכולת להתחרט ולתקן טעויות של סוכנים. גיט הומצא במקרה ב 2005. הענן של AWS הוקם ב 2002. טרפורם ב 2014. אין סיבה להניח שהכלים האלה יישארו איתנו לעד.
1 420
4. תרגום המילים - לא התיחסתי בפרומפט בכלל לשאלה איך לתרגם את המילים הבודדות ולכן קלוד יצר מנגנון בזבזני שפונה ל LLM כדי לתרגם כל מילה בנפרד. שיפור הכרחי היה לקבץ מילים יחד בשביל התרגום.
מה נשאר? להכניס תכנים בעוד שפות ולראות אם פלאש עומד בזה, לוודא שקריוקי לא שובר את הצפייה ממובייל.
רוצים לראות את הקריוקי בפעולה? הנה שיר ראשון שהעליתי איתו:
https://langlets.app/courses/860/lessons/kissy-face-and-red-hearts
1 420
📌 ניתוח פרומפט - מצב קריוקי
לפני חצי שנה דנשי הציע שאוסיף ל langlets מצב קריוקי שמדגיש את המילה הנוכחית כשמנגנים שיר. באותו זמן לא האמנתי שזה אפשרי - מודל AI שיודע למצוא זמן לכל מילה? הם בקושי מצליחים ליצור לי התאמה ברזולוציה של משפטים.
חצי שנה עברה ו Gemini 3.5 Flash עומד במשימה בצורה מרשימה. אחרי שראיתי את התוצאות הלכתי לשנות את הקוד, או יותר נכון נתתי לקלוד לשנות. אני רוצה להציג כאן את הפרומפט ולדבר על השיקולים בכתיבתו וגם לסכם מה עבד ומה עוד היה צריך להוסיף.
✏ לפני הכל הפרומפט
התחלתי עם הפרומפט הזה כקלט למצב תכנון (עד כדי הקטעים הטכניים שאני מצמצם פה כדי למקד את הפרומפט):
Switch to per word timing maintaining backward compatibility
Step 1 - Create Song Pipeline
Refactor the extract lyrics prompt to the following version:
[...]
each "word" in the output should create a token translation
Then create_token_translations step doesn't have to do language alignment, it should just translate each "word". Still give the LLM the full phrase for context though.
Step 2 - Schema
Add timestamp to token_translation
Step 3 - UI
Show karaoke style word highlight marking current token when playing in the video players
Step 4 - Verification and format
Use the existing apt_word_timing.json output with song URL as the result of extract youtube lyrics
https://www.youtube.com/watch?v=ekr2nIex040
Run the pipeline steps AFTER extract youtube lyrics to create the rest of the data and save it to a JSON file. I'll then manually import that JSON file to check the result. Create a reusable script so we can repeat our testing
Step 5 - Backward Compatibility
The video players should support 2 cases:
token_translations with timestamps (new functionality) - use karaoke style word highlight
token_trnslations without timestamps (existing data) - continue to use phrase level highlight
Pass a boolean value from rails video activity params to indicate if it should use the new or old style
מצב סוכן שאל אותי כמה שאלות הבהרה, יצר תוכנית ואז מצב אוטומטי מימש אותה והסרט הראשון נפתח במצב קריוקי כך שכמעט אפשר לסמן הצלחה.
✏ מה טוב בפרומפט
אלה הנקודות החזקות של הפרומפט:
1. הצגת ההשפעה - ברור על איזה חלקים בקוד השינוי משפיע. החלקים העיקריים הם השינוי במנגנון תמלול הסרט והשינוי במנגנון תרגום המילים.
2. שינויים בסכימה - שינויים במודל ואיך ששומרים את המידע מופיעים בצורה מפורשת ועוזרים למקד את קלוד. במקרה שלנו יש תוספת של עמודה אחת לטבלת token_translations.
3. ממשק משתמש - שימוש בביטוי Karaoke Style מבהיר בדיוק איך דברים צריכים להיראות, בלי להכנס לפרטים הטכניים של איך זה ימומש. אין שום דבר מיוחד בקריוקי באפליקציה זו בהשוואה למצב קריוקי בכל מקום אחר ולכן אין צורך להרחיב על זה.
4. איך לוודא הצלחה - הבלוק האחרון Verification and format כולל צירוף קובץ שמראה את הפורמט המדויק של הנתונים עוזר לקלוד להבין שהוא יצר משהו שעובד.
5. מתאר את ה"מה" ולא את ה"איך" - מה שהכי אהבתי בפרומפט. הוא מתאר מה המערכת צריכה להיות אחרי השינוי אבל לא נכנס לקוד עצמו שצריך להיות שם. זאת עליית המדרגה באבסטרקציה שאנחנו צריכים לאמץ.
✏ מה לא טוב בפרומפט
הנקודות שקלוד פספס במימוש היו מאוד ספציפיות ל Codebase. סופר הגיוני ומזכיר למה חשוב לשמור קבצי תיעוד שמתארים את הקוד ומחדדים נקודות שלנו אולי נראות ברורות מאליהן:
1. למרות שביקשתי רק תוספת של עמודה אחת, קלוד הוסיף שתי עמודות "זמן התחלה" ו"זמן סיום", להתאים למקומות אחרים בקוד.
2. מנגנון זיהוי המילה הנוכחית לקריוקי מאוד בזבזני. זה הקוד שקלוד יצר:
updateWordHighlight(currentTime) {
if (!this.tokenSpans) {
this.tokenSpans = Array.from(this.element.querySelectorAll('[data-token-start]'));
}
for (const span of this.tokenSpans) {
const start = Number(span.dataset.tokenStart);
const end = Number(span.dataset.tokenEnd);
span.classList.toggle('s-token-active', currentTime >= start && currentTime <= end);
}
}
3. קלוד בחר להציג בסגנון קריוקי תוכן אם לחלק מהמילים יש תווית זמן. אבל האמת היא שאם רק לחלק קטן מהמילים יש תווית זמן עדיף לוותר על מצב קריוקי. הסיבה היא שלא התיחסתי בפרומפט למצבים בהם יש מידע רק על חלק מהמילים.1 420
📌 כפל קוד ואבסטרקציה לא נכונה
במאמר בנושא אבסטרקציות בתקופה שמפתחים עוד כתבו קוד סנדי מץ מתארת את התרחיש הנפוץ הבא:
1. מפתח A רואה את אותו קוד מופיע בשני מקומות. מוציא לפונקציה משותפת ושמח על החסכון בקוד.
2. הזמן עובר ונכנסת דרישה חדשה למערכת.
3. מפתח B מזהה שהקוד כמעט מתאים לדרישה החדשה אז הוא רק מוסיף פרמטר לפונקציה המשותפת ועוד if קטן והכל מסתדר.
4. הזמן ממשיך לעבור, מפתחים נוספים מרחיבים את הקוד המשותף, ומה שפעם היה אבסטרקציה יפה הפך לערימה לא ברורה של תנאים וחריגים.
המסקנה של סנדי היתה שהאבסטרקציה הלא נכונה יקרה בהרבה מקוד משוכפל כיוון שעלות הפיתוח שלה כוללת את כל התחזוקה של קטעי הקוד המחוברים. האבסטרקציה הלא נכונה יחד עם אינרציה הכניסה אותנו לברוך שאף אחד כבר לא יודע איך לצאת ממנו.
אגב התרחיש המקורי ממנו מפתח A ניסה להתגונן היה:
1. הזמן עובר ונכנסת דרישה חדשה למערכת.
2. מפתח B מתקן את הקוד במקום אחד ושוכח שהקוד משוכפל ומופיע במקום נוסף במערכת.
3. משתמשים כועסים כי עכשיו כפתור אחד מתנהג נכון ושני עדיין שבור.
עשר שנים אחרי המאמר של סנדי מץ ואנחנו צריכים להוסיף שיקול חדש למערכת. איזה מהתרחישים יותר מטריד אותנו כש AI כותב את הקוד. הנה מה שאנחנו יודעים:
1. סוכני קידוד לא אוהבים ליצור אבסטרקציות חדשות או לשבור אבסטרקציות קיימות. ב 2026 סוכן קידוד הולך להתנהג בדיוק כמו מפתח B בדוגמה הראשונה ויעמיס עוד התנהגות על המערכת הקיימת. אולי בעתיד זה ישתנה.
2. סוכני קידוד צריכים עזרה בשביל למצוא את כל המקומות שמשפיעים על פעולה מסוימת. אם יש לי במערכת שתי קומפוננטות של נגן וידאו וביקשתי שינוי בנגן סוכני קידוד בסבירות גבוהה יתקנו רק את אחד מהנגנים.
ואת כל זה צריך להכפיל פי 10 כי סוכני קידוד כותבים הרבה יותר קוד הרבה יותר מהר מבני אדם.
אז האם כפל קוד עדיין יותר זול? טוב בשביל לענות צריך קודם להבין איך בכלל אנחנו מתמודדים עם שני המצבים בעולם של סוכני קידוד:
1. את התרחיש הראשון אנחנו נזהה רק אם נקרא את הקוד ש AI מייצר ורק אם נהיה רגישים לראות אבסטרקציה לא נכונה שהולכת ומסתבכת. תקלה פונקציונאלית תקרה כנראה רק כשכבר יהיה מאוחר מדי והקוד יהיה מסובך מדי בשביל לתקן. כשכבר נראה את הבעיה נצטרך לארגן מחדש את הקוד ובזה AI לא ממש יוכל לעזור כי נכון להיום קשה מאוד ל AI לראות אבסטרקציה לא נכונה ולדמיין דרך יותר טובה לארגן את הקוד.
2. בתרחיש השני הקוד הכפול יעודד את ה AI ליצור עוד כפילויות, כי כך המערכת בנויה. אנחנו נבין שיש כפילות רק כשנקרא את הקוד. מצד שני פה יש סיכוי יותר טוב שמשהו פונקציונאלית יישבר כי AI תיקן או עדכן משהו רק במקום אחד ולא בכל המופעים של הקוד הכפול ואז מפתחים אנושיים ייכנסו לתמונה. כשכבר יש כפל קוד ל AI די קל ליצור אבסטרקציה שמתאימה לקוד הכפול הנוכחי (בניגוד לשינוי אבסטרקציה שעדיין לא עובד טוב) ולכן בכל נקודה יהיה קל לעבור מקוד משוכפל לקוד משותף. מי שירצה להמשיך לעבוד עם הקוד המשוכפל לאורך זמן יוכל להוסיף קובץ תיעוד שמסביר איפה כל המקומות שהקוד הכפול מופיע.
לדוגמה ב langlets יש לי שני נגני וידאו ומספר layouts לכל נגן. חלק מהקוד משותף וחלק משוכפל בין הנגנים ובאמת מדי פעם כשביקשתי לעדכן פיצ'ר בנגן הוידאו ה AI מימש את השינוי רק באחד מהם. הפתרון היה להוסיף קובץ טקסט שמסביר על ארכיטקטורת הוידאו במערכת, אפשר לראות אותו כאן:
https://github.com/ynonp/langlets-rails/blob/main/docs/video-player.md
אחרי התוספת ה AI כבר הצליח להתמודד עם הקוד המשוכפל ולתקן בכל הנגנים גם בלי שאציין את זה בצורה מפורשת בפרומפט.
מסקנות? לדעתי הלקח של סנדי מץ עדיין רלוונטי ואפילו הפך יותר רלוונטי מאז שילוב AI בתהליך הפיתוח. כן כפל קוד מעודד יותר כפל קוד. כן ה AI כותב קוד כל כך מהר שאף אחד אפילו לא מרגיש שצריך יותר קוד בשביל לפתור בעיה. אבל - אנחנו כן נרגיש כשהפונקציונאליות של המערכת נשברת ופיצ'ר לא פותר את כל הבעיה, בעזרת קבצי תיעוד אפשר להישאר עם קוד משוכפל ולקבל תוצאות טובות וכשנרצה ליצור אבסטרקציה המעבר מקוד משוכפל לאבסטרקציה יהיה מאוד קל בזכות סוכן הקידוד.
1 420
print(result.output)
יש להם תמיכה מלאה ב Server Side Tools כמו חיפוש ברשת של OpenAI ושליטה על מידת החשיבה:
from pydantic_ai import Agent
from pydantic_ai.capabilities import Thinking, WebSearch
agent = Agent(
'anthropic:claude-opus-4-6',
instructions='You are a research assistant. Be thorough and cite sources.',
capabilities=[
Thinking(effort='high'),
WebSearch(local='duckduckgo'),
],
)
סך הכל הספריה מאוד כיפית - דברים שלא נכנסו פה לרשימה אבל שווים אזכור הם החיבור למערכת הלוגים שלהם logfire (במקום זאת של OpenAI), תמיכה ב MCP, תמיכה מובנית בבדיקות עם TestModel וממשק Web Interface מבוסס צ'ט כדי לנסות את הסוכן שלכם.
אם אהבתם את OpenAI Agents SDK שווה מאוד לנסות את Pydantic AI.1 420
📌 שלושה דברים שאהבתי במיוחד ב Pydantic AI
ספריית פיתוח הסוכנים Pydantic AI היא משב רוח מרענן של פשטות. היא מזכירה את OpenAI Agents SDK האהובה עליי רק בלי החיבור ל OpenAI ועם הרבה יותר שליטה בפרמטרים של המודלים. העבודה איתה בהחלט יכולה להרגיש כמו מישהו שהבין מה ש OpenAI ניסו לעשות עם Agents SDK ופשוט המשיך עם זה.
אפשר לקרוא את התיעוד המלא ודוגמאות שלהם כאן:
https://pydantic.dev/docs/ai/overview/
והנה שלושה דברים שאהבתי במיוחד בספריה.
✏ תיעוד מלא וידידותי לסוכן
ב 2026 הדרישה הראשונה שלי מספריית קוד היא שאפשר יהיה לעבוד איתה בקלות מתוך סוכן קידוד. הבעיה של OpenAI Agents SDK היא שאף אחד לא משתמש בקודקס והם לא רוצים לקדם את קלוד קוד. התשובה שלהם למי שביקשו Agent Skill היתה:
Thanks for writing in. Our current recommendation is to use codex for loading and utilizing agent skills because the codex layer already has a robust mechanism for it. We prefer to avoid reinventing the wheel for now.פידנטיק לא צריכים את הפוזיציה ולכן בעמוד התיעוד הראשי הם שילבו מדריך איך להתקין את הסקיל לכל הסוכנים. אחרי ההתקנה כשהפעלתי קלוד קוד וביקשתי סוכן ראשון המימוש היה מיידי. ✏ מנגנון Fallback מודלים לא תמיד יענו את התשובה לה אנחנו מצפים - זה יכול להיות המודל עצמו שטועה, כשלון בהפעלת כלי, תוכן לא הולם או אפילו תשובה שלא קשורה למה ששאלנו. מנגנון Fallback Model של פידנטיק מספק דרך קלה ומדויקת לזהות ולטפל במקרים כאלה. התיעוד כאן: https://pydantic.dev/docs/ai/models/overview/#fallback-model ובגדול זה נראה כך:
from pydantic_ai.exceptions import ModelAPIError
from pydantic_ai.models.fallback import FallbackModel
from fallback_on_native_tool import anthropic_model, google_model, web_fetch_failed
fallback_model = FallbackModel(
google_model,
anthropic_model,
fallback_on=[
ModelAPIError, # Exception type
lambda exc: 'rate limit' in str(exc).lower(), # Exception handler (untyped lambda)
web_fetch_failed, # Response handler (auto-detected via type hint)
],
)
מגדירים את המעבר והתנאים במקום אחד ועובדים עם המודל רגיל בכל שאר התוכנית. בדוגמה שהדבקתי אם המודל של גוגל לא מצליח לענות על הבקשה יש לעבור למודל של אנטרופיק ויש 3 מנגנוני כשלון, או שהמודל זורק שגיאה מסוג ModelAPIError, או שהמודל זורק שגיאה עם הטקסט rate limit או שהתשובה של המודל כוללת כשלון בהרצת כלי.
✏ גמישות במודלים ושליטה בכל הפרמטרים
זה פיצ'ר שמאוד היה חסר לי ב OpenAI Agents SDK שם היתה העדפה ברורה למודלים של OpenAI. פידנטיק לא רק שמאפשרים לי לבחור כל ספק אלא שאפשר גם לשלוט בפרמטרים הספציפיים של המודל. לדוגמה עם OpenAI אפשר להתחבר ל Responses API שלהם אבל גם ל Chat API, וגם לפרוביידרים הסינים לדוגמה:
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.alibaba import AlibabaProvider
model = OpenAIChatModel(
'qwen-max',
provider=AlibabaProvider(
api_key='your-api-key',
base_url='https://dashscope.aliyuncs.com/compatible-mode/v1', # China region
),
)
agent = Agent(model)
...
באנטרופיק יש אפשרות להשתמש במנגנון ה Cache System Instructions שלהם כדי לחסוך טוקנים:
from datetime import date
from pydantic_ai import Agent, RunContext
from pydantic_ai.models.anthropic import AnthropicModelSettings
agent = Agent(
'anthropic:claude-sonnet-4-6',
deps_type=str,
instructions='You are a helpful customer service agent. Follow company policy.',
model_settings=AnthropicModelSettings(
anthropic_cache_instructions=True,
),
)
@agent.instructions
def dynamic_context(ctx: RunContext[str]) -> str:
return f"Customer name: {ctx.deps}. Today's date: {date.today()}."
result = agent.run_sync('What is your return policy?', deps='Alice')
print(result.output)
ואפילו יש אפשרות להגדיר Cache Breakpoints ידנית.
לגוגל אפשר להעביר וידאו מיוטיוב שוב לפי הפורמט של ג'מני:
from pydantic_ai import Agent, VideoUrl
from pydantic_ai.models.google import GoogleModel
agent = Agent(GoogleModel('gemini-3-flash-preview'))
result = agent.run_sync(
[
'What is this video about?',
VideoUrl(url='https://www.youtube.com/watch?v=dQw4w9WgXcQ'),
]
)1 420
📌 מתי נוכל להפסיק לקרוא את הקוד?
ההבטחה של Spec Driven Development היא פשוטה - במקום לכתוב ולקרוא קוד נעבור לכתוב ולקרוא איפיונים. במאמר ארוך בנושא ראיתי את ההמלצה הבאה:
Don’t kill the code reviews; just move the human checkpoint upstream to reviewing intent, specs, plans, constraints, and acceptance criteria. Code is actually the least important part of the reviews.
אין ספק שהתעשייה וגם המפתחים עצמם רוצים להגיע לשם. הרבה יותר מעניין לתכנן ארכיטקטורת מערכת ולשחק עם אילוצים מאשר לקודד לולאות ולהיזכר בשמות מדויקים של פונקציות. ועדיין אני חושב שטכנית אנחנו עוד לא שם. אלה הסיבות בגללן אני עדיין קורא את הקוד:
1. סוכן קידוד לא תמיד מייצר את הקוד באיפיון - זאת לדעתי הבעיה הכי משמעותית עם SDD. שינוי קטן בקוד יכול לגרום למערכת להתנהג אחרת ממה שהתכוונתי באיפיון, ובלי לקרוא את הקוד אין לי איך לדעת על הבעיה. (לא קל לבדוק 100% מהפונקציונאליות במערכת מעניינת).
2. איפיונים לא תמיד מדויקים - אולי עם השנים נלמד לכתוב אפיונים יותר מדויקים. בינתיים אני רואה הרבה פעמים שאני כותב איפיון שנראה לי מאוד מדויק אבל כשהוא הופך לקוד אני מגלה שפספסתי מקרים חשובים. זה לא משהו שהייתי רואה בלי לקרוא את הקוד שנוצר. הקוד בעצם ממקד ומחזק את האפיון.
דוגמה קטנה מ langlets - ביקשתי מהסוכן פיצ'ר שכשלוחצים על מילה בטקסט יקפוץ פופאפ עם התרגום שלה. הסוכן הוסיף onclick על המילה אבל בגלל שמדובר ב JavaScript רגיל (לא ריאקט) בעמוד שהכיל הרבה טקסט נוצרו המון Event Handlers. לכל מילה היה את ה onclick שלה.
כמובן שיכולתי לכתוב באיפיון לייצר רק Event Listener אחד על הקונטיינר. לא חשבתי על זה כשכתבתי את האיפיון. אולי גם יכולתי לשים לב לבעיה כשהסתכלתי על האפליקציה עובדת אבל בדף הבדיקה שלי לא היה מספיק טקסט בשביל לגרום לעומס. העומס נוצר רק בדף ארוך במיוחד שנפתח מתוך דפדפן מוטמע מתוך אפליקציה. בקריאת הקוד רואים מיד את הטעות ואפשר להחליט מה לעשות איתה. בלי קריאה של הקוד צריך להתמודד עם משתמשים שבסיטואציה מסוימת המערכת לא עובדת להם בלי להבין למה.
יכול להיות שבעוד כמה שנים המודלים יהיו כל כך טובים שלא נצטרך לקרוא את הקוד. כרגע אני לא רואה איך זה יקרה. קריאת הקוד היא עדיין הדרך הטובה ביותר שיש לי להבין את המערכת.
שאלה שחוזרת בהקשר הזה היא על ההבדל בין קריאת קוד של AI לקריאת קוד של קומפיילר. למה חשוב לקרוא את הקוד ש AI מייצר בעוד שאין צורך לקרוא את קוד המכונה שהקומפיילר מייצר? התשובה עונה לנו בדיוק על השאלה בכותרת. מתי נוכל להפסיק לקרוא קוד? כשכבר לא נצטרך. כשנוכל לקבל הבנה מלאה של המערכת רק מקריאת האיפיונים והתיעוד שכותב הסוכן. בינתיים אנחנו לא שם.1 420
📌 הקפיצה הבאה
בין 2015 ל 2018 לימדתי קורסים בפיתוח Web ו Mobile Web. למרות שאנגולר יצאה לשוק כבר ב 2010 וריאקט ב 2013, רק באזור 2015 הטכנולוגיות האלה הבשילו ושינו את שוק העבודה. באותן שנים פגשתי המון מפתחי PHP ו jQuery שידעו הרבה יותר ממני על דפדפנים ובמיוחד על דפדפנים ישנים ובכל זאת התקשו למצוא עבודה.
באותו הזמן חברות השקיעו מאמצים אדירים בגיוס מפתחי Front End. כולם שאלו את כולם מי מכיר ויכול להמליץ על מפתחים, אפילו בוגרי קורסים טריים מצאו עבודה יחסית בקלות. כולם חוץ מאנשי ה PHP וה jQuery. רק לרשום את המילים האלה בקורות החיים היה יכול לעלות לך בפסילה מראיונות.
מישהו היה יכול לחשוב שמדובר באיזו התנגדות של אותם אנשי PHP לטכנולוגיה החדשה אבל האמת יותר מורכבת. הבעיה של אותם מפתחים למצוא עבודה היתה שגם כשהם למדו את הכלים החדשים הם המשיכו להשתמש בהם באותה גישה של הכלים הישנים. זה לא התחביר השונה שהיה קשה אלא ההתרגלות לשיטת העבודה - לחיפוש חבילות מוכנות ב npm, למימוש קוד גם כשהוא לא נראה אותו דבר או אפילו עובד על כל הדפדפנים, להתמקדות בביצועים כי עכשיו זה אפשרי.
ועכשיו אנחנו על סף קפיצה נוספת וכל רעידת אדמה כזו מביאה איתה שינויים, פיטורים, קידומים ותיאוריות שמשתנות חדשות לבקרים.
למרות שזה נראה כאילו אנחנו רק רוצים לרוץ מהר ולדלוור עוד פיצ'רים, הסיפור של ה AI הוא יותר מורכב:
1. המפתחים של העתיד ישימו דגש על פיתוח תשתיות חזקות ויסודות חזקים שיוכלו לאפשר ל AI לבנות פיצ'רים ולשנות דברים מהר.
2. המפתחים של העתיד יהיו הרבה יותר טובים בלקרוא קוד ויהיו הרבה יותר רגישים כשהקוד לא תואם לתבניות ול Best Practices שהם הכתיבו.
3. המפתחים של העתיד ינהלו מערכות שבונות מערכות ויצטרכו לנטר את השינויים בקוד ואירועים בלוגים בזמן אמת.
4. המפתחים של העתיד יצטרכו לסנכרן ולבצע אופטימיזציה לצוותים של סוכנים תוך שיפור איכות הקוד, מהירות הפיתוח וצמצום עלויות. כן זאת הנדסה.
שלא נטעה - בניגוד לרושם שאולי היה באותה תקופה ולסטראוטיפים עקב פרשיות כמו leftpad, אנשי הפרונטאנד לא "התקינו כל חבילה מ npm בלי לחשוב", ידעו לכתוב קוד גם בעצמם ולמעשה השקיעו המון מאמץ בכתיבת קוד, פשוט קוד אחר מזה שכתבו אנשי ה PHP שבאו לפניהם. גם היום בעבודה עם AI בשביל להתקדם עלינו להבין איך להשתמש במיומנות ובידע שלנו ולשלב בינו לבין הכלים החדשים.
בסוף הקפיצה מערכות תוכנה וצוותי פיתוח יראו אחרת ממה שהם נראים היום. המעבר ל FrontEnd הוציא אותנו מהמונוליט, פתח את הדלת לפיתוח Micro Services ובמידה רבה לתשתיות ענן. אף אחד לא יודע איך יראה הפיתוח בעתיד ואיזה מיומנויות נצטרך, מה שבטוח הוא שהדרך היא לא להבין איך להשתמש בכלים החדשים כדי לעשות את אותו דבר שעשית עד עכשיו. הדרך קדימה היא להשתמש בכלים החדשים ובאמצעותם לגלות רעיונות חדשים שאי אפשר היה לממש קודם.
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
