Metadata-Version: 2.4
Name: model-witness
Version: 0.5.4
Summary: Cryptographic audit infrastructure for AI inference — hash, sign, and blockchain-anchor every LLM call
Project-URL: Homepage, https://modelwitness.io
Project-URL: Documentation, https://modelwitness.io/docs
Project-URL: Changelog, https://modelwitness.io/changelog
Author-email: Model Witness <admin@modelwitness.io>
License: MIT
License-File: LICENSE
Keywords: ai,anthropic,audit,blockchain,byok,compliance,crewai,cryptography,decryption,encryption,langchain,llamaindex,llm,merkle,openai,verification
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.11
Requires-Dist: anthropic>=0.20
Requires-Dist: click>=8.0
Requires-Dist: cryptography>=41.0
Requires-Dist: google-genai>=1.0
Requires-Dist: openai>=1.0
Requires-Dist: requests>=2.31
Provides-Extra: all-frameworks
Requires-Dist: crewai>=0.1.0; extra == 'all-frameworks'
Requires-Dist: langchain-core>=0.1.0; extra == 'all-frameworks'
Requires-Dist: llama-index-core>=0.10.0; extra == 'all-frameworks'
Provides-Extra: crewai
Requires-Dist: crewai>=0.1.0; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: httpx; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.10.0; extra == 'llamaindex'
Description-Content-Type: text/markdown

# Model Witness Python API library

Cryptographic audit infrastructure for AI inference. Every prompt and response is hashed, signed,
and anchored to an immutable ledger — giving you a tamper-evident chain of evidence for every AI
call your application makes.

[Documentation](https://modelwitness.io/docs) · [Keys](https://modelwitness.io/keys) · [Pricing](https://modelwitness.io/pricing) · [Changelog](https://modelwitness.io/changelog)

> For the most current reference — including new providers, framework integrations, and API changes — see [modelwitness.io/docs](https://modelwitness.io/docs).

---

## Quick start

### Install

```bash
pip install model-witness
```

For the full REST API reference, see https://api.modelwitness.io/docs

### Get an API key

Sign in and go to [Keys](https://modelwitness.io/keys) — issue a key and attach it to a
ledger. Each key is scoped to one ledger.

### Log your first inference

Wrap your existing AI client with `ModelWitness` — one line change, same API you already know.
The record is logged asynchronously in the background.

**OpenAI**

```python
from model_witness import ModelWitness
import openai

client = ModelWitness(openai.OpenAI())

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.choices[0].message.content)
```

**Anthropic**

```python
from model_witness import ModelWitness
import anthropic

client = ModelWitness(anthropic.Anthropic())

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.content[0].text)
```

**Azure OpenAI**

```python
from model_witness import ModelWitness
from openai import AzureOpenAI

client = ModelWitness(
    AzureOpenAI(
        azure_endpoint="https://<resource>.openai.azure.com/",
        api_version="2024-10-21",
    )
)

response = client.chat.completions.create(
    model="gpt-4o",  # your deployment name
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.choices[0].message.content)
```

**Google Gemini**

```python
from model_witness import ModelWitness
from google import genai

client = ModelWitness(genai.Client())

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Hello"
)
print(response.text)
```

---

## Capture inferences

### ModelWitness client

`ModelWitness` wraps your existing AI client and intercepts every inference call. Use this when
you want zero changes to your existing code — one line to wrap, everything else stays the same.

- `client` — An `openai.OpenAI`, `openai.AzureOpenAI`, `anthropic.Anthropic`, or
  `google.genai.Client` instance.
- `api_key` — Your MW API key. Pass directly or set the `MW_API_KEY` env var.
- `base_url` — Optional. MW server URL. Defaults to `MW_API_URL` env var or
  `https://api.modelwitness.io`.
- `capture_content` — Optional. See [Content encryption](#content-encryption) below.
- `on_record` — Optional callback. See [Accessing the record ID](#accessing-the-record-id) below.

### Accessing the record ID

Every inference call produces a `record_id` you can store alongside your own data to link back to
the audit trail. The ID is available synchronously — no added latency to the LLM call.

**Pattern A — `last_record_id()`**

Simple. Use this when you're making one call at a time.

```python
client = ModelWitness(openai.OpenAI())

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": user_input}]
)

record_id = client.last_record_id()
db.save({"application_id": app_id, "mw_record_id": record_id})
```

`last_record_id()` is not thread-safe for concurrent calls. Use the callback below if you're
making simultaneous requests.

**Pattern B — `on_record` callback**

Use this for concurrent inference. Each invocation receives its own record, isolated to that call.

```python
def handle_record(record):
    db.save({"application_id": app_id, "mw_record_id": record.record_id})

client = ModelWitness(openai.OpenAI(), on_record=handle_record)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": user_input}]
)
```

The callback receives the full `InferenceRecord` — `record_id`, `model_id`, `input_hash`,
`output_hash`, token counts, and latency.

### Environment variables

- `MW_API_KEY` — Your API key. Recommended over passing `api_key=` directly.
- `MW_API_URL` — Server base URL. Defaults to `https://api.modelwitness.io`.
- `MW_KEY_CACHE_TTL` — Seconds a successful key validation is trusted before re-checking.
  Defaults to `1800` (30 min). Set lower for tighter revocation windows.

---

## Content encryption

> Enterprise plan only. Requires BYOK encryption configured on the ledger via the dashboard.

By default, Model Witness captures only SHA-256 hashes of your prompts and responses — raw
content never leaves your environment. If you need to store and retrieve the actual content
(for example, to decrypt and audit specific records later), enable `capture_content`:

```python
client = ModelWitness(
    openai.OpenAI(),
    capture_content=True,
)
```

With `capture_content=True`, the SDK transmits the raw prompt and response text to the server,
where it is encrypted with AES-256-GCM using your registered AWS KMS key before storage.
Model Witness stores only ciphertext — the plaintext is never persisted unencrypted.

`capture_content=True` requires an Enterprise plan with a KMS key registered on the ledger.
On Starter or Growth plans, the record is committed in hash-only mode and a `UserWarning` is
emitted explaining the plan requirement. The inference call completes normally — no data is lost.

To set up content encryption, register a KMS key in the dashboard under Settings → Encryption.
Contact [sales@modelwitness.io](mailto:sales@modelwitness.io) to upgrade to Enterprise.

---

## Agent framework support

> Plan requirements and supported frameworks may change. See [modelwitness.io/docs](https://modelwitness.io/docs) for the current list.

Model Witness integrates natively with major agent frameworks via callbacks. Every LLM call made
by your agents is captured as a separate cryptographically signed record — giving you a complete
audit trail of every autonomous AI decision.

Agent framework support requires a Growth or Enterprise plan. Starter plan accounts will receive
a clear error at initialization time with a link to upgrade.

### Install

```bash
# Install the integration you need
pip install model-witness[langchain]     # LangChain and LangGraph
pip install model-witness[crewai]        # CrewAI
pip install model-witness[llamaindex]    # LlamaIndex

# Or install all framework integrations at once
pip install model-witness[all-frameworks]
```

### LangChain

Attach `ModelWitnessCallbackHandler` to any LLM. Reads `MW_API_KEY` and `MW_API_URL` from
environment automatically.

```python
from model_witness.callbacks.langchain import ModelWitnessCallbackHandler
from langchain_openai import ChatOpenAI

# Reads MW_API_KEY and MW_API_URL from environment automatically
handler = ModelWitnessCallbackHandler()
llm = ChatOpenAI(callbacks=[handler])
```

### LangGraph

Uses the same handler as LangChain. Attach to the LLM before passing to your StateGraph nodes.

```python
from model_witness.callbacks.langchain import ModelWitnessCallbackHandler
from langchain_openai import ChatOpenAI

# Reads MW_API_KEY and MW_API_URL from environment automatically
handler = ModelWitnessCallbackHandler()
# Attach to the LLM before passing to your StateGraph nodes
llm = ChatOpenAI(callbacks=[handler])
```

### CrewAI

Instantiate `ModelWitnessEventListener` — self-registers on the CrewAI event bus, no `Crew`
changes needed.

```python
from model_witness.callbacks.crewai import ModelWitnessEventListener
from crewai import Agent, Task, Crew

# Reads MW_API_KEY and MW_API_URL from environment automatically
# Just instantiating is enough — self-registers on the CrewAI event bus
listener = ModelWitnessEventListener()

crew = Crew(
    agents=[...],
    tasks=[...],
)
```

### LlamaIndex

Attach `ModelWitnessCallbackHandler` via `Settings.callback_manager`.

```python
from model_witness.callbacks.llamaindex import ModelWitnessCallbackHandler
from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager

# Reads MW_API_KEY and MW_API_URL from environment automatically
handler = ModelWitnessCallbackHandler()
Settings.callback_manager = CallbackManager([handler])
```

---

## Verify records

Every record goes through three independent checks. All three can be run without relying on
Model Witness infrastructure.

### How verification works

**Signature** — Each record is signed with ECDSA (secp256k1). The verifier recomputes the
canonical hash of the record fields and confirms it against the stored signature and public key.

**Merkle proof** — Records are leaves in a Merkle tree. The verifier walks the sibling path from
the leaf hash up to the root, confirming the record is part of the committed tree.

**Blockchain anchor** — The Merkle root is written to a smart contract on Polygon. The verifier
queries the transaction receipt directly and confirms the root appears in an on-chain
`RootAnchored` event — independent of Model Witness infrastructure.

### CLI

The `mw` command is included with the package.

**mw verify** — Verify a record by ID. Checks signature, Merkle proof, and confirms the anchor
on Polygon.

```bash
mw verify --record 833d3199-0a16-44c4-85a2-a46bf01a7031

✓ Signature
✓ Merkle proof
✓ Blockchain anchor
  └─ Polygon block #35,938,705

✓ VERIFIED
```

You can also verify a locally saved proof bundle without hitting the API:

```bash
mw verify --file proof.json
```

**mw records** — List recent records from your ledger.

```bash
mw records --limit 10
```

**mw status** — Show current ledger state — Merkle root, leaf count, and latest anchor
transaction.

```bash
mw status
```

### SDK

Use `ModelWitnessClient` for programmatic access to your ledger — read records, verify,
fetch proof bundles, generate compliance reports, and decrypt records. Unlike `ModelWitness`
which wraps an AI client, `ModelWitnessClient` is used independently for operations that
don't involve making inference calls.

```python
from model_witness.client import ModelWitnessClient

client = ModelWitnessClient()

# List recent records for this ledger
records = client.get_records(limit=20)
# → list of record dicts

# Ledger status (anchor state, record count, etc.)
status = client.get_status()

# Verify a record — checks signature, Merkle proof, and blockchain anchor
result = client.verify_record("833d3199-0a16-44c4-85a2-a46bf01a7031")
result.valid                   # True
result.signature_valid         # True
result.merkle_proof_valid      # True
result.blockchain_anchor_valid # True
result.block_number            # 35938705
result.anchored_at             # "2026-03-28T14:22:01Z"
result.errors                  # []

# Fetch a raw proof bundle (signature, Merkle path, anchor details)
bundle = client.get_proof_bundle("833d3199-0a16-44c4-85a2-a46bf01a7031")
# → dict with record, merkle_path, merkle_root, anchor_tx_hash, block_number
```

---

## Compliance reports

> Supported compliance frameworks and report fields may change. See [modelwitness.io/docs](https://modelwitness.io/docs) for the current list.

Generate structured audit reports directly from your signed, blockchain-anchored inference
ledger. Every figure is a direct computation from verified records — no AI, no estimates.
Reports are stored server-side and retrievable at any time.

Supported frameworks:

- `eu_ai_act` — EU AI Act, Article 13 transparency and Article 9 risk management
- `hipaa` — §164.312(b) Audit Controls
- `finra` — Rule 3110 Supervisory Systems
- `soc2` — CC7 System Operations

### Generate a report

Pass the framework constant, an ISO 8601 date range, and an optional title. An unrecognised
framework raises `ValueError` before any network call.

```python
from model_witness.client import ModelWitnessClient
from dotenv import load_dotenv

load_dotenv()

client = ModelWitnessClient()

report = client.generate_compliance_report(
    framework="eu_ai_act",
    from_ts="2026-01-01T00:00:00+00:00",
    to_ts="2026-03-31T23:59:59+00:00",
    report_title="Q1 2026 EU AI Act Transparency Report",
)

print(report["report_id"])
print(report["summary"]["total_inferences"])
print(report["summary"]["anchor_rate_pct"])
```

### Customer attestations

Some compliance fields — organization name, use case description, oversight policies — cannot be
derived from inference records. Supply them via `customer_attestations`. All five fields are
optional.

```python
report = client.generate_compliance_report(
    framework="hipaa",
    from_ts="2026-01-01T00:00:00+00:00",
    to_ts="2026-03-31T23:59:59+00:00",
    customer_attestations={
        "organization_name":       "Acme Health Systems",
        "prepared_by":             "Jane Smith, Compliance Officer",
        "use_case_description":    "Clinical decision support for triage workflows",
        "human_oversight_policy":  "All AI outputs reviewed by licensed clinician before action",
        "data_handling_practices": "PHI de-identified before prompt construction per §164.514(b)",
    },
)
```

### List and fetch saved reports

Every generated report is persisted to the ledger. Use `list_compliance_reports` to see what has
been generated, and `get_compliance_report` to retrieve the full body of any saved report.

```python
# List report summaries for this key's ledger
reports = client.list_compliance_reports()
for r in reports:
    print(r["report_id"], r["framework"], r["generated_at"])

# Fetch a specific report in full
report = client.get_compliance_report(report_id="a3f2c1d8-...")
```

### Evidence packs

Every compliance report can be exported as an evidence pack. The report is the saved audit object;
the evidence pack is the auditor handoff ZIP for that report. Each pack includes a PDF summary,
signed manifest, report JSON, record inventory CSV, anchor metadata, verification instructions,
and proof bundle JSON files.

```python
report = client.generate_compliance_report(
    framework="eu_ai_act",
    from_ts="2026-01-01T00:00:00+00:00",
    to_ts="2026-03-31T23:59:59+00:00",
    report_title="Q1 2026 EU AI Act Report",
)

client.get_evidence_pack(
    report_id=report["report_id"],
    output_path="mw-evidence-pack.zip",
)
```

---

## Decryption

> Enterprise plan only. Requires BYOK encryption configured on the ledger via the dashboard.

`decrypt_record()` is available on `ModelWitnessClient` and decrypts previously captured
inference records programmatically via API key. The record must have been captured with
`capture_content=True` and content encryption enabled on the ledger.

```python
from model_witness.client import ModelWitnessClient

client = ModelWitnessClient()

# Decrypt all fields
decrypted = client.decrypt_record(
    record_id="833d3199-0a16-44c4-85a2-a46bf01a7031"
)
print(decrypted.input)
print(decrypted.output)
print(decrypted.system_prompt)

# Decrypt specific fields only
decrypted = client.decrypt_record(
    record_id="833d3199-0a16-44c4-85a2-a46bf01a7031",
    fields=["input", "output"],
)
```

`decrypt_record()` returns a `DecryptedRecord` with `.record_id`, `.ledger_id`, `.input`,
`.output`, `.system_prompt`, and `.decrypted_at` attributes. `__repr__` intentionally omits
decrypted content to prevent accidental logging of sensitive data in application logs.

`decrypt_record()` raises `ModelWitnessError` on failure. Common error cases:

- `403` — API key is not on an Enterprise plan
- `422` — Record was not encrypted with BYOK, or no KMS key is registered for the ledger
- `429` — Rate limit exceeded (100 requests per minute)
- `503` — KMS key unavailable or IAM role assumption failed

Contact [sales@modelwitness.io](mailto:sales@modelwitness.io) to enable BYOK on your account.
For full AWS IAM setup instructions, see [modelwitness.io/docs](https://modelwitness.io/docs).

---

## Security and privacy

Raw prompts and responses never leave your environment. The SDK computes SHA-256 hashes locally
and transmits only the hashes. Anyone with the original data can verify the record matches — no
data exposure required.

Merkle roots are anchored on Polygon (PoS). Records are batched per epoch and share a single
transaction, keeping anchor costs low. Polygon checkpoints to Ethereum mainnet provide long-term
finality.

ECDSA signing keys are generated and held server-side. Enterprise accounts can enable BYOK
(Bring Your Own Key) encryption via the dashboard — see [Content encryption](#content-encryption)
above for setup requirements and usage.

---

## Contact

- General support: [admin@modelwitness.io](mailto:admin@modelwitness.io)
- Sales and enterprise: [sales@modelwitness.io](mailto:sales@modelwitness.io)
- Security disclosures: [security@modelwitness.io](mailto:security@modelwitness.io)
- Privacy and data: [privacy@modelwitness.io](mailto:privacy@modelwitness.io)
- Legal and terms: [legal@modelwitness.io](mailto:legal@modelwitness.io)

---

[Terms](https://modelwitness.io/terms) · [Documentation](https://modelwitness.io/docs)

API reference: https://api.modelwitness.io/docs
