> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> QWED Python SDK documentation. Install via pip, configure sync or async clients, and verify LLM outputs with math, logic, code, and SQL engines.

The official Python SDK for QWED.

## Installation

```bash theme={null}
pip install qwed
```

## Quick start

```python theme={null}
from qwed_sdk import QWEDClient

client = QWEDClient(api_key="qwed_your_key")

# Basic verification
result = client.verify("Is 2+2=4?")
print(result.verified)  # True
print(result.status)    # "VERIFIED"
```

<Note>
  As of v5.0.0, the `status` field may return `INCONCLUSIVE`, `BLOCKED`, or `UNKNOWN` in addition to `VERIFIED` and `ERROR`. Natural-language math queries return `INCONCLUSIVE` when the inner engine succeeds, because the LLM translation step is non-deterministic. See the [trust boundary documentation](/api/endpoints#post-%2Fverify%2Fnatural_language) for details.
</Note>

## Async client

```python theme={null}
from qwed_sdk import QWEDAsyncClient
import asyncio

async def main():
    async with QWEDAsyncClient(api_key="qwed_...") as client:
        result = await client.verify("2+2=4")
        print(result.verified)

asyncio.run(main())
```

## Methods

### verify(query)

Auto-detect and verify any claim.

```python theme={null}
result = client.verify("What is 15% of 200?")
```

### verify\_math(expression)

Verify mathematical expressions.

```python theme={null}
result = client.verify_math("x**2 + 2*x + 1 = (x+1)**2")
```

### verify\_logic(query)

Verify logical constraints (QWED-Logic DSL).

```python theme={null}
result = client.verify_logic("(AND (GT x 5) (LT y 10))")
print(result.model)  # {"x": 6, "y": 9}
```

### verify\_code(code, language)

Check code for security vulnerabilities.

```python theme={null}
result = client.verify_code(code, language="python")
for vuln in result.vulnerabilities:
    print(f"{vuln.severity}: {vuln.message}")
```

### verify\_sql(query, schema\_ddl, dialect)

Validate SQL queries against a schema.

```python theme={null}
result = client.verify_sql(
    query="SELECT * FROM users WHERE id = 1",
    schema_ddl="CREATE TABLE users (id INT, name TEXT)",
    dialect="postgresql"
)
```

### verify\_fact(claim, context)

<Info>New in v4.0.0</Info>

Verify factual claims against a provided context. Includes client-side PII pre-check.

```python theme={null}
result = client.verify_fact(
    claim="The company was founded in 2020",
    context="Acme Corp was established in 2020 in San Francisco."
)
print(result.verified)  # True
```

### verify\_stats(query, file\_path)

<Info>New in v4.0.0</Info>

Verify statistical claims against CSV data.

```python theme={null}
result = client.verify_stats(
    query="The average salary is above 50000",
    file_path="data.csv"
)
```

### verify\_consensus(query, mode, min\_confidence)

<Info>New in v4.0.0</Info>

Multi-engine consensus verification.

```python theme={null}
result = client.verify_consensus(
    query="The square root of 144 is 12",
    mode="high",
    min_confidence=0.8
)
print(result.verified)     # True
print(result.confidence)   # 0.95
```

| Parameter        | Type  | Default    | Description                    |
| ---------------- | ----- | ---------- | ------------------------------ |
| `query`          | str   | —          | Claim to verify                |
| `mode`           | str   | `"single"` | `single`, `high`, or `maximum` |
| `min_confidence` | float | `0.8`      | Minimum confidence threshold   |

### verify\_image(image\_path, claim)

<Info>New in v4.0.0</Info>

Verify claims about image content.

```python theme={null}
result = client.verify_image(
    image_path="photo.jpg",
    claim="This image contains a cat"
)
```

### verify\_batch(items)

Verify multiple items at once. The response is a `BatchResult` whose `items` are per-item records — for math items the verdict is a [`DiagnosticResult`](/advanced/diagnostics) nested under each item's `result`, so check `result.status` and `result.proof_ref` per item rather than relying on an aggregate.

```python theme={null}
response = client.verify_batch([
    {"query": "2+2=4", "type": "math"},
    {"query": "x + x", "type": "math"},
])

for item in response.items:
    result = item.result or {}
    if result.get("status") == "VERIFIED" and result.get("proof_ref"):
        # Authoritative — admissible for control flow
        print("verified:", result.get("agent_message"), result.get("proof_ref"))
    else:
        # UNVERIFIABLE or BLOCKED — must reject
        print("rejected:", result.get("status"), result.get("agent_message"))
```

For `math` items, supply equality claims (e.g., `"x + x = 2*x"`) to receive proof results. Bare expressions without an `=` return `status: "UNVERIFIABLE"` with the simplified form in `developer_fields.simplified` — they are never reported as verified. The legacy `is_valid` flag is preserved inside `developer_fields.is_valid` for backward compatibility.

## Verification Context

<Info>New in v7.1.0</Info>

The SDK exposes [Verification Context v1.0](/specs/verification-context) — the standardized JSON record of a verification — as client methods, re-exported types, and a `to_verification_context()` method on every verifier.

### Client methods

All three methods are available on both `QWEDClient` and `QWEDAsyncClient` and call the corresponding [Verification Context endpoints](/api/endpoints#verification-context-endpoints).

#### create\_verification\_context\_from\_diagnostic(diagnostic, query, verifier)

Create a schema-valid Verification Context document from a [`DiagnosticResult`](/advanced/diagnostics) dict. Optional keyword arguments: `verifier_version` and `attestation_token`. A `VERIFIED` diagnostic without a valid attestation token is demoted to `UNVERIFIABLE` (fail-closed).

```python theme={null}
document = client.create_verification_context_from_diagnostic(
    diagnostic={
        "status": "VERIFIED",
        "agent_message": "Identity verified",
        "developer_fields": {"is_valid": True},
        "proof_ref": "sha256:9f2c…",
    },
    query="x**2 + 2*x + 1 = (x+1)**2",
    verifier="MathVerifier",
    attestation_token=token,
)
print(document["verdict"])                             # "VERIFIED"
print(document["context"]["decision"]["admission"])    # "ADMIT"
```

#### validate\_verification\_context(document)

Validate a document against the v1.0 JSON Schema.

```python theme={null}
result = client.validate_verification_context(document)
print(result["valid"])  # True or False
```

#### resolve\_verification\_context(document)

Resolve the document's `proof_ref` evidence commitment.

```python theme={null}
result = client.resolve_verification_context(document)
print(result["resolved"])  # True only if the stored proof_ref matches
```

### Re-exported types and helpers

`qwed_sdk` re-exports the Verification Context v1.0 model, so you can build, validate, and resolve documents locally without an API round trip:

```python theme={null}
from qwed_sdk import (
    # Enums and model types
    Verdict,
    Admission,
    VerificationContext,
    VerificationContextDocument,
    VerificationContextValidationError,
    Formalization,
    VerifiedObject,
    Interpretation,
    Proof,
    Evidence,
    Decision,
    # proof_ref helpers
    compute_document_proof_ref,
    resolve_document_proof_ref,
    compute_context_proof_ref,
    resolve_context_proof_ref,
    # Schema validation helpers
    validate_document,
    is_valid_document,
)

# Validate a document dict (raises VerificationContextValidationError on failure)
validate_document(document)

# Boolean variant
if is_valid_document(document):
    ...

# Recompute and check the evidence commitment locally
assert resolve_document_proof_ref(document)
```

* `compute_document_proof_ref(document)` derives the canonical `sha256:<64-hex>` commitment for a document.
* `resolve_document_proof_ref(document)` returns `True` only when the document is `VERIFIED`, schema-valid, and the stored `proof_ref` matches the re-derived commitment. Everything else returns `False` (fail-closed).

### to\_verification\_context() on verifiers

Every verification engine (all 13 verifiers, including Math, Logic, Symbolic, SQL, Code, Schema, Fact, Image, Graph, Reasoning, Stats, and Consensus) exposes `to_verification_context()`, which maps the engine's `DiagnosticResult` to a Verification Context document:

```python theme={null}
document = verifier.to_verification_context(result, query="x**2 + 2*x + 1 = (x+1)**2")
document.validate()
print(document.to_dict()["verdict"])
```

The mapping preserves the fail-closed invariants: `VERIFIED` documents carry a `sha256` `proof_ref`, while `UNVERIFIABLE` and `BLOCKED` documents carry `proof_ref: null` with admission `DENY`.

## Verification cache

`qwed_sdk.cache.VerificationCache` is the persistent SQLite-backed cache for verification results. It reduces LLM cost on repeated queries.

Cache entries are **context-bound**. A hit requires an exact match of both the normalized query and the trust-bound `CacheContext`.

A mismatch on any context dimension is a deterministic miss. The cache never falls back to a query-only match. This prevents cross-provider, cross-model, cross-policy, and cross-tenant replay of `VERIFIED` results.

### `CacheContext`

Every `get()` and `set()` call requires a `CacheContext`. All fields participate in the cache key.

| Field             | Type            | Required | Description                                                 |
| ----------------- | --------------- | -------- | ----------------------------------------------------------- |
| `provider`        | `str`           | yes      | Provider or API endpoint identifier (e.g., `"openai"`).     |
| `model`           | `str`           | yes      | Model or deployment name (e.g., `"gpt-4o"`).                |
| `policy_version`  | `str`           | yes      | Verifier policy version string (e.g., `"v1"`).              |
| `tenant_id`       | `Optional[str]` | no       | Tenant or session scope identifier.                         |
| `env_fingerprint` | `Optional[str]` | no       | Environment or configuration fingerprint for extra binding. |

### Usage

```python theme={null}
from qwed_sdk.cache import CacheContext, VerificationCache

cache = VerificationCache()  # defaults to ~/.qwed/cache, TTL 24h

ctx = CacheContext(
    provider="openai",
    model="gpt-4o",
    policy_version="v1",
    tenant_id="tenant-alpha",
)

# Cache miss on first call
result = cache.get("Is 2+2=4?", ctx)         # None

# Populate the cache for this (query, context) pair
cache.set("Is 2+2=4?", {"verified": True, "value": 4}, ctx)

# Same query, same context → hit
cache.get("Is 2+2=4?", ctx)                  # {"verified": True, "value": 4}

# Same query, different provider → deterministic miss
ctx_other = CacheContext(provider="anthropic", model="claude-opus-4-5", policy_version="v1")
cache.get("Is 2+2=4?", ctx_other)            # None
```

### Miss conditions

`cache.get(query, context)` returns `None` when any of the following holds:

* No entry exists for this `(query, context)` pair.
* The stored entry has expired (TTL, default 24 hours).
* The stored context fingerprint does not match `context` (replay guard).
* The entry uses the legacy v1 schema. The cache never returns legacy rows.

### Constructor options

```python theme={null}
VerificationCache(cache_dir: Optional[str] = None, ttl: int = 86400)
```

* `cache_dir` — Directory for the SQLite database. Defaults to `~/.qwed/cache`.
* `ttl` — Time-to-live in seconds. Defaults to 24 hours.

<Warning>
  `get()` and `set()` require a `CacheContext` argument as of the May 17, 2026 release. Calls that omit the context raise `TypeError`. See the [changelog entry](/changelog-archive#qwed-verification-—-context-bound-verification-cache-replay-prevention) for migration details.
</Warning>

## CLI

```bash theme={null}
# Verify
qwed verify "2+2=4"

# Verify logic
qwed verify-logic "(AND (GT x 5) (LT y 10))"

# Verify code file
qwed verify-code -f script.py

# Batch verify
qwed batch -f queries.json

# Verification Context v1.0 utilities (v7.1.0)
qwed context validate document.json
qwed context resolve document.json
qwed context from-diagnostic --diagnostic-file diag.json \
  --query "x + x = 2*x" --verifier MathVerifier
```

See the [CLI reference](/advanced/cli#qwed-context-verification-context-utilities) for details on the `qwed context` command group.

## Verification cache

`qwed_sdk.cache` provides a context-bound SQLite cache for verification results.

A cache hit requires an exact match of both the normalized query **and** the trust-bound `CacheContext`. Any mismatch on a context dimension yields a deterministic miss — the cache never crosses trust boundaries. The cache also ignores legacy entries that lack a context fingerprint.

```python theme={null}
from qwed_sdk.cache import CacheContext, VerificationCache

ctx = CacheContext(
    provider="openai",
    model="gpt-4o",
    policy_version="v1",
    tenant_id="tenant-alpha",       # optional
    env_fingerprint="sha256-...",   # optional
)

cache = VerificationCache()

cache.get("2+2", ctx)              # None (miss)
cache.set("2+2", {"verified": True}, ctx)
cache.get("2+2", ctx)              # {"verified": True}

# Different provider — deterministic miss (replay prevention)
ctx2 = CacheContext(provider="claude", model="claude-opus-4-5", policy_version="v1")
cache.get("2+2", ctx2)             # None
```

### `CacheContext`

Trust-bound context dimensions required for every cache operation. All fields participate in the cache key; omitting or changing any field causes a deterministic cache miss.

<ParamField path="provider" type="str" required>
  Provider or API endpoint identifier (for example, `"openai"`, `"claude"`).
</ParamField>

<ParamField path="model" type="str" required>
  Model or deployment name (for example, `"gpt-4o"`).
</ParamField>

<ParamField path="policy_version" type="str" required>
  Verifier policy version string (for example, `"v1"`).
</ParamField>

<ParamField path="tenant_id" type="Optional[str]" default="None">
  Tenant or session scope identifier. Use to prevent cross-tenant replay.
</ParamField>

<ParamField path="env_fingerprint" type="Optional[str]" default="None">
  Environment or config fingerprint for additional binding.
</ParamField>

### `VerificationCache(cache_dir=None, ttl=86400)`

<ParamField path="cache_dir" type="Optional[str]" default="~/.qwed/cache">
  Directory for the SQLite cache database.
</ParamField>

<ParamField path="ttl" type="int" default="86400">
  Time-to-live in seconds (default: 24 hours). `get()` treats entries older than `ttl` as a miss and deletes them on access.
</ParamField>

The cache caps total entries at `MAX_ENTRIES = 1000`. When `set()` grows the table past the cap, `set()` evicts the least-recently-accessed entries.

#### `get(query, context)`

Return the cached result for `(query, context)`, or `None` on miss.

`get()` returns `None` when:

* No entry exists for the `(query, context)` pair.
* The entry has expired (older than `ttl`).
* The stored context fingerprint does not match `context` (defence-in-depth replay guard).

<ParamField path="query" type="str" required>
  The verification query string. Normalized (lowercased, whitespace-collapsed) before hashing.
</ParamField>

<ParamField path="context" type="CacheContext" required>
  Trust-bound context that must match exactly. Omitting this argument raises `TypeError`.
</ParamField>

<ResponseField name="result" type="Optional[Dict[str, Any]]">
  The cached result dict, or `None` on any miss.
</ResponseField>

#### `set(query, result, context)`

Store a verification result bound to `(query, context)`.

The composite primary key `(key, context_fingerprint)` keeps identical queries under different contexts as independent entries.

<ParamField path="query" type="str" required>
  The verification query string.
</ParamField>

<ParamField path="result" type="Dict[str, Any]" required>
  Verification result to cache. `set()` serializes this dict as JSON.
</ParamField>

<ParamField path="context" type="CacheContext" required>
  Trust-bound context to bind this entry to.
</ParamField>

#### `clear()`

Remove all cached entries and reset stats.

#### `get_stats()`

Return a `CacheStats` dataclass with `hits`, `misses`, `total_entries`, `cache_size_bytes`, and a `hit_rate` property.

#### `print_stats()`

Print a formatted summary of cache statistics to stdout. Uses ANSI colors when `colorama` is installed.

<Note>
  **Breaking change (Issue #187).** `get()` and `set()` now require a `CacheContext` argument. Calls like `cache.get("2+2")` or `cache.set("2+2", result)` raise `TypeError`. The on-disk schema is `cache_v2` with composite `PRIMARY KEY (key, context_fingerprint)`. The cache never returns entries from the legacy v1 `cache` table.
</Note>

## Guards

The SDK includes security guards for protecting AI agent pipelines:

```python theme={null}
from qwed_sdk.guards import (
    RAGGuard,
    ExfiltrationGuard,
    MCPPoisonGuard,
    SelfInitiatedCoTGuard,
    SovereigntyGuard,
    SystemGuard,
    ConfigGuard,
)

# Prevent RAG hallucinations from wrong document chunks
rag_guard = RAGGuard(max_drm_rate="1/10")
result = rag_guard.verify_retrieval_context(
    target_document_id="contract_v2",
    retrieved_chunks=chunks
)

# Block data exfiltration to unauthorized endpoints
exfil_guard = ExfiltrationGuard(allowed_endpoints=["https://api.openai.com"])
result = exfil_guard.verify_outbound_call(
    destination_url=url,
    payload=data
)

# Detect poisoned MCP tool definitions
mcp_guard = MCPPoisonGuard()
result = mcp_guard.verify_tool_definition(tool_schema)

# Block dangerous shell commands (v4.0.0)
sys_guard = SystemGuard()
result = sys_guard.verify_shell_command("rm -rf /")

# Detect plaintext secrets in config (v4.0.0)
cfg_guard = ConfigGuard()
result = cfg_guard.verify_config_safety(config_data)
```

See the [SDK Guards reference](/sdks/guards) for complete documentation.

## Environment variables

| Variable        | Description  |
| --------------- | ------------ |
| `QWED_API_KEY`  | API key      |
| `QWED_BASE_URL` | API base URL |
