Metadata-Version: 2.4
Name: gravito-guardrails
Version: 0.1.0
Summary: Gravito auto-instrumentation SDK for Python — wrap OpenAI / Anthropic / LangChain / LlamaIndex / Google GenAI / Mistral / Cohere / Together clients so every LLM call is observed by the Gravito coherence ledger.
Author: Gravito
License: MIT
Project-URL: Homepage, https://gravito.ai
Project-URL: Documentation, https://gravito.ai/docs
Project-URL: Repository, https://github.com/samuelrkestenbaum-dot/empathiq-website
Keywords: llm,observability,openai,anthropic,langchain,llamaindex,ai,ml,ledger
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28.0
Provides-Extra: async
Requires-Dist: aiohttp>=3.8.0; extra == "async"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"

# gravito-guardrails (Python)

Python mirror of [`@gravitoai/guardrails`](../guardrails-sdk). Wrap an LLM client once at construction and every call POSTs an observation to the Gravito coherence ledger at `/api/gravito/v1/llm-event`.

## Install

```bash
pip install gravito-guardrails
```

## Quick start

```python
import openai
from gravito import wrap_openai, with_surface

client = wrap_openai(
    openai.OpenAI(),
    api_key="grav_...",       # or set GRAVITO_API_KEY env var
    org_id=1,                  # or set GRAVITO_ORG_ID env var
    default_surface="my_app.llm_call",
)

# Every call below is now silently observed.
res = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "hello"}],
)

# Tag a code region with a specific surface:
with with_surface("checkout.fraud_check"):
    client.chat.completions.create(model="gpt-4o", messages=[...])
```

## Supported wrappers

| Function | SDK | Hooks |
|---|---|---|
| `wrap_openai(client)` | `openai>=1.0` | `chat.completions.create`, `completions.create`, `responses.create`, `embeddings.create`, `images.generate` |
| `wrap_anthropic(client)` | `anthropic` | `messages.create`, `completions.create` |
| `wrap_mistral(client)` | `mistralai` | `chat.complete/stream`, `fim.complete`, `embeddings.create`, `agents.complete` |
| `wrap_cohere(client)` | `cohere` | `chat`, `chat_stream`, `generate`, `generate_stream`, `embed`, `rerank`, `classify`, `summarize` |
| `wrap_together(client)` | `together` | `chat.completions.create`, `completions.create`, `embeddings.create`, `images.generate` |
| `wrap_bedrock(client)` | `boto3.client('bedrock-runtime')` | `invoke_model`, `invoke_model_with_response_stream`, `converse`, `converse_stream` |
| `wrap_ollama(client)` | `ollama` | `chat`, `generate`, `embed`, `embeddings` |
| `wrap_groq(client)` | `groq` | `chat.completions.create`, `audio.transcriptions/translations.create`, `embeddings.create` |
| `wrap_perplexity(client)` | `openai` (with `base_url='https://api.perplexity.ai'`) | `chat.completions.create`, `completions.create` |
| `wrap_google_genai(model)` | `google-generativeai` | `generate_content`, `generate_content_async`, `count_tokens`, `embed_content`, plus chat session `send_message`/`send_message_async` |
| `wrap_langchain(runnable)` | `langchain` | `invoke`, `ainvoke`, `stream`, `astream`, `batch`, `abatch`, `call`, `predict`, `generate`, `agenerate` |
| `wrap_llamaindex(obj)` | `llama_index` | `chat`, `complete`, `query`, `predict`, async variants, stream variants |

## Surface context

`with_surface(...)` is a `contextvars`-backed context manager. It propagates through async (`asyncio`), threads spawned via `threading.Thread` (when you `contextvars.copy_context().run(...)`), and standard call stacks. It mirrors the JS `withSurface()` exactly.

```python
from gravito import with_surface

with with_surface("rag_pipeline.synthesize", metadata={"user_id": user.id}):
    answer = chain.invoke({"question": q})
```

## Design constraints

Same as the TypeScript package:

- **Fire-and-forget.** Every observation POST is a daemon thread; never adds latency to the LLM call.
- **Fail-open.** Errors POSTing to Gravito are logged and swallowed; the wrapped LLM call's return value is untouched.
- **No code changes after install.** Wrap once at construction.

## Production checklist

1. Wrap **every** native SDK client and every LangChain/LlamaIndex top-level object.
2. After your first wrapped call, hit the coverage endpoint to confirm rows landed:
   ```bash
   curl -H "X-Gravito-Api-Key: $GRAVITO_API_KEY" \
        -H "X-Gravito-Org-Id: $YOUR_ORG_ID" \
        https://gravito.ai/api/gravito/v1/coverage
   ```
3. Schedule the daily integrity check (see the main repo's `check-ingest-key-validity.yml`) so silent key rotations don't break the integration for 40 days.

## Differences from the TypeScript SDK

- Python doesn't have JS's `Proxy` primitive, so wrappers monkey-patch bound methods on the client instance. The client object is **mutated** by the wrap call (and also returned for convenience).
- Surface context uses `contextvars.ContextVar`, the Python equivalent of `AsyncLocalStorage`. Behavior matches exactly for `asyncio` code; for raw threads use `contextvars.copy_context().run(...)` to propagate.
- The Vercel AI SDK wrapper (`wrapVercelAI`) isn't included — the Vercel AI SDK is TypeScript-only.

## Related packages

- [`@gravitoai/guardrails`](../guardrails-sdk) — TypeScript / Node.js mirror
- [`@gravitoai/mcp-shim`](../gravito-mcp-shim) — proxy MCP server that observes every `tools/call`
- [Chrome extension](../../gravito-chrome-extension/) — DOM observer for 14 hosted AI assistants

Together these cover the dominant production AI surface area above the protocol layer.
