Multi-layer visibility конкретно для LLM-flow: как LLM-вызов из браузера до Gemini/OpenAI и обратно проходит через 4 слоя — client ↔ edge LBapplicationgatewayprovider — и какой слой что видит. Ни один слой в одиночку не закрывает картину «что упало где»; 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=M paginated 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 LBApp / LangfuseBifrostProviderГде смотреть
e2e request latency (browser→browser)partialLB 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/acaller 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 responsepartial (если 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 (selectAlgorithmsmatchCalculatorscomputeCalculatorSmart × N) полностью invisible. До 2026-05-19 хоть bifrost через /api/logs фиксировал «что-через-него-прошло»; после отключения internal logging (bifrost-logging) и этот канал ушёл. Виден только e2e на LB и общий stdout — кто кого триггерит, в каком порядке, что было fallback’ом, что вернул LLM — никто не знает. Это самый критичный gap в current state — приоритет на интеграцию @langfuse/node SDK в 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 и /health endpoint. См. 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/node SDK + LANGFUSE_* env через externalsecret (как у b2c-dashboard) + wrap selectAlgorithms и 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 traceparent header пропагандирован от 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

Источники