Metadata-Version: 2.4
Name: syrin-sdk
Version: 1.0.0
Summary: AI agent observability and control SDK for Syrin
Project-URL: Homepage, https://syrin.dev
Project-URL: Documentation, https://docs.syrin.dev
Project-URL: Repository, https://github.com/syrin-labs/syrin-sdk
Project-URL: Bug Tracker, https://github.com/syrin-labs/syrin-sdk/issues
Project-URL: Changelog, https://github.com/syrin-labs/syrin-sdk/blob/main/CHANGELOG.md
Author-email: Syrin Labs <sdk@syrin.dev>
License: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,governance,llm,observability,openai,opentelemetry
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: httpx>=0.24.0
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
Requires-Dist: opentelemetry-sdk>=1.20.0
Provides-Extra: adapters
Requires-Dist: anthropic>=0.25.0; extra == 'adapters'
Requires-Dist: langchain-core>=0.2; extra == 'adapters'
Requires-Dist: langchain-openai>=0.1; extra == 'adapters'
Requires-Dist: langgraph>=0.1; extra == 'adapters'
Requires-Dist: openai>=1.0.0; extra == 'adapters'
Requires-Dist: pydantic-ai>=0.0.9; extra == 'adapters'
Provides-Extra: dev
Requires-Dist: fastapi>=0.100.0; extra == 'dev'
Requires-Dist: httpx>=0.24.0; extra == 'dev'
Requires-Dist: openai>=1.0.0; extra == 'dev'
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest-httpserver>=1.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Requires-Dist: uvicorn>=0.23.0; extra == 'dev'
Provides-Extra: mock-backend
Requires-Dist: fastapi>=0.100.0; extra == 'mock-backend'
Requires-Dist: uvicorn>=0.23.0; extra == 'mock-backend'
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == 'openai'
Description-Content-Type: text/markdown

# Syrin SDK for Python

[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![OpenTelemetry](https://img.shields.io/badge/OpenTelemetry-enabled-orange.svg)](https://opentelemetry.io/)

**Mixpanel for AI agents** — observability, remote configuration, evals, and debugging for
any AI agent, framework, or LLM library. Minimal integration, maximum insight.

---

## What you get

- **Dashboard visibility** — every LLM call, session, agent run, tool call, and custom event in one place
- **Remote config** — change model, temperature, prompts, and any parameter live from the dashboard — no redeploy
- **Governance** — stop or constrain agent behaviour from the backend at runtime
- **Checkpoints** — save and restore conversation state for recovery flows
- **Custom logging** — emit structured events that appear on the session timeline
- **OpenTelemetry** — standard `gen_ai.*` spans + `syrin.*` extensions

---

## Setup — 4 lines

```bash
pip install syrin-sdk
```

```python
import syrin_sdk

syrin_sdk.init(api_key="syrin_...")
```

That is the entire setup. The SDK connects to your Syrin project and you can immediately
start emitting events, using remote config, and scoping sessions.

---

## Core API

### 1. Session scoping

Group all events for a user request into a single dashboard session.

```python
# Web app — one session per request
with syrin_sdk.session(user_id=user_id, window="day") as sid:
    reply = call_llm(messages)

# Background job — independent run each time
with syrin_sdk.session() as sid:
    result = run_pipeline()

# Async
async with syrin_sdk.async_session(user_id=user_id) as sid:
    reply = await call_llm_async(messages)
```

Session IDs are **stable within a window** — all of Alice's requests today share one
session, letting you see her full conversation history in the dashboard.

| Call | Session ID |
|------|-----------|
| `session()` | `ses_a1b2c3d` (auto) |
| `session(user_id="alice", window="day")` | `u:alice:2026-04-19` |
| `session(user_id="alice", window="forever")` | `u:alice` |
| `session(key="batch-etl", window="day")` | `k:batch-etl:2026-04-19` |

### 2. Agent scoping

Tag events with the agent that produced them.

```python
with syrin_sdk.agent("researcher"):
    response = client.chat.completions.create(...)

# Register with metadata for the dashboard
syrin_sdk.register_agent("researcher", description="Searches and summarises")
```

Combine with `workflow()` or `swarm()` for multi-agent pipelines:

```python
with syrin_sdk.workflow("pipeline"):
    with syrin_sdk.agent("planner"):
        plan = call_llm(plan_prompt)
    with syrin_sdk.agent("executor"):
        result = call_llm(exec_prompt)
```

### 3. Remote config — `cfg()`

Declare any parameter as remotely configurable. The dashboard shows a live control panel.

```python
response = client.chat.completions.create(
    model=syrin_sdk.cfg("llm.model", "gpt-4o"),
    temperature=syrin_sdk.cfg("llm.temperature", 0.7, ge=0.0, le=2.0),
    system_prompt=syrin_sdk.cfg("prompt.system", "You are a helpful assistant.", multiline=True),
    messages=[...],
)
```

- `key` uses dot-notation: `"section.field"` — sections become accordion groups in the dashboard
- The **default** is used until you push an override from the dashboard
- Inside `with agent("name"):` the field is automatically scoped to that agent

### 4. Custom logging

Emit structured events that appear on the session timeline.

```python
syrin_sdk.log("Retrieved 42 documents", metadata={"collection": "kb", "latency_ms": 45})
syrin_sdk.log("Cost budget at 80%", level="warning")
syrin_sdk.log("Tool call failed", level="error", metadata={"tool": "web_search"})
```

### 5. Governance

The backend can stop an agent mid-run. Catch `GovernanceStopError`:

```python
from syrin_sdk import GovernanceStopError

try:
    response = call_llm(messages)
except GovernanceStopError as e:
    logger.warning("Agent stopped by governance: %s", e.reason)
    return {"error": "request_blocked"}
```

---

## Common patterns

### Instrument a FastAPI route

```python
import syrin_sdk
from fastapi import FastAPI

syrin_sdk.init(api_key="syrin_...")

app = FastAPI()

@app.post("/chat")
async def chat(request: ChatRequest):
    async with syrin_sdk.async_session(user_id=request.user_id) as sid:
        async with syrin_sdk.async_agent("chat-agent"):
            reply = await call_llm(request.messages)
    return {"reply": reply, "session_id": sid}
```

### Instrument a tool

```python
@syrin_sdk.tool("web_search")
def search(query: str) -> list[str]:
    return web_search_api(query)

# Async
@syrin_sdk.async_tool("database_lookup")
async def lookup(id: str) -> dict:
    return await db.get(id)
```

### Checkpoints (save / restore conversation state)

```python
# Save before a risky operation
checkpoint = syrin_sdk.create_checkpoint(messages, label="pre-tool-call")

# If the tool call fails, restore
try:
    result = risky_tool_call()
except Exception:
    messages = syrin_sdk.restore_checkpoint(checkpoint.checkpoint_id)
```

### Typed agent config schema

For agents with many configurable fields, use `AgentSchema`:

```python
from syrin_sdk import AgentSchema, field

class ResearchConfig(AgentSchema):
    temperature: float = field(0.3, ge=0.0, le=2.0, label="Temperature")
    system_prompt: str = field("Research thoroughly.", section="prompt", multiline=True)
    max_results: int = field(10, ge=1, le=100)

syrin_sdk.register_agent("researcher", schema=ResearchConfig)
```

### Skip telemetry for specific calls

```python
with syrin_sdk.skip():
    client.chat.completions.create(...)  # not tracked — useful for health pings
```

---

## Configuration

```python
sdk = syrin_sdk.init(
    api_key="syrin_...",        # required
    agent_id="my-agent",        # optional — ties all calls to this agent by default
    offline=False,              # True = no network calls (local dev)
    capture_content=False,      # True = record prompt/response text (PII-sensitive)
    capture_tool_calls=True,    # record tool call events
    backend_url="https://...",  # defaults to Syrin cloud
    otel_endpoint="http://...", # optional OTLP endpoint for Jaeger / Tempo
)
```

---

## API reference

### Always needed

| Symbol | What it does |
|--------|-------------|
| `init(api_key, ...)` | Initialize the SDK |
| `shutdown()` | Flush events, tear down |
| `session(user_id, window)` | Scope events to a session |
| `async_session(...)` | Async version |
| `agent(id)` | Scope events to an agent |
| `async_agent(id)` | Async version |
| `cfg(key, default)` | Remote-configurable value |
| `log(message, level)` | Emit custom event |
| `GovernanceStopError` | Catch governance stops |

### Common

| Symbol | What it does |
|--------|-------------|
| `register_agent(id, ...)` | Register agent with dashboard |
| `register_endpoint(name, schema)` | Register run form schema |
| `configure(**overrides)` | Local config overrides |
| `on_config_change(callback)` | React to remote config pushes |
| `on_alert(callback)` | React to backend alerts |
| `workflow(id)` / `swarm(id)` | Multi-agent scoping |
| `tool(name)` / `async_tool(name)` | Instrument tool functions |
| `memory(name)` | Instrument memory access |
| `create_checkpoint(messages)` | Save conversation state |
| `restore_checkpoint(id)` | Restore conversation state |
| `skip()` | Exclude a block from telemetry |
| `health_check()` | Ping the backend |

### Advanced (importable, not advertised)

`ConfigGuard`, `ConfigFuse`, `ConfigAnchor` — circuit-breaker and rollback for config changes  
`tunable` / `tune()` — remote-tunable class decorator  
`TraceSpan` — manual custom trace spans  
`SyrinSDKCore` — raw instrumentation engine for framework authors  
Event types (`LLMCallEvent`, `SessionStartedEvent`, etc.) — for `on_config_change` callbacks

---

## Multi-instance

Most apps use the module-level helpers (`syrin_sdk.cfg(...)`, etc.) which target the
**default** instance. For multiple named instances:

```python
sdk_a = syrin_sdk.init(api_key="...", instance_name="agent-a")
sdk_b = syrin_sdk.init(api_key="...", instance_name="agent-b")

sdk_a.configure(temperature=0.3)
sdk_b.configure(temperature=0.7)
```

---

## Docs

- [Quickstart Guide](docs/quickstart.md)
- [ConfigStore, Tunable, ConfigGuard](docs/config_store.md)
- [OTel Schema Reference](docs/otel_reference.md)
- [Backend API Reference](docs/backend_api.md)
