langfuse-tracing

Use when an LLM app needs production tracing, monitoring or debugging of model calls, or production traces should feed offline eval datasets. Adds Langfuse with @observe or startActiveObservation, records metadata, cost and latency, scores traces and manages prompts as code.

/plugin install qa-llm-evaluation@testland-qa

First time in Claude Code? Add the marketplace with /plugin marketplace add testland/qa

View source (opens in a new tab)

Overview

Langfuse is a production LLM observability platform (per lf-gh (opens in new window)).

Step 1 - Install

Per lf-gh (opens in new window):

pip install langfuse

For TypeScript:

npm install @langfuse/tracing

Set up project credentials per Langfuse self-hosted or cloud project (LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY + LANGFUSE_HOST).

Step 2 - Instrument with @observe

Per langfuse.com/docs/sdk/python/decorators (opens in new window):

Python:

from langfuse import observe

@observe(name="llm-call", as_type="generation")  # auto-captures inputs, outputs, timings, errors
async def my_async_llm_call(prompt_text):
    return "LLM response"

TypeScript - create an observation, do work inside it, then end it:

import { startActiveObservation } from "@langfuse/tracing";

const { observation, end } = startActiveObservation({
  name: "llm-call",
  type: "generation",
  input: { prompt: promptText },
});

try {
  const result = await callMyLLM(promptText);
  observation.update({ output: result });
  return result;
} finally {
  end();
}

Validation: After your first instrumented call, open the Langfuse UI → Traces. You should see a new trace with nested observations, inputs, outputs, and timings. If no trace appears within ~30 s:

  • Confirm LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and LANGFUSE_HOST are set in the process environment.
  • Call await langfuse.flushAsync() (TS) / langfuse.flush() (Python) before process exit to force-drain the queue.
  • Check langfuse.debug() output for connection errors.

Step 3 - Update current observation with metadata

Per lf-py-deco (opens in new window):

from langfuse import get_client

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="data-processing"):
    langfuse.update_current_span(metadata={"step1_complete": True})

Common metadata fields used in production:

  • model - the model name (e.g., claude-haiku-4-5)
  • model_parameters - temperature / top_p / max_tokens
  • usage - input/output tokens, cost
  • tags - environment (prod / staging), feature flag, customer ID
  • level - DEBUG / DEFAULT / WARNING / ERROR

Step 4 - Score traces

Per langfuse.com/docs/scores (opens in new window):

langfuse.score(
    trace_id="...",
    name="answer_relevance",
    value=0.87,           # numeric (0-1); also supports categorical (string) and boolean
    comment="Judged by GPT-4 rubric"
)

Scores can come from:

  • Manual review (human raters via the Langfuse UI)
  • Automated eval (run a metric in batch, score by trace_id)
  • User feedback (thumbs-up / thumbs-down from the app)

Validation: After scoring, open the Langfuse UI → Traces → select the trace. The score should appear in the Scores panel. If missing, confirm the trace_id matches an existing trace and that the score call did not raise an exception.

Step 5 - Datasets for offline eval

Datasets are collections of (input, expected_output) items built from production traces, CSV / JSONL imports, or the UI. Run one against your app to diff vs a baseline:

items = langfuse.get_dataset_items(dataset_id="...")
for item in items:
    actual = my_llm_app(item.input)
    item.run(actual)  # links the run back to the dataset for diff vs baseline

See references/datasets.md for building datasets from production traces, the lf-ds (opens in new window) API signature, and the run-validation checkpoint.

Step 6 - Prompt management

Fetch the current production prompt at runtime and pin its version so prompt drift is traceable:

from langfuse import get_client

langfuse = get_client()

# Fetch the production-labelled prompt; falls back to cache if offline
prompt = langfuse.get_prompt("my-prompt-name", label="production")
compiled = prompt.compile(user_input=user_query)

# Pass compiled text to your LLM call
response = my_llm_call(compiled)

