ToCode
1.43K subscribers
4.04K links
טיפים קצרים למתכנתים מאת ינון פרק
Download Telegram
📌 יום 10 - חיפוש בעזרת Vector DB

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

✏ מחיפוש טקסט מלא לחיפוש וקטורי

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

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

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

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

✏ מה אנחנו בונים

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

המערכת עובדת באופן הבא:

1. תחילה עוברים על כל התנ"ך ומקודדים כל קבוצה של 6 פסוקים לוקטור. בין הקבוצות אני שומר חפיפה של שני פסוקים כדי שמידע סמנטי לא ילך לאיבוד במעבר בין קבוצות. את הוקטורים שומרים בבסיס נתונים Postgres עם הרחבה בשם pgvector שמאפשרת שמירת וקטורים והשוואה ביניהם. זה תהליך חד פעמי שקורה ביצירת המערכת.
2. כשהמערכת באוויר לוקחים שאלה מהמשתמשים, מקודדים גם אותה לוקטור ומחפשים 5 וקטורים קרובים לה. מושכים מבסיס הנתונים את הטקסטים שמתאימים לוקטורים אלה ושולחים הכל לג'מיני.

קוד הדוגמה המלא זמין בתיקיית הדוגמאות בקישור:

https://github.com/ynonp/pydanticai-demos/tree/main/10-bible-vectordb

המודל עצמו נקרא BAAI/bge-m3 ואפשר להתקין אותו כספריית פייתון מתוך Hugging Face. זה מודל סמנטי בעברית ולכן יתאים לטקסט שלנו. אחרי שיש לי את המודל מותקן קידוד של טקסט לוקטור הוא בסך הכל קריאת פונקציה בפייתון:

def embed_texts(texts: list[str]) -> list[list[float]]:
"""Embed a batch of texts, returning one 768-dim vector per text."""
embeddings = get_model().encode(texts, show_progress_bar=True)
return [vec.tolist() for vec in embeddings]


✏ קידוד התנך לוקטורים

הקידוד לוקטורים ממומש בקובץ index.py.

הפונקציה המעניינת בקובץ נקראת build_chunks וזה הקוד שלה:

def build_chunks(
verses: list[tuple[str, int, int, str]],
chunk_size: int,
overlap: int,
) -> list[dict]:
"""Group verses per (book, chapter) and split into overlapping windows."""
if chunk_size <= overlap:
raise ValueError(f"CHUNK_SIZE ({chunk_size}) must be greater than OVERLAP ({overlap})")
step = chunk_size - overlap

# Preserve file order while grouping by (book, chapter).
chapters: dict[tuple[str, int], list[tuple[int, str]]] = {}
order: list[tuple[str, int]] = []
for book, chapter, verse, text in verses:
key = (book, chapter)
        if key not in chapters:
chapters[key] = []
order.append(key)
chapters[key].append((verse, text))

chunks: list[dict] = []
for book, chapter in order:
entries = chapters[(book, chapter)]
for start in range(0, len(entries), step):
window = entries[start : start + chunk_size]
if not window:
continue
first_verse = window[0][0]
last_verse = window[-1][0]
verses_range = str(first_verse) if first_verse == last_verse else f"{first_verse}-{last_verse}"
chunk_text = " ".join(text for _, text in window)
chunks.append(
{
"book": book,
"chapter": chapter,
"verses_range": verses_range,
"chunk_text": chunk_text,
}
)
# Stop once the window has consumed the tail of the chapter.
if start + chunk_size >= len(entries):
break
return chunks


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

בהמשך אני מריץ את הפקודה:

embeddings = embed_texts([c["chunk_text"] for c in chunks])


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

    rows = [
(
chunk["book"],
chunk["chapter"],
chunk["verses_range"],
chunk["chunk_text"],
embedding,
)
for chunk, embedding in zip(chunks, embeddings)
]
with conn.cursor() as cur:
cur.execute(f"TRUNCATE {TABLE} RESTART IDENTITY")
cur.executemany(
f"INSERT INTO {TABLE} (book, chapter, verses_range, chunk_text, embedding) "
"VALUES (%s, %s, %s, %s, %s)",
rows,
)
conn.commit()


✏ חיפוש וקטורי

החיפוש מבוצע בקובץ app.py בפונקציה inject_rag_context. זה הקוד הרלוונטי:

embedding = embed_query(query)
with ctx.deps.conn.cursor() as cur:
cur.execute(
f"""
SELECT book, chapter, verses_range, chunk_text
FROM {TABLE}
ORDER BY embedding <=> %s
LIMIT %s
""",
(embedding, ctx.deps.top_k),
)
hits = cur.fetchall()


את התוצאה שולחים לסוכן וזה כל הסיפור.

✏ עכשיו אתם

1. הריצו את המערכת אצלכם. שאלו שאלות ובדקו האם הסוכן מצא את הקטעים הרלוונטים וענה תשובות מדויקות.
2. מצאו שאלות שהסוכן מצליח לפתור ושאלות אחרות שהוא לא מצליח. על כל שאלה שהוא לא הצליח נסו לחשוב איפה היתה הבעיה.
3. בדקו מודלים אחרים של Embedding. האם יש הבדל בתוצאות? נסו למצוא מודל טוב יותר מזה שאני מצאתי.
4. שלבו חיפוש טקסט מלא במערכת כך שהסוכן יקבל גם את הקטעים שקרובים סמנטית וגם 5 קטעים שמכילים הכי הרבה מילים משותפות עם השאלה. שימו לב איך זה משנה את התוצאות.
👍1🔥1
📌 יום 11 - סידור סרטים בקורס

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

✏ מה אנחנו בונים

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

1. חותכים את ההקלטה לקטעים של 30 דקות כדי שיהיה ל AI קל להתמודד איתם.
2. אנחנו לא יודעים עדיין איפה הכי הגיוני לסמן "שיעור" לכן נדאג לחפיפה של 5 דקות בין הקטעים הקצרים וכך שיעור לא ייפול בדיוק באמצע ביניהם.
3. נעלה כל קטע ל Gemini דרך Google File API.
4. נבקש מהסוכן שיעבור על כל קטע וייתן לי את ה Timestamps בהם הכי הגיוני לחתוך את הקטע לשיעורים.
5. נמזג ונחתוך את ההקלטה לפי הזמנים שקיבלנו מהסוכן ונצרף את הסיכומים.

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

https://github.com/ynonp/pydanticai-demos/blob/main/11-course-builder/build-course.py

נעבור יחד על החלקים המעניינים.

✏ מבנה הפלט

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

class Lesson(BaseModel):
index: int = Field(description="1-based lesson number within this chunk")


קלאס השיעור המלא כולל כל ההסברים הוא הקלאס הראשון בקובץ:

class Lesson(BaseModel):
"""A single lesson extracted from the course video."""

