От 0.034 до 0.791 и обратно: соревнование по Legal RAG, 17 итераций и стена масштабирования
Мне давно хотелось погрузиться в RAG, но повода не было. Я решил поучаствовать вARLC 2026— юридическом AI-челлендже, где нужно строить RAG-пайплайн поверх корпуса судебных решений и законов DIFC – находить нужные страницы в нужных документах, извлекать ответы и давать точные ссылки на источники. Соло, с Claude Code в качестве напарника.
До этого я работал с ML, но RAG-пайплайны не строил. За 5 дней прошёл путь от первой подачи с grounding ≈ 0 до 0.791 на первом этапе. А потом вышел в финал — и pipeline, который отлично работал на 30 документах, потерял 42% на 300.
В этой статье — архитектура, конкретный код, математика F-beta, полная таблица 17 итераций с метриками и честный разбор работы с AI-ассистентом на соревновании. Кодоткрыт на GitHub.
Кто я такой
Я заканчивал направление NLP в НИУ ВШЭ. На работе я занимаюсь аналитикой (работал в Яндексе, Ozon, Альфа-Банке). Регулярно участвую в соревнованиях (топ-600 наKaggle), но RAG до этого челленджа не строил. Еще я веду каналы про аналитику –Тагир АнализируетиЗарплатник Аналитика. Сейчас активно строю свой продукт —Карьерник, Duolingo для подготовки к собеседованиям на аналитика.
Что за челлендж
ARLC 2026 (Arab Region Legal Challenge)— это соревнование по Retrieval-Augmented Generation на юридических документах. Тебе дают корпус PDF-документов (судебные решения DIFC Courts, законы, указы) и набор вопросов к ним.
Задача: для каждого вопроса найти нужные страницы в нужных документах, извлечь ответ и предоставить точные ссылки на источники.
Звучит просто. Но вот нюансы.
Типы ответов
Это не просто «ответь текстом». Каждый вопрос имеет свойanswer_type, и от типа зависит и стратегия ответа, и скоринг:
число (int/float)
±1% допуск
true/false
точное совпадение
normalized exact match
YYYY-MM-DD
точное совпадение
массив строк
Jaccard similarity
текст до 280 символов
LLM-судья, 5 критериев
Для любого типаnull— валидный ответ, означающий «информации нет в корпусе». Если и в gold-ответе null — получаешь 1 балл. Если только у тебя null — получаешь 0.
Формула скоринга
- S_det— точность на типизированных вопросах (number, bool, date, name, names)
- S_asst— оценка LLM-судьи для free_text (5 критериев: correctness, completeness, grounding, confidence calibration, clarity)
- G— grounding (F-beta, β=2.5 по page-level ссылкам)
- T— telemetry factor (0.9 если телеметрия невалидна, 1.0 если ок)
- F— TTFT factor (бонус/штраф за скорость)
G — этомножитель. Если grounding = 0, весь скор = 0. Неважно, насколько идеальные ответы.
TTFT-бонус — бесплатные проценты
Быстрый ответ = +5% к финальному скору. Медленный — штраф до -15%. При прочих равных — это бесплатные очки.
Warmup vs. Финал
- Warmup:30 документов, 100 вопросов, 15 попыток подачи
- Финал:300 документов, 900 вопросов,2 попытки
У каждой фазы свой корпус — нельзя использовать документы из warmup в финале.
День первый: четыре символа, которые стоили три попытки
Первая подача — v1. Скор:0.034.
Grounding — 0.05. Из 100 вопросов система почти ни разу не сослалась на правильные страницы. При этом Det = 0.857 — ответы-то были неплохие.
Но G — множитель. 0.857 × 0.05 ≈ ничего.
Ещё две попытки (v2, v3) с мелкими правками. 0.035. 0.036. Grounding не двигался. Я перебрал всё: может, страницы нумеруются с нуля? Может, doc_id — это не имя файла? Может, ретривал совсем мимо?
А потом я открыл submission.json и увидел:
А в документации API:
doc_idmust be the PDF filename (SHA-like string)
Файл называетсяabc123def456.pdf. Имя файла —abc123def456. Без.pdf.
Три попытки из пятнадцати ушли на четыре символа.
v4 — одна строчкаdoc_id = filename.replace('.pdf', ''):
G: 0.05 → 0.55. Скор: 0.034 → 0.438. В 13 раз.
Урок:сначала валидируй формат вывода. Потом улучшай качество. Никакой retrieval, reranking или fine-tuning не спасёт, если submission не матчится с gold по формату. Это кажется очевидным, но когда ты с нуля собираешь пайплайн — голова занята архитектурой, а не тем, есть ли.pdfв doc_id.
Архитектура: что и почему
К v14 (лучший скор) пайплайн выглядит так:
Разберём каждый компонент.
Ingestion: парсинг PDF
Юридические PDF бывают двух видов: цифровые (текст копируется) и сканированные (картинка). Нужно обрабатывать оба.
Ключевое решение: page-level, а не чанки.Grounding считается по(doc_id, page_number)— значит и индексировать нужно по страницам. Чанки (куски по 500 токенов) создают проблему маппинга обратно на страницы: чанк может пересекать границу страниц, и непонятно, на какую страницу ссылаться.
Минус page-level: страница может быть длинной (1500+ символов), и контекст для LLM раздувается. Решение — context distillation перед вызовом модели (об этом ниже).
Гибридный поиск: BM25 + embeddings + RRF
Чистый BM25 хорошо находит точные совпадения — номер статьи, имя судьи, номер дела. Но плохо ловит переформулировки: вопрос «What is the penalty?» не матчится с текстом «shall be liable to a fine».
Чистые embeddings наоборот — ловят семантику, но теряют точные термины.
Решение —гибридный поиск с Reciprocal Rank Fusion:
RRF с k=60 — стандартная формула. Она не требует нормализации скоров (BM25 и cosine similarity имеют разные шкалы), а просто использует ранги. Страница, которая в топ-5 по обоим методам, получит высокий combined score.
Модель для embeddings:all-MiniLM-L6-v2— 22M параметров, 90MB, работает локально. Для 30 документов warmup этого хватает с запасом. Для финала (300 документов) тоже — индексация занимает ~10 секунд.
Document routing: comparison-вопросы
Comparison-вопросы типа «В каком из дел CFI 001/2020 и CFI 002/2021 судья был назначен раньше?» требуют контекста из двух документов. Обычный retrieval может найти страницы только из одного.
Решение —отдельный routing-индекс, построенный по первым двум страницам каждого документа:
При обнаружении comparison-вопроса (два номера дел в тексте) делаемdual query— отдельный BM25-запрос для каждого дела, результаты чередуем round-robin:
Зачем чередование: если все результаты case A идут первыми, а max_pages маленький (4) — case B может не попасть в контекст совсем. Чередование гарантирует представительство обоих дел.
Cross-encoder reranking
BM25 + embeddings дают грубую сортировку. Cross-encoder (ms-marco-MiniLM-L-6-v2) пересортировывает топ-30 по настоящей релевантности:
Hard cap 30— не прихоть, а необходимость. Cross-encoder делает forward pass для каждой пары (query, page). 30 пар — ~50ms. 100 пар — ~500ms. 1500 пар (весь корпус) — несколько секунд, и TTFT улетает за 3000ms → штраф к скору.
Priority tagging— самый важный паттерн. Некоторые страницы должны быть в контекстевсегда(определение цитируемой статьи, титульная страница дела). Cross-encoder может их опустить (например, если вопрос сформулирован иначе, чем текст статьи). Флаг_priority=Trueгарантирует, что эти страницы не выпадут.
Типизированные ответы: когда LLM не нужен
Одно из ключевых решений —детерминированные fast-pathsдля типизированных вопросов. Зачем тратить 700ms на вызов Haiku, если можно за 5ms извлечь ответ регулярным выражением?
Извлечение номера закона
Сравнение дат
Сравнение денежных сумм
Эти fast-paths покрывают ~30-40% типизированных вопросов. Выигрыш: -700ms на TTFT (бонус к F) и 0 токенов на API.
Adversarial detection
В корпусе DIFC есть вопросы-ловушки — про институты, которых в DIFC-праве просто нет:
Если вопрос adversarial, ответ —nullдля типизированных и стандартный fallback для free_text:
“There is no information on this question in the provided documents.”
v15 показал, что менять этот fallback нельзя.Я попробовал заменить его на конкретные DIFC-объяснения («DIFC Courts do not use jury trials…»). Gold ожидал generic fallback. Asst упал с 0.720 до 0.640.
Context distillation: сжимаем контекст для LLM
Страница юридического документа — это ~1500 символов. Если в контексте 5 страниц — это 7500 символов, а большая часть нерелевантна.
Context distillation выбирает только релевантные абзацы:
Результат: ~40% сжатие (1500 → 900 символов) с сохранением ключевой информации. Экономит ~200 input-токенов на вопрос.
Бюджеты по типам ответов:
Chars/page
Max tokens (output)
Для boolean — больше страниц (comparison требует контекст из двух документов), но маленький output. Для free_text — больше всего и контекста, и output.
LLM: промпты, модели, парсинг цитат
Выбор модели
Почему не Sonnet для всего: +300ms TTFT на каждый вопрос. При 30% free_text вопросов это снижает средний F на ~0.009. Sonnet даёт +0.10 на Asst, что в формуле = +0.03 × G. При G=0.86 это +0.026. Чистый выигрыш: +0.026 - 0.009 =+0.017— стоит того.
Type-specific промпты
Для каждого типа — отдельная инструкция. Вот пример дляnumber:
«NOT the article number» — результат конкретного бага. LLM получает контекст про Article 19, видит вопрос «What is the time limit under Article 19?» и отвечает… 19. Вместо 6 (месяцев). Эта строчка в промпте — хардкод-фикс одной из самых частых ошибок.
Comparison boolean: CoT
Для сравнительных boolean-вопросов добавлен Chain-of-Thought:
Вместо прямого «true/false» модель сначала извлекает факты, потом сравнивает. Это снижает ошибки сравнения на ~15%.
Парсинг цитат из ответа LLM
LLM возвращает что-то вроде:
Эти индексы — 0-based номера «блоков» контекста (страниц), которые модель считает релевантными. Но модель может цитировать неточно — поэтому дальше идёт verification.
Post-LLM verification: ловим галлюцинации
LLM иногда ошибается — возвращает число из другого контекста, путает артикул с его значением, или отвечает generic фразой вместо конкретного имени. Post-LLM verification это ловит.
Проверка «ответ есть в тексте»
Если ответ не найден в тексте — значит, LLM галлюцинирует. Fallback на детерминированное извлечение:
Article-number confusion
Одна из самых коварных ошибок:
Пример: «What is the notice period under Article 14?» → LLM отвечает 14 вместо 30 (дней).
Territorial scope check
DIFC-законы действуют только в DIFC. Если вопрос спрашивает «Does DIFC Law apply in London?» — ответ false, даже если LLM говорит true:
Evidence-based grounding: главный компонент
Grounding — множитель в формуле. Ему посвящено больше всего кода и итераций. Система выбора страниц для цитирования — трёхуровневая:
Уровень 1: Article index pages
Для вопросов про статьи закона — сначала ищем страницу, где статьяопределена(заголовок «Article 14»), а не просто упоминается:
Почему definitions, а не all mentions:страница, где написано «see Article 14» в тексте другой статьи — это не gold-страница. Gold — это страница, где Article 14 определена и содержит ответ.
Это изменение (article index → CITE ordering) дало+0.065 к G(v12→v13).
Уровень 2: LLM citations (CITE)
Если article index не помог — используем цитаты из ответа LLM:
Уровень 3: Evidence verification
Финальная проверка: содержит ли кандидат-страницареальное доказательствоответа?
Ключевая идея: страница, содержащаяи ответ, и ссылку на нужную статью— почти наверняка gold. Страница только со ссылкой или только со значением — менее вероятна.
Smart article continuation
Статьи часто разбиты на две страницы. Наивный подход — всегда добавлять следующую страницу. Проблема: следующая страница может быть Article 15, а нам нужна 14.
Это изменение дало+0.037 к G(v13→v14).
Page caps по типам
Тип вопроса
Max pages в grounding
boolean (не comparison)
Gold обычно 1 страница
Одно число — одна страница
Одна дата — одна страница
name (не comparison)
Одно имя — одна страница
comparison
Минимум по 1 странице на каждое дело
Синтез из нескольких источников
v11 показал: cap 3 → 4 для non-comparison = +43% страниц, но Gупална 0.059. v14 показал: cap 3 → 2 для non-comparison = -13% страниц, Gвыросна 0.037.
Математика grounding: β=2.5 под микроскопом
Это самый важный раздел для тех, кто строит RAG с grounding-скорингом. β=2.5 кажется очевидным — «recall важнее, добавляй больше страниц». На практике всё сложнее.
Формула F-beta
При β=2.5: β²=6.25. Recall взвешен в 6.25 раз тяжелее precision.
Сценарий 1: Пропущенная золотая страница
Gold = 2 страницы. Ты цитируешь 1 из 2:
Ты цитируешь 2 из 2 + 1 лишнюю:
+74%.Одна правильная страница (даже с шумом) радикально поднимает G.
Сценарий 2: Лишняя страница
Gold = 1 страница. Ты цитируешь её:
Ты цитируешь её + 1 лишнюю:
-12%.Каждая лишняя страница — это реальная потеря.
Сценарий 3: Две лишних
Gold = 1. Цитируешь 1 + 2 лишних:
-22%.Две лишних страницы = минус пятая часть grounding.
Практический вывод
Формула наказывает за лишние страницы сильнее, чем кажется при β=2.5. Оптимальная стратегия:
- Всегда включай все golden pages(recall = 1.0 критичен)
- Минимизируй шум(каждая лишняя страница стоит 10-22%)
- Лучше недоцитировать, чем перецитировать— если не уверен, что страница gold, не добавляй
Вот как это проявилось в экспериментах:
Avg pages/q
Комментарий
+43% страниц, Gупал
-13% страниц, Gвырос
Precision > volume.Это контринтуитивно, когда β=2.5 кричит «recall!», но формула не врёт.
История итераций: 15 версий, 3 провала, 1 финальный скор
Полная таблица:
Ключевое изменение
Первая подача
Asst tweak
.pdffix → G x11
Embeddings, Det up
Hybrid search, TTFT halved
Adversarial detection
Page expansion + β math
REGRESSION: adj pages
Evidence overhaul + hardcodes
REGRESSION: more pages
Partial revert
Article index first
Smart continuation + caps
REGRESSION: judge override
Три регрессии — три урока
v9 (0.626, -0.078):«Если статья длинная, соседняя страница тоже релевантна». Нет. Соседняя страница часто содержит другую статью. Adjacent page retention без проверки содержимого — антипаттерн.
v11 (0.667, -0.049):«β=2.5 значит больше страниц = лучше». Нет. +43% страниц = -7.5% G. Дополнительные страницы были шумом. Precision matters даже при recall-ориентированной метрике.
v15 (0.749, -0.042):«Мы можем улучшить Det, добавив domain-специфичные overrides». Нет. «Assistant Registrar» — не судья в gold-ответах. Промпт для free_text — калиброван, менять формулировки опасно. Два изменения — два провала.
Что работает, а что нет: сводка для строителей RAG
Что работает
Page-level retrieval (не чанки)
Фундаментальное
Grounding считается по страницам
Гибридный BM25 + embeddings
+0.089 G (v4→v6)
BM25 для точных терминов, embeddings для семантики
Article index first, CITE second
+0.065 G (v12→v13)
Определение статьи > случайное упоминание
Evidence verify (answer + article ref)
+0.044 G (v12→v13)
Страница с И ответом И артикулом — почти наверняка gold
Smart article continuation
+0.037 G (v13→v14)
Только если следующая страница — та же статья
Adversarial detection
+0.129 Det (v6→v7)
null для невозможных вопросов
Post-LLM text verification
+0.042 Det (v9→v10)
Ловит галлюцинированные числа и имена
Type-specific page caps
+0.037 G (v13→v14)
2 для non-comparison, 4 для comparison
Deterministic fast-paths
+0.03 F factor
-700ms TTFT, 0 API-токенов
Context distillation
~0 скор, -0.05/run
Экономит токены без потери качества
Что НЕ работает (проверено экспериментами)
Антипаттерн
Больше страниц в grounding
G -0.059 (v11)
Шум > сигнал, precision penalty
Adjacent page retention
G -0.098 (v9)
Следующая страница ≠ та же статья
Domain-specific fallback для adversarial
Asst -0.080 (v15)
Gold ожидает generic fallback
Post-LLM boolean overrides
Det -0.042 (v15)
«Assistant Registrar» ≠ judge
Изменение формулировок free_text промпта
Asst falls
Промпт калиброван, любое изменение — риск
Sonnet для boolean вопросов
11/19 null
Sonnet «рассуждает» и сомневается, Haiku извлекает
Batch-изменения (17 за раз в v8)
Непонятно, что помогло
Невозможно сделать ablation
Разработка с Claude Code: честный разбор
Весь пайплайн написан через Claude Code — ~3000 строк в 7 модулях за 5 дней.
Типичная итерация
Я: «В v13 мы переставили article index перед CITE. Теперь нужно добавить проверку: если следующая страница начинается с Article N+1, не добавляй её. Только если продолжается та же статья.»
Claude Code: читает retrieve.py и llm.py → находит функцию article continuation → добавляет regex-проверку → обновляет caps → прогоняет build_submission.py → показывает diff.
Три минуты. Без Claude Code — час.
Что Claude Code делает хорошо
Скорость итераций.17 версий за 5 дней — больше 3 в день. Каждая версия включает изменения в 3-5 файлах, 50-200 строк кода, и прогон submission. Руками я бы сделал 3-5 итераций за это время.
Рефакторинг.Когда архитектура менялась (а она менялась 15 раз), Claude перестраивал зависимости, обновлял типы, правил вызовы.
Память и история.Claude Code хранит контекст между сессиями в.claude/memory/. Закрыл ноутбук, открыл через 4 часа — он помнит, что v11 сломал grounding и почему. Плюс каждое изменение коммитится в git с осмысленным описанием, а CHANGELOG.md ведётся автоматически. Когда работаешь с AI-агентом, версионирование — must have: без него невозможно откатиться или понять, какое изменение что сломало.
Код-ревью своих же изменений.Перед каждой подачей я просил: «проверь, не сломали ли мы что-нибудь». Claude читал diff и иногда находил проблемы до submission.
Исследование подходов.Claude сам искал современные техники (RRF fusion, cross-encoder reranking, context distillation), объяснял trade-offs и предлагал, что стоит попробовать следующим. Не просто исполнитель — активный участник в выборе направления оптимизации.
Где нужен человек
Приоритизация.«Что оптимизировать дальше: Det или G?» — вопрос, на который модель отвечает «и то, и другое». А тебе нужно выбрать одно, потому что осталось 5 submission.
Знание evaluation protocol.Что Assistant Registrar — не судья в gold-ответах, что generic fallback — это ожидаемый gold для adversarial вопросов. Эти нюансы оценки узнаёшь только из результатов подач и Discord-чата с организаторами.
Интерпретация провалов.Когда v11 сломал G, Claude предложил «увеличить recall floor ещё больше». Правильное решение было противоположным — уменьшить число страниц. Для этого нужно было понятьпочемуупало, а не просто «что-то упало, давай крутанём ручку дальше».
Главный компромисс
Claude Code ускоряет итерации в 5-10 раз, но снижает глубину понимания кода. Я хуже знаю свой codebase, чем если бы писал всё руками. Баг с.pdfмог бы всплыть раньше, если бы я сам строчка за строчкой писал парсер. Но без Claude Code за 5 дней было бы 3-5 итераций вместо 17 — и скор остался бы где-то в районе 0.4.
Результаты warmup
v1 (старт)
v14 (лучший)
Лидер warmup — CPBD с 0.959 (Det=1.0, G=0.976). Основной гэп — grounding (0.862 vs 0.976) и Asst (0.720 vs 0.840).
Финал: стена масштабирования
30 → 300 документов
Финальная фаза: 303 документа, 4244 страницы, 900 вопросов,2 попытки подачи. Код зафиксирован на v14.
Первая проблема обнаружилась до подачи:кеш warmup перезаписал данные финала. Пайплайн радостно проиндексировал 30 warmup-документов вместо 303 финальных. 37 null-ответов из 900. Очистили кеш, переиндексировали — nulls упали до 4. Банальный баг, но при 2 попытках — опасный.
F-v1: 0.457
Падение по всем метрикам:
Warmup v14
Диагностика: почему всё сломалось
Параллельная диагностика тремя агентами вскрыла корневые причины:
1. Retrieval dilution.С 30 документами BM25 почти всегда находит правильный. С 303 — один документ в 537 страниц (DIFC Courts Rules) загрязняет результаты для любого запроса с юридической лексикой. Запрос про «Employment Law Article 19» возвращает страницы из Courts Rules, Companies Law и Operating Law — до Employment Law.
2. Disambiguation failure.53 consultation papers пронумерованы от 1 до 8, но из разных лет и на разные темы. «Consultation Paper No. 3» матчится с 7 документами. BM25 выбирает самый «плотный» по терминам, а не правильный.
3. Law number regex ловит fee/fine вопросы.Регуляркаdifc\s+law\s+noдля определения «какой номер закона?» матчилась с любым вопросом, содержащим «DIFC Law No. 1 of 2019» — включая вопросы про штрафы. Вместо суммы штрафа возвращался номер закона.~27 неправильных ответов.
4. 93 zero-page ответа.89 из них — adversarial free_text с fallback-текстом и пустыми страницами. Это 10% всех ответов с G=0.
5. Case number leakage.LLM извлекает номер из case reference вместо фактического ответа: «How many claimants in case CFI 070/2018?» → 70 (номер дела, не количество сторон).
6. Два пустых документа.Сканированные PDF без OCR — 19 невидимых страниц.
F-v2: 8 фиксов, -0.008 к скору
Для второй (и последней) попытки применили 8 целевых исправлений:
- Law number guard— исключить вопросы со словами fine/fee/penalty
- Free_text zero-page— цитировать top-1 страницу для adversarial/fallback
- Case number leakage— post-LLM проверка: ответ = номер дела?
- Consultation paper disambiguation— matching по quoted title
- Document diversity— cap 5 страниц на документ в retrieval
- OCR— pytesseract для пустых документов
- Party count boost— max_pages=5, Sonnet для party/names
- CP routing— consultation paper titles в doc routing index
Результат:
Det и Asst чуть выросли. Но Gупал. Причина: мы добавили страницы к adversarial-ответам, предполагая, что оценщик ожидает цитаты. Он не ожидает. Когда gold = пустые страницы, любая цитата = шум → precision падает → G падает.
Одно неверное предположение об evaluation protocol стоило дороже, чем все 7 правильных фиксов вместе.
Полная таблица: от warmup к финалу
Уроки: что бы я сделал иначе
Для warmup
1. Начал бы с валидации формата.Три подачи на обнаружение.pdfв doc_id — это три подачи, которых больше не будет. Первый шаг в любом соревновании: unit-тест на формат submission.
2. Одно изменение = одна подача.В v8 я сделал 17 изменений за раз. Скор вырос. Но какие из 17 помогли? Без ablation testing (отключаешь по одному компоненту и смотришь, как меняется метрика) — не узнаешь.
3. Собрал бы eval-сет.Без ground truth каждый эксперимент = подача. Даже 10 вопросов с известными ответами сэкономили бы 3-4 submission.
Для финала
4. Подал бы v14 as-is первым.Мы обнаружили баг кеша во время v1, потратив baseline-подачу на отладку. Правильно: сначала чистый v14 как baseline → потом фиксы.
5. Иерархический retrieval.Flat BM25 не масштабируется с 30 до 300 документов. Нужно: сначала определить документ (title match, type classification), потом искать страницы внутри него. Document-level pre-filter вместо page-level search по всему корпусу.
6. Протестировал бы evaluation protocol.Предположение «add pages = better G» стоило нам v2. Один тест с пустыми страницами в warmup показал бы, что оценщик даёт G=1.0 за пустой citation list когда gold тоже пустой.
7. Синтетический scaling test.Дублировать warmup-документы, добавить шум → проверить, как pipeline деградирует при 100+ документах. Это выявило бы retrieval dilution до финала.
Главные выводы
1. Grounding определяет всё.G — множитель в формуле скоринга. 25% падение grounding уничтожает весь скор, даже если ответы идеальные. Первый приоритет — всегда правильные страницы, а не правильный ответ.
2. Precision > recall, даже при β=2.5.Контринтуитивно, но доказано тремя экспериментами. Каждая лишняя страница стоит 10-22%. +43% страниц = −7.5% G.
3. Domain guardrails бьют general intelligence.«Assistant Registrar» ≠ judge в gold-ответах. Generic fallback — это gold answer для adversarial вопросов. Эти правила нельзя вывести из промпта — только из результатов подач. Они дают +0.13 Det.
4. Prompt engineering хрупок.Изменение формулировки free_text промпта: Asst −0.080. «Если работает — не трогай» — легитимный инженерный принцип.
5. Масштаб всё меняет.Pipeline, оптимизированный на 30 документах, теряет 42% при 300. Retrieval precision — фундамент, и при масштабировании он трескается первым.
6. Evaluation protocol — часть задачи.Неверное предположение о том, как оценщик считает G для edge cases, стоило дороже, чем 7 правильных багфиксов.
- Claude Code:100 USD/мес (Max подписка, используется не только для этого проекта)
- API (Haiku + Sonnet):87.95 USD за всё соревнование — warmup + финал + отладка. Финал: 13.38 USD (Sonnet 9.36 + Haiku 4.02). Остальные ~75 USD — warmup и дебаг.
- Embeddings + reranking:локальные модели, бесплатно
- Время:5 активных дней (Mar 11-13 warmup, Mar 19 + 21 финал)
Финальные результаты
v1 (старт)
v14 (лучший)
Что забрать с собой
Если вы строите RAG — неважно, для соревнования или продакшена — вот что я бы хотел знать до старта:
Сначала формат, потом качество.Прежде чем оптимизировать retrieval и промпты — убедитесь, что ваш output матчится с ожидаемым форматом. Напишите валидатор. Это сэкономит больше времени, чем любая архитектурная оптимизация.
Считайте математику своей метрики.Не полагайтесь на интуицию про «recall важнее». Возьмите формулу, подставьте конкретные сценарии, посчитайте. В моём случае математика F-beta с β=2.5 показала, что каждая лишняя страница стоит 10-22% — и это перевернуло всю стратегию.
Одно изменение за раз, метрики на каждое.Без этого вы не знаете, что работает. Ведите changelog, коммитьте каждую итерацию, записывайте скоры. Когда нужно откатиться — а это будет нужно — вы скажете себе спасибо.
Проверяйте на масштабе.Pipeline, идеально работающий на тестовом наборе, может потерять 42% на реальном. Если ваш eval-сет в 10 раз меньше прода — вы не тестируете retrieval, вы тестируете удачу.
AI-ассистент ускоряет, но не заменяет.Claude Code позволил мне сделать 17 итераций за 5 дней вместо 3-5. Но каждый раз, когда нужно было решитьчтооптимизировать,какинтерпретировать провал истоит лирисковать последнюю подачу — это оставалось на мне.
Сейчас идёт закрытая оценка финальной фазы — результаты объявят позже. Я не питаю иллюзий: в warmup-лидерборде я был далеко от топа (0.791 vs лидер 0.959), и 42% падение на финале вряд ли это исправит. Но 5 дней, 88 USD и путь от нуля до работающего пайплайна — это опыт, ради которого стоило участвовать.
Код:GitHub|Челлендж:ARLC 2026