langprobe
Guides

Tracing & instrumentation

Send traces over plain OTLP/HTTP with OpenInference instrumentors — CrewAI, DSPy, Pydantic AI, OpenAI Agents, LlamaIndex, or bare providers. No proprietary SDK.

langprobe ingests plain OTLP/HTTP at POST <host>/v1/traces. Every integration is the same shape: install an OpenInference instrumentor for your framework plus the stock opentelemetry-exporter-otlp-proto-http exporter, wire them into a TracerProvider, point the endpoint at your langprobe host, and set the API-key header. There is no langprobe-specific runtime library.

The endpoint accepts both application/x-protobuf (what the stock exporter sends) and application/json. Protobuf works out of the box — don't force JSON.

The fast path: the Python SDK

If you're in Python, the langprobe SDK wires the provider, exporter, and instrumentor for you in four lines:

import langprobe

langprobe.init(
    api_key="lt_<public_id>.<secret>",
    endpoint="https://app.langprobe.com",
    instrumentations=["crewai"],
)
# your framework code runs unchanged; traces flow to /v1/traces

Install the extras you need: pip install "langprobe[otel,crewai]" (also dspy, pydantic-ai, openai-agents, llama-index, openai, anthropic, litellm). See the Python SDK for per-framework examples.

The manual path: OpenTelemetry directly

Any language with an OTLP exporter can send traces — no SDK required. The shared bootstrap (Python shown) wires a provider once:

# langprobe_tracing.py
import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

def build_provider(service_name: str) -> TracerProvider:
    endpoint = os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"].rstrip("/") + "/v1/traces"
    headers = {"Authorization": f"Bearer {os.environ['LANGPROBE_API_KEY']}"}
    provider = TracerProvider(resource=Resource.create({"service.name": service_name}))
    provider.add_span_processor(
        BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, headers=headers))
    )
    trace.set_tracer_provider(provider)
    return provider

Point it at your host (the bootstrap appends /v1/traces):

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:7080"
export LANGPROBE_API_KEY="lt_<public_id>.<secret>"

Scripts must flush before exit or the last batch is lost: provider.force_flush(); provider.shutdown(). Servers build the provider once at startup and flush/shutdown once at shutdown (FastAPI lifespan / atexit) — never per request.

Framework recipes

Install the framework's OpenInference instrumentor and call .instrument() with the provider. Worked example — CrewAI:

from langprobe_tracing import build_provider
from openinference.instrumentation.crewai import CrewAIInstrumentor
from openinference.instrumentation.openai import OpenAIInstrumentor

provider = build_provider("crew-app")
CrewAIInstrumentor().instrument(tracer_provider=provider)
OpenAIInstrumentor().instrument(tracer_provider=provider)  # provider match!
# ... run your crew ...
provider.force_flush(); provider.shutdown()
FrameworkInstrumentor package
CrewAIopeninference-instrumentation-crewai (+ your LLM provider's instrumentor)
DSPyopeninference-instrumentation-dspy
Pydantic AIopeninference-instrumentation-pydantic-ai
OpenAI Agentsopeninference-instrumentation-openai-agents
LlamaIndexopeninference-instrumentation-llama-index
Bare OpenAI / Anthropicopeninference-instrumentation-openai / -anthropic
LiteLLM-routedopeninference-instrumentation-litellm

CrewAI dispatches the actual LLM call through the provider SDK. The CrewAI instrumentor gives you the Agent/Task/Crew structure, but the llm-kind span (model + tokens) comes from the provider instrumentor — match it to your crewai.LLM(model=...). Installing only the CrewAI instrumentor is the #1 papercut: you get the tree but no model or tokens.

Custom spans

langprobe has no proprietary decorator API — you emit plain OpenTelemetry spans with OpenInference conventions. Reach for this only for code an instrumentor doesn't cover (a hand-rolled tool, a custom retriever, a guardrail):

from opentelemetry import trace
tracer = trace.get_tracer("my-agent")

with tracer.start_as_current_span("web_search") as span:
    span.set_attribute("openinference.span.kind", "TOOL")  # UPPERCASE
    span.set_attribute("input.value", query)
    results = search_api.search(query)
    span.set_attribute("output.value", results)

start_as_current_span nests the span under whatever span is active, so the run tree in /runs follows your call structure. langprobe classifies the span from openinference.span.kind first, then OTel gen_ai.operation.name, then a span-name heuristic — see Core concepts for the kind set.

End-user identity

Attribute a run to an end user by setting enduser.id on the root span (or langprobe.identify(...) via the SDK). langprobe reads it so you can filter and group runs by the user they served.

Once traces are landing, the debugger loop begins: Replay an edit and diff what changed.

On this page