Metadata-Version: 2.4
Name: spantree-sdk
Version: 0.1.0
Summary: Trace your AI agent's LLM calls with one line of code
Project-URL: Homepage, https://spantree.dev
Project-URL: Repository, https://github.com/spantree-io/spantree-python
Project-URL: Issues, https://github.com/spantree-io/spantree-python/issues
Author-email: Spantree <hello@spantree.dev>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,ai,anthropic,llm,observability,openai,tracing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# spantree

Trace your AI agent's LLM calls with one line of code.

[![PyPI](https://img.shields.io/pypi/v/spantree-sdk)](https://pypi.org/project/spantree-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)

## Install

```bash
pip install spantree-sdk
```

## Quick Start

```python
from spantree import observe
from anthropic import Anthropic

client = observe(Anthropic())

# All calls are now automatically traced
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    messages=[{"role": "user", "content": "Hello!"}],
    max_tokens=1024,
)
```

That's it. Every LLM call is captured — model, tokens, latency, input/output — and sent to the Spantree dashboard in the background with zero latency impact.

## Grouping Calls with `trace()`

By default, each LLM call creates its own trace. Use `trace()` to group multiple calls under a single trace — ideal for agent loops, multi-step workflows, and tool-use patterns.

```python
from spantree import observe, trace
from anthropic import Anthropic

client = observe(Anthropic())

with trace("chat-session"):
    messages = []
    while True:
        messages.append({"role": "user", "content": input("> ")})
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            messages=messages,
            max_tokens=1024,
        )
        messages.append({"role": "assistant", "content": response.content[0].text})
```

All LLM calls inside the `with trace(...)` block share the same `trace_id` and appear as spans within a single trace in the dashboard.

### Tool use

```python
with trace("agent-task"):
    response = client.messages.create(...)   # span 1
    tool_result = run_tool(response)          # not traced (your code)
    response = client.messages.create(...)   # span 2, same trace
```

### Multi-agent

```python
with trace("research-pipeline"):
    plan = orchestrator.messages.create(...)      # span 1
    research = researcher.messages.create(...)    # span 2
    final = orchestrator.messages.create(...)     # span 3
```

### Nested traces

```python
with trace("pipeline") as t:
    print(t.trace_id)  # access the trace ID
    response = client.messages.create(...)       # child of "pipeline"

    with trace("sub-agent"):
        response = client.messages.create(...)   # child of "sub-agent"

    response = client.messages.create(...)       # child of "pipeline"
```

### Without `trace()` — unchanged behavior

```python
client = observe(Anthropic())
response = client.messages.create(...)  # trace A
response = client.messages.create(...)  # trace B (independent)
```

## Custom Metadata

Attach arbitrary key-value data to spans for filtering and analysis in the dashboard.

### Global metadata (all spans from a client)

```python
client = observe(Anthropic(), metadata={"user_id": "u_123", "env": "prod"})

response = client.messages.create(...)  # span gets {"user_id": "u_123", "env": "prod"}
```

### Per-call metadata

```python
client = observe(Anthropic(), metadata={"user_id": "u_123"})

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    messages=[{"role": "user", "content": "Hello!"}],
    max_tokens=1024,
    spantree_metadata={"session_id": "sess_abc", "feature_flag": "new-flow"},
)
# span gets {"user_id": "u_123", "session_id": "sess_abc", "feature_flag": "new-flow"}
```

Per-call metadata merges with global metadata. Per-call values win on key conflicts.

### On trace spans

```python
with trace("my-workflow") as t:
    t.metadata = {"pipeline": "v2", "run_id": "run_xyz"}
    response = client.messages.create(...)
```

## Configuration

Set your API key via environment variable:

```bash
export SPANTREE_API_KEY=sk_your_key_here
```

Or configure programmatically:

```python
from spantree import configure

configure(
    api_key="sk_your_key_here",
    base_url="https://api.spantree.dev",
)
```

## Supported Providers

| Provider | Wrapper | What's traced |
|----------|---------|---------------|
| **Anthropic** | `observe(Anthropic())` | `messages.create()` |
| **OpenAI** | `observe(OpenAI())` | `chat.completions.create()` |

## How It Works

1. `observe()` detects your LLM client type
2. Wraps API methods to capture timing, tokens, and I/O
3. Buffers spans in a background thread (zero latency impact)
4. Flushes batches to the Spantree API every 5s or 100 spans

No monkey-patching. No import hooks. Just a thin wrapper around your existing client.

## Requirements

- Python 3.9+
- Only runtime dependency: `httpx`

## Contributing

Contributions are welcome! Please open an issue or submit a pull request.

```bash
# Clone and install dev dependencies
git clone https://github.com/spantree-io/spantree-python.git
cd spantree-python
pip install -e ".[dev]"

# Run tests
pytest

# Lint
ruff check .
```

## License

[MIT](LICENSE)
