Metadata-Version: 2.4
Name: verica-observability
Version: 0.1.7
Summary: Two-line LLM tracing for Verica: init(token) and your OpenAI/Anthropic calls land as evaluable traces.
Project-URL: Homepage, https://verica.app
License-Expression: MIT
Keywords: evals,llm,observability,opentelemetry,tracing,verica
Requires-Python: >=3.9
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.33
Requires-Dist: opentelemetry-instrumentation-google-genai>=0.1b0
Requires-Dist: opentelemetry-instrumentation-openai>=0.33
Requires-Dist: opentelemetry-sdk>=1.27
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# verica-observability

Two-line LLM tracing for [Verica](https://verica.app): your OpenAI/Anthropic
calls land as evaluable traces.

## Install

```bash
pip install verica-observability
```

## Use

```python
import verica

verica.init(token=os.environ["VERICA_TOKEN"])

# Import the clients AFTER init so they are patched. OpenAI, Anthropic, and
# Gemini (the google-genai SDK) are all auto-traced, no extra code.
from openai import OpenAI
from anthropic import Anthropic
from google import genai
```

## Sessions (multi-turn)

Traces sharing a conversation id reassemble into a Verica session. In a server
handling many conversations, scope the id per request:

```python
with verica.conversation(f"chat-{chat_id}"):
    response = client.chat.completions.create(...)
```

Context-local (async- and thread-safe); it overrides the global
`conversation_id` from `init()`, and `verica.conversation(None)` suppresses the
attribute for the block. For a single-conversation script, passing
`conversation_id="..."` to `init()` is enough.

**Resend history exactly as sent.** Turns are stored as deltas: at ingest,
Verica matches the history you resend against what previous turns already
stored, and that match is exact (byte-identical text). If your app mutates
prior messages between requests (for example, appending "Respond in JSON" to
the last user message and stripping it from earlier turns when rebuilding the
history), no prefix ever matches and every turn falls back to storing, and
showing, the full conversation again. Keep injected instructions in the system
prompt, or resend them exactly as originally sent.

## Agent loops

By default each LLM call lands as its own single-span trace. Wrap a whole
agent run in `verica.span` so every auto-instrumented call inside it (and any
nested `verica.span` / `verica.tool`) becomes a child: one trace per run.
`verica.tool` does the same and marks the span as a tool execution (the GenAI
semconv `gen_ai.operation.name = execute_tool` + `gen_ai.tool.name`, which
Verica detects for tool-span counting and `span_check`).

```python
with verica.span("agent.run"):
    client.chat.completions.create(...)          # child span
    with verica.tool("lookup_weather"):
        weather = lookup_weather("Cordoba")
    client.chat.completions.create(...)          # child span
```

Both take a name and are context managers. The span name is your string
verbatim. On a thrown error the span is marked with ERROR status and the
exception propagates unchanged. Fail-open: without `init()` (or if tracing is
broken) the block just runs and nothing is emitted. See `examples/agent.py` for
a full two-turn run with two tools.

### Adopting in an agent app (e.g. Routina)

Wrap the loop once and each tool execution once; everything else is unchanged:

```python
def run_agent(...):
    with verica.span("agent.run"):
        ...existing loop: each client call lands as a child span...

# in the tool dispatcher:
with verica.tool(name):
    result = agent_tools.execute(name, args, ...)
```

## Tags

Tags land on each trace (`traces.tags`): filter the workbench by them, and bind
criteria to them so evaluation preselects the right criteria per tag.

```python
verica.init(token=..., tags=["routina", "prod"])   # global, every trace

with verica.tags(["chat", "premium"]):              # adds to the globals
    client.chat.completions.create(...)
```

Per-request tags UNION with the globals (dedup, order preserved); nested scopes
accumulate. Context-local, async- and thread-safe. Values are coerced with
`str()`; the server caps at 20 tags x 120 chars.

## Serverless

Call `verica.flush()` (or `verica.shutdown()`) before the runtime freezes so
the span batch is exported.

## Options

| Option / env var                             | Default    | Notes                                                                   |
| -------------------------------------------- | ---------- | ----------------------------------------------------------------------- |
| `token` / `VERICA_TOKEN`                     | (required) | ingest-scoped API token                                                 |
| `capture_content` / `VERICA_CAPTURE_CONTENT` | `true`     | send prompt/response content                                            |
| `conversation_id`                            | (none)     | global `gen_ai.conversation.id`; per-request: `verica.conversation(id)` |
| `tags`                                       | (none)     | global tags; per-request: `verica.tags([...])`                          |
| `service_name` / `OTEL_SERVICE_NAME`         | `app`      | resource service.name                                                   |
| `debug` / `VERICA_DEBUG`                     | `false`    | log export errors                                                       |

Fail-open by design: if Verica is unreachable or the token is invalid, spans are
dropped and your app is never affected. Export errors are silent unless `debug`
is on.
