Tracing: Langfuse and OpenTelemetry
Every run becomes a trace: workflow nodes are spans, model calls are generations (model, input, output, tokens), tool calls are tool observations, failures are marked as errors. Export goes over OTLP/HTTP to Langfuse or any OpenTelemetry collector.
from criogenai import Agent, Telemetry
# LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL
langfuse = Telemetry.langfuse()
# Or any OpenTelemetry collector:
otel = Telemetry.otlp("http://collector:4318/v1/traces")
# Prompts and answers stay out unless you ask: capture_content=True.
agent = Agent("openai:gpt-5-mini", telemetry=langfuse) # every run is a trace
await agent.run("Where is order A-1001?", user_id="u-42")
await langfuse.flush() # before the script endsimport { Agent, Telemetry } from "criogenai";
// LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL
const langfuse = Telemetry.langfuse();
// Or any OpenTelemetry collector:
const otel = Telemetry.otlp("http://collector:4318/v1/traces");
// Prompts and answers stay out unless you ask: { captureContent: true }.
const agent = new Agent({ model: "openai:gpt-5-mini", telemetry: langfuse }); // every run is a trace
await agent.run("Where is order A-1001?", { state: { user_id: "u-42" } });
await langfuse.flush(); // before the script endsuse criogenai_telemetry::{Langfuse, OtlpConfig, OtlpExporter};
use serde_json::json;
// LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL
let langfuse = OtlpExporter::start(Langfuse::from_env()?)?;
// Or any OpenTelemetry collector, without message content:
let otel =
OtlpExporter::start(OtlpConfig::new("http://collector:4318").capture_content(false))?;
let options = agent
.run_options()
.observe(langfuse.observer())
.metadata(json!({"user_id": "u-42", "session_id": "chat-7"}));
agent.run_with("Where is order A-1001?", options).await?;
langfuse.flush().await;See runs on your own computer#
The local trace viewer shows every run on a page, with no account and nothing leaving the computer: model calls, tool calls and steps on a time axis, durations, tokens, cost (from your prices), failures, and where the time went (model, tools, CriogenAI and your code).
# In a terminal: python -m criogenai.trace (then open http://127.0.0.1:4318)
from criogenai import Agent, Telemetry
traces = Telemetry.local() # capture_content=True to see prompts and outputs too
agent = Agent("openai:gpt-5-mini", telemetry=traces)
await agent.run("Where is order A-1001?")
await traces.flush()// In a terminal: npx criogenai trace (then open http://127.0.0.1:4318)
const traces = Telemetry.local(); // { captureContent: true } to see prompts and outputs too
const agent = new Agent({ model: "openai:gpt-5-mini", telemetry: traces });
await agent.run("Where is order A-1001?");
await traces.flush();// In a terminal: criogenai-trace (then open http://127.0.0.1:4318)
use criogenai_telemetry::{OtlpConfig, OtlpExporter};
let traces = OtlpExporter::start(OtlpConfig::local())?; // .capture_content(true) for prompts
agent
.run_with(
"Where is order A-1001?",
agent.run_options().observe(traces.observer()),
)
.await?;
traces.flush().await;-
Start it with
python -m criogenai.trace,npx criogenai traceor thecriogenai-tracebinary, and openhttp://127.0.0.1:4318. Runs are kept in~/.criogen/traces.db(--memoryfor none): the newest 10 000, for 14 days. -
Prompts and outputs appear only with content capture on; otherwise you see structure, timing, tokens and error codes.
-
Cost: pass
--prices prices.json(USD per million tokens byprovider:modelor model name, e.g.{"gpt-5-mini": {"input": 0.25, "output": 2}}). CriogenAI ships no price list: a stale price is worse than none. -
It listens on
127.0.0.1only. To share one on a network, pass--allow-remote --token-env NAME; clients then send that token. -
It is an ordinary OTLP/HTTP (JSON) receiver, so the same runs can also go to Langfuse: give an agent one exporter, or run the viewer while you develop and Langfuse in production.
-
Never slows or breaks a run: the observer only queues events; a background task batches and sends them, and retries on transient errors.
-
Sensitive data: in every language, prompts, answers, tool data, error messages (they echo what failed) and the run's
user_id,session_idand custom metadata stay out of traces unless you setcapture_content=True/captureContent: true/.capture_content(true). Structure, timing, usage, status and error codes always go. Amaskfunction redacts what is exported when capture is on. -
Metadata:
name,release,environmentandtagsare always attached to the trace;user_id,session_idand any other JSON only with content capture on.