Metadata-Version: 2.4
Name: sigil-telemetry
Version: 0.2.0
Summary: Universal AI agent telemetry for Sigil — auto-instruments any LLM SDK and exports to the Sigil collector.
Author: Zurain Khan
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: opentelemetry-api>=1.20.0
Requires-Dist: opentelemetry-sdk>=1.20.0
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
Requires-Dist: opentelemetry-semantic-conventions>=0.41b0
Provides-Extra: anthropic
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.30.0; extra == "anthropic"
Provides-Extra: openai
Requires-Dist: opentelemetry-instrumentation-openai>=0.30.0; extra == "openai"
Provides-Extra: langchain
Requires-Dist: opentelemetry-instrumentation-langchain>=0.30.0; extra == "langchain"
Provides-Extra: crewai
Requires-Dist: opentelemetry-instrumentation-crewai>=0.30.0; extra == "crewai"
Provides-Extra: llamaindex
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.30.0; extra == "llamaindex"
Provides-Extra: vertexai
Requires-Dist: opentelemetry-instrumentation-vertexai>=0.30.0; extra == "vertexai"
Provides-Extra: mistral
Requires-Dist: opentelemetry-instrumentation-mistralai>=0.30.0; extra == "mistral"
Provides-Extra: bedrock
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.30.0; extra == "bedrock"
Provides-Extra: litellm
Requires-Dist: openinference-instrumentation-litellm>=0.1.0; extra == "litellm"
Provides-Extra: fastapi
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.41b0; extra == "fastapi"
Provides-Extra: flask
Requires-Dist: opentelemetry-instrumentation-flask>=0.41b0; extra == "flask"
Provides-Extra: django
Requires-Dist: opentelemetry-instrumentation-django>=0.41b0; extra == "django"
Provides-Extra: databricks
Requires-Dist: databricks-sql-connector>=4.0.0; extra == "databricks"
Provides-Extra: all
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-openai>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-langchain>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-crewai>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-vertexai>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-mistralai>=0.30.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.30.0; extra == "all"
Requires-Dist: openinference-instrumentation-litellm>=0.1.0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.41b0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-flask>=0.41b0; extra == "all"
Requires-Dist: opentelemetry-instrumentation-django>=0.41b0; extra == "all"

# sigil-telemetry

Plug-and-play telemetry for AI agents. Install it, call `init()`, and every LLM call your agent makes is automatically tracked in Sigil.

## Quick Start

```bash
pip install sigil-telemetry[all]
```

```python
from sigil_telemetry import init
init()
```

```bash
# Set your agent's ID and collector endpoint
SIGIL_AGENT_ID=sigil-agent-your-agent-slug
SIGIL_COLLECTOR_URL=https://your-collector-endpoint/
```

That's it. Every LLM API call is now captured — tokens, model, latency, errors — and sent to the Sigil collector.

---

## What's New in v0.2.0

- **Web framework auto-instrumentation** — FastAPI, Flask, and Django are auto-detected and instrumented. All LLM calls within one HTTP request share a single `trace_id` (operation_Id), so you can count agent "runs" with `COUNT(DISTINCT trace_id)`.
- **Noise span filtering** — `http send` / `http send body` spans from web frameworks are silently dropped before they leave the process. They never reach your collector, so you're not billed for them.
- **Health check exclusion** — Routes like `/health`, `/healthz`, `/ready`, `/alive`, `/ping` are excluded from tracing entirely. No spans generated, no storage cost.
- **Graceful shutdown** — `atexit` handler flushes all pending spans when the process exits, so you never lose the last batch.
- **Lighter install** — Removed unnecessary dependencies from the core install.

---

## Full Example: What Actually Happens

Here's a real agent that summarizes documents using Claude. Let's walk through exactly what the telemetry captures and where it ends up.

### 1. The Agent Code

```python
# document_summarizer.py
import anthropic
from sigil_telemetry import init

# Initialize telemetry — call this ONCE at startup
init()

# Your normal agent code — no changes needed
client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Summarize this document: ..."}
    ]
)
print(response.content[0].text)
```

### 2. What Gets Captured (Per LLM Call)

Every time `client.messages.create()` runs, a **span** is automatically created with:

