Metadata-Version: 2.4
Name: zespan
Version: 1.2.0
Summary: Zespan Agent Observability SDK
Author-email: Zespan <letstalk@zespan.com>
License-Expression: Apache-2.0
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: hypothesis>=6.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Provides-Extra: haystack
Requires-Dist: haystack-ai>=2.0.0; extra == 'haystack'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.9.0; extra == 'llamaindex'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
Provides-Extra: semantic-kernel
Requires-Dist: semantic-kernel>=1.0.0; extra == 'semantic-kernel'
Description-Content-Type: text/markdown

# zespan

LLM observability for Python. Trace every AI call — latency, tokens, cost, errors — with one `init()`.

## Install

```bash
pip install zespan
```

Optional extras:

```bash
pip install "zespan[otel]"             # OpenTelemetry export
pip install "zespan[llamaindex]"       # LlamaIndex integration
pip install "zespan[haystack]"         # Haystack integration
pip install "zespan[semantic-kernel]"  # Semantic Kernel integration
```

## Quick start

```python
import zespan

zespan.init(
    api_key="zsp_your_key_here",
    project_id="your_project_id",
)
zespan.autopatch()
```

Get your API key and project ID from [app.zespan.com](https://app.zespan.com) → Project Settings → API Keys.

After `init()` + `autopatch()`, all calls to OpenAI, Anthropic, Google GenAI, and other installed providers are automatically traced.

## Auto-instrumentation

`autopatch()` detects and patches every AI provider that is installed:

```python
import zespan
import openai

zespan.init(api_key="zsp_...", project_id="proj_...")
zespan.autopatch()

client = openai.OpenAI()
# This call is automatically traced
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
```

**Supported providers:**

| Function | Provider |
|---|---|
| `patch_openai()` | OpenAI |
| `patch_anthropic()` | Anthropic |
| `patch_google()` | Google Generative AI |
| `patch_openrouter()` | OpenRouter |
| `patch_bedrock()` | AWS Bedrock |
| `patch_mistral()` | Mistral |
| `patch_groq()` | Groq |
| `patch_litellm()` | LiteLLM |

Patch individual providers manually:

```python
from zespan import patch_openai, patch_anthropic

patch_openai()
patch_anthropic()
```

## Configuration

```python
zespan.init(
    api_key="zsp_...",           # required
    project_id="proj_...",       # required — links traces to a project
    environment="production",    # default: "production"
    store_prompts=False,         # store prompt/completion text (default: False)
    redact_keys=["password", "secret", "token", "api_key"],  # keys to redact
    sample_rate=1.0,             # 0.0–1.0, fraction of events to send
    debug=False,                 # log SDK activity to console
    batch_size=50,               # events per batch flush
    flush_interval=2.0,          # seconds between batch flushes
)
```

## User and session context

Tag traces with user ID, session ID, or custom metadata:

```python
from zespan import with_zespan_context

with with_zespan_context(
    user_id="user_123",
    session_id="sess_abc",
    tags={"feature": "chat"},
):
    # All AI calls inside are tagged with this context
    client.chat.completions.create(...)
```

Works as a decorator too:

```python
@with_zespan_context(user_id="user_123")
def handle_request():
    client.chat.completions.create(...)
```

## Agent tracing

Group multi-step agent workflows into a single trace:

```python
from zespan import with_agent

with with_agent(
    name="SupportAgent",
    role="specialist",
    framework="custom",
    tools=[{"name": "lookup_order", "description": "Look up order by ID"}],
) as agent:
    agent.log_plan(["Look up order", "Check policy", "Draft reply"])

    order = agent.trace_tool(
        "lookup_order",
        {"order_id": "123"},
        lambda: fetch_order("123"),
    )

    agent.delegate_to("RefundAgent", "refund requested")

    # Wrapped LLM calls inside here inherit agent context
    client.chat.completions.create(...)
```

**Agent span types emitted:**

| `span_kind` | When |
|---|---|
| `agent` | Agent start/end |
| `planning` | `log_plan()` |
| `tool` | `trace_tool()` |
| `handoff` | `delegate_to()` |

## Manual spans

Trace custom operations that aren't LLM calls:

```python
from zespan import start_span

with start_span(name="document-retrieval", span_kind="retrieval") as span:
    docs = vector_store.search(query)
    span.end(status="success")
```

## Framework integrations

### LangChain

```python
from zespan import ZespanCallbackHandler

handler = ZespanCallbackHandler()

chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler])
```

### Google ADK

```python
from zespan import ZespanADKTracer

tracer = ZespanADKTracer()
# pass tracer to your ADK runner
```

Or wrap a specific agent:

```python
from zespan import wrap_adk_agent

traced_agent = wrap_adk_agent(my_agent, name="MyAgent")
```

### LlamaIndex

```python
from zespan import LumiqLlamaIndexCallbackHandler
from llama_index.core import Settings

handler = LumiqLlamaIndexCallbackHandler()
Settings.callback_manager.add_handler(handler)
```

### Haystack

```python
from zespan import LumiqHaystackTracer

tracer = LumiqHaystackTracer()
# use as Haystack tracer
```

### Semantic Kernel

```python
from zespan import instrument_semantic_kernel

instrument_semantic_kernel(kernel)
```

### CrewAI

CrewAI uses OTEL-based instrumentation. Set the env vars Zespan exposes:

```python
from zespan.integrations.crewai_handler import get_crewai_env_vars

env = get_crewai_env_vars()
# apply env to your process before launching CrewAI
```

### AutoGen

```python
from zespan import injectAutoGenContext, extractAutoGenContext

# Inject trace context into an AutoGen message
msg = injectAutoGenContext(msg, trace_id="trace_123")

# Extract it on the receiving end
ctx = extractAutoGenContext(msg)
```

## Prompt management

Fetch versioned prompts from Zespan with 5-minute client-side cache:

```python
import zespan

client = zespan.get_client()

# Latest version
prompt = await client.prompts.get("support-reply")

# Specific version or label
prompt = await client.prompts.get("support-reply", version=3)
prompt = await client.prompts.get("support-reply", label="production")

# Compile variables
text = client.prompts.compile(prompt, {"user_name": "Alice", "product": "Pro"})
```

## Guardrails

Check content against your configured guardrail rules before or after LLM calls:

```python
import zespan

check = zespan.check_guardrails(
    text=user_message,
    phase="pre",
)

if not check["allowed"]:
    return "Request blocked by content policy."

response = client.chat.completions.create(...)

post_check = zespan.check_guardrails(
    text=response.choices[0].message.content,
    phase="post",
)

return post_check["modifiedText"] or response.choices[0].message.content
```

## Flush on exit

The SDK flushes automatically on process exit via `atexit`. For short-lived scripts or serverless functions, flush manually:

```python
import zespan

zespan.get_client().flush()
```

## Async support

All SDK methods work in async contexts. Context propagation uses Python's `contextvars` so async tasks inherit their parent's trace context automatically.

```python
import asyncio
import zespan
from zespan import with_zespan_context

zespan.init(api_key="zsp_...", project_id="proj_...")
zespan.autopatch()

async def main():
    with with_zespan_context(user_id="user_123"):
        response = await async_openai_client.chat.completions.create(...)

asyncio.run(main())
```

## OpenTelemetry

Enable native OTEL export alongside Zespan:

```python
zespan.init(
    api_key="zsp_...",
    enable_otel=True,
    otel_endpoint="http://localhost:4318",
    otel_service_name="my-service",
)
```
