How to send traces to Logfire and LangSmith
In shortSend 10xGraph graph, node, LLM, and tool spans to Pydantic Logfire or LangSmith over OpenTelemetry using Python helpers, publishers, or 10xgraph.json.
- 3 min read
- 9 sections
- Updated
- v0.9.2
- Markdown
Pydantic Logfire and LangSmith are both OpenTelemetry backends. 10xGraph already reconstructs a full span tree (graph → node → LLM → tool) with GenAI semantic-convention attributes (gen_ai.usage.input_tokens, gen_ai.request.model, session.id, …) through its OtelPublisher. So sending traces to either backend means configuring the right OpenTelemetry TracerProvider/exporter, there is no per-vendor event plumbing.
You have three ways to wire it up:
- Python helpers,
setup_logfire,setup_langsmith, or the unifiedsetup_observability. - Dedicated publishers,
LogfirePublisher/LangsmithPublisher, if you prefer a publisher object to assign or compose. - Declarative config, an
observabilityblock in10xgraph.json(auto-wired by the API server; see below).
Install
pip install '10xgraph[logfire]' # Logfire
pip install '10xgraph[langsmith]' # LangSmith (OTLP HTTP exporter)
pip install '10xgraph[observability]' # both + otelThe langsmith extra pulls only the OpenTelemetry OTLP HTTP exporter, not the LangSmith SDK, because spans are sent over OTLP, not RunTree.
Secrets stay in the environment
Never put tokens in code or 10xgraph.json. Set them as environment variables:
# Logfire
export LOGFIRE_TOKEN="your-logfire-write-token"
# LangSmith
export LANGSMITH_API_KEY="your-langsmith-api-key"Both helpers fall back to these variables when you do not pass token= / api_key= explicitly.
Logfire
Call setup_logfire(graph, ...) before graph.compile(). It runs logfire.configure(...) to install the global TracerProvider, then attaches the OtelPublisher.
from tenxgraph.core.graph import StateGraph, Agent
from tenxgraph.runtime.publisher import setup_logfire, ObservabilityLevel
from tenxgraph.utils import END
graph = StateGraph()
graph.add_node("MAIN", Agent(model="gpt-4o"))
graph.set_entry_point("MAIN")
graph.add_edge("MAIN", END)
# Configure Logfire and instrument the graph, before compile()
setup_logfire(
graph,
service_name="my-agent",
level=ObservabilityLevel.STANDARD,
)
app = graph.compile()setup_logfire accepts token, service_name, send_to_logfire (default True), console (pass False to silence local console output), level, and any extra keyword arguments forwarded verbatim to logfire.configure() (e.g. environment="staging").
LangSmith
Call setup_langsmith(graph, ...) before graph.compile(). It builds an OTLP HTTP exporter pointing at LangSmith, wraps it in a BatchSpanProcessor, and attaches the OtelPublisher.
from tenxgraph.runtime.publisher import setup_langsmith, ObservabilityLevel
setup_langsmith(
graph,
project="my-agent", # sent as the Langsmith-Project header
level=ObservabilityLevel.STANDARD,
)
app = graph.compile()For a regional deployment, pass the full base endpoint (10xGraph appends /v1/traces):
setup_langsmith(graph, project="my-agent", endpoint="https://eu.api.smith.langchain.com/otel")Both at once
setup_observability reads a config dict and enables Logfire and/or LangSmith. When both are on, they share a single TracerProvider (the LangSmith processor is passed to Logfire via additional_span_processors):
from tenxgraph.runtime.publisher import setup_observability
setup_observability(graph, {
"level": "standard",
"logfire": {"enabled": True, "service_name": "my-agent"},
"langsmith": {"enabled": True, "project": "my-agent"},
})
app = graph.compile()Dedicated publishers
If you prefer a publisher object, for example to fan out with CompositePublisher, use LogfirePublisher or LangsmithPublisher. They subclass OtelPublisher and configure the provider on construction, so assign them before compile():
from tenxgraph.runtime.publisher import LangsmithPublisher, ObservabilityLevel
publisher = LangsmithPublisher(project="my-agent", level=ObservabilityLevel.STANDARD)
graph = StateGraph(publisher=publisher)
# ... add nodes, edges
app = graph.compile()LogfirePublisher takes the same options as setup_logfire; LangsmithPublisher takes the same options as setup_langsmith.
Observability levels and PII
The level controls how much data lands on each span. It reuses ObservabilityLevel:
| Level | What it emits | PII risk |
|---|---|---|
SPANS |
Timing and structure only | None |
STANDARD (default) |
+ token counts, model, request params. No message content. | Low |
FULL |
+ prompt and completion content | High - opt in deliberately |
FULL puts prompt/response text on spans. The framework’s log redaction (install_secret_redaction()) does not scrub span content, so treat FULL traces as sensitive and restrict who can view them in Logfire/LangSmith.
Declarative config in 10xgraph.json
When you serve a graph with 10xgraph api, you do not call the helpers yourself. Add an observability block to 10xgraph.json and the server wires it up during startup:
{
"agent": "graph.react:app",
"observability": {
"level": "standard",
"logfire": { "enabled": true, "service_name": "my-agent", "send_to_logfire": true, "console": false },
"langsmith": { "enabled": true, "project": "my-agent", "endpoint": null }
}
}Keep LOGFIRE_TOKEN / LANGSMITH_API_KEY in your .env, never in 10xgraph.json. If a backend is enabled but its package or key is missing, the server logs a warning and starts without that exporter rather than failing.
Related
- How to use publishers, the full publisher catalog, including the raw
OtelPublisher. - Configure 10xgraph.json, every top-level config key.