Рассматриваем варианты; конкретный driver — algo-hub (нет инструментации, BG-1429 followup), но решение задаёт pattern и для остальных сервисов где сейчас manual wrapping.
Контекст
/api/analyze-stream в algo-hub делает ~12 LLM-call’ов на одну patient analysis: один selection-call к algorithmSelectionAgent (242s) + по 3-4 multi-turn LLM-call’а на каждый calculator без hardcoded formula к computeAgent/fallbackAgent. В bifrost admin API эти 12 записей flat — без иерархии «analyze-stream → phase → calc → LLM call». Хочется чтобы Langfuse рендерил один trace tree с правильной структурой business → LLM, а не россыпь несвязанных generations.
Текущая инструментация в стеке — manual callback wrappers withTrace/withSpan/withGeneration в packages/analysis-core/src/lib/langfuse-wrapper.ts (используется в analysis-worker, b2c-dashboard). Они работают, но требуют:
- import wrapper’а в каждом call-site
- проброс
parent: LangfuseTraceClient | LangfuseSpanClientчерез signature chain нижестоящих функций - обёртку каждого LLM-call’а в
withGeneration(parent, ...)отдельно
Для algo-hub’а с 2 .generate() call-sites + Mastra agents под капотом — этот pattern добавляет signature-проп langfuseParent в selectAlgorithms, computeWithLLM, computeCalculatorSmart (3 функции × 5 LOC ≈ 15 LOC + изменение публичных контрактов).
Ищем менее инвазивный подход.
Рассматривали
1. Bifrost-only OTel plugin → Langfuse
Bifrost имеет встроенный OTel plugin (bifrost upstream docs/features/observability/otel.mdx); при включении пушит каждый LLM-call как OTLP span в Langfuse OTLP endpoint (https://hipaa.cloud.langfuse.com/api/public/otel/v1/traces). Trace-format genai_extension — OTel GenAI semantic conventions (model, tokens, cost, prompt, response в attributes). HTTP/protobuf, gRPC не поддерживается Langfuse.
Pros: zero кода в любом приложении. Покрывает все сервисы стека за одну config-правку в bifrost values.yaml. Тоже unblock’ает наш b2b-platform analytics dashboard который сейчас зависит от Langfuse-traces.
Cons: flat — каждый LLM-call отдельный trace, без иерархии. Когда у одного analyze-stream’а 12 LLM-вызовов, в Langfuse это 12 несвязанных traces, корреляция только по timestamp/user-id если их сами выставляем. Не даёт answer на «сколько времени из 281s — selection vs match vs compute».
2. Manual Langfuse SDK wrappers (текущий pattern)
withTrace({ traceId, spanName, input }, async (root) => { withSpan(root, ..., async (span) => ...) }) — callback-style. Каждый span сам делает .end() в try/catch + flushAsync() на выход из root.
Pros: уже work’ает в analysis-worker / b2c-dashboard. Известный pattern, документирован своим кодом. Полный контроль над тем что попадает в span.
Cons: signature change в нижних функциях (langfuseParent proapping). Каждый LLM-call отдельно оборачивается withGeneration (дубль информации, которую и так знает Mastra/bifrost). Verbose: один analyze-stream = ~25 LOC wrapping. Vendor-coupled (Langfuse-specific API).
3. Mastra OtelBridge + OTel NodeSDK
@mastra/otel-bridge — adapter который читает OTel ambient context (AsyncLocalStorage) и автоматически создаёт child spans для каждого agent.generate() call. Setup один раз в instrumentation.ts (Node SDK + OTLPTraceExporter на Langfuse) + один раз в Mastra-config (bridge: new OtelBridge()).
В business-коде — одна обёртка на route: tracer.startActiveSpan("analyze-stream", async () => { ...existing... }). Внутри все .generate() от трёх Mastra agents автоматически становятся children. Tool calls, multi-turn LLM responses — все вложены правильно через OTel ALS.
Pros: минимум кода (~30 LOC включая instrumentation.ts + helm values). Никаких signature changes в нижнем коде. Native vendor-neutral OTel pattern. Combines с bifrost OTel plugin через стандартный traceparent HTTP header — bifrost spans становятся children Mastra-spans автоматически (полный e2e tree от browser→app→bifrost→provider).
Cons: новые dependencies (5 OTel packages + 2 Mastra packages). Загрузка instrumentation.ts до Mastra code — Next.js 13+ native hook, но требует verify в проде. Compatibility @mastra/otel-bridge с Mastra 1.1.0 — TBD verify. Pattern шаг в сторону от существующего withTrace/withSpan в стеке — open вопрос про migration / coexistence.
4. Vercel AI SDK experimental_telemetry
@ai-sdk/openai (Mastra использует под капотом) поддерживает experimental_telemetry: { isEnabled: true, functionId: '...' } per-call. Эмитит OTel spans с model/tokens/prompt.
Pros: ещё один path к auto-tracing без Mastra-specific bridge.
Cons: per-call параметр (нужно проставлять в каждый generate-call → similar pain to manual wrappers). Mastra скрывает AI SDK call site — добавить telemetry означает либо patch Mastra либо forking. Менее clean чем OtelBridge для нашей ситуации.
Кандидат-направление
Комбинация #1 + #3: bifrost OTel plugin для baseline gateway-level coverage (zero app code, моментальная видимость), плюс Mastra OtelBridge в algo-hub для tree-структуры с business root span. Bifrost spans автоматически прицепляются как children через traceparent propagation (OTel HTTP instrumentation выставляет header при fetch к bifrost).
Этапы (порядок риска):
- Шаг 1 (low risk): bifrost OTel plugin. Config-only change в
argocd-applications/common/bifrost-proxy/values.yaml+ 1Password секретGCP_APP_BIFROST_LANGFUSE_AUTH. Сразу даёт flat coverage всех LLM-call’ов через bifrost для всех сервисов. - Шаг 2 (medium risk): algo-hub Mastra OtelBridge. ~30 LOC: instrumentation.ts + central Mastra instance с OtelBridge + 2-строки обёртка в
/api/analyze-stream/route.ts. Даёт tree через AsyncLocalStorage propagation. - Шаг 3 (optional): добавить per-phase
tracer.startActiveSpan("phase:select", ...)для ещё более чёткой группировки + business metadata (calc_id, etc.) черезtracingOptions.tags.
Что нужно для разрешения
- Verify
@mastra/otel-bridgeсовместим с@mastra/core@1.1.0(npm + changelog) — есть ли релиз; есть ли breaking-change между Mastra-versions - Понять текущую Mastra-init structure в algo-hub: агенты сейчас инстанцируются inline в
select-algorithms.ts:203иcompute-with-llm.ts:230, без centralnew Mastra({...})instance. Для OtelBridge нужен центральный instance. Refactor — sized - Решить про duplication. Если включён bifrost OTel и Mastra OtelBridge — каждый LLM-call от algo-hub потенциально создаст две generations в Langfuse: одна от Mastra (через ai-sdk side), одна от bifrost (server-side). Trace propagation должна их слинковать как parent-child (Mastra parent, bifrost child), но реальный shape зависит от того кто эмитит generation-attributes. Verify в Langfuse UI на test trace
- Решить статус существующих
withTrace/withSpan/withGenerationв analysis-worker / b2c-dashboard. Сосуществование с OtelBridge или гибридный pattern — TBD. Migration не блокирует первый roll-out - Назначить driver + window (Шаг 1 — quick, Шаг 2 — нужен PR review + test)
Следствия (если выберем кандидат-направление)
- Algo-hub: новые dependencies (5 OTel + 2 Mastra packages); один
instrumentation.ts; central Mastra instance (рефакторинг inline-agent definitions); 2-строки обёртка route. Нет правок вselectAlgorithms/computeWithLLM/computeCalculatorSmart/lib/model.ts. - Bifrost: enable OTel plugin в config.json; новый externalsecret
LANGFUSE_AUTH. Все сервисы за bifrost получают per-call trace в Langfuse автоматически (включая b2c-dashboard / analysis-worker / b2b-api — даже хотя у них уже есть Langfuse SDK). - Существующие services с Langfuse SDK: дублирование generations. Либо acceptable (gateway level дополняет business level), либо требует обсуждения. Возможный путь — disable bifrost OTel для virtual_keys которые соответствуют сервисам с app-level SDK, или filter в OTel collector конфиге. TBD.
- Wiki: llm-observability-stack нужно обновить — текущее «algo-hub не интегрирован» становится «через Mastra OtelBridge + bifrost OTel push». Visibility matrix меняется. App-side rejection blind spot частично закрывается per-Mastra agent generation.
- Long-term: pattern переиспользуется для b2c-dashboard / analysis-worker / b2b-api. Migration от manual
withTraceк OtelBridge — отдельный track, не блокирует.
Открытые вопросы
- Compatibility
@mastra/otel-bridge× Mastra 1.1.0 — TBD verify - Central Mastra instance в algo-hub — refactor scope?
- Duplicate generations (bifrost OTel vs Mastra OtelBridge на одном LLM-call) — нужен реальный test trace в Langfuse, посмотреть как parent-child выстраивается. Гипотеза: stack ground truth = bifrost generation (с full input_history + tokens + cost), Mastra-level = wrapping span (timing + agent name + tool sequence)
- Migration
withTrace/withSpan→ OtelBridge в analysis-worker / b2c-dashboard — нужно ли, когда, кто - Retention concerns. Bifrost SQLite уже ephemeral (llm-observability-stack §Retention) — push в Langfuse решает retention для трейсов; bifrost SQLite остаётся как local debug. Но Langfuse cost при scale — TBD measure
- Authoring agent.generate metadata. Текущие Mastra agents в algo-hub не передают
tracingOptions.tags— для бизнес-context (calc_id, phase) нужно добавить per-call. Это не блокирует baseline tree, но улучшает filtering / aggregation в Langfuse UI
Связано
- llm-observability-stack — что-где-видно (parental concept page)
- observability-stack — surface map; LLM-flow один из доменов
- llm-resilience-stack — paired: transport reliability (что не сломалось)
- bifrost — entity-page proxy: OTel plugin + admin API
- langfuse — entity-page observability: cloud HIPAA region, OTLP endpoint
- mastra — TS framework; agents в algo-hub
- llm-call-failure-classes — таксономия сбоев; observability таксономии оршогональна но complement
- no-self-rolled-queues — analogous principle: «не велосипедь то что фреймворк сам делает»; здесь параллель — не велосипедь span-management поверх Mastra-already-instrumented call site
Источники
Сноски
-
Сессия
ildar/772c82be, 2026-05-13 — `) — практический trigger: обнаружено что algo-hub без Langfuse. ↩ -
Mastra docs: [OTel Bridge](, accessed 2026-05-17, https://mastra.ai/docs/observability/tracing/bridges/otel — OTLP Exporter. ↩
-
Langfuse OpenTelemetry integration [langfuse.com/integrations/native/opentelemetry](, accessed 2026-05-17, https://langfuse.com/integrations/native/opentelemetry — OTLP HTTP/protobuf поддержка, HIPAA endpoint. ↩