# AGENTS.md — plyra-trace v2.0 Integration Reference

Generic tracing for any LLM agent, regardless of framework or provider.

---

## Three Integration Modes

### Mode 1 — Env vars only (zero Python changes)

Set environment variables before running your agent.  
Works with any OTel-instrumented SDK.

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_SERVICE_NAME=my-agent
export OTEL_RESOURCE_ATTRIBUTES=plyra.project.id=my-project
```

The plyra-trace collector accepts standard OTLP/gRPC or OTLP/HTTP.  
Spans are automatically normalised on ingest.

---

### Mode 2 — Python decorators (any framework)

```python
import plyra_trace

plyra_trace.init(project="my-project")

@plyra_trace.agent(name="my-agent")
def run(question: str) -> str:
    ...

@plyra_trace.chain(name="preprocess")
def preprocess(data):
    ...

@plyra_trace.tool(name="search")
def search(query: str) -> list:
    ...

@plyra_trace.guard(name="safety-check", policy="content-safety")
def check(text: str) -> bool:
    ...
```

---

### Mode 3 — Framework callbacks / instrumentors

Install a standalone instrumentation package and it patches the framework
automatically.

```bash
pip install plyra-instrument-openai     # OpenAI SDK
pip install plyra-instrument-anthropic  # Anthropic SDK
pip install plyra-instrument-langchain  # LangChain / LangGraph
pip install plyra-instrument-litellm    # LiteLLM
```

Then enable via `auto_instrument` or explicit `framework` parameter:

```python
plyra_trace.init(
    project="my-project",
    auto_instrument=True,             # detect any installed instrumentor
    # framework="openai+langchain",   # or explicit
)
```

---

## `plyra_trace.init()` Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `project` | `str` | `None` | Project identifier (also via `PLYRA_PROJECT`) |
| `endpoint` | `str` | `None` | OTLP endpoint (also via `PLYRA_ENDPOINT` or `OTEL_EXPORTER_OTLP_ENDPOINT`) |
| `service_name` | `str` | `None` | Service name (also via `OTEL_SERVICE_NAME`) |
| `framework` | `str\|None` | `None` | Comma/plus-separated: `"openai+langchain"` |
| `auto_instrument` | `bool` | `False` | Auto-detect installed frameworks and instrument |
| `capture_content` | `bool` | `True` | Record prompt/response text |
| `capture_usage` | `bool` | `True` | Record token counts |
| `debug` | `bool` | `False` | Emit spans to stdout instead of OTLP |

---

## Framework-Specific Notes

### OpenAI

```python
plyra_trace.init(project="openai-app", framework="openai")
from openai import OpenAI
client = OpenAI()
# All chat + embedding calls are traced automatically
```

Captured attributes:
- `llm.model_name`, `llm.provider`, `llm.token_count.*`, `llm.cost_usd`
- `llm.cache_hit`, `input.value`, `output.value`

---

### Anthropic

```python
plyra_trace.init(project="anthropic-app", framework="anthropic")
import anthropic
client = anthropic.Anthropic()
# Messages.create (sync + async) both traced
```

---

### LangChain / LangGraph

**Option A — global instrumentation:**
```python
plyra_trace.init(project="lc-app", framework="langchain")
```

**Option B — per-chain callback (recommended for fine-grained control):**
```python
from plyra_instrument_langchain import PlyraCallbackHandler
handler = PlyraCallbackHandler()
llm = ChatOpenAI(callbacks=[handler])
chain.invoke({"input": "..."}, config={"callbacks": [handler]})
```

---

### LiteLLM

```python
plyra_trace.init(project="ll-app", framework="litellm")
import litellm
response = litellm.completion(model="gpt-4o-mini", messages=[...])
# Uses litellm's built-in cost tracking when available
```

---

## Span Kinds

| Kind | Icon | Use |
|------|------|-----|
| `CHAIN` | ◻ | Top-level pipeline, workflow, or callable |
| `AGENT` | ◈ | Autonomous agent (ReAct, plan-execute, etc.) |
| `LLM` | ◎ | Direct LLM call |
| `TOOL` | ⬡ | Tool/function invocation |
| `GUARD` | ⊗ | Safety filter, policy check |
| `RETRIEVER` | ⊞ | Vector search, document retrieval |
| `EMBEDDING` | ⊙ | Embedding generation |
| `EVALUATOR` | ◉ | LLM-as-judge, scoring |
| `ROUTER` | ⊿ | Message routing, intent classification |
| `PROMPT` | ◫ | Prompt template rendering |
| `RERANKER` | ⇅ | Document reranking |

Set manually via attribute: `span.set_attribute("plyra.span.kind", "TOOL")`

---

## Canonical Span Attributes

### Universal
| Attribute | Type | Description |
|-----------|------|-------------|
| `plyra.span.kind` | str | Span kind (see table above) |
| `input.value` | str | JSON-encoded input |
| `output.value` | str | JSON-encoded output |
| `session.id` | str | Conversation / session ID |
| `user.id` | str | User identifier |

### LLM spans
| Attribute | Type | Description |
|-----------|------|-------------|
| `llm.model_name` | str | Model identifier |
| `llm.provider` | str | `openai`, `anthropic`, etc. |
| `llm.token_count.prompt` | int | Input tokens |
| `llm.token_count.completion` | int | Output tokens |
| `llm.token_count.total` | int | Total tokens |
| `llm.cost_usd` | float | Estimated cost in USD |
| `llm.cache_hit` | bool | Whether response was cached |

### Guard spans
| Attribute | Type | Description |
|-----------|------|-------------|
| `guard.policy` | str | Policy name |
| `guard.action` | str | `allow`, `block`, `flag`, `redact` |
| `guard.triggered` | bool | Whether the guard fired |
| `guard.confidence` | float | Confidence score 0–1 |
| `guard.provider` | str | `custom`, `guardrails-ai`, etc. |

---

## Collector API

| Endpoint | Description |
|----------|-------------|
| `POST /v1/traces` | OTLP/HTTP protobuf ingest |
| `GET /api/v1/projects` | List all projects |
| `GET /api/v1/traces?project=X&limit=200` | List traces |
| `GET /api/v1/traces/{id}/spans` | Get spans for a trace |
| `GET /api/v1/stats/llm?project=X&since=24h` | LLM usage stats |
| `GET /api/v1/stats/agents?project=X` | Agent performance stats |
| `GET /api/v1/stats/guards?project=X` | Guard events |
| `GET /api/v1/stats/db` | Database metadata |

Start the collector:
```bash
plyra-trace collector --host 0.0.0.0 --port 8000 --db traces.db
```

---

## Compatibility

plyra-trace v2.0 accepts spans from:
- **OpenInference** (`openinference.span.kind`, `llm.input_messages`, …)
- **OpenLLMetry** (`traceloop.span.kind`, `llm.completions`, …)
- **LangChain** (LangSmith-style run events via callback)
- **gen_ai** (OpenTelemetry semantic conventions `gen_ai.*`)
- **Raw OTLP** (infer kind from span name patterns)

All formats are normalised to canonical attributes on ingest.
