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
154 lines
3.9 KiB
Markdown
154 lines
3.9 KiB
Markdown
# Langfuse Integration Plan
|
|
|
|
## Dependencies
|
|
|
|
```json
|
|
"@langfuse/tracing": "^1.0.0",
|
|
"@langfuse/otel": "^1.0.0",
|
|
"@opentelemetry/sdk-node": "^0.50.0"
|
|
```
|
|
|
|
## Files
|
|
|
|
### `src/lib/langfuse-transport.ts` (new)
|
|
|
|
```typescript
|
|
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`:
|
|
```typescript
|
|
interface TraceContext {
|
|
traceId: string;
|
|
spanId: string;
|
|
parentSpanId: string | null;
|
|
depth: number;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
```
|
|
|
|
Add `setupTracing()` function:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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()`:
|
|
```typescript
|
|
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()`:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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`
|
|
|
|
```bash
|
|
# 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.metadata` OTel 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_ai` generations for cost tracking
|
|
- **All traces and logs include**: version and transport metadata
|