Перенос LLM-сервисов BloodGPT с Python на TS (Mastra-агенты или ai-sdk напрямую — см. mastra). Главная сложность портажа — parity-гэпы не видны на компиляции и часто не видны даже на бенчмарк-числах: TS проходит тесты, но генерирует subtly другой output, потому что где-то потерялся production-промпт, порядок полей в схеме, словарь или кейс. Эта страница — что мы из этого вынесли; конкретные кейсы — в session-дайджестах ниже.

Валидация портажа — только через реальный LLM-вызов

Process rule: каждый портированный шаг валидируется реальным LLM-вызовом с output-comparison, а не «compile-time проходит / теоретически работает / LLM может быть buggy — забьём».

Ильдар: «нужна ПОЛНАЯ ПРОВЕРКА. С LLM. НИКАКИХ ОБСУЖДЕНИЙ, ЧТО МЫ ЧТО-ТО НЕ БУДЕМ ПРОВЕРЯТЬ» (session dd17b3fd)

Обоснование — в портажах LOINC (833ec924 / 82806132) и V2 param analysis (4791d030) parity-гэпы находились только при реальных LLM-вызовах с side-by-side comparison; compile-time валидация их не ловит.

Production reference output — обязателен, бенчмарк-чисел мало

Parity на бенчмарк-числах недостаточен. Нужно:

  • production reference output на том же patient’е (например, full_report_output.json от Никиты — для V2 param analysis);
  • side-by-side text comparison сгенерированного контента;
  • трейсить промпты и version-pin’ить их.

В session 18b47185 после parity на бенчмарке всплыла divergence в промптах — продакшен уехал на новую версию промпта, которую TS-порт не скопировал («Погоди, у Никиты новый, у Никиты другой промпт, мы его не скопировали»). Без production-reference V2 проходит tests, но выдаёт subtly другой output.

Side-by-side VERBOSE comparison Python ↔ TS

Для портажа до архитектурного parity (а не только функционального) — обязателен side-by-side trace comparison: гонять один и тот же кейс в обоих пайплайнах и сравнивать score-traces. Отдельные баги через бенчмарк-числа не видны.

В LOINC-портаже (833ec924 + 82806132) эта стратегия нашла 4 разных issue, которые без VERBOSE-сравнения все выглядели бы как «LLM judgment difference»:

  • getGeneralSystems("bld") возвращал [] в TS (vs корректный array в Python) → +0.3 score bonus не применялся;
  • Zod stripping generalSystems на tool boundary (см. mastra § Gotchas);
  • compound keyword expansion отсутствовал;
  • кириллические единицы — на самом деле уже портированы (false alarm, verified).

Воспроизводить порядок полей в схеме точно

Порядок полей во входной/выходной схеме ("sex":"male" перед "age":35 и т.п.) влияет на iteration count и качество — модель «ловит» порядок при reasoning, по сути это chain-of-thought-сигнал. По нашей гипотезе это general LLM property (не Mastra-specific) — измерением строго не доказано, но при портаже воспроизводить порядок полей точно (не только содержание) дёшево и того стоит. Развёрнуто — structured-output-field-order-cot; всплыло в 4791d030.

snake_case ↔ camelCase — конвертация на границе LLM ↔ TS

LLM генерит output по схеме, которая для Python-parity остаётся snake_case (parameter_range_type, loinc_code, reference_range); TS-код следует JS-конвенции — camelCase. Смешивание на consumer-side даёт silent field-mismatch (один потребитель видит оба варианта, трактует как разные поля).

Решение — boundary conversion: на границе LLM-output → TS-consumer единая утилита (snakeToCamelDeep / inverse) конвертирует snake_case → camelCase; LLM-схема остаётся snake_case (Python parity), TS-downstream видит только camelCase.

Ильдар: «давай зафиксируем, да что на границех у нас происходит конвертация кейса» · «для других агентов тоже будет актуально… эта разница между кейсами» (session dd17b3fd)

Применимо ко всем портированным агентам. Открытое: где живёт утилита (packages/analysis-core vs packages/utils) и testing-конвенция.

Promise.all — TS может обогнать Python

Где LLM-вызовы независимы — в TS их гоняют конкурентно через Promise.all, что часто быстрее последовательного Python. V2 param analysis: 312s (TS, personalizer ∥ generator) vs 608s (Python, sequential) — вдвое (session 7cc1d514). Это инвертирует расхожее «Python быстрее, TS — overhead абстракций»: архитектурный паттерн важнее языка, и при портаже синхронного Python стоит искать места под Promise.all (независимые LLM-вызовы / tools).

Связано

Источники

Источники: 1 2 3 4 5 6.

Сноски

  1. Сессия ildar/833ec924, 2026-03-24 — ` (LOINC port).

  2. Сессия ildar/82806132, 2026-04-05 — ` (LOINC closeout BG-1140 — Zod stripping.

  3. Сессия ildar/4791d030, 2026-04-10 — ` (V2 param analysis BG-1191 — hidden layers.

  4. Сессия ildar/18b47185, 2026-04-15 — ` (V2 расширение — production-reference divergence).

  5. Сессия ildar/dd17b3fd, 2026-04-22 — ` (V2.

  6. Сессия ildar/7cc1d514, 2026-04-23 — ` (V2.