ToCode
رفتن به کانال در Telegram
1 419
مشترکین
اطلاعاتی وجود ندارد24 ساعت
اطلاعاتی وجود ندارد7 روز
-630 روز
آرشیو پست ها
1 419
else:
# Try skipping SRT words
found = False
for sahead in range(1, MAX_SKIPS + 1):
if sp + sahead < len(srt_flat):
if words_match(srt_flat[sp + sahead][0], ts_norms[tp]):
sp += sahead
found = True
break
if not found:
sp += 1
tp += 1
# Compute timestamps per entry
results = []
for entry in srt_entries:
ts_indices = entry_ts_map.get(entry["index"], [])
if ts_indices:
new_start = ts_words[ts_indices[0]]["ms"]
last_idx = ts_indices[-1]
if last_idx + 1 < len(ts_words):
new_end = ts_words[last_idx + 1]["ms"]
else:
new_end = ts_words[last_idx]["ms"] + 500
else:
new_start = None
new_end = None
results.append((entry["index"], new_start, new_end))
# Interpolate gaps
for i, (idx, start, end) in enumerate(results):
if start is None:
prev_match = next(
(results[j] for j in range(i - 1, -1, -1) if results[j][1] is not None),
None,
)
next_match = next(
(results[j] for j in range(i + 1, len(results)) if results[j][1] is not None),
None,
)
if prev_match and next_match:
old_prev = srt_entries[prev_match[0] - 1]["old_start_ms"]
old_next = srt_entries[next_match[0] - 1]["old_start_ms"]
old_curr = srt_entries[idx - 1]["old_start_ms"]
if old_next != old_prev:
frac = (old_curr - old_prev) / (old_next - old_prev)
else:
frac = 0.5
interp_start = int(prev_match[1] + frac * (next_match[1] - prev_match[1]))
interp_end = int(prev_match[2] + frac * (next_match[2] - prev_match[2]))
results[i] = (idx, interp_start, interp_end)
elif prev_match:
results[i] = (idx, prev_match[1], prev_match[2])
elif next_match:
results[i] = (idx, next_match[1], next_match[2])
return results
עכשיו אני מודה, לא קראתי את הקוד ואני לא מתכנן לקרוא אותו. לא בטוח שאוכל גם אם ארצה. זה לא קוד טוב. וזה בסדר, כי הקוד הלא טוב הזה מתאים לדרישות בשביל סקריפט חד פעמי.
הרבה פעמים בעולם האמיתי אנחנו כן רוצים קוד טוב כי קוד טוב ניתן להרחבה ואפשר להמשיך לתחזק אותו, זה אומר שנצטרך להיות יותר ספציפיים בהוראות ל AI - איזה קלאסים ליצור, איך לסדר את הקוד בקבצים, באיזה מקרי קצה רוצים לטפל, במה לא רוצים לטפל ומעדיפים לרסק את הסקריפט, מה השמות והאבסטרקציות שיגרמו לקוד להיות קריא.
בעולם האמיתי בעבודה עם AI הדיכוטומיה בין "עומק פיתוח וחשיבה הנדסית" לבין "קידוד עם AI" היא פשוט לא נכונה. ה AI פשוט לקח את רמת המימוש ומסוגל לבצע אותה מהר יותר בצורה אוטומטית. וזה כיף כי עכשיו אני יכול להתמקד באותה חשיבה הנדסית שממילא היתה הדבר החשוב.1 419
📌 שתי הרמות של הקוד
חברה שואלת - האם לדעתך עומק פיתוח וחשיבה הנדסית עדיין יהיו הבסיס המרכזי גם בשנים הקרובות?
אנשים אוהבים לדבר על AI כאילו הוא מבטל את החשיבה של המפתחים והופך אותנו לרובוטים שרק מאשרים קוד שאנחנו אפילו לא מבינים. אין ספק שיש אנשים שבוחרים בגישה זו אבל היא לא פרודוקטיבית ולאורך זמן לא תחזיק מעמד.
מה שאנחנו רואים היום הוא שינוי טקטוני בתפקיד המפתחים. בעבר מפתחים ביצעו שני תפקידים בו זמנית: גם תכננו איך מנגנון מסוים ישתלב בקוד המערכת וגם כתבו את הקוד הספציפי עבור אותו מנגנון. יש חברות שניסו להפריד את התפקיד כך שארכיטקטים יסבירו איך המנגנונים משתלבים במערכת ומפתחים רק יכתבו את הקוד, אבל במציאות זה לא עבד. מפתחים היו צריכים לקבל אינסוף החלטות טכניות על המימושים והרבה פעמים תוך כדי מימוש גילו שהארכיטקטורה לא תואמת. בסופו של דבר ארכיטקטים נדחפו למעלה להחליט איך תתי מערכות שונות יתחברו אבל התרחקו מהקוד. המפתחים היו אלה שהחליטו איפה עושים Code Reuse ואיפה משכפלים מנגנון, איפה יוצרים קלאס חדש ואיפה מוסיפים רק פונקציה, איפה מגדירים Builder נפרד במקום להעביר עוד פרמטרים לבנאי, מה לשמור ב Cache ולכמה זמן. כשמפתחים לא הבינו מה הם מחליטים מערכות התדרדרו.
היום תפקיד המימוש ברובו מבוצע על ידי AI, כך שמפתחים יכולים לתכנן ו AI מיישם מהר יותר מאיתנו. דוגמה? בטח. היה לי קובץ SRT עם כתוביות וזמנים לא מדויקים וקובץ נפרד של timestamps של מילים בו המילים עצמן לא היו מדויקות אבל ה timestamps כן, כלומר חלק מהמילים הופיעו עם שגיאות כתיב, חלק מהמילים לא הופיעו, חלק הופיעה מילה אחרת, אבל בגדול הזמנים שליד כל מילה היו מדויקים. עכשיו צריך לשלב בין השניים, לקחת את הטקסט מה SRT ולהתאים לכל תחילת משפט את הזמן האמיתי של תחילת המשפט לפי קובץ הזמנים. כתבתי ל AI:
write a script to fix the SRT timestamps based on transcript.txt assembly ai times. Walk both files in parallel ignoring small differences and fix timestamps in the SRT based on the
transcript.txt word timestamps
הפרומפט הזה הוא ירייה באפילה. אין אפיון, אין הסבר איך לעשות את זה, פשוט צא לדרך ונסה למזג. ה AI הסתבך עם עצמו ובסוף בוחר לנסות להתאים את הטקסטים מילה-מילה. זה כמובן לא הגיוני כי השגיאות בקובץ המילים הן רנדומליות לגמרי. הדרך הנכונה לרוץ על הקבצים היא באמצעות sliding window שמניח שפעם בכמה מילים יש מילה לא מתאימה אבל זה בסדר ורק אם יש יותר מדי מילים לא מתאימות אז מבינים שלגמרי יצאנו מסינכרון ואז צריך לנסות למצוא עוגן אחר.
בנקודה הזאת עצרתי את ה AI והוספתי הודעת הכוונה:
Better -
start with the SRT, first line in the SRT includes N words
take these N words from the list of word-timestamps transcript.txt and use the first timestamp
Then continue iterating SRT lines
when a word doesn't match you can skip it, but if multiple words don't match (maybe 3) you should backtrack to check if a word was just skipped (missing)
if you can't match or backtrack raise an exception as there's a problem with either the script or the data
עכשיו ממשיכים ושואלים - האם הקוד עצמו חשוב? האם אני רוצה להשתמש במנגנון הזה במקומות נוספים או שזה סקריפט חד פעמי? איך אני רוצה לארגן אותו? אלה לא שאלות ש AI יכול לענות עליהן. הן קשורות למבנה המערכת הספציפי שלי.
מה ש AI כן יכול לעשות זה לכתוב את אותה לולאת פייתון לפי ההוראות שכתבתי, בסיפור שלנו הפונקציה נראית כך:
def align_and_fix(
srt_entries: list[dict], ts_words: list[dict]
) -> list[dict]:
"""
Align SRT entries with transcript word timestamps.
Returns the entries list with new_start_ms / new_end_ms set.
"""
# Flatten SRT words with entry tracking
srt_flat = [] # (norm_word, entry_index)
for entry in srt_entries:
for w in entry["norm_words"]:
srt_flat.append((w, entry["index"]))
ts_norms = [w["norm"] for w in ts_words]
sp = 0
tp = 0
entry_ts_map: dict[int, list[int]] = {}
while sp < len(srt_flat) and tp < len(ts_norms):
srt_word, ei = srt_flat[sp]
if words_match(srt_word, ts_norms[tp]):
entry_ts_map.setdefault(ei, []).append(tp)
sp += 1
tp += 1
else:
offset = find_sync(
[w[0] for w in srt_flat], ts_norms, sp, tp
)
if offset is not None:
tp += offset1 419
📌 אנטרופיק לא בונים מודל
ביקשתי מקלוד לכתוב Managed Agent שמתרגם מאנגלית לספרדית ותוך דקה קיבלתי ממשק שעובד לתרגום דרך AI. ביקשתי מ GPT את אותו דבר והוא הסביר לי מה צריך לעשות בשביל לבנות כזה סוכן.
ההבדל בין השניים הוא לא היכולות של המודל. בהנתן אותו System Prompt ואותם כלים גם אופוס וגם GPT יכולים לבנות את המערכת. אבל כמובן שקלוד נתן חוויה כוללת טובה בהרבה.
אנטרופיק לא בונים מודל. כלומר בטח הם מאמנים את קלוד אבל הראייה שלהם רחבה בהרבה. העתיד שלהם הוא פלטפורמה עליה ירוצו מערכות התוכנה של העתיד. אנטרופיק אולי מפרסמים שהם חברת מחקר ובטיחות AI אבל בפועל הם רוצים להיות ה AWS הבאה. והם לא היחידים:
1. לגוגל כבר יש ענן ומגנון להריץ עליו סוכנים. פעם קראו לזה Vertex AI אבל היום הם כבר Gemini Enterprise Agent Platform.
2. ל AWS יש את Bedrock וגם את AgentCore.
3. למייקרוסופט יש את Foundry
חברות SaaS מתו? כל אחד יבנה לעצמו את התוכנה? הצחקתם אותם. התעשייה במירוץ למצוא מי יהיה ענן ה Saas הבא, ענן שמשלב את כל שירותי הענן הקלאסיים יחד עם פלטפורמת Gen AI. אנחנו רק בהתחלה. עכשיו הזמן הכי טוב ללמוד קוד.
1 419
📌 שלושה ימים של מחקר, עשר דקות פתרון
בימים שלפני ה AI הבאגים הכי מעניינים היו אלה שדרשו מחקר רב כדי להבין מה קורה שם, ואז תיקון של שורה או פונקציה כדי לפתור את הבעיה.
היום זה מאפיין של כמעט כל פיתוח. ו AI רק הופך את החלק הקשה ליותר קשה.
בסיפור היום הסתכלתי על מנגנון בלנגלטס שמייצר מילים דומות לתרגיל שמיעה, כלומר אנחנו לוקחים מילה מהקטע מוחקים אותה ורוצים לראות שהמשתמש מצליח לזהות משמיעה את המילה. בתרגיל מציגים למשתמש את המילה מהקטע ומילה אחרת קרובה לה וצריך לבחור את המילה שנשמעה.
במקור ניסיתי לגשת למודל שפה כדי לייצר את "המילה הדומה". בצ'אט היה נראה שהם ממש טובים בזה ומצליחים להפוך את house ל mouse. בעבודה האמיתית הם באמת היו טובים בזה חלק מהזמן, אבל בחלק אחר מהזמן נתקעו ב thinking אינסופי. לקח מעל שלוש שעות כדי לייצר מילים חלופיות לקטע של עשר דקות ואפילו זה לא תמיד הצליח.
כל כמה שניסיתי לבקש מ AI לשפר את הקוד הוא התמקד ב Prompt Engineering. הוא הציע המון שינויים בפרומפטים, מודלים אחרים, שימוש חוזר במילים שכבר מצאנו - אבל בשום שלב לא חשב לוותר על הפנייה למודל שפה ולעבור למנגנון אחר. בגלל שהיה כל כך קל לנסות את ההצעות של קלוד (הוא כל הזמן מימש עוד הצעה), וכל בדיקה לקחה כמה שעות יצא שנסחפתי אחריו בלופ.
רק אחרי שעצרתי לחשוב הבנתי שצריך כיוון אחר. מצאתי ספריה בשם symspell שיודעת למצוא מילים עם כתיב דומה למילה. הבעיה? הספריה ב rust והקוד שלי ברובי. אבל עכשיו זאת היתה ההזדמנות של קלוד להתחיל להיות מועיל. תוך עשר דקות הוא בנה ממשק שורת פקודה לספריית ה rust, מצא ברשת והוריד מילונים לערבית, אנגלית, ספרדית, צרפתית ורוסית והחליף את כל מנגנון חיפוש המילים החלופיות מ LLM לחיפוש דטרמניסטי עם symspell.
המתכנתים המהירים בעתיד לא יהיו אלה שיודעים לכתוב קוד יותר מהר. זה חסר משמעות. המפתחים הטובים ביותר יהיו אלה שיודעים לראות איך המערכת מתחברת ולכוון את הקוד למקום אליו הוא צריך להגיע. הרבה פרסומות על AI יספרו לכם שהמפתח לעבודה מהירה עם AI הוא לדעת "לכוון" את הסוכן לקוד שאתם רוצים לקבל. האמת הקשה יותר היא שהמפתח לעבודה מהירה הוא ההבנה איזה קוד אנחנו רוצים לקבל.
1 419
📌 תוכנות שמשנות את עצמן
העבודה עם פאי היתה מאירת עיניים לגבי העתיד ולגבי תוכנות שמסוגלות לשנות את עצמן. פאי הוא סוכן קידוד בקוד פתוח שגם מכיר את הקוד של עצמו וניתן להרחבה. בעבודה עם ריילס סוכני קידוד נוטים לכתוב סקריפטים במקום להשתמש ב REPL. יש כמה שרתי MCP שמחברים מודלים לריילס אבל לא ניסיתי אותם ובאופן כללי אני לא נלהב להוסיף שרתי MCP לסטאפ שלי.
וככה הגעתי לפאי עם בקשה פשוטה - כתוב חיבור שיאפשר לך לפתוח Rails Console ולעבוד ב REPL שלו בלי לתקוע את ממשק הסוכן כלומר בלי לחכות שהתהליך "יסתיים", כי REPL-ים לא מסתיימים. עשר דקות מאוחר יותר התוסף לפאי היה באוויר. זה הקוד שנכתב:
https://gist.github.com/ynonp/eba94cfebeecddbcae2cf1127d69f092
יש שם כמעט 500 שורות, טיפול בשגיאות, טיפול בסגירת ה console כשסוגרים את פאי, טיימאאוטים - במבט ראשון קוד הרבה יותר טוב ממה שאני הייתי כותב למטרה זו.
והקוד אפילו עובד, כלומר הוא מתנהג כאילו הפעלתי את ה rails c וכאילו הסוכן יכול לכתוב בתוך ה REPL.
הבעיה היחידה שזה לא מה שקורה שם.
קריאה בקוד עצמו מראה לנו שהסוכן בחר לכתוב סקריפט REPL משלו שמתקשר עם התוסף באמצעות JSON. הוא גם מקצץ הדפסות כשהפלט ארוך מדי לטענתו "כמו שעושה פאי", אבל הכלים המובנים בפאי שומרים את הפלט המקוצץ לקובץ והתוסף החדש לא ממש. אין התאוששות משגיאות ואין התיחסות למקרי קצה או נעילות. בקיצור קוד ש"רוב הזמן עובד אצלי על המכונה".
אפילו אם הקוד היה מושלם אני חושב שהבעיה הכי גדולה היא המימוש לא לפי ה Spec. כן AI יכול לכתוב קוד טוב אבל זה דורש תשומת לב, מפתחים שיקראו את הקוד והרבה פעמים יותר מנסיון אחד. מפתחים היום שחושבים שהם כותבים Spec והכל עובד אז לא צריך לקרוא את הקוד עלולים להתעורר בעוד כמה חודשים ולמצוא מערכת מאוד שונה ממה שהם חשבו שיש להם.
1 419
📌 חדש בפייתון מילון קפוא
פייתון 15 הוסיפה מנגנון חדש שפותר כאב ראש מאוד ישן. זה נקרא frozendict וזה פשוט dict שאי אפשר לשנות אותו. עכשיו אני מבין שאתם מופתעים, מי צריך מילון שאי אפשר לשנות את מה שכתוב בו? מה לא מספיק לך dataclasses? נכון dataclass לא קפוא בברירת מחדל אבל אפשר בקלות להעביר frozen=True בבנאי ולקבל אותו קפוא.
התשובה היא שתמיד טוב שיש דברים שלא משתנים ולא תמיד צריך או רצוי להגדיר dataclass. לפעמים יש לי מידע שקיבלתי דרך API או מידע שאני לא בטוח מה המבנה שלו, ובכל זאת אני רוצה להקפיא אותו.
בשביל ליצור מילון קפוא מספיק להשתמש במילה החדשה frozendict החל מפייתון 3.15:
frozendict({"a": 10 })
או אם יש לכם מילון ביד אפשר לעטוף אותו ב frozendict:
d = {"a": 10}
f = frozendict(d)
והחלק הכי טוב במילון הקפוא הוא שיש לו hash ולכן אפשר להשתמש בו בתור ערך במילון. בדוגמה הבאה אני מפעיל lru_cache על פונקציה שמקבלת מילון וסוכמת ברקורסיה את כל הערכים המספריים באותו מילון. עד פייתון 3.15 זה לא היה עובד כי lru_cache דורשת שנשמור את הפרמטרים במילון המטמון. עכשיו עם frozendict הערך נשמר במילון ואנחנו מקבלים מטמון במתנה:
from functools import lru_cache
@lru_cache
def sum_values(d):
numbers = sum(v for v in d.values() if type(v) == int)
others = {k: v for k, v in d.items() if not isinstance(v, int)}
if len(others) == 0:
return numbers
return numbers + sum_values(frozendict({ k: sum_values(v) for k, v in others.items() }))
print(sum_values(frozendict(
{
"a": 10,
"b": 20,
"c": frozendict({ "a": 30, "b": 40 }),
"d": frozendict({ "e": frozendict({"f": 100}) })
})))1 419
📌 באג רודף באג
אחד הדברים שמבלבלים מפתחים הוא כשיש מערכת בה באג אחד מסתיר באג משמעותי יותר ואז כשאתה מתקן את הבאג הקטן פתאום משתמשים מתחילים להתלונן על דברים לא קשורים.
דבר דוצה קורה גם עם AI ואני חושב ששם זה אפילו יותר מבלבל. הנה שיחה קצרה על רובי עם קלוד. קלוד כתב פונקציה כזאת:
def retry_delay
delay = settings.dig('retry_delay')
delay.present? ? delay.to_i : 1
end
ואני רציתי לשנות אותה ל:
what about just
settings.dig('retry_delay')&.to_i || 1
קלוד שינה ואז כתב:
Done. Note: this has a slight behavioral difference — if retry_delay is "0", the original would return 0 (truthy string → to_i), while "0".to_i is 0 which is falsy in Ruby, so || 1 would return 1 instead. If 0 is a valid value, you'd want || 1 replaced with something like .then { |v| v || 1 }. Probably not an issue in practice, but worth knowing.
מה בעצם הוא אומר פה?
1. שיניתי לך את הקוד כמו שביקשת.
2. שים לב שבגרסה החדשה שלך יש באג - הפונקציה לעולם לא תחזיר 0 כי 0 נחשב false ברובי ולכן יוחזר הערך בצד ימין של סימן ה ||.
3. זה כנראה לא בעיה כי בטח הפונקציה שלך אף פעם לא מחזירה 0 ממילא אז אפשר להתעלם.
שלושה סעיפים, שניים מהם שגויים. הפונקציה בהחלט יכולה וצריכה לפעמים להחזיר 0. אם 2 היה נכון זה היה באג נוראי. אבל 2 כמובן לא נכון. ברובי 0 הוא ערך אמת:
3.4.4 :001 > 0 || 1
=> 0
3.4.4 :002 > nil || 1
=> 1
3.4.4 :003 > !!0
=> true
3.4.4 :004 > puts "ruby" if 0
ruby
=> nil
3.4.4 :005 >
לקחים? אני לא יודע. אולי רק תזכורת לקרוא גם את הטקסט ש AI מדפיס ולא רק את הקוד. וכמובן להזהר עם הסכינים החדות.1 419
📌 אינטראופ
אינטראופרביליות, או תפעוליות בינית בעברית (כן זה התרגום לפי ויקיפדיה), היא היכולת של לפחות שתי מערכות להחליף מידע ולעשות שימוש במידע שהוחלף. מילה מסובכת שמתארת רעיון די פשוט.
לא משנה מי יצרן הטלפון שלך, מי בנה את ציוד התקשורת או לאיזה חברה משלמים את חשבון הטלפון, כולם יכולים לדבר אחד עם השני.
כשמערכת שומרת מידע בצורה פשוטה או שהמערכת עצמה כתובה בקוד פתוח קל לבנות ממשקים שונים לגישה לאותו מידע. בגלל זה יש המון ממשקים ל git אבל כולם אינטראופרביליים, מתחילים ב GUI, כשחסרה אופציה אפשר להכנס לשורת הפקודה לכתוב את הפקודה שרצית ולחזור ל GUI. ממילא כולם עובדים על אותו מידע. אפשר לכתוב המון דפדפני אינטרנט וכולם יראו את אותו עמוד. יש המון יצרני מכוניות וכולן נוסעות על אותם כבישים.
אבל אינטראופרביליות מורגשת הכי חזק כשהיא לא נמצאת.
לפייסבוק אין מתחרים כי אי אפשר לקחת אתנו את כל הרשת החברתית שכבר יש לנו בפלטפורמה.
בשביל לשלוח הודעת ווטסאפ הבן אדם שמקבל צריך להתקין ווטסאפ.
כל עוד VS Code Copilot תמך בשיחות עם מודלים רק דרך המנוי של קופיילוט היה אפשר להחליף מודל באמצע השיחה והכל עבד. כשהם התחילו להוסיף ספקים כמו Ollama פתאום גילינו שאי אפשר להתחיל שיחה עם קלוד ואחרי כמה הודעות לעבור לדיפסיק. כל ספק מודלים מצפה לרשימת הודעות במבנה אחר ולא מצליח להתמודד עם רשימה שיצר ספק אחר.
החלק הטכני בפיתוח מערכות אינטראופרביליות הוא החלק הקל של המשוואה. האתגר האמיתי הוא להתמיד, לשמור על מבנה המידע אליו התחייבתי, לאפשר ללקוחות למשוך את המידע שלהם ולקוות שהמתחרים ישתפו פעולה. דפדפני אינטרנט זו דוגמה מעניינת כי אותן חברות שב 1999 עשו הכל כדי שכל דפדפן יוכל להציג אתרים שונים גילו ב 2008 ששיתוף הפעולה מאפשר להזניק קדימה את כל התעשייה. טוב יעשו הספקים של מודלי שפה אם יאמצו את אותה גישה.
1 419
📌 פייברים, חיבורים ל DB וסוכנים חכמים.
אחד האתגרים בפיתוח סוכנים חכמים הוא התמודדות עם סוג חדש של עבודה ברקע.
בעבר אפליקציית ווב ביצעה מספר מוגבל של משימות ברקע ולא היה לנו כזה חשוב לבצע את כולן במקביל. לדוגמה:
1. אתר שולח מייל לנרשמים חדשים.
2. אתר לוח מודעות מטפל במשתמש שמעלה מודעה חדשה עם תמונה. התמונה עוברת עיבוד ושינוי גודל ברקע והמודעה עולה לאתר.
3. מערכת לבניית דפי נחיתה מוציאה ברקע תעודות SSL לדומיין של משתמשים.
המאפיין של פעולות שהעברנו לרקע בעבר היה הרצון להחזיר לדפדפן תשובה מהר כדי שמשתמש יוכל להמשיך לעבוד ולהפריד בין פעולות שמשתמשים רואים לאותן פעולות רקע שמתבצעות בקצב שלהן. אם אני צריך להוציא אלף מיילים זה לא משנה אם אני מוציא אותם אחד אחרי השני או את כולם במקביל, וממילא להוציא מייל ל SMTP זו פעולה מהירה.
אבל היום עברנו לכתוב סוכנים חכמים, והם משנים את האילוצים. סוכן חכם פונה למודל שפה גדול דרך API ומקבל תשובה לאט לאט. את התשובה צריך להציג למשתמשים ב Streaming מיד כשמקבלים אותה וכל המשתמשים של הסוכן מצפים לקבל את התשובות שלהם כמה שיותר מהר.
בעולם הישן מערכות השתמשו ב Thread Pool כדי לנהל את כל התהליכים במקביל. היה לך מאגר של 5-8 תהליכונים שרצים ברקע (20 למיטיבי לכת). כל תהליך תופס משימה, מבצע אותה, משחרר וממשיך למשימה הבאה. צריכים לשלוח מייל למישהו שנרשם? מצוין תוסיף את המשימה הזאת לתור המשימות, כשיתפנה אחד הפועלים הוא ייקח את המשימה מהתור, ישלח את המייל וימשיך למשימה הבאה. קל.
אבל מאגר של 5-8 תהליכונים, או אפילו 20 אם אתם מיטיבי לכת, לא עוזר למי שרוצה לכתוב סוכן חכם. הסוכן החכם רוב הזמן צריך לקבל תשובות ממודל השפה ולהעביר אותן לגולשים. 8 תהליכונים אומר שאני יכול להעביר רק 8 תשובות במקביל. כל תהליכון "מוקצה" להעברת הודעה מסוימת ועד שההודעה לא נגמרת התהליכון תקוע ב Streaming.
מה עושים? זורקים את מודל התהליכונים ועוברים למודל לא כל כך חדש של מענה אסינכרוני, או למודל קצת יותר חדש של Fibers (ברובי) או Virtual Threads (ב Java). בשני המקרים אנחנו עולים בקיבולת ומטפלים בעזרת מספר קטן של תהליכונים בעשרות אלפי בקשות במקביל. התהליכון "מקפץ" בין התשובות כמו נציג שירות שמדבר אתכם בווטסאפ, וכל פעם שיש מידע חדש מאיזשהו מודל שפה מערכת ההפעלה מעבירה את המידע לאותו תהליכון שיודע לנתב את המידע חזרה לגולש.
אגב - בהתלבטות בין Fibers לתכנות אסינכרוני אני מעדיף פייברים כי איתם לא צריך לשנות את הקוד בכלל כי הם מתנהגים כמו Threads, ובאופן אוטומטי יודעים לשחרר את השליטה כשמתחילה בקשת רשת.
ועוד אגב - אם אתם ב Rails ועושים את המשחק הזה חשוב לשים לב לחיבורים ל DB. באופן אוטומטי ריילס שומר לכל Fiber חיבור פתוח לבסיס הנתונים. סוכנים חכמים יפתחו בשמחה אלפי ועשרות אלפי פייברים כלומר עשרות אלפי חיבורים לבסיס הנתונים, הרבה יותר מדי אפילו בשביל הפוסטגרס שלי. הפתרון הוא לשחרר את החיבור לבסיס הנתונים לפני הפנייה למודל כך שרוב הזמן לא נחזיק חיבור פתוח. בקוד זה נראה כך:
ActiveRecord::Base.connection_pool.release_connection
chat.complete1 419
course = Course.find(course_id)
medium = course.medium
Rails.logger.info "Starting ResyncTimestampsJob for course #{course.id} (#{course.slug})"
phrases = medium.phrases.ordered_by_timestamp.to_a
verify_json_data!(json_data, phrases)
# Update all phrase timestamps in a single pass
json_data.zip(phrases).each do |entry, phrase|
phrase.update!(timestamp: entry["timestamp"])
end
# Update lesson timestamps based on first and last phrase in each lesson
course.sync_lesson_timestamps
Rails.logger.info "ResyncTimestampsJob completed for course #{course.id}. Updated #{phrases.length} phrases."
rescue => e
Rails.logger.error "ResyncTimestampsJob failed for course #{course_id}: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
raise e
end
private
# Expected json_data structure:
# [{ "timestamp" => "00:01:23.456", ... }, ...]
# One entry per phrase, ordered to match medium.phrases.ordered_by_timestamp.
def verify_json_data!(json_data, phrases)
if phrases.length != json_data.length
raise "Phrase count mismatch: medium has #{phrases.length} phrases, JSON has #{json_data.length} entries"
end
missing = json_data.each_with_index.select { |entry, _| entry["timestamp"].blank? }
if missing.any?
raise "Missing timestamps at JSON indices: #{missing.map(&:last).join(', ')}"
end
end
end
עכשיו הפונקציה נכנסת בעמוד אחד על המסך, מופרדת ל-3 חלקים ברורים כשכל חלק ממומש במקום המתאים לו. החלק שמסנכרן זמנים של שעורים נשאר מסובך, אבל המימוש המסובך כבר לא מפריע לקרוא את הג'וב:
def sync_lesson_timestamps
ordered_lessons = lessons.includes(activities: { activity_phrases: :phrase }).sort_by(&:order)
# Precompute sorted unique phrases per lesson to avoid N+1 inside the loop
lesson_phrases_map = ordered_lessons.to_h do |lesson|
phrases = lesson.activities.flat_map { |a| a.activity_phrases.map(&:phrase) }.uniq.sort_by(&:timestamp)
[lesson, phrases]
end
ordered_lessons.each_with_index do |lesson, index|
lesson_phrases = lesson_phrases_map[lesson]
next if lesson_phrases.empty?
start_ts = lesson_phrases.first.timestamp
end_ts = if (next_lesson = ordered_lessons[index + 1])
next_phrases = lesson_phrases_map[next_lesson]
next_phrases.any? ? next_phrases.first.timestamp : lesson_phrases.last.timestamp
else
Phrase.to_string_timestamp(lesson_phrases.last.timestamp_seconds + 5)
end
lesson.update!(start_timestamp: start_ts, end_timestamp: end_ts)
end
end
עכשיו צריך להגיד - אני לא כתבתי פה שורת קוד אחת. הסיפור הוא לא ההקלדה אלא להכנס לקוד, להבין שמתחת לבעיה שמפריעה לי יש עוד שתי בעיות שצריך לפתור לפני שאפשר להתקדם ולהסביר ל AI איך הקוד צריך להיות בנוי כדי לתקן את שתי הבעיות בשכבות הבסיסיות יותר. אחרי שזה נפתר אותו פרומפט שקודם הסתבך מימש את מנגנון תיקון השיעורים ואפילו כתב לו בדיקות אוטומטיות.
קריאה ותכנון הן המיומנויות שהכי משפיעות על מהירות הקידוד שלי היום. האם אפשר לעשות אוטומציה גם להן? ייתכן. בינתיים לא ראיתי שאנחנו מתקרבים.1 419
📌 כמה שכבות של בעיות
אחת התבניות שעדיין מבלבלת סוכנים חכמים היא כשיש כמה שכבות של באגים באותו קטע קוד. לאנגלטס הוא פרויקט קוד פתוח שאני בונה כדי לתרגל ערבית דרך סרטוני וידאו. יש AI שעובר על הסרט, מייצר תמלול ותרגום ומדביק לכל זה תרגילים על המילים. זה אחלה.
הבעיה היא שלפעמים הזמנים של הטקסט לא מסונכרנים עם הוידאו ואז צריך להריץ סיבוב נוסף של AI כדי לסנכרן את הזמנים. הקוד הבא מקבל קובץ זמנים מעודכן ומתקן את הזמנים בקורס קיים:
https://github.com/ynonp/langlets-rails/blob/c846dfe220fa9e25c1f54b3b3b22aaff1a463592/app/jobs/resync_timestamps_job.rb
class ResyncTimestampsJob < ApplicationJob
queue_as :default
def perform(course_id, json_data)
course = Course.find(course_id)
medium = course.medium
Rails.logger.info "Starting ResyncTimestampsJob for course #{course.id} (#{course.slug})"
phrases = medium.phrases.ordered_by_timestamp.to_a
if phrases.length != json_data.length
raise "Phrase count mismatch: medium has #{phrases.length} phrases, JSON has #{json_data.length} entries"
end
# First pass: build a hash of phrase.id => new timestamp
updates = {}
json_data.each_with_index do |entry, index|
phrase = phrases[index]
new_timestamp = entry["timestamp"]
if new_timestamp.blank?
raise "Missing timestamp at JSON index #{index}"
end
updates[phrase.id] = new_timestamp
Rails.logger.info "Mapping phrase #{phrase.id} ('#{phrase.text_l1}') => #{new_timestamp}"
end
# Second pass: apply all updates
updates.each do |phrase_id, new_timestamp|
phrase = Phrase.find(phrase_id)
old_timestamp = phrase.timestamp
phrase.update!(timestamp: new_timestamp)
Rails.logger.info "Updated phrase #{phrase_id} timestamp: #{old_timestamp} => #{new_timestamp}"
end
# Update lesson timestamps based on first and last phrase in each lesson
course.lessons.includes(:activities).find_each do |lesson|
lesson_phrase_ids = lesson.activities.joins(:phrases).pluck("phrases.id").uniq
lesson_phrases = phrases.select { |p| lesson_phrase_ids.include?(p.id) }.sort_by(&:timestamp)
if lesson_phrases.any?
first_timestamp = lesson_phrases.first.timestamp
last_timestamp = lesson_phrases.last.timestamp
lesson.update!(start_timestamp: first_timestamp, end_timestamp: last_timestamp)
Rails.logger.info "Updated lesson #{lesson.id} timestamps: #{first_timestamp} => #{last_timestamp}"
end
end
Rails.logger.info "ResyncTimestampsJob completed for course #{course.id}. Updated #{updates.count} phrases."
rescue => e
Rails.logger.error "ResyncTimestampsJob failed for course #{course_id}: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
raise e
end
end
הקוד גרוע מהרבה בחינות ובדיוק מתאים לתבנית של כמה שכבות של באגים. בקצרה:
1. כל הטקסט של הוידאו נטען לזכרון בשורה 10 (הופכת את כל השורות למערך).
2. אחרי זה רצים בלולאה על כל הטקסט בזכרון ובונים מיפוי בין הזמן החדש לכל משפט. המיפוי הזה הוא בעצם כפילות של מערך הטקסטים.
3. אחרי זה מעדכנים את כל הטקסטים לזמן החדש שלהם.
4. בסיום רצים על השיעורים בקורס ומעדכנים את זמני ההתחלה והסיום שלהם כדי להתאים לזמנים החדשים מהקובץ.
למה AI הסתבך עם זה? אני לא יודע, אבל אני חושד שהקוד כולל כמה תבניות לא נכונות והיה קשה לפתור את הסבך. הבעיות המרכזיות בקוד:
1. הוא ארוך מדי. אני יודע זה לא באג אבל זה חשוב, כי כשקוד ארוך מדי קשה להבין אותו.
2. הוא עושה עבודה מיותרת - טוען לזכרון את כל הטקסטים ואז בונה את המילון, כל זה מבלבל את ה AI.
3. התקלה הברורה היא קביעת הזמנים לשיעורים. זמן סיום של שעור צריך להיות זמן ההתחלה של השעור שאחריו ולא זמן ההתחלה של הטקסט האחרון בו, כדי לתת לוידאו זמן לסיים לנגן.
אבל כל פעם שביקשתי מ AI לתקן את 3 הוא הסתבך והקוד שנוצר לא עבד או לא עבד תמיד.
הפתרון כמובן היה לארגן מחדש את הקוד. ככה זה נראה אחרי שפירקתי אותו:
https://github.com/ynonp/langlets-rails/blob/main/app/jobs/resync_timestamps_job.rb
class ResyncTimestampsJob < ApplicationJob
queue_as :default
def perform(course_id, json_data)
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