index: int = Field(description="1-based lesson number within this chunk")
start_timestamp: str = Field(
description="Start time relative to THIS CHUNK as HH:MM:SS, e.g. '00:02:30'"
)
end_timestamp: str = Field(
description="End time relative to THIS CHUNK as HH:MM:SS, e.g. '00:14:45'"
)
is_break: bool = Field(
default=False,
description=(
"True if this segment is a BREAK with no teaching content — e.g. "
"students chatting, silence, instructor away, coffee/lunch break. "
"False for a normal lesson."
),
)
slug: str = Field(
description=(
"URL-safe slug for the lesson, e.g. 'intro-to-pydantic'. "
"If is_break is True, this is ignored (the slug 'breaktime' is "
"applied automatically) — still provide a placeholder value."
)
)
title: str = Field(description="Lesson title in the original language")
summary_markdown_hebrew: str = Field(
description=(
"A full, self-contained lesson write-up in Hebrew markdown, written so "
"that someone who never watched the video can read it and actually "
"learn the material — NOT a table of contents or a bullet-point index "
"of what was covered. Write it like an article/tutorial: "
"Structure it as numbered sections ('### 1. <topic title>', "
"'### 2. <topic title>', ...), one per sub-topic covered in the "
"lesson, in the order they were taught. Under each heading, write "
"full explanatory paragraphs in flowing Hebrew prose that actually "
"teach the concept (what it is, why it matters, how it works) the "
"way the instructor explained it — not short summaries or fragments. "
"Include exact code shown in the video in fenced code blocks, "
"including commands, filenames, and terminal output where relevant, "
"each with a sentence or two explaining what the code does and why. "
"Include any links or external references mentioned, and end with "
"practical conclusions/recommendations if the instructor gave any. "
            "Be thorough, detailed, and written in the same didactic style as a "
"professional programming course text (not a summary/recap). "
"If is_break is True, skip all of this and just write a brief note "
"that this was a break (e.g. 'הפסקה — אין תוכן לימודי בקטע זה.')."
)
)


class ChunkOutline(BaseModel):
"""Lessons found within a single video chunk."""

lessons: list[Lesson] = Field(description="Ordered list of lessons in this chunk")


✏ העלאת הקבצים לגוגל

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

def upload_chunk(chunk_path: str, chunk_index: int) -> UploadedFile:
"""Upload a single chunk to Google File API."""
print(f" 📤 Uploading chunk {chunk_index} ({Path(chunk_path).name})...")
client = genai.Client()
t0 = time.time()

uploaded = client.files.upload(
file=chunk_path,
config={"display_name": Path(chunk_path).name},
)

elapsed = time.time() - t0
print(f" ↑ {elapsed:.0f}s, state={uploaded.state.name}")

while uploaded.state.name != "ACTIVE":
time.sleep(5)
uploaded = client.files.get(name=uploaded.name)

print(f" ✅ ACTIVE")
return UploadedFile(
file_id=uploaded.uri,
provider_name="google",
media_type=uploaded.mime_type or "video/mp4",
)


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

✏ פיענוח השיעורים

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

def build_agent() -> Agent:
"""Create the course-builder agent with Google Gemini."""
model = GoogleModel("gemini-3-flash-preview")
return Agent(
model,
output_type=ChunkOutline,
system_prompt=(
"You are an expert course builder and video content analyst. "
"You receive a SEGMENT (chunk) of a longer course recording and must "
"identify complete, self-contained lessons of 10-15 minutes each "
"within this segment.\n\n"
"CRITICAL RULES:\n"
"- Timestamps MUST be relative to THIS CHUNK (00:00:00 = chunk start)\n"
"- Only include lessons that are FULLY or MOSTLY contained in this chunk\n"
"- If a lesson is cut off at the start or end, do NOT include it — "
"the overlap with adjacent chunks will capture it\n"
"- Each lesson should be 10-15 minutes of coherent content\n"
"- Identify natural topic boundaries\n"
"- Create descriptive, URL-safe English slugs (lowercase, hyphens)\n"
"- Write comprehensive Hebrew markdown summaries including:\n"
" * Sub-topics covered\n"
" * ALL code snippets shown (in ``` code blocks)\n"
" * Links or references mentioned\n"
" * Key takeaways\n\n"
"BREAK-TIME DETECTION:\n"
"- Also detect BREAKS: segments with no teaching content, such as "
"coffee/lunch breaks, silence, students chatting among themselves, "
"the instructor stepping away, or any other non-lesson downtime.\n"
"- Treat a break exactly like a lesson entry — give it a start/end "
"timestamp — but set is_break=True and give it a short title such "
"as 'הפסקה'. The slug field is ignored for breaks, just put any "
"placeholder.\n"
"- For a break's summary_markdown_hebrew, just write a brief note "
"that this was a break (e.g. 'הפסקה — אין תוכן לימודי בקטע זה.'), "
"no need for a full lesson write-up.\n"
            "- Only mark genuine downtime as a break — do not use it for slow "
"or informal but still on-topic teaching."
),
)