Iterate prompt text in the Langfuse UI and roll out new versions per environment (production / staging labels) without code deploys. Version history and A/B comparison are available in the UI.

Validation: After an instrumented call using a managed prompt, open the Langfuse UI → Traces → select the trace. The prompt name and version should appear in the trace metadata, confirming prompt version is pinned and attributable.

Step 7 - CI integration

Langfuse is observability-side, not pre-deploy CI-side. Post-deploy CI patterns query the Langfuse API for recent traces and assert on aggregate metrics (a score-query gate, eval-on-trace, cost-regression, score-based alerting). Runnable gate script and the full pattern list: references/ci-integration.md.

Anti-patterns

Anti-patternWhy it failsFix
Trace everything in production with no samplingCost explodes at scaleUse level=DEBUG + UI-side sampling (Step 3)
Score traces only via UI (no automated path)Can't catch silent regressionsAutomated langfuse.score() per trace (Step 4)
Pull production trace inputs without privacy reviewPII leakage into eval datasetsCross-ref synthetic-pii-generator for fixture sanitization before promotion
Skip prompt versioningPrompt drift breaks attributionlangfuse.get_prompt() with version pin (Step 6)
Conflate Langfuse with pre-deploy evalTries to be both; wins neitherPair Langfuse (post-deploy) with Promptfoo/DeepEval/Ragas (pre-deploy)

Version notes

Per lf-gh (opens in new window), the SDK was rewritten in v4 and released in March 2026; this skill targets the v4 API. v3 patterns are no longer supported - pin the SDK version in requirements, and for v3 codebases see the upstream migration guide.

Limitations

References

Langfuse is observability-side, not pre-deploy CI-side. Typical post-deploy CI patterns use the Langfuse API to query recent traces and assert on aggregate metrics.

Score-query gate

import httpx, os, sys
from datetime import datetime, timedelta, timezone

LANGFUSE_HOST = os.environ["LANGFUSE_HOST"]
headers = {"Authorization": f"Bearer {os.environ['LANGFUSE_SECRET_KEY']}"}

# Fetch average answer_relevance score over the last hour
one_hour_ago = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()
resp = httpx.get(
    f"{LANGFUSE_HOST}/api/public/scores",
    params={"name": "answer_relevance", "fromTimestamp": one_hour_ago},
    headers=headers,
)
scores = resp.json()["data"]
avg = sum(s["value"] for s in scores) / len(scores) if scores else 1.0

if avg < 0.75:
    print(f"answer_relevance regression: {avg:.2f} < 0.75 threshold")
    sys.exit(1)

Validation: After the CI job runs, confirm the script exits 0 and that the queried score window covers the expected deployment window. If scores is empty, verify the fromTimestamp range and that instrumented calls have been scored in that period.

Other CI wiring patterns

  • Eval-on-trace: post-deploy, run an offline eval sweep against production traces from the previous N hours; fail if regression.
  • Cost regression: alert on per-trace cost increase that exceeds budget.
  • Score-based alerting: route to PagerDuty / Slack / Datadog on drop in average score over a rolling window.
Reference

Langfuse datasets for offline eval

View source (opens in a new tab) (opens in new window)

Langfuse datasets are collections of (input, expected_output) items. Build them from three sources:

  • Production traces: promote real traces into a dataset for regression coverage. Sanitize PII before promotion - cross-ref synthetic-pii-generator.
  • CSV / JSONL import: load a curated fixture set.
  • UI: hand-author items.

Shipping traces from production into a dataset, then replaying them, is the core offline-regression workflow: item.run() links each replay back to the dataset so the Langfuse UI can diff the run against a baseline. See langfuse.com/docs/datasets (opens in new window) for the current API signature.

Validation

After running a dataset, open the Langfuse UI → Datasets → select your dataset. Each item run should appear under the Runs tab linked to its trace. If runs are missing, confirm dataset_id is correct and that item.run() did not raise an exception.