initial commit

This commit is contained in:
2026-05-11 12:36:20 +05:30
commit 384cbe8019
15377 changed files with 2360544 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
"""OpenTelemetry integration for LangSmith."""
import logging
from typing import Optional, cast
from langsmith import utils as ls_utils
from .processor import OtelExporter, OtelSpanProcessor
logger = logging.getLogger(__name__)
__all__ = ["configure", "OtelSpanProcessor", "OtelExporter"]
def configure(
api_key: Optional[str] = None,
project_name: Optional[str] = None,
SpanProcessor: Optional[type] = None,
) -> bool:
"""Configure OpenTelemetry with LangSmith as the `TracerProvider`.
Initializes OpenTelemetry with LangSmith as the primary and only `TracerProvider`.
Usage:
>>> from langsmith.integrations.otel import configure
>>> configure( # doctest: +SKIP
... api_key="your-api-key", project_name="your-project"
... )
Using environment variables:
>>> # Set LANGSMITH_API_KEY and LANGSMITH_PROJECT
>>> configure() # Will use env vars # doctest: +SKIP
!!! warning
This function is only for when LangSmith is your ONLY OpenTelemetry source.
It sets the global TracerProvider, which can only be done once per application.
This function will fail if OpenTelemetry is already initialized with another
`TracerProvider` (you cannot override an existing `TracerProvider`).
If you already have OpenTelemetry set up with other tools, use `OtelSpanProcessor`
directly to add LangSmith to your existing setup:
!!! example "Adding LangSmith to existing OTEL setup"
```python
from opentelemetry import trace
from langsmith.integrations.otel.processor import OtelSpanProcessor
# Use your existing provider (already initialized)
provider = trace.get_tracer_provider()
# Add LangSmith processor to existing provider
langsmith_processor = OtelSpanProcessor(
api_key="your-api-key", project="your-project"
)
provider.add_span_processor(langsmith_processor)
```
Args:
api_key: LangSmith API key. Defaults to `LANGSMITH_API_KEY` env var.
project_name: Project name. Defaults to `LANGSMITH_PROJECT` env var.
SpanProcessor: Span processor class to use. Defaults to `BatchSpanProcessor`.
Returns:
`True` if configuration succeeded, `False` if `TracerProvider` already exists.
"""
try:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.trace import NoOpTracer, ProxyTracer, ProxyTracerProvider
existing_provider = cast(TracerProvider, trace.get_tracer_provider())
tracer = existing_provider.get_tracer(__name__)
# Check if OpenTelemetry is in its default uninitialized state
# (ProxyTracerProvider with NoOpTracer means no real TracerProvider was set)
if (
isinstance(existing_provider, ProxyTracerProvider)
and hasattr(tracer, "_tracer")
and isinstance(
cast(
ProxyTracer, # type: ignore[attr-defined, name-defined]
tracer,
)._tracer,
NoOpTracer,
)
):
# Safe to set TracerProvider since none exists yet
provider = TracerProvider()
trace.set_tracer_provider(provider)
else:
logger.warning(
"OpenTelemetry TracerProvider is already set. "
"Cannot override existing TracerProvider. Use OtelSpanProcessor "
"directly to add LangSmith to your existing provider instead."
)
return False
api_key = api_key or ls_utils.get_api_key(None)
if not api_key:
return False
project_name = project_name or ls_utils.get_tracer_project()
processor = OtelSpanProcessor(
api_key=api_key, project=project_name, SpanProcessor=SpanProcessor
)
provider.add_span_processor(processor) # type: ignore
return True
except Exception as e:
logger.warning("Failed to initialize Otel for LangSmith:", e)
return False

View File