def analyze_chunk(
agent: Agent, chunk: dict, chunk_index: int, total_chunks: int
) -> list[Lesson]:
"""
Analyze one chunk → list of lessons with timestamps converted to
original video time.
"""
offset = chunk["offset_seconds"]
video_file = upload_chunk(chunk["path"], chunk_index)

print(f" 🤖 Analyzing chunk {chunk_index}/{total_chunks - 1} "
f"(offset={seconds_to_ts(offset)})...")
t0 = time.time()

result = agent.run_sync(
[
(
f"This is chunk {chunk_index} of a longer course video. "
f"The chunk starts at {seconds_to_ts(offset)} in the original video. "
f"Identify all complete 10-15 minute lessons within this chunk. "
f"Timestamps must be relative to THIS CHUNK (00:00:00 = chunk start). "
f"Do NOT include lessons that are cut off at chunk boundaries — "
f"adjacent overlapping chunks will capture them."
),
video_file,
]
)

elapsed = time.time() - t0
chunk_lessons = result.output.lessons
print(f" ✅ {len(chunk_lessons)} lessons found in {elapsed:.0f}s")

# Convert chunk-relative timestamps to original video timestamps
converted = []
for lesson in chunk_lessons:
rel_start = ts_to_seconds(lesson.start_timestamp)
rel_end = ts_to_seconds(lesson.end_timestamp)
abs_start = rel_start + offset
abs_end = rel_end + offset
converted.append(Lesson(
index=0, # will be re-indexed later
start_timestamp=seconds_to_ts(abs_start),
end_timestamp=seconds_to_ts(abs_end),
is_break=lesson.is_break,
# Force the canonical slug for breaks in code, rather than
# trusting the model to use the right literal string.
slug="breaktime" if lesson.is_break else lesson.slug,
title=lesson.title,
summary_markdown_hebrew=lesson.summary_markdown_hebrew,
))

return converted


המשך הקוד לוקח את כל המידע שהסוכן החזיר ובעזרתו שובר את ההקלטה הארוכה לקטעים קצרים ומוסיף את קבצי הסיכום. סך הכל התוכנית לא מאוד ארוכה (פחות מ 500 שורות) וחושפת את היופי בפיתוח סקריפטים היום: יש לנו יכולת חדשה שקודם לא היתה, היכולת להיעזר במודלי שפה כדי לפענח מידע. מתוך 500 שורות הרוב המוחלט הוא קוד פייתון שאפשר היה לכתוב גם לפני שלוש שנים והיה עובד אותו דבר - קריאת API אחת היא זו שמחזיקה את כל הקסם.
👍1
📌 יום 12 - לקחים מפיתוח סוכנים שכדאי לקחת לפרודקשן

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

