Status

active. Реализовано в Realai-plus/bifrost-plugins (локально), не задеплоено на стейдж на 2026-05-07.

Контекст

BloodGPT pipeline зовёт LLM с response_format: json_schema (structured output) на каждом шаге recognition / normalization / FHIR-генерации. На стейдже это:

  • Дорого по cost, особенно при rapid iteration
  • Медленно (Gemini Pro ~500ms, GPT-5.2 ~400ms на запрос; pipeline делает их десятками)
  • Недетерминированно (тестам сложно assert’ить против stochastic output’а)

При этом структуру output’а pipeline контролирует — он сам подаёт schema. Идея mock’а: получать обратно валидный JSON по schema, без LLM-вызова. Дальше pipeline может прогнать end-to-end (recognize → normalize → FHIR build → DiagnosticReport), без зависимости от network/API key/quota.

Старая попытка — TS apps/mock-llm-server в bloodgpt-for-business (BG-1068). Был standalone HTTP-сервер с json-schema-faker + medical biomarker dictionary + fixFhirValues для FHIR oneOf value[x]. Источник потерялся в working copy, hover в dangling commit ae3728e8. После перехода на bifrost как gateway вместо litellm — нужно либо restore TS-сервер и подключать как provider, либо переписывать как bifrost-плагин.

bifrost уже research-направление зафиксировал: «нашёл Go-плагин для Bifrost Proxy, который генерирует ответы по schema без реального LLM-вызова». Upstream maximhq/bifrost/plugins/mocker/ существует, но возвращает только canned strings — response_format: json_schema не понимает.

Рассматривали

Restore TS mock-llm-server, подключить как bifrost provider. Минимум кода. Минус — отдельный сервис в k8s (deployment + service + secrets), отдельный point of failure. Plus dependency Bun runtime в стейдже.

Один большой плагин который делает всё: schema generation + medical biomarker substitution + FHIR fix + latency simulation + error injection. Полностью замещает upstream mocker плюс наши фичи. Минус — domain-specific код (FHIR, medical) сцеплен с generic schema-faker’ом, тяжело поддерживать совместно с upstream.

Schema-only плагин без BloodGPT-специфики. Generic, ship’абельный upstream. Но тогда Parameter_Info в prompt’е никем не парсится, FHIR valueQuantity остаётся random — pipeline получает garbage values, тесты падают по non-trivial причинам.

Два плагина в цепочке — один generic schema-mocker, второй BloodGPT-specific fhir-postprocessor. Первый short-circuit’ит запрос синтетическим JSON. Второй extract’ит из prompt’а ParameterValue / Unit и патчит fhir_value в response уже сгенерированный первым.

Выбрали

Два плагина в цепочке.

schema-mocker (Realai-plus/bifrost-plugins/schema-mocker/):

  • Domain-agnostic: walks JSON schema, генерирует value матчинга shape
  • Pluggable preset registry: presets/medical/ — отдельный subpackage с biomarker / unit / interpretation generators, регистрируется через init()
  • PreLLMHook short-circuit: если запрос имеет response_format.json_schema.schema и model в allowlist’е — вернуть synthetic response

fhir-postprocessor (Realai-plus/bifrost-plugins/fhir-postprocessor/):

  • BloodGPT-specific
  • PreLLMHook: regex-extract Parameter_Info JSON из user-message → stash в BifrostContext
  • PostLLMHook: парсит assistant content, патчит fhir_value.valueQuantity extracted значениями
  • Безопасный no-op если в response нет fhir_value — можно держать enabled всегда

Plugin chain: fhir-postprocessor order=1 → schema-mocker order=2. Pre: fhir-Pre stashes → schema-Pre short-circuit’ит. Post (reverse): schema-Post no-op → fhir-Post patches.

Почему

  • Single responsibility. schema-mocker domain-agnostic — потенциально шиппабельный upstream maximhq. fhir-postprocessor BloodGPT-only, остаётся в нашем форке
  • Composable с upstream mocker. Latency simulation, error injection, MessageTemplate — можно делегировать upstream’у. Не дублируем то что уже есть
  • Нет отдельного сервиса. Оба плагина живут в том же процессе что и bifrost — никакого нового deployment / service / SA / secret. Деплой = build + push + image bump
  • Toggle layers. bifrost-mock-plugins подробно — header / model gating / plugin enable. Каждый layer отвечает своей аудитории (app dev / DevOps / operator)

Следствия

  • Plugin ordering критичен. Если operator случайно поменяет order в config.json — fhir-postprocessor не успеет stash’нуть до short-circuit’а. Тесты молча начнут получать random valueQuantity value. Гард не реализован — опираемся на знание operator’а
  • Streaming не покрывается. PreLLMHook гейтится на ChatCompletionRequest. ChatCompletionStreamRequest проходит through. BloodGPT pipeline на 2026-05-07 не стримит structured output, но если апп начнёт — silent break
  • dataAbsentReason не патчится. TS-parity. Если RNG выпадает на этой ветке oneOf — наблюдение пойдёт в pipeline как absent. У TS-варианта то же поведение, у нас same. Если на проде это начнёт ронять тесты — нужен force_value_quantity flag
  • Pinned bifrost-core версия. v1.5.8. Bump core требует одновременного rebuild наших плагинов + UI build. Drift detection — manual
  • Re-enable через runtime API сломан — описано в bifrost-custom-plugin-loading

Открытые вопросы

  • Стратегия model-gating’а в production: mock-suffix (gpt-5.2-mock) безопасный default vs all-models (mock everything) для max savings. Не зафиксировано
  • Submit schema-mocker upstream maximhq/bifrost. Плюс — community maintenance. Минус — coupling on upstream PR review pace, возможно дополнительный config knob под их governance
  • force_value_quantity flag в fhir-postprocessor — добавить если dataAbsentReason начнёт мешать на нагрузке

Связано

Источники

Источники: 1.

Сноски

  1. Upstream mocker plugin (для сравнения), accessed 2026-05-17, https://github.com/maximhq/bifrost/tree/main/plugins/mocker.