mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
Add Langfuse as an OTel trace transport for MCP request lifecycle tracking.
Changes:
- New src/lib/langfuse-transport.ts: initializes LangfuseSpanProcessor when
ASK262_LANGFUSE_ENABLED=true with valid credentials
- src/lib/tracing.ts: add metadata to TraceContext, propagate it to child spans,
use startActiveSpan for proper OTel context, add setupTracing() entry point,
add getSessionMetadata() for version/transport metadata
- src/lib/logger.ts: include TraceContext.metadata in JSON log mixin
- src/mcp-server-{http,stdio}.ts: call setupTracing() at startup, pass
version/transport metadata through withSpan/withSpanContext
- src/lib/fireworks-embeddings.ts: wrap API calls in withSpan() with gen_ai
attributes for Langfuse generation/cost tracking
- .env.example: add LANGFUSE_* configuration variables
- AGENTS.md: document Langfuse integration
- package.json: add @langfuse/tracing, @langfuse/otel, @opentelemetry/sdk-node
3.9 KiB
3.9 KiB
Langfuse Integration Plan
Dependencies
"@langfuse/tracing": "^1.0.0",
"@langfuse/otel": "^1.0.0",
"@opentelemetry/sdk-node": "^0.50.0"
Files
src/lib/langfuse-transport.ts (new)
import { NodeTracerProvider } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import pkg from "../../package.json" assert { type: "json" };
let provider: NodeTracerProvider | null = null;
export function initializeLangfuseTransport(): void {
if (process.env.ASK262_LANGFUSE_ENABLED !== "true") return;
if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) {
console.error("[LANGFUSE] Missing credentials, skipping");
return;
}
provider = new NodeTracerProvider({
spanProcessors: [new LangfuseSpanProcessor()],
});
provider.register();
}
export function getLangfuseSessionMetadata(transport: "http" | "stdio"): Record<string, unknown> {
return {
version: pkg.version,
transport,
};
}
src/lib/tracing.ts
Add metadata to TraceContext:
interface TraceContext {
traceId: string;
spanId: string;
parentSpanId: string | null;
depth: number;
metadata?: Record<string, unknown>;
}
Add setupTracing() function:
import { initializeLangfuseTransport } from "./langfuse-transport.js";
export function setupTracing(): void {
initializeLangfuseTransport();
}
Propagate metadata to child contexts in withSpan() and withSpanContext().
src/lib/logger.ts
Include metadata in Pino mixin:
mixin() {
const traceCtx = getTraceContext();
if (!traceCtx) return {};
return {
trace_id: traceCtx.traceId,
span_id: traceCtx.spanId,
parent_span_id: traceCtx.parentSpanId,
...(traceCtx.metadata && { metadata: traceCtx.metadata }),
};
}
src/mcp-server-http.ts
Add at top of main():
import { setupTracing } from "./lib/tracing.js";
setupTracing();
In /mcp handler, set session metadata on root span via withSpan attributes.
src/mcp-server-stdio.ts
Add at top of main():
import { setupTracing } from "./lib/tracing.js";
setupTracing();
Set session metadata on the process-scoped trace.
src/lib/fireworks-embeddings.ts
Wrap actual Fireworks API call in withSpan() with generation attributes:
import { withSpan } from "./tracing.js";
import { trace } from "@opentelemetry/api";
return await withSpan(
LogOperation.EMBEDDING_DOCUMENTS,
{ model: this.modelName, batch_size: this.batchSize },
async () => {
const activeSpan = trace.getActiveSpan();
activeSpan?.setAttribute("gen_ai.system", "fireworks");
activeSpan?.setAttribute("gen_ai.request.model", this.modelName);
activeSpan?.setAttribute("gen_ai.usage.input_tokens", estimatedTokens);
return embeddings;
}
);
.env.example
# Langfuse observability (opt-in)
# ASK262_LANGFUSE_ENABLED=true
# LANGFUSE_PUBLIC_KEY=pk-lf-...
# LANGFUSE_SECRET_KEY=sk-lf-...
# LANGFUSE_BASE_URL=https://cloud.langfuse.com
Session Strategy
| Transport | Session ID | Metadata |
|---|---|---|
| Stdio | sessionTraceId (existing process-scoped trace ID) |
{ version: "0.0.15", transport: "stdio" } |
| HTTP | x-request-id or generated UUID |
{ version: "0.0.15", transport: "http" } |
Metadata flows to both destinations:
- JSON logs: via
TraceContext.metadata→ logger mixin - Langfuse: via
langfuse.trace.metadataOTel span attribute
What stays unchanged
src/agent-tools/*— no changes, spans flow automatically- All tool internals — no changes needed
Trace structure
- Stdio: one trace per session, all tool calls share
sessionTraceId - HTTP: one trace per request
- Embedding calls: marked as
gen_aigenerations for cost tracking - All traces and logs include: version and transport metadata