Metadata-Version: 2.5
Name: tls-llm-observer
Version: 0.1.1
Summary: OpenTelemetry-first LLM tracing SDK with OpenAI instrumentation
Project-URL: Homepage, https://www.volcengine.com/product/tls
Project-URL: Documentation, https://www.volcengine.com/docs/6470/71996
Author: Volcengine TLS Team
Maintainer: Volcengine TLS Team
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: llm,observability,openai,opentelemetry,tracing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Logging
Requires-Python: >=3.10
Requires-Dist: opentelemetry-api<2,>=1.28
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.28
Requires-Dist: opentelemetry-sdk<2,>=1.28
Provides-Extra: dev
Requires-Dist: openai<3,>=1.0; extra == 'dev'
Requires-Dist: packaging<27,>=23; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=0.24; extra == 'dev'
Requires-Dist: pytest<10,>=8; extra == 'dev'
Requires-Dist: ruff<1,>=0.9; extra == 'dev'
Requires-Dist: wrapt<3,>=1.16; extra == 'dev'
Provides-Extra: openai
Requires-Dist: openai<3,>=1.0; extra == 'openai'
Requires-Dist: packaging<27,>=23; extra == 'openai'
Requires-Dist: wrapt<3,>=1.16; extra == 'openai'
Description-Content-Type: text/markdown

# TLS LLM Observer Python SDK

OpenTelemetry-first tracing for manual instrumentation, decorators, and OpenAI clients.
The first release exports traces only through OTLP/HTTP protobuf.

## Install

Install the core tracing SDK and OpenTelemetry dependencies:

```bash
pip install tls-llm-observer
```

Install the OpenAI integration together with `openai`, `packaging`, and `wrapt`:

```bash
pip install "tls-llm-observer[openai]"
```

The default endpoint is `http://localhost:4318/v1/traces`. Configure it with
`TLS_TRACE_OTEL_ENDPOINT` or the standard `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`.
Applications configure tracing through `TraceConfig`; externally supplied providers or
exporters are not accepted.

## Manual instrumentation

```python
from tls_llm_observer import TraceClient, trace

client = TraceClient()

with client.start_as_current_span("answer-question") as span:
    span.update(input={"question": "Why is the sky blue?"})
    span.update(output={"answer": "Rayleigh scattering"})

@trace(client=client)
def normalize(value: str) -> str:
    return value.strip().lower()

client.flush()
client.shutdown()
```

`span.update(input=...)` and `span.update(output=...)` write
`gen_ai.input.messages` and `gen_ai.output.messages`. The supplied value is stored
directly using the SDK's normal OpenTelemetry attribute conversion; values that do
not match the GenAI message schemas are not wrapped or reshaped.

`start_span()` does not replace the current context. Use `start_as_current_span()` when
new operations should automatically become children. Span names, kinds, attributes, and
parenting remain user-controlled.

```python
from opentelemetry.trace import SpanKind
from tls_llm_observer import TraceClient, propagate_attributes

client = TraceClient()
with propagate_attributes(session_id="session-1", conversation_id="conversation-1"):
    with client.start_as_current_span("request", kind=SpanKind.SERVER):
        child = client.start_span("background-work")
        child.update(output={"ok": True}).end()
```

The context manager also supports `async with`.

Use the tool-specific helpers for actual tool execution. They create an
`execute_tool {name}` `INTERNAL` Span and set the required GenAI semantic
attributes at Span creation time:

```python
with client.start_as_current_tool_span(
    "get_weather",
    tool_call_id="call_123",
    arguments={"city": "Beijing"},
) as span:
    result = {"temperature": 28}
    span.set_tool_result(result)
```

`@trace(span_type="tool")` applies the same execute-tool defaults and records
captured function arguments/results on `gen_ai.tool.call.arguments` and
`gen_ai.tool.call.result`.

## Decorator and streams

`@trace` supports sync/async functions, generators, async generators, and Starlette-style
responses with a `body_iterator`. Streaming spans end on exhaustion or explicit close.

```python
from tls_llm_observer import trace

@trace(name="token-stream")
def tokens():
    yield "hello"
    yield " world"
```

## OpenAI

OpenAI is an optional dependency:

```python
from tls_llm_observer import TraceClient
from tls_llm_observer.openai import OpenAI

client = TraceClient()
openai_client = OpenAI(trace_client=client)
response = openai_client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
)
```

The integration supports chat completions, completions, responses, embeddings, structured
parse, synchronous and asynchronous calls, and streams. `OpenAI`, `AsyncOpenAI`,
`AzureOpenAI`, and `AsyncAzureOpenAI` use the supplied `TraceClient`; clients created from
the exported official `openai` module use the process default `TraceClient`.

Native `openai.Stream` and `openai.AsyncStream` objects keep their original type and
identity. Streaming spans end on exhaustion, explicit close, context manager exit,
cancellation, or iteration error.

Tracing-only keyword arguments are removed before the provider call:

```python
response = openai_client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
    trace_name="welcome-message",
    trace_attributes={"tenant.id": "example"},
    capture_content=False,
)
```

Successful non-streaming raw responses retain their original response type. Raw streaming
response APIs are not traced. Set `TLS_TRACE_OPENAI_SKIP_RAW_RESPONSES=true` to skip all
raw-response calls. `uninstrument_openai(client)` disables tracing for one client;
`uninstrument_openai()` disables the OpenAI integration globally.

OpenAI Assistants are intentionally not instrumented.

## Environment variables

| Variable | Purpose |
| --- | --- |
| `TLS_TRACE_ENABLED` | Enable or disable tracing |
| `TLS_TRACE_SAMPLE_RATE` | Parent-based trace ratio, from `0.0` to `1.0` |
| `TLS_TRACE_CAPTURE_CONTENT` | Capture decorator/OpenAI input and output |
| `TLS_TRACE_OPENAI_SKIP_RAW_RESPONSES` | Do not trace OpenAI raw-response APIs |
| `TLS_TRACE_OTEL_ENDPOINT` | OTLP HTTP traces endpoint |
| `TLS_TRACE_OTEL_HEADERS` | Comma-separated `key=value` headers |
| `TLS_TRACE_OTEL_TRACE_TOPIC_ID` / `TLS_TRACE_TOPIC_ID` | TLS Trace Topic ID |
| `TLS_TRACE_OTEL_REGION` / `TLS_REGION` | TLS region |
| `TLS_TRACE_OTEL_AK` / `TLS_AK` | TLS access key |
| `TLS_TRACE_OTEL_SK` / `TLS_SK` | TLS secret key |
| `TLS_TRACE_OTEL_SECURITY_TOKEN` / `TLS_SECURITY_TOKEN` | Optional TLS STS token |
| `TLS_TRACE_OTEL_API_KEY` / `TLS_API_KEY` | TLS API Key alternative to AK/SK |
| `TLS_TRACE_OTEL_COMPRESSION` | `none`, `gzip`, or `deflate` |
| `TLS_TRACE_MAX_QUEUE_SIZE` | BatchSpanProcessor queue capacity |
| `TLS_TRACE_MAX_EXPORT_BATCH_SIZE` | Maximum spans per OTLP batch |
| `TLS_TRACE_SCHEDULE_DELAY_MS` | Batch scheduling delay |