1. תעדו את כל השיחות - עובד על המכונה שלי זה לא מספיק. לא רק כי דברים עובדים אחרת בפרודקשן אלא שספציפית סוכנים באמת מחזירים תשובות שונות כל פעם שנפנה אליהם. לקוח יכול להתלונן על בעיה ולא יהיה לכם מושג איפה להתחיל לחפש אותה. יש אינסוף פלטפורמות ששומרות היסטוריית שיחות של סוכנים וגם אין בעיה לשמור את כל ההודעות בבסיס הנתונים אצלכם באפליקציה. מה שחשוב שמרו גישה לכל מה שקרה, עוד תצטרכו אותה.
2. שימו לב לטוקנים - כשאתם בודקים את הסוכן אתם יודעים בדיוק מה לכתוב כי אתם כתבתם את הפרומפט. המשתמשים שלכם הולכים להשתמש בטקסטים הרבה יותר ארוכים ולשאול שאלות לא רלוונטיות. שימו לב להכניס מנגנונים שבודקים כמה טוקנים שיחה מסוימת עולה לכם ואולי לעצור שיחות שלא הולכות לכיוון הנכון. טריק נפוץ כאן הוא מנגנון של Guardrails שם סוכן נוסף קורא את השיחה בשביל להבין אם היא פרודוקטיבית או שעדיף לחתוך אותה.
3. חשבו על העומס - שיחה עם סוכן תופסת Thread וחיבור רשת כי הגולשים מחכים לתשובה שמגיעה ב Streaming. זו תבנית עבודה יחסית חדשה ולכן עלינו לבדוק שכל השרשרת עובדת כמו שצריך איתה. שימו סביבת Staging תגייסו חברים ותעמיסו הודעות על השרת לפני שמגיעים למשתמשים אמיתיים כדי לראות מה נשבר. לכל חברות הענן יש היום פתרונות Deployment לסוכנים שווה לבדוק אותם לפני שאתם מקימים סביבה משלכם.
4. בנו מנגנוני Fallback - כל סוכן עם טמפרטורה מעל 0 הולך להחזיר תשובות שונות בכל הפעלה. הוא יכול לא להחזיר תשובה בלבד, להחזיר תשובה חלקית או להחזיר ממש שטויות. נסו להגדיר fallback למודל אחר אם משהו נכשל ותמיד וודאו מה קורה כשהמודל יענה תשובות אחרות ממה שציפיתם.
5. הוציאו כמה שיותר עבודה מחוץ למודל - קוד הוא דטרמניסטי, מודל לא. אם אני צריך לתרגם טקסט אין לי ברירה אני צריך מודל שפה, אבל בשביל להוריד רווחים מסוף או תחילת שורה אני לא צריך אותו. כמה שתסדרו יותר טוב את המידע לפני שתגיעו למודל תקבלו תוצאות טובות ומדויקות יותר.
6. השקיעו בבחירת מודלים ופרומפטים (ושימו לב שזה הולך יחד) - המודל הכי יקר הוא לא תמיד הכי טוב ולא רק בגלל מהירות. כשאתם משנים מודל סיכוי טוב שתצטרכו לדייק מחדש את הפרומפט. הגדירו סט של קלטי בדיקה והפעילו אותם מול קבוצה של פרומפטים ומודלים שהכנתם מראש. כשיגיע מודל חדש תוכלו להריץ אותו על אותו סט של קלטי בדיקה. בצורה כזאת תוכלו להבין איזה מודל באמת נותן את התוצאה המדויקת ביותר ל Use Case שלכם.
7. שימו לב להרשאות - סוכן חכם לא יודע לקבל החלטות בצורה אמינה ולכן תמיד כדאי להניח שהפלט שנקבל ממנו הוא זדוני. איזה כלים הסוכן יכול להפעיל? איזה בקרה יעבור המידע מהסוכן לפני שייכנס למערכת? ולא, להוסיף עוד סוכן לא יעזור. יש לנקות את הפלט של הסוכנים בכל מעבר מסוכן לקוד רגיל.
8. לא חייבים להעביר למודל את כל השיחה ולא חייבים לכתוב רק סוכן אחד - כשאתם כותבים סוכן שיחה שימו לב שאתם לא צריכים להעביר את כל היסטוריית השיחה ולא חייבים להמשיך שיחה שלמה עם אותו סוכן. תבנית שעבדה לי טוב היתה להריץ סוכן "מיון" בתחילת השיחה וכשהשיחה מקבלת כיוון להעביר את השליטה לסוכן ספציפי יותר, וגם לתת לכל סוכן אפשרות לזרוק את השיחה חזרה לסוכן המיון (העברת שיחה לסוכן היא בסך הכל הפעלת כלי).
9. נסיונות חוזרים - מותר לשלוח את אותה שאלה לסוכן כמה פעמים אבל רוב הזמן זה לא אפקטיבי. אם יש משהו ששבר את הסוכן פעם ראשונה הרבה פעמים הוא ישבור את הסוכן גם פעם שניה ובכל מקרה אתם לא רוצים להיות על 30% כשלונות. השתמשו בכשלונות כדי ללמוד על הסוכנים שכתבתם, על ניקוי הקלט והפרומפטים ולהתאים אותם כדי לקבל תשובה נכונה על אותו קלט שנכשל.
10. צאו לדרך, גם אם זה לא מושלם - סוכנים חכמים הם עדיין טריק חדש בספר וכל סוכן שתכתבו ילמד אתכם המון. כל יום שתחכו רק תצא עוד דרך חדשה לכתוב סוכנים ושוב תחזרו להתחלה. קחו את Pydantic AI Agents או כל ספריה אחרת שאהבתם ובנו את הסוכן הראשון שלכם.

יש לכם עוד? ספרו לי בתגובות בטלגרם.
👍1
📌 מצאתי 8 בעיות

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

I found eight concrete cases where the uncommitted views cause redundant or oversized queries.


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

המתח היותר משמעותי הוא "האם יש דרך שהתהליך כולו יהיה אוטומטי?" ו"האם הבעיות האלה בכלל חשובות?".

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

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

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

השנה 2026. ואנחנו עדיין מתלבטים מה זה קוד טוב ואם בכלל צריך כזה.
👍1
בוקר טוב כולם שימו לב העליתי תוכנית וובינרים חדשה החל משבוע הבא
https://www.tocode.co.il/talking_ai

תקבלו גם מייל מסודר אם אתם ברשימת הדיוור
👍3
📌 מה מעניין בסוכני קידוד?

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

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

Work with Claude directly in your codebase. Build, debug, and ship from your terminal, IDE, Slack, web, and more.


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

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

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

ומה קורה ב OpenAI? הנה כמה פיצ'רים עדכניים שלהם:

1. מצב "מטרה" שנועד לטפל במשימות ארוכות.
2. הפעלת סוכנים במקביל והפרדה ביניהם, כולל אינטגרציה עם git worktrees ושליטה במקביל על כל הסוכנים מתוך האפליקציה.
3. שימוש במחשב וחיבור סוכן הקידוד לתוכנות אחרות שמותקנות לי על המכונה.
4. חיבור מרחוק דרך הטלפון.
5. מערכת בניית אתרים שיודעת גם לפרסם את העבודה שלכם לרשת.
6. הצגת Diff של עבודת הסוכן ומעבר על הקוד מתוך קודקס.

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

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

https://fx.sh/

הוא מגיע אחרי פאי שמתגאה בליבה מינימליסטית ותשתית הרחבות מפותחת:

https://pi.dev/

ועוד סוכנים וותיקים יותר כמו opencode, goose ו cline.

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

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

כלי בשם agent deck מספק יכולות דומות אבל מתוך סביבת Terminal.

וייב קנבן שלוקח את לוח ה Kanban בתור המבנה המרכזי ובונה סוכנים סביבו.

ו kepler מהיוצרים של Git Kraken.

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

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

https://www.tocode.co.il/talking_ai
👍2❤1
📌 חלוקת פיצ'רים או חלוקת מומחיות

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

אף אחד לא אהב את זה.

הרבה לפני ה AI בנינו פריימוורקים לצמצום הפער. בנינו תשתיות של Infra As Code וחיבורים לענן כדי שמפתחים יוכלו להקים לעצמם את התשתית ונפרדנו מתפקידי Sysadmin ו DB Admin לטובת Devops. בנינו פריימוורקים לפיתוח צד שרת וצד לקוח יחד והתחלנו לגייס מפתחי Full Stack. פריימוורק כמו Next.js עם האינטגרציה שלו ל Vercel מיועד לתת פתרון פיתוח מקצה לקצה בו מפתח אחד שמכיר את הפריימוורק יוכל לבנות פיצ'ר מלא או מוצר מלא לבד.

