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 unified setup_observability.
  • Dedicated publishers, LogfirePublisher / LangsmithPublisher, if you prefer a publisher object to assign or compose.
  • Declarative config, an observability block in 10xgraph.json (auto-wired by the API server; see below).

Install

Terminal
pip install '10xgraph[logfire]'        # Logfire
pip install '10xgraph[langsmith]'      # LangSmith (OTLP HTTP exporter)
pip install '10xgraph[observability]'  # both + otel

The 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:

Terminal
# 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.

Python
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.

Python
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):

Python
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):

Python
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():

Python
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:

JSON
{
  "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.


Last updated for v0.9.2Edit this page on GitHubReport an issue