Skip to main content

Documentation Index

Fetch the complete documentation index at: https://cognis.vasanth.xyz/llms.txt

Use this file to discover all available pages before exploring further.

Langfuse is the default production observability backend for Cognis. The cognis-trace crate ships a TracingHandler that translates Cognis runtime events into Langfuse spans, generations, and scores — including token usage, USD cost, prompts, and trace metadata. Three steps and you’re shipping data.

Step 1 — Add the dependency

cognis-trace = { version = "0.3", features = ["langfuse"] }

Step 2 — Set credentials

export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...
export LANGFUSE_HOST=https://cloud.langfuse.com   # optional, override for self-hosted

Step 3 — Build a TracingHandler

use cognis_trace::{LangfuseExporter, TracingHandler};

let handler = TracingHandler::builder()
    .with_exporter(LangfuseExporter::from_env()?)
    .with_default_pricing()
    .build();

Step 4 — Attach to your runs

TracingHandler implements CallbackHandler. Wrap it with HandlerObserver to use it on a RunnableConfig:
use std::sync::Arc;
use cognis::prelude::*;
use cognis_core::HandlerObserver;

let observer: Arc<dyn Observer> = Arc::new(HandlerObserver(handler));
let cfg = RunnableConfig::default().with_observer(observer);

agent.run(Message::human("…")).await?;
Every chain, model call, tool, and graph node now becomes a span in Langfuse.

Step 5 — Drain on shutdown

TracingHandler runs background batchers per exporter so a slow Langfuse never blocks your hot path. Before the process exits, drain them:
handler.shutdown().await;

Trace metadata

Set session / user / release / environment so traces are filterable in the Langfuse UI:
use cognis_trace::{meta::merge_into, TraceMeta};
use serde_json::Value;

let metadata = [
    TraceMeta::session("session-abc"),
    TraceMeta::user("user-123"),
    TraceMeta::release("v1.4.2"),
    TraceMeta::environment("production"),
]
.into_iter()
.fold(Value::Null, merge_into);

let cfg = RunnableConfig::default()
    .with_observer(observer.clone())
    .with_tag("checkout");
let cfg = RunnableConfig { metadata, ..cfg };
The handler promotes these fields to trace-level when the trace root closes.

Multiple exporters

Stack stdout (for local logs) and Langfuse (for production) on the same handler:
use cognis_trace::{StdoutExporter, LangfuseExporter, TracingHandler};

let handler = TracingHandler::builder()
    .with_exporter(StdoutExporter::compact())
    .with_exporter(LangfuseExporter::from_env()?)
    .with_default_pricing()
    .build();
Each exporter has its own background batcher. A failure in one doesn’t block the other; you can read per-exporter metrics with handler.stats(name).

How it works

  • TracingHandler is a CallbackHandler. Lifecycle hooks turn into Langfuse spans (chains, tools, retrievers) and generations (LLM calls).
  • Token usage and cost are computed automatically. with_default_pricing() loads a 2026-05 snapshot for the major providers; override with override_price for negotiated rates.
  • Trace nesting follows parent_run_id. Every Cognis composition site already propagates this — sub-runs nest correctly.
  • Batching keeps the hot path fast. No blocking HTTP in on_llm_end. Slow Langfuse → batches accumulate → emergency dropping (logged) before they OOM.

Beyond traces

cognis-trace also surfaces:

What about OpenTelemetry?

OpenTelemetry support is on the roadmap. Until it ships, implement TraceExporter (it’s a thin async trait) and forward to your OTel collector — the batching infrastructure is already there, you only write the format conversion. The same path works for any other observability backend you’d want to send spans to.

See also

Cost tracking

USD cost per request from a price table.

Prompts and scores

Versioned prompts and eval scores.

Callbacks and observers

The lower-level interface.