Skip to main content

Cryptographic audit infrastructure for AI inference — hash, sign, and blockchain-anchor every LLM call

Project description

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 · Keys · Pricing · Changelog

For the most current reference — including new providers, framework integrations, and API changes — see modelwitness.io/docs.


Quick start

Install

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 — 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

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

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

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

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 below.
  • on_record — Optional callback. See 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.

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.

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 InferenceRecordrecord_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:

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 to upgrade to Enterprise.


Agent framework support

Plan requirements and supported frameworks may change. See 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

# 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.

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.

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.

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.

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.

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:

mw verify --file proof.json

mw records — List recent records from your ledger.

mw records --limit 10

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

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.

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 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.

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.

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.

# 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.

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.

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 to enable BYOK on your account. For full AWS IAM setup instructions, see 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 above for setup requirements and usage.


Contact


Terms · Documentation

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

model_witness-0.5.4.tar.gz (44.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

model_witness-0.5.4-py3-none-any.whl (33.0 kB view details)

Uploaded Python 3

File details

Details for the file model_witness-0.5.4.tar.gz.

File metadata

  • Download URL: model_witness-0.5.4.tar.gz
  • Upload date:
  • Size: 44.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for model_witness-0.5.4.tar.gz
Algorithm Hash digest
SHA256 124cbd827a3adbfd97275129c2f774083a72839afbde458861362ceba3820163
MD5 3e9c941eb41c222211c25c1983cec64c
BLAKE2b-256 66f13d1e4aa5cb24b8ea695982ac76d6468b4db78d9df115a533156261de0f85

See more details on using hashes here.

Provenance

The following attestation bundles were made for model_witness-0.5.4.tar.gz:

Publisher: publish.yml on Model-Witness/model-witness

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file model_witness-0.5.4-py3-none-any.whl.

File metadata

  • Download URL: model_witness-0.5.4-py3-none-any.whl
  • Upload date:
  • Size: 33.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for model_witness-0.5.4-py3-none-any.whl
Algorithm Hash digest
SHA256 cfdda875d210b0d35854468b03205023a079402f37015c1a94542fc61a0bc36f
MD5 ae6f76649d96fdd2a77003ad4e66ac76
BLAKE2b-256 f8187f4a9de5a33185dccc34146649c169646994bb770cb0fcee3d76c0cdc65a

See more details on using hashes here.

Provenance

The following attestation bundles were made for model_witness-0.5.4-py3-none-any.whl:

Publisher: publish.yml on Model-Witness/model-witness

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page