Metadata-Version: 2.4
Name: sigil-telemetry
Version: 0.5.0
Summary: Plug-and-play telemetry for AI agents — auto-instruments any LLM SDK and exports traces via OpenTelemetry.
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: cohere
Requires-Dist: opentelemetry-instrumentation-cohere>=0.30.0; extra == "cohere"
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-cohere>=0.30.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.

## 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=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 your collector.

---

## What's New in v0.3.0

- **Automatic user identity capture** — Extracts the calling user from Azure AD / SSO headers or JWT Bearer tokens on every request. No code changes needed — works out of the box with `init()`. Disable with `SIGIL_CAPTURE_USER=false`.
- **Worker agent support** — Set `SIGIL_USER_ID` env var for scheduled jobs and queue workers that don't have HTTP requests.

## 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.
- **Health check exclusion** — Routes like `/health`, `/healthz`, `/ready`, `/alive`, `/ping` are excluded from tracing entirely.
- **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

Here's a real agent that summarizes documents using Claude:

### 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` | `doc-summarizer` | Which agent made the call |
| `sigil.agent.version` | `0.1.0` | Agent version |
| `sigil.agent.frameworks` | `Anthropic,FastAPI` | Which SDKs and frameworks were detected |
| `sigil.user.id` | `z.mohammed@company.com` | Who triggered this run (auto-captured) |
| `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):** Wrap your `main()` function in a custom span. All LLM calls within one job 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.)
```

---

## User Identity Capture

v0.3.0 automatically captures who triggered each agent run. No code changes needed.

**How it works:**

| Auth Method | How Identity Is Captured |
|-------------|------------------------|
| Azure AD / SSO (Easy Auth) | Reads `X-MS-CLIENT-PRINCIPAL-NAME` header automatically |
| Azure AD / SSO (in-app JWT) | Decodes Bearer token, extracts `preferred_username` → `upn` → `email` → `name` → `oid` |
| Worker agents (no HTTP) | Reads `SIGIL_USER_ID` env var |

The captured user ID is attached as `sigil.user.id` on every request span. All LLM calls within that request inherit the user context through the shared trace.

**Disable user capture:**

```bash
SIGIL_CAPTURE_USER=false
```

Or in code:

```python
init(SigilConfig(capture_user=False))
```

---

## 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 unique ID |
| `SIGIL_AGENT_VERSION` | `0.1.0` | Track deployments |
| `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 |
| `SIGIL_CAPTURE_USER` | `true` | Set `false` to disable automatic user identity capture |
| `SIGIL_USER_ID` | — | Static user ID for worker agents with no HTTP context |

Or pass config in code:

```python
from sigil_telemetry import init, SigilConfig

init(SigilConfig(
    agent_id="my-agent",
    environment="development",
    console_export=True,
    capture_user=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
```