@@ -0,0 +1,222 @@
"""OpenTelemetry span processor and exporter for LangSmith."""
import logging
import warnings
from typing import Optional
from urllib.parse import urljoin
from langsmith import utils as ls_utils
try:
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
OTEL_AVAILABLE = True
except ImportError:
warnings.warn(
"OpenTelemetry packages are not installed. "
"Install optional OpenTelemetry dependencies with: "
"pip install langsmith[otel]",
UserWarning,
stacklevel=2,
)
class OTLPSpanExporter: # type: ignore[no-redef]
"""Mock otlp span exporter class."""
def __init__(self, *args, **kwargs):
"""Mock init method."""
raise ImportError(
"OpenTelemetry packages are not installed. "
"Install optional OpenTelemetry dependencies with: "
"pip install langsmith[otel]"
)
class BatchSpanProcessor: # type: ignore[no-redef]
"""Mock batch span processor class."""
def __init__(self, *args, **kwargs):
"""Mock init method."""
raise ImportError(
"OpenTelemetry packages are not installed. "
"Install optional OpenTelemetry dependencies with: "
"pip install langsmith[otel]"
)
class trace:
"""Mock trace class."""
@staticmethod
def get_tracer_provider():
"""Mock get tracer provider method."""
raise ImportError(
"OpenTelemetry packages are not installed. "
"Install optional OpenTelemetry dependencies with: "
"pip install langsmith[otel]"
)
OTEL_AVAILABLE = False
class OtelExporter(OTLPSpanExporter):
"""A subclass of `OTLPSpanExporter` configured for LangSmith.
Environment Variables:
- `LANGSMITH_API_KEY`: Your LangSmith API key.
- `LANGSMITH_ENDPOINT`: Base URL for LangSmith API (defaults to `https://api.smith.langchain.com`).
- `LANGSMITH_PROJECT`: Project identifier.
"""
def __init__(
self,
url: Optional[str] = None,
api_key: Optional[str] = None,
project: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
**kwargs,
):
"""Initialize the `OtelExporter`.
Args:
url: OTLP endpoint URL. Defaults to `{LANGSMITH_ENDPOINT}/otel/v1/traces`.
api_key: LangSmith API key. Defaults to `LANGSMITH_API_KEY` env var.
parent: Parent identifier (e.g., `'project_name:test'`).
Defaults to `LANGSMITH_PARENT` env var.
headers: Additional headers to include in requests.
**kwargs: Additional arguments passed to `OTLPSpanExporter`.
"""
base_url = ls_utils.get_api_url(None)
# Ensure base_url ends with / for proper joining
if not base_url.endswith("/"):
base_url += "/"
endpoint = url or urljoin(base_url, "otel/v1/traces")
api_key = api_key or ls_utils.get_api_key(None)
project = project or ls_utils.get_tracer_project()
headers = headers or {}
if not api_key:
raise ValueError(
"API key is required. Provide it via api_key parameter or "
"LANGSMITH_API_KEY environment variable."
)
if not project:
project = "default"
logging.info(
"No project specified, using default. "
"Configure with LANGSMITH_PROJECT environment variable or "
"project parameter."
)
exporter_headers = {
"x-api-key": api_key,
**headers,
}
if project:
exporter_headers["Langsmith-Project"] = project
self.project = project
super().__init__(endpoint=endpoint, headers=exporter_headers, **kwargs)
class OtelSpanProcessor:
"""A span processor for adding LangSmith to OpenTelemetry setups.
This class combines the `OtelExporter` and `BatchSpanProcessor`
into a single processor that can be added to any `TracerProvider`.
Use this when:
1. You already have OpenTelemetry initialized with other tools
2. You want to add LangSmith alongside existing OTEL exporters
Examples:
# Fresh OpenTelemetry setup (LangSmith only):
from langsmith.integrations.otel import configure
configure(api_key="your-key", project="your-project")
# Add LangSmith to existing OpenTelemetry setup:
from opentelemetry import trace
from langsmith.integrations.otel.processor import OtelSpanProcessor
# Get your existing TracerProvider (already set by other tools)
provider = trace.get_tracer_provider()
# Add LangSmith processor alongside existing processors
langsmith_processor = OtelSpanProcessor(
project="your-project",
)
provider.add_span_processor(langsmith_processor)
"""
def __init__(
self,
api_key: Optional[str] = None,
project: Optional[str] = None,
url: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
SpanProcessor: Optional[type] = None,
):
"""Initialize the `OtelSpanProcessor`.
Args:
api_key: LangSmith API key. Defaults to `LANGSMITH_API_KEY` env var.
project: Project identifier. Defaults to `LANGSMITH_PROJECT` env var.
url: Base URL for LangSmith API. Defaults to `LANGSMITH_ENDPOINT` env var
or `https://api.smith.langchain.com`.
headers: Additional headers to include in requests.
SpanProcessor: Optional span processor class. Defaults to
`BatchSpanProcessor`.
"""
# Create the exporter
# Convert url to the full endpoint URL that OtelExporter expects
exporter_url = None
if url:
exporter_url = f"{url.rstrip('/')}/otel/v1/traces"
self._exporter = OtelExporter(
url=exporter_url, api_key=api_key, project=project, headers=headers
)
# Create the processor chain
if not OTEL_AVAILABLE:
raise ImportError(
"OpenTelemetry packages are not installed. "
"Install optional OpenTelemetry dependencies with: "
"pip install langsmith[otel]"
)
if SpanProcessor is None:
SpanProcessor = BatchSpanProcessor
self._processor = SpanProcessor(self._exporter)
def on_start(self, span, parent_context=None):
"""Forward span start events to the inner processor."""
self._processor.on_start(span, parent_context)
def on_end(self, span):
"""Forward span end events to the inner processor."""
self._processor.on_end(span)
def shutdown(self):
"""Shutdown processor."""
self._processor.shutdown()
def force_flush(self, timeout_millis=30000):
"""Force flush the inner processor."""
return self._processor.force_flush(timeout_millis)
@property
def exporter(self):
"""The underlying OtelExporter."""
return self._exporter
@property
def processor(self):
"""The underlying span processor."""
return self._processor