Metadata-Version: 2.4
Name: vaxelia-ai-openai
Version: 0.1.0
Summary: Drop-in, compliance-instrumented wrapper around the official OpenAI Python SDK.
Project-URL: Homepage, https://scm.bradaa.com/1873/labs
Author: Vaxelia
License-Expression: Apache-2.0
Keywords: ai,audit,compliance,gpt,openai
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Requires-Dist: openai>=1.0
Requires-Dist: vaxelia-ai-core>=0.1.0
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# vaxelia-ai-openai

A **drop-in, compliance-instrumented** wrapper around the official
[OpenAI Python SDK](https://pypi.org/project/openai/). Every non-streaming
`chat.completions.create` call is logged to your tenant's decision-log endpoint
via [`vaxelia-ai-core`](../vaxelia-ai-core/README.md) — with its resilient,
encrypted-on-disk buffer — so an AI decision is never silently lost. Every other
method of the OpenAI SDK passes straight through to the real client untouched.

`openai` is a regular dependency of this package, so the wrapper is a true
single-install drop-in: you do not install the OpenAI SDK separately.

## Install

```bash
pip install vaxelia-ai-openai
```

Requires Python >= 3.9.

## The one-line swap

Change your import — keep everything else:

```diff
- from openai import OpenAI
+ from vaxelia_ai_openai import OpenAI
```

## Usage

```python
import os
from vaxelia_ai_openai import OpenAI

client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    # Optional compliance block. Every field falls back to an env var (below),
    # so you can also pass nothing here and configure entirely from the env.
    compliance={
        "ai_system_id": os.environ.get("VAXELIA_AI_SYSTEM_ID"),
        "tenant_api_url": os.environ.get("VAXELIA_TENANT_API_URL"),
        "api_key": os.environ.get("VAXELIA_API_KEY"),
        "buffer_key": os.environ.get("VAXELIA_BUFFER_KEY"),
    },
)

# Identical to the official SDK. The decision is logged for you.
completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Summarize the attached contract."}],
)
```

With env vars set, the `compliance` block can be omitted entirely:

```python
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
```

The `compliance` argument accepts either a `vaxelia_ai_core.ReporterConfig` or a
plain `dict` with the same field names.

### Reaching the raw client

For methods this wrapper does not instrument, the underlying OpenAI client is
available at `client.raw`, and every other attribute/method (`models`,
`embeddings`, `responses`, `with_options`, …) delegates to the real client
automatically. On the `chat` resource, every member other than
`completions.create` (`completions.parse`, `completions.stream`,
`with_raw_response`, …) delegates directly to the real resource.

## Configuration & environment variables

The `compliance` block is a `ReporterConfig` from `vaxelia-ai-core`. Every field
is optional and falls back to a matching environment variable (12-factor):

| `compliance` field | Environment variable     | Description                                                  |
| ------------------ | ------------------------ | ------------------------------------------------------------ |
| `ai_system_id`     | `VAXELIA_AI_SYSTEM_ID`   | Registered AI system identifier.                             |
| `tenant_api_url`   | `VAXELIA_TENANT_API_URL` | Full decision-log POST endpoint URL.                         |
| `api_key`          | `VAXELIA_API_KEY`        | Bearer token for the tenant API.                             |
| `buffer_key`       | `VAXELIA_BUFFER_KEY`     | 32-byte AES-256-GCM key, base64-encoded. Required to buffer. |

For the encrypted disk buffer, the fail-closed buffer-key rule, retention, and
backoff behaviour, see the
[`vaxelia-ai-core` README](../vaxelia-ai-core/README.md). Note: when buffering is
enabled (the default) and no valid 32-byte buffer key is resolvable,
**construction raises** (`MissingBufferKeyError` / `InvalidBufferKeyError`) —
this wrapper surfaces that error rather than swallow it. To run without a disk
buffer, set `compliance.buffering_enabled = False`.

## What gets logged

On each instrumented `chat.completions.create` call, one decision envelope is
reported:

| Field        | Source                                                                              |
| ------------ | ----------------------------------------------------------------------------------- |
| `aiSystemId` | `compliance.ai_system_id` / `VAXELIA_AI_SYSTEM_ID`                                  |
| `modelUsed`  | the response's `model`, falling back to the request `model`                         |
| `input`      | the `chat.completions.create` request kwargs                                        |
| `output`     | the provider response (via `model_dump()`), or on failure `{"error": ...}`          |
| `status`     | `completed` on success, `failed` when the provider call raises                      |
| `decidedAt`  | ISO-8601 timestamp at the moment of the call                                        |

## Reporter-error ordering (no silent drops)

The provider response is always obtained **first**; only then is the decision
logged. If `vaxelia-ai-core` cannot deliver the decision *and* cannot durably
buffer it, it raises (`DecisionNotRecordedError`) — and this wrapper lets that
error surface to your caller. This is deliberate: the whole point of the
compliance layer is that a recorded decision is never silently dropped. If you
prefer delivery failures to be buffered instead of raised, keep buffering enabled
(the default) with a valid buffer key — transient failures (network, 5xx, 429)
are then retried with backoff and buffered to encrypted disk for later flush.

On a **failed** provider call, the `failed` decision is reported first (capturing
only the error *message*, never the raw exception, which may carry request bodies
or credentials), and then the original provider exception is re-raised unchanged.

## Scope & limitations

- **Streaming is not instrumented in v1.** A call with `stream=True` passes
  straight through to the real client uninstrumented, so streaming usage never
  breaks — but no decision is logged for it yet. Streaming compliance capture is
  a planned enhancement.
- **The async `AsyncOpenAI` client is out of scope for v1.** Only the
  synchronous `OpenAI` client is instrumented; an async sibling is a planned
  follow-up.
- Only `chat.completions.create` is instrumented. All other SDK methods delegate
  to the real client; reach `client.raw` for the underlying object when needed.

## Development

```bash
# From sdks/python, with the shared venv:
.venv/bin/pip install -e vaxelia-ai-core
.venv/bin/pip install -e vaxelia-ai-openai
.venv/bin/python -m pytest vaxelia-ai-openai
```
