Multi-layer visibility конкретно для LLM-flow: как LLM-вызов из браузера до Gemini/OpenAI и обратно проходит через 4 слоя — client ↔ edge LB ↔ application ↔ gateway ↔ provider — и какой слой что видит. Ни один слой в одиночку не закрывает картину «что упало где»; observability = сшить сигналы с разных уровней по trace-id / timestamp / request-id.
Это одна из surfaces в общей карте observability-stack (там же — infra/k8s, FHIR audit, billing, product analytics и т.д.). Парная — llm-resilience-stack (что делаем чтобы транспорт не сломался); эта — что видно когда что-то происходит.
Контекст
LLM-вызов из браузера до Gemini/OpenAI и обратно проходит как минимум 5 hop’ов: browser → Cloudflare → GCP Gateway LB → application pod → bifrost → provider. Каждый hop может сломаться (timeout, network, governance reject, OOM, panic). Без cross-layer корреляции отладить «UI показал network error через 2 минуты» невозможно — каждый слой видит только свой обрывок.
Слои (по роли)
Edge — Cloudflare + GCP Load Balancer
Internet-facing TLS termination + reverse proxy + DDoS / Cloud Armor. Все request’ы к *.bloodgpt.tech сейчас проходят через CF (см. bloodgpt-stage-cf-only security policy на BackendService).
Что пишет в Cloud Logging: GCP LB access logs. Включаются на BackendService через GCPBackendPolicy.logging.enabled: true + sampleRate (parts-per-million; 1000000 = 100%). Алго-hub в стейдже стоит на 100%.
Полезные поля каждой записи: httpRequest.requestUrl, httpRequest.latency (e2e от первого до последнего байта на LB-side), httpRequest.status, httpRequest.responseSize, httpRequest.userAgent, httpRequest.remoteIp.
Доступ — gcloud --project=bg-stage-general logging read 'httpRequest.requestUrl=~"<host>" AND timestamp>="..."' или Logs Explorer в Console.
Что НЕ видим: body (только sizes), per-step внутри backend, что было до CF (browser errors), причины timeout’а (только что он случился).
CF-side имеет свои analytics (cf-ray, rate limiting events) — у нас сейчас не подключены к нашему observability pipeline.
Application — algo-hub, b2c-dashboard, analysis-worker, b2b-api, …
Бизнес-логика. Делает LLM-вызов (через bifrost), декодирует, складывает в FHIR / SSE-стрим / DB. Главное место где смысл запроса встречается с транспортом.
Что пишется куда (по сервису):
- langfuse (HIPAA region):
analysis-worker,b2c-dashboard,b2b-api,recommendations-portal,patient-portal— традиционная инструментация: trace на бизнес-операцию (напримерBlood Test Analysis - <patientId>), generation на каждый LLM-call (модель, prompt-version, токены, latency, cost, score’ы). См. список services-which-trace на langfuse. - stdout (kubectl logs / Cloud Logging) — у всех сервисов; что попало в
console.*или structured logger. Через GKE Cloud Logging доступноkubectl logs deploy/<svc>или через Logs Explorer (resource.type="k8s_container"). algo-hub— Langfuse НЕ интегрирован (no SDK import, no env vars, no secrets). Per-prompt observability отсутствует; виден только stdout + сторонние слои (LB, bifrost).- Sentry / front-end error tracking — пока не подключён ни в одном сервисе (TBD verify).
Batching & durability gotcha (Langfuse). SDK по умолчанию накапливает events в памяти и flush’ит каждые 30s / 100 events / при flushAsync() / shutdown(). Если процесс упал до flush — последние traces теряются. Pattern, который покрывает обрыв: await langfuse.flushAsync() сразу после generation.end() — стоит 50–200ms, но гарантирует persist на каждом step (важно для долгих SSE-handler’ов). Дополнительно — SIGTERM handler с langfuse.shutdown() чтобы k8s rolling restart успел flush до kill.
Что НЕ видим на app-уровне: что случилось с request до того как handler получил его (CF/LB) и что было после того как bifrost вернул response (если app упал/задержался при post-process — Langfuse trace будет «успешный generation, потом ничего»).
Gateway — Bifrost
Прокси между application и LLM-provider’ом (Vertex, OpenAI). Server-side по отношению к caller’у. См. bifrost.
Текущее состояние (с 2026-05-19): internal logging отключён. В configmap стоит client.enable_logging: false + logs_store.enabled: false. Bifrost больше не пишет per-call inference data ни в SQLite, ни куда-либо ещё — он работает как чистый passthrough-прокси. Rationale, альтернативы (Bifrost native flags / Postgres logs_store / PVC / emptyDir / OTLP-export в Langfuse / stdout→Cloud Logging) и Phase 2-триггеры — bifrost-logging.
Что осталось от gateway-слоя как источника наблюдаемости:
stdout— startup, plugin status, kube-probe metrics, HTTP-access events для admin UI. Inference requests в stdout не идут (enable_logging: falseотключает их).- Bifrost admin UI на
:8080— конфигурация providers и virtual keys,/api/providers,/api/config. Доступ через port-forward (Bash(kubectl --context gke_bg-stage-* -n common-bifrost-proxy port-forward *)в.claude/settings.local.json). Per-call dashboard и/api/logsбольше не возвращают данные — endpoint отдаёт SPA-fallback HTML вместо JSON. - Prometheus plugin — capability есть, не активирован (
prometheus plugin not foundв startup). Активация даст p50/p95/p99 latency, RPS, error-rate per provider в pull-режиме. До Phase 1 это давало бы aggregated stats поверх per-call SQLite; теперь — единственный возможный канал для gateway-side метрик, поэтому ценность включения выросла. - Provider-side проблемы видны для caller’а как HTTP-статус ответа bifrost’а (5xx от Vertex, rate-limit от OpenAI, timeout по
default_request_timeout_in_seconds: 600) — но без следа на gateway. Если у caller’а есть Langfuse — он зафиксирует failure там; если нет (Algo-hub) — теряется.
Governance rejects не expose’ятся. VK denials, provider routing rejects (Provider 'vertex' is not allowed for this virtual key, наблюдалось в BG-1429), allowed-models violations — application получает HTTP 403 от bifrost, gateway-side следа нет ни в stdout, ни в admin UI, ни через REST. До Phase 1 они тоже не были в /api/logs (плагин отказывает в request middleware до logging-хука) — изменение симметричное, но теперь и audit-recovery через sqlite3 на pod’е невозможен.
До 2026-05-19 на этом слое работало (теперь нет):
GET /api/logs?limit=N&offset=Mpaginated list +GET /api/logs/<id>detail сoutput_messageиinput_history— использовалось для post-hoc reconstruction app-side rejection (BG-1429: bifrost фиксировал 6 compute-LLM-calls со status=success, app-side отвергал 3 по schema mismatch — без app-side инструментации причина recoverable была через bifrost detail-records). Workflow описан в bifrost-logging для исторической attribution; восстановление теперь требует либо app-side инструментации, либо Phase 2 gateway-audit.- Двухуровневые retention-quirks (pod restart обнуляет SQLite + count/size-based rotation на живом pod’е поверх 365-day retention — записи ~37 минут old недоступны при uptime 157m) — больше не релевантны, нет SQLite.
Provider — Vertex AI / OpenAI / Anthropic
Сами LLM-провайдеры пишут aggregated usage и billing в свои dashboards (GCP Cloud Console → Vertex AI metrics, OpenAI Platform → Usage). Это не наш primary observability канал — задержка часов до видимости + только counters/quotas, не per-request.
Используется только когда: rate-limit / quota подозрение, billing reconcile, проверить что provider-side не деградировал отдельно от нашего стека. См. vertex-gemini-quotas для конкретики Vertex.
Visibility matrix — что-где-видно
Колонка Bifrost отражает реальность с 2026-05-19 — internal logging выключен (bifrost-logging). До Phase 1 gateway-слой покрывал большую часть событий per-call (latency, provider errors, token usage, cache hit, client disconnect); теперь он чистый passthrough и не источник для observability.
| Событие | Edge LB | App / Langfuse | Bifrost | Provider | Где смотреть |
|---|---|---|---|---|---|
| e2e request latency (browser→browser) | ✓ | partial | ✗ | ✗ | LB access logs |
| Per-step business latency (selectAlgorithms vs compute) | ✗ | ✓ (если Langfuse есть) | ✗ | ✗ | Langfuse traces |
| LLM-call latency (caller→provider→caller) | ✗ | ✓ | ✗ (logging off) | ✓ (aggregated) | Langfuse only |
| Provider 5xx / timeout | ✗ | ✓ (если app caught + flushed) | ✗ (logging off) | ✓ (с задержкой часов) | Langfuse first, provider dashboards backup |
| Governance reject (VK / provider not allowed) | ✗ | ✓ (caller получает 403) | ✗ | n/a | caller stdout via 403 error |
| Token usage / cost | ✗ | ✓ | ✗ (logging off) | ✓ (billing) | Langfuse |
| Cache hit | ✗ | ✗ | ✗ (logging off) | ✗ | не видно нигде |
| Client TCP close (mid-response) | ✓ (как short latency) | partial | ✗ (logging off) | ✗ | LB only |
| App crash после LLM response | ✗ | partial (если flush был) | ✗ | ✗ | app stdout / Sentry pending |
| CF/LB idle cut (100s CF, 600s LB) | ✓ | ✗ | ✗ | ✗ | LB access logs only |
| Frontend / browser error | ✗ | ✗ | ✗ | ✗ | браузер DevTools / Sentry pending / PostHog |
Слепые пятна
- Algo-hub без Langfuse — absolute blind per-LLM-call. Internal multi-step structure (
selectAlgorithms→matchCalculators→computeCalculatorSmart× N) полностью invisible. До 2026-05-19 хоть bifrost через/api/logsфиксировал «что-через-него-прошло»; после отключения internal logging (bifrost-logging) и этот канал ушёл. Виден только e2e на LB и общий stdout — кто кого триггерит, в каком порядке, что было fallback’ом, что вернул LLM — никто не знает. Это самый критичный gap в current state — приоритет на интеграцию@langfuse/nodeSDK в algo-hub. - Inter-layer gap caller ↔ provider. Bifrost вернул success → caller начал stream → CF cut на 100s → юзер видит
network error. С отключённым bifrost-logging gateway-side следа нет. LB: latency 100s status=200. App: Langfuse generation закрыт ранее. Только сшивка двух слоёв (LB + Langfuse app-side) по timestamp восстанавливает картину; до Phase 1 у нас был третий слой (bifrost /api/logs) для cross-reference — теперь он отсутствует. - Frontend errors invisible. Если React упал при рендере SSE chunk’а — нет ни логов, ни алертов. PostHog в b2c-dashboard есть как product analytics, но не как error tracker; Sentry в стек не интегрирован.
- Bifrost own state. Internal logging выключен, поэтому bifrost crash больше не теряет последние inference entries (терять нечего). Зато сам факт degraded состояния — GC thrashing, FD exhaustion, plugin chain stuck — наблюдается только через
kubectl top podи/healthendpoint. См. bifrost-memory-model для диагностики. - Governance rejects gateway-side untracable. VK denials, provider routing rejects, allowed-models violations не expose’ятся ни в
/api/logs(logging off), ни в stdout. Caller получает 403, gateway-side audit-recovery невозможна. Если возникнет регуляторный или дебаг-кейс — потребуется Phase 2 (bifrost-logging).
Связанные решения
- llm-proxy-choice — выбор bifrost как gateway, включая observability tradeoffs
- no-self-rolled-queues — почему retries на уровне inngest, не в коде каждого сервиса (логирование retry decisions — там же)
Открытые вопросы
- Algo-hub Langfuse integration — pending, резко повысился приоритет с 2026-05-19. Нужен
@langfuse/nodeSDK +LANGFUSE_*env через externalsecret (как у b2c-dashboard) + wrapselectAlgorithmsиcomputeCalculator. Без этого/api/analyze-streamостаётся абсолютно слепым endpoint’ом — bifrost больше не fallback-канал. - Bifrost prometheus plugin — capability есть, не активирован (
prometheus plugin not foundв startup log). Активация даст RPS / latency percentiles / error-rate per provider в pull-формате. Теперь это единственный возможный канал для gateway-side aggregated stats — ценность включения выросла. - Phase 2 gateway audit logs — пересматривается по триггерам (bifrost-logging): если algo-hub Langfuse не покрывает all нужные сигналы, если потребуется debug «что именно отбил gateway» без app-side инструментации, если регуляторика потребует независимый audit trail. Кандидаты: OTLP-export в Langfuse-OTel (предпочтительный) или Postgres
logs_storeвinngest-analysis-db. - Frontend error tracking — нужен Sentry (или аналог) хотя бы для prod-сервисов. Сейчас полный blind spot.
- Cross-layer trace correlation. Сейчас сшивка делается вручную по timestamp. Был бы W3C
traceparentheader пропагандирован от browser → CF → app → bifrost → provider — distributed trace в Cloud Trace / Langfuse выстраивался бы автоматически.
Связано
- bifrost — vendor entity; admin UI на :8080 остаётся для governance/providers, но без per-call dashboard
- bifrost-logging — почему internal logging отключён, Phase 2 (OTLP/Postgres) триггеры — draft
- bifrost-memory-model — техническая карта memory pressure и failure modes на gateway-слое
- langfuse — application-level LLM tracing, HIPAA region, per-service integration
- llm-resilience-stack — парная страница, transport-layer fallbacks / retries / circuit breakers
- llm-call-failure-classes — таксономия: транспорт / схема / семантика — какой слой что отлавливает
- inngest — где живут retries / concurrency limits, и где логи step-runs