| Field | Example Value | Description |
|-------|--------------|-------------|
| `operation_Id` | `a1b2c3d4e5f6...` | Trace ID — groups all LLM calls in a single agent run |
| `sigil.agent.id` | `sigil-agent-doc-summarizer` | Which agent made the call |
| `sigil.agent.version` | `sha-abc1234` | Agent version (set by deploy workflow) |
| `sigil.agent.frameworks` | `Anthropic,FastAPI` | Which SDKs and frameworks were detected |
| `gen_ai.system` | `anthropic` | LLM provider |
| `gen_ai.request.model` | `claude-sonnet-4-20250514` | Model used |
| `gen_ai.usage.input_tokens` | `1250` | Tokens sent |
| `gen_ai.usage.output_tokens` | `340` | Tokens received |
| `duration` | `2.3s` | How long the call took |
| `status` | `OK` or `ERROR` | Whether the call succeeded |
| `sigil.environment` | `production` | Environment |
| `sigil.agent.division` | `Sales` | Business division (if set) |
| `sigil.agent.risk_classification` | `low` | Risk level (if set) |

If the agent makes **multiple LLM calls** in one run (e.g., calls Claude then GPT-4), all calls share the same `operation_Id` so you can see the full trace.

### 3. How Trace Grouping Works

**API agents (FastAPI/Flask/Django):** The web framework instrumentor creates a root span per HTTP request. All LLM calls within that request automatically become child spans sharing the same `trace_id`. You don't need to do anything — `init()` handles it.

**Worker agents (scheduled jobs, listeners):** The template wraps your `main()` function in a root span. All LLM calls within one job or message share the same `trace_id`.

In both cases: `COUNT(DISTINCT trace_id)` = number of agent runs.

### 4. Where the Data Goes

```
Agent makes LLM call
        │
        ▼
sigil-telemetry auto-captures it as an OpenTelemetry span
(noise spans like "http send" are filtered out here)
        │
        ▼
Span is batched and sent via OTLP to:
  → Your configured collector endpoint
        │
        ▼
Collector forwards to:
  → Your observability backend (Jaeger, Zipkin, Datadog, etc.)
```

---

## Supported SDKs

Use `[all]` to install everything. Only the SDKs your agent actually uses get activated.

| SDK | Install Extra | What It Covers |
|-----|--------------|----------------|
| Anthropic | `[anthropic]` | Anthropic API |
| OpenAI | `[openai]` | OpenAI API (including compatible endpoints) |
| LangChain | `[langchain]` | LangChain, LangGraph, any LangChain-wrapped model |
| CrewAI | `[crewai]` | CrewAI multi-agent framework |
| LlamaIndex | `[llamaindex]` | LlamaIndex agents and pipelines |
| Vertex AI | `[vertexai]` | Google Vertex AI, Gemini models |
| Mistral AI | `[mistral]` | Mistral API |
| AWS Bedrock | `[bedrock]` | Claude, Llama, Titan via AWS |
| LiteLLM | `[litellm]` | Unified proxy across 100+ LLM providers |

## Web Framework Auto-Instrumentation

These are included in `[all]` and auto-detected by `init()`:

| Framework | Install Extra | What It Does |
|-----------|--------------|--------------|
| FastAPI | `[fastapi]` | Creates root span per HTTP request — all LLM calls in that request share one trace_id |
| Flask | `[flask]` | Same trace grouping for Flask apps |
| Django | `[django]` | Same trace grouping for Django apps |

Health check routes (`/health`, `/healthz`, `/ready`, `/alive`, `/ping`, `/startup`, `/liveness`, `/readiness`) are automatically excluded from tracing.

## Configuration

| Env Variable | Default | Description |
|-------------|---------|-------------|
| `SIGIL_AGENT_ID` | — | **Required.** Your agent's Sigil ID |
| `SIGIL_AGENT_VERSION` | `0.1.0` | Track deployments (set automatically by deploy workflow) |
| `SIGIL_COLLECTOR_URL` | — | **Required.** Your collector endpoint URL |
| `SIGIL_ENVIRONMENT` | `production` | `production`, `staging`, `development` |
| `SIGIL_CONSOLE_EXPORT` | `false` | Print spans to console for debugging |
| `SIGIL_DIVISION` | — | Business division (e.g., `Sales`, `Engineering`) |
| `SIGIL_RISK_CLASSIFICATION` | — | Agent risk level (`low`, `medium`, `high`) |
| `SIGIL_HOURS_SAVED` | — | Estimated hours saved per run |

Or pass config in code:

```python
from sigil_telemetry import init, SigilConfig

init(SigilConfig(
    agent_id="sigil-agent-my-agent",
    environment="development",
    console_export=True
))
```

## Custom Spans

Track things beyond LLM calls (document parsing, tool use, etc.):

```python
from sigil_telemetry import get_tracer, record_error

tracer = get_tracer()

with tracer.start_as_current_span("parse-contract") as span:
    span.set_attribute("document.pages", 42)
    try:
        result = parse_pdf(file)
    except Exception as e:
        record_error(span, e)
        raise
```