ואז הגיע ה AI והאיץ את התהליך.

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

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

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

1. צוות Backend אחראי על קריאת קוד ה Backend של המערכת, הטבלאות בבסיס הנתונים, השאילתות. כשהם מזהים בקוד בעיות או כפילויות אחריותם לבצע Refactor לחלקים הבעייתיים.
2. צוות בדיקות אחראי על שיפור בדיקות המערכת. הם מקבלים זמן ספציפי לקריאה ושיפור תשתית הבדיקות כדי לוודא שכולם משתמשים בתשתית טובה יותר ובודקים את הדברים הנכונים.
3. צוות FrontEnd אחראי על חווית המשתמש וקוד ה Front, שיפור הארכיטקטורה, יצירת אחידות בקומפוננטות ושיפורי ביצועים שקשורים לממשק.

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

אלה שני מושגים שאנחנו הרבה פעמים מבלבלים.

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

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

ההבדל הוא נקודת המבט - בשיפור ביצועים אנחנו נוטים לחפש מקסימום מקומי אבל השיפור הבאמת משמעותי יגיע מחיפוש מקסימום מוחלט.
👍1
📌 לא מיועד לקריאה על ידי בני אדם

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

1. בפרויקט יש תיקיית _components ולידה תיקייה בשם components. כן הבנתי שאתם משתמשים ב shadcn אבל האילוצים הטכניים הם בחירה שלכם.
2. הפרויקט מכיל 3 תיקיות עזר נסתרות: .vercel, .next ו .eve.
3. הפרויקט כולל 3 מנגנוני אימות משתמשים: אחד דרך vercel, שני חיבור מקומי בפיתוח ושלישי הוא Placeholder שעלינו להחליף בפלאגין אותנטיקציה אמיתי אחרי שנבחר אחד.
4. גישה ל Sessions מבוצעת דרך נתיב בשם s. כן רק האות s ואחריה מזהה ה Session.

הנה DHH מתוך הדוקטרינה של ריילס בשביל השוואה:

We write code not just to be understood by the computer or other programmers, but to bask in the warm glow of beauty. Aesthetically pleasing code is a value unto itself and should be pursued with vigor. That doesn’t mean that beautiful code always trumps other concerns, but it should have a full seat at the table of priorities.


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

class Project < ApplicationRecord
belongs_to :account
has_many :participants, class_name: 'Person'
validates_presence_of :name
end


מעט שורות, הרבה משמעות ונקרא כשפה טבעית.

בפרויקט eve החדש זה התוכן של page.tsx:

import { AgentChat } from "@/app/_components/agent-chat";

export default function Page() {
return <AgentChat />;
}


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

כשקוראים את הקוד של eve אי אפשר שלא להשוות עם ספריית הסוכנים Pydantic AI שסקרתי כאן ממש לא מזמן. אותו סוכן שהגיע ב eve לאלפי שורות בנוי ב Pydantic עם הקוד הזה:

from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2', instructions='You are a helpful assistant.')
app = agent.to_web()


הקוד של eve מכוער לא בגלל שהמפתחים ב Vercel לא מספיק מיומנים או מגבלה טכנית. להערכתי הקוד מכוער כי לאף אחד לא אכפת מזה. ממילא, הם חושבים, היחידים שיקראו את הקוד הם סוכני קידוד. אני לא בטוח שהם טועים אבל מבחינת רושם ראשוני פידנטיק AI בעיניי מנצחת בגדול.
👍2
📌 מה בעצם הבעיה שם?

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

"מה בעצם הבעיה?"

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

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

אבל זאת לא ההשוואה הנכונה.

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

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