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

```python theme={null}
results = client.verify_batch([
    {"query": "2+2=4", "type": "math"},
    {"query": "3*3=9", "type": "math"},
])
print(results.summary.success_rate)
```

For `math` items, supply equality claims (e.g., `"x + x = 2*x"`) to receive proof results. Non-equality expressions (e.g., `"x + x"`) are returned as simplification-only results with `is_valid: false` and `status: "SIMPLIFIED"` — they are not reported as verified.

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