Skip to main content

Latitude Telemetry for Python

Project description

Latitude Telemetry for Python

Instrument your AI application and send traces to Latitude. Built on OpenTelemetry.

Installation

pip install latitude-telemetry

Requires Python 3.11+.

Quick Start

Bootstrap (Recommended)

The fastest way to start tracing your LLM calls. One class sets up everything:

import openai
from openai import OpenAI

from latitude_telemetry import Latitude

client = OpenAI()

latitude = Latitude(
    api_key="your-api-key",
    project="your-project-slug",
    instrumentations={"openai": openai},  # Pass the LLM SDK module you use in app code.
)

# Your LLM calls will now be traced and sent to Latitude
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello"}],
)

latitude.shutdown()

instrumentations takes a dict mapping integration name (openai, anthropic, …) to the LLM SDK module the consumer imports. Passing the module reference the consumer's own code uses sidesteps a class of import-cache bugs where the SDK could patch a different module instance than the app loads.

What this does:

  • Creates a complete OpenTelemetry setup
  • Registers LLM auto-instrumentation (OpenAI, Anthropic, etc.)
  • Configures the Latitude span processor and exporter
  • Sets up async context propagation (for passing context through async operations)

When to use this: Most applications should start here. It's the simplest path to get LLM observability into Latitude.

When you might need the advanced setup:

  • You already have OpenTelemetry configured for other backends (Datadog, Sentry, Jaeger)
  • You need custom span processing, sampling, or filtering
  • You want multiple observability backends receiving the same spans

Existing OpenTelemetry Setup (Advanced)

If your app already uses OpenTelemetry, add Latitude alongside your existing setup:

import openai
from openai import OpenAI

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from latitude_telemetry import LatitudeSpanProcessor, register_latitude_instrumentations

provider = TracerProvider()
# Add Latitude as an additional processor
provider.add_span_processor(LatitudeSpanProcessor("api-key", "project-slug"))
# Add your other processors (Datadog, console exporter, etc.)

trace.set_tracer_provider(provider)

# Enable LLM auto-instrumentation
register_latitude_instrumentations(
    instrumentations={"openai": openai},
    tracer_provider=provider,
)

# Your LLM calls will now be traced and sent to Latitude
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello"}],
)

Important: LatitudeSpanProcessor only exports spans to Latitude. You still need LLM instrumentations to create those spans—use register_latitude_instrumentations() or bring your own OTel-compatible LLM instrumentation.

Using capture() for Context and Boundaries

The SDK automatically traces LLM calls when you use auto-instrumentation. However, you may want to add additional context (user ID, session ID, tags, or metadata) to group related spans together.

What capture() Does

capture() wraps your code to attach Latitude context to all LLM spans created inside the callback:

  • Adds attributes like user.id, session.id, latitude.tags, and latitude.metadata to every span
  • Creates a named boundary for grouping related traces
  • Uses OpenTelemetry's native context API for reliable async propagation

When to Use It

You don't need capture() to get started—auto-instrumentation handles LLM calls automatically. Use capture() when you want to:

  • Group traces by user or session — Track all LLM calls from a specific user or session
  • Add business context — Tag traces with deployment environment, feature flags, or request IDs
  • Mark agent boundaries — Wrap an entire agent run or conversation turn with a name and metadata
  • Filter and analyze — Use tags and metadata to filter traces in the Latitude UI

Example

import openai

from latitude_telemetry import Latitude, capture

latitude = Latitude(
    api_key="your-api-key",
    project="your-project-slug",
    instrumentations={"openai": openai},
)

# Wrap a request or agent run to add context
capture(
    "handle-user-request",
    lambda: agent.process(user_message),
    {
        "user_id": "user_123",
        "session_id": "session_abc",
        "tags": ["production", "v2-agent"],
        "metadata": {"request_id": "req-xyz", "feature_flag": "new-prompt"},
    },
)

latitude.shutdown()

Important: capture() does not create spans—it only attaches context. The LLM spans are created by the auto-instrumentation. You only need one capture() call at the request or agent boundary, not for every internal step.

Key Concepts

  • Latitude — The primary way to use Latitude. Bootstraps a complete OpenTelemetry setup with LLM auto-instrumentation and the Latitude exporter, attaching to an existing provider when one is already registered.
  • LatitudeSpanProcessor — For advanced use cases where you already have an OpenTelemetry setup. Exports spans to Latitude alongside your existing observability stack.
  • register_latitude_instrumentations() — Registers LLM auto-instrumentations (OpenAI, Anthropic, etc.) when using the advanced setup with your own provider.
  • capture() — Optional. Wraps your code to attach Latitude context (tags, user_id, session_id, metadata) to all spans created inside the callback. Use this when you want to group traces by user, session, or add business context.

Important: Auto-instrumentation traces LLM calls without capture(). Use capture() only when you need to add context or mark boundaries. Wrap the request, job, or agent entrypoint once—you don't need to wrap every internal step.

Why OpenTelemetry?

Latitude Telemetry is built entirely on OpenTelemetry standards. When you're ready to add other observability tools (Datadog, Sentry, Jaeger, etc.), you can use them alongside Latitude without conflicts:

  • Standard span processorsLatitudeSpanProcessor works with any TracerProvider
  • Smart filtering — Only LLM-relevant spans are exported to Latitude (spans with gen_ai.*, llm.*, openinference.*, or ai.* attributes, plus known LLM instrumentation scopes)
  • Compatible with existing instrumentations — Works alongside HTTP, DB, and other OTel instrumentations
  • No vendor lock-in — Standard OTLP export, no proprietary wire format

Public API

from latitude_telemetry import (
    Latitude,
    LatitudeOptions,
    LatitudeSpanProcessor,
    capture,
    register_latitude_instrumentations,
)

Latitude(**options)

The primary entry point. Bootstraps a complete OpenTelemetry setup with LLM instrumentations and Latitude export. If an OpenTelemetry provider is already registered, Latitude attaches its span processor to that provider instead of replacing it.

class Latitude:
    def __init__(
        self,
        *,
        api_key: str,
        # Default project for spans. Optional — every `capture()` can override.
        # Sent as the `X-Latitude-Project` header on every export.
        project: str | None = None,
        # DEPRECATED alias for `project`. Still accepted; logs a one-time warning and will be
        # removed in a future release. When both are passed, `project` wins.
        project_slug: str | None = None,
        # Dict mapping integration name → the LLM SDK module the consumer imports.
        # Example: {"openai": openai, "anthropic": anthropic}.
        # Anything else (list, primitive, unknown key, non-dict) raises TypeError at register time.
        instrumentations: InstrumentationsInput | None = None,
        service_name: str | None = None,
        disable_batch: bool = False,
        disable_smart_filter: bool = False,
        should_export_span: Callable[[ReadableSpan], bool] | None = None,
        blocked_instrumentation_scopes: list[str] | None = None,
        disable_redact: bool = False,
        redact: RedactSpanProcessorOptions | None = None,
        exporter: SpanExporter | None = None,
        tracer_provider: TracerProvider | None = None,
    ):
        ...

    provider: TracerProvider
    def flush(self) -> None: ...
    def shutdown(self) -> None: ...

init_latitude() remains available as a backwards-compatible wrapper that returns {"provider", "flush", "shutdown"}.

LatitudeSpanProcessor

Span processor for shared-provider setups. Reads Latitude context from OTel context and stamps attributes onto spans.

class LatitudeSpanProcessor:
    def __init__(
        self,
        api_key: str,
        project: str | None,
        options: LatitudeSpanProcessorOptions | None = None,
    ):
        ...

@dataclass
class LatitudeSpanProcessorOptions:
    disable_redact: bool = False
    disable_batch: bool = False
    disable_smart_filter: bool = False
    should_export_span: Callable[[ReadableSpan], bool] | None = None
    blocked_instrumentation_scopes: tuple[str, ...] = ()
    exporter: SpanExporter | None = None
    service_name: str | None = None

capture(name, fn, options=None)

Wraps a function to attach Latitude context to all spans created inside. Uses OpenTelemetry's native context API for scoping.

def capture(
    name: str,
    fn: Callable[[], T],
    options: ContextOptions | None = None,
) -> T:
    ...

# ContextOptions:
# {
#     "name": str | None,        # Override the capture name
#     "user_id": str | None,     # User identifier (session.id attribute)
#     "session_id": str | None,  # Session identifier (user.id attribute)
#     "tags": list[str] | None,  # Tags for filtering traces
#     "metadata": dict | None,   # Arbitrary key-value metadata
#     "project": str | None,     # Route this capture (and child spans) to a specific
#                                # Latitude project, overriding the constructor default.
#     "project_slug": str | None, # DEPRECATED alias for `project`. Still accepted.
# }

Nested capture() behavior:

  • user_id: last-write-wins
  • session_id: last-write-wins
  • metadata: shallow merge
  • tags: append and dedupe while preserving order

register_latitude_instrumentations(instrumentations, tracer_provider)

Registers LLM auto-instrumentations against a specific tracer provider.

# InstrumentationName = Literal[
#   "openai", "openai-agents", "anthropic", "bedrock", "cohere",
#   "langchain", "llamaindex", "togetherai", "vertexai", "aiplatform",
#   "aleph_alpha", "crewai", "dspy", "google_generativeai", "groq",
#   "haystack", "litellm", "mistralai", "ollama", "replicate",
#   "sagemaker", "transformers", "watsonx",
# ]
# InstrumentationsInput = dict[InstrumentationName, object]

def register_latitude_instrumentations(
    # Dict mapping integration name → the LLM SDK module the consumer imports.
    # Anything else throws at register time.
    instrumentations: InstrumentationsInput,
    tracer_provider: TracerProvider,
) -> None:
    ...

Migrating from instrumentations=["openai"] (3.0.0a6 and earlier)

The list-of-strings form is removed with no fallback in 3.0.0a7. Anything other than a plain dict — including the old string list — raises TypeError at register time. Migration:

- from latitude_telemetry import Latitude
+ import openai
+ import anthropic
+ from latitude_telemetry import Latitude

  latitude = Latitude(
      api_key="your-api-key",
      project="your-project-slug",
-     instrumentations=["openai", "anthropic"],
+     instrumentations={"openai": openai, "anthropic": anthropic},
  )

Supported AI Providers

Set the integration's key on the instrumentations dict to the LLM SDK module the consumer imports.

Key PyPI package What to pass
openai openai import openaiopenai
openai-agents openai-agents import agentsagents
anthropic anthropic import anthropicanthropic
bedrock boto3 import boto3boto3
cohere cohere import coherecohere
langchain langchain-core import langchain_corelangchain_core
llamaindex llama-index import llama_indexllama_index
togetherai together import togethertogether
vertexai google-cloud-aiplatform import vertexaivertexai
aiplatform google-cloud-aiplatform import google.cloud.aiplatform → that module
aleph_alpha aleph-alpha-client import aleph_alpha_client
crewai crewai import crewai
dspy dspy-ai import dspy
google_generativeai google-generativeai from google import genaigenai
groq groq import groq
haystack haystack-ai import haystack
litellm litellm import litellm
mistralai mistralai import mistralai
ollama ollama import ollama
replicate replicate import replicate
sagemaker boto3 import boto3boto3
transformers transformers import transformers
watsonx ibm-watson-machine-learning import ibm_watsonx_ai

Context Options

capture() accepts these context options:

Option Type OTel Attribute Description
name str latitude.capture.name Name for the capture context
tags list[str] latitude.tags Tags for filtering traces
metadata dict[str, Any] latitude.metadata Arbitrary key-value metadata
session_id str session.id Group traces by session
user_id str user.id Associate traces with a user

Configuration Options

Smart Filtering

By default, only LLM-relevant spans are exported:

from latitude_telemetry import LatitudeSpanProcessor

processor = LatitudeSpanProcessor(
    "api-key",
    "project-slug",
    LatitudeSpanProcessorOptions(
        disable_smart_filter=True,  # Export all spans
    ),
)

Redaction

PII redaction is enabled by default for security-sensitive attributes only:

Redacted by default:

  • HTTP authorization headers
  • HTTP cookies
  • HTTP API key headers (x-api-key)
  • Database statements
from latitude_telemetry import LatitudeSpanProcessor, RedactSpanProcessorOptions

processor = LatitudeSpanProcessor(
    "api-key",
    "project-slug",
    LatitudeSpanProcessorOptions(
        disable_redact=True,  # Disable all redaction
        redact=RedactSpanProcessorOptions(
            attributes=[r"^password$", r"secret"],  # Add custom patterns
            mask=lambda attr, value: "[REDACTED]",
        ),
    ),
)

Custom Filtering

from latitude_telemetry import LatitudeSpanProcessor, LatitudeSpanProcessorOptions

processor = LatitudeSpanProcessor(
    "api-key",
    "project-slug",
    LatitudeSpanProcessorOptions(
        should_export_span=lambda span: span.attributes.get("custom") is True,
        blocked_instrumentation_scopes=["opentelemetry.instrumentation.fs"],
    ),
)

Environment Variables

Variable Default Description
LATITUDE_TELEMETRY_URL http://localhost:3002 OTLP exporter endpoint

Troubleshooting

Spans not appearing in Latitude

  1. Check API key and project slug — Must be non-empty strings
  2. Verify instrumentations are registered — Create Latitude(...) before importing or constructing clients when possible, or use register_latitude_instrumentations() for manual setups
  3. Flush before exit — Call latitude.flush() or provider.force_flush()
  4. Check smart filter — Only LLM spans are exported by default. Use disable_smart_filter=True to export all spans
  5. Ensure capture() wraps the code that creates spanscapture() itself doesn't create spans; it only attaches context to spans created by instrumentations

No spans created inside capture()

capture() only attaches context. You need:

  1. An active instrumentation (e.g., opentelemetry-instrumentation-openai)
  2. That instrumentation to create spans for the operations inside your callback

Context not propagating

Ensure you have a functioning OpenTelemetry context manager registered:

from opentelemetry.context import set_global_textmap
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.baggage.propagation import W3CBaggagePropagator

set_global_textmap(
    CompositePropagator([TraceContextTextMapPropagator(), W3CBaggagePropagator()])
)

Latitude(...) does this automatically when it owns the provider. For shared-provider setups, your app's existing OTel setup should already have this.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

latitude_telemetry-3.0.1.tar.gz (231.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

latitude_telemetry-3.0.1-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file latitude_telemetry-3.0.1.tar.gz.

File metadata

  • Download URL: latitude_telemetry-3.0.1.tar.gz
  • Upload date:
  • Size: 231.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for latitude_telemetry-3.0.1.tar.gz
Algorithm Hash digest
SHA256 4f982200fddd01e29af190f7dfce26e285534f023bf8e06e1d7a9d2622452b93
MD5 909d6369e50d0f3bcbb650ad201306a6
BLAKE2b-256 d848af0433e951fea0ef5530cb41fdc9f21a3e4ef7649a2db36fd3ae769ac673

See more details on using hashes here.

File details

Details for the file latitude_telemetry-3.0.1-py3-none-any.whl.

File metadata

  • Download URL: latitude_telemetry-3.0.1-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for latitude_telemetry-3.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2667c256f6cb31c70c371eca3669a55308b24f4e23c47e7871d9b5a22e154f17
MD5 6d7645eb454537a1f8238b539be5f252
BLAKE2b-256 ee00fbfcf468baeb25063c5bdd5893c06b1e08a5079cf9096681509855472219

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page