כמה שכבות של בעיות
אחת התבניות שעדיין מבלבלת סוכנים חכמים היא כשיש כמה שכבות של באגים באותו קטע קוד. לאנגלטס הוא פרויקט קוד פתוח שאני בונה כדי לתרגל ערבית דרך סרטוני וידאו. יש AI שעובר על הסרט, מייצר תמלול ותרגום ומדביק לכל זה תרגילים על המילים. זה אחלה.
הבעיה היא שלפעמים הזמנים של הטקסט לא מסונכרנים עם הוידאו ואז צריך להריץ סיבוב נוסף של AI כדי לסנכרן את הזמנים. הקוד הבא מקבל קובץ זמנים מעודכן ומתקן את הזמנים בקורס קיים:
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
הקוד גרוע מהרבה בחינות ובדיוק מתאים לתבנית של כמה שכבות של באגים. בקצרה:
כל הטקסט של הוידאו נטען לזכרון בשורה 10 (הופכת את כל השורות למערך).
אחרי זה רצים בלולאה על כל הטקסט בזכרון ובונים מיפוי בין הזמן החדש לכל משפט. המיפוי הזה הוא בעצם כפילות של מערך הטקסטים.
אחרי זה מעדכנים את כל הטקסטים לזמן החדש שלהם.
בסיום רצים על השיעורים בקורס ומעדכנים את זמני ההתחלה והסיום שלהם כדי להתאים לזמנים החדשים מהקובץ.
למה AI הסתבך עם זה? אני לא יודע, אבל אני חושד שהקוד כולל כמה תבניות לא נכונות והיה קשה לפתור את הסבך. הבעיות המרכזיות בקוד:
הוא ארוך מדי. אני יודע זה לא באג אבל זה חשוב, כי כשקוד ארוך מדי קשה להבין אותו.
הוא עושה עבודה מיותרת - טוען לזכרון את כל הטקסטים ואז בונה את המילון, כל זה מבלבל את ה AI.
התקלה הברורה היא קביעת הזמנים לשיעורים. זמן סיום של שעור צריך להיות זמן ההתחלה של השעור שאחריו ולא זמן ההתחלה של הטקסט האחרון בו, כדי לתת לוידאו זמן לסיים לנגן.
אבל כל פעם שביקשתי מ AI לתקן את 3 הוא הסתבך והקוד שנוצר לא עבד או לא עבד תמיד.
הפתרון כמובן היה לארגן מחדש את הקוד. ככה זה נראה אחרי שפירקתי אותו:
https://github.com/ynonp/langlets-rails/blob/main/app/jobs/resynctimestampsjob.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
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 איך הקוד צריך להיות בנוי כדי לתקן את שתי הבעיות בשכבות הבסיסיות יותר. אחרי שזה נפתר אותו פרומפט שקודם הסתבך מימש את מנגנון תיקון השיעורים ואפילו כתב לו בדיקות אוטומטיות.
קריאה ותכנון הן המיומנויות שהכי משפיעות על מהירות הקידוד שלי היום. האם אפשר לעשות אוטומציה גם להן? ייתכן. בינתיים לא ראיתי שאנחנו מתקרבים.