Skip to content
This repository was archived by the owner on Jul 19, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **README rewritten**: removed stale "57 models" literal count claims; examples now use `model="ainfera-inference"` (the flagship route) instead of pinned `claude-haiku-4-5`; concept page links updated to point to live `/docs` sections.
- **`examples/quickstart.py`**: inference model changed from `claude-haiku-4-5` to `ainfera-inference`.
- **`python/examples/04_inference.py`**: model changed to `ainfera-inference`; fixed broken `.receipt.audit_url` reference (replaced with `.receipt_id`).
- **`examples/README.md`**: updated model reference and version pin (`>=1.1.0` instead of `==1.0.1`).

### Added

- **Compatibility table** in README: explicit feature matrix across `ainfera` SDK, `openai` SDK, and `anthropic` SDK.
- **Streaming example** (`examples/streaming.py`): SSE via the OpenAI-compatible endpoint.
- **Tools example** (`examples/tools.py`): function calling with `tools=` and `tool_choice=`.
- **Retries example** (`examples/retries.py`): exponential backoff for transient 5xx errors with semantic exception handling.
- **Async example** (`examples/async_usage.py`): `AsyncAinferaClient` with wallet and audit chain verification.
- **Receipt verification example** (`examples/receipt_verification.py`): inline receipt + local AuditChain verification.
- **Error handling example** (`examples/error_handling.py`): full semantic exception hierarchy mapping.
- **API versioning** section in README documenting the SDK↔API compatibility contract.
- **Local verification** clarification in README: tamper-evidence vs tamper-prevention; proves log integrity relative to the published key.
- **Semantic-release config** (`releaserc.toml`): automated version bumping from Conventional Commits.
- **Routing hints** example in README: `routing_hint`, `pool`, budget/latency/quality caps.

### Fixed

- **AsyncAgent.wallet** (AIN-196): use `wallet = await agent.wallet` or `await agent.get_wallet()` — no longer a coroutine method that breaks property-style access.
- **verify_chain([])** now raises `AuditChainBroken` instead of returning true on empty chains.
- **AuditEvent.previous_hash** accepts API `prev_hash` alias.
- **AinferaClient.from_signup()** / **AsyncAinferaClient.from_signup()** implemented.
- **403 mapping** (FLAW-02): only `spend_policy_exceeded` bodies raise `SpendPolicyExceeded`; other 403s are `APIError`.
- **Agent.ledger** / **AsyncAgent.ledger** (BUG-07): handle for `GET /v1/ledger/{agent_id}`.
- **04_inference.py**: broken `.receipt.audit_url` reference — `Receipt` model has no `audit_url` field accessible via `.receipt` on `InferenceResponse`; replaced with `.receipt_id` which is the actual response field.
- **examples/README.md**: stale `ainfera==1.0.1` pin updated to `>=1.1.0`.

## [1.1.0] — 2026-05-19

Expand Down
241 changes: 233 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ client = AinferaClient.from_signup(result)
agent = client.agents.retrieve(result.agent_id)

response = agent.inference(
model="claude-haiku-4-5",
model="ainfera-inference", # the flagship route — Ainfera picks the best model
messages=[{"role": "user", "content": "Hello"}],
max_tokens=20,
)
Expand Down Expand Up @@ -64,24 +64,249 @@ ok = agent.audit_chain.verify() # walks the chain, verifies hashes offline

## What is Ainfera?

**The Inference of AI Agents.** Ainfera Inference (the flagship product — the routing brain) picks the best model under your agent's budget and latency caps. Point at `ainfera-inference` and trust the researched decision. One Agent Card across 57 models. Every routing decision and inference call cryptographically audited. See [ainfera.ai](https://ainfera.ai) and the [`ainfera-routing`](https://github.com/ainfera-ai/routing) decision library.
**The Inference of AI Agents.** Ainfera Inference (the flagship product — the routing brain) picks the best model under your agent's budget and latency caps. Point at `ainfera-inference` and trust the researched decision. Every routing decision and inference call cryptographically audited. See [ainfera.ai](https://ainfera.ai) and the [`ainfera-routing`](https://github.com/ainfera-ai/routing) decision library.

## Features

- **Signed AgentCards** per Agent (JWS, RFC 7515)
- **Provider-neutral routing** across 57 models from many providers (Anthropic, OpenAI, Together, and more)
- **Provider-neutral routing** — pass `model="ainfera-inference"` and Ainfera selects the best model across all enrolled providers (Anthropic, OpenAI, Together, and more)
- **Atomic per-call settlement** out of an Agent-scoped Wallet
- **Tamper-evident hash-chained AuditChain** for every Agent
- **Local verification** — auditors can verify a chain offline, no Ainfera trust required
- **Sync + async** clients sharing one resource surface
- **Tools & tool_choice** pass-through for function-calling workflows
- **Routing hints** — budget caps, latency caps, quality floors, pool filters

## Compatibility table

The Ainfera API is wire-compatible with both OpenAI and Anthropic SDKs. This
Python SDK (`ainfera`) is the native wrapper with first-class typed access to
Ainfera-specific features (AgentCards, Wallets, AuditChains, Ledger).

| Feature | `ainfera` SDK | `openai` SDK (chat-completions) | `anthropic` SDK (chat-completions) |
|---|---|---|---|
| Routed inference (`ainfera-inference`) | ✅ | ✅ `model="ainfera-inference"` | ✅ `model="ainfera-inference"` |
| Pinned model (e.g. `claude-opus-4-7`) | ✅ | ✅ | ✅ |
| Streaming (SSE) | ⚠️ API returns 501 on `/v1/inference`; use OpenAI/Anthropic SDK for streaming | ✅ `stream=True` | ✅ `stream=True` |
| Tools / function calling | ✅ `tools=`, `tool_choice=` pass-through | ✅ | ✅ |
| Spend policies (caps) | ✅ `per_call_cap_usd`, `daily_cap_usd` | ✅ via `extra_body` | ✅ via `extra_body` |
| AgentCards (JWS-signed) | ✅ | — | — |
| Wallets & topup | ✅ | — | — |
| AuditChain local verify | ✅ `agent.audit_chain.verify()` | — | — |
| Ledger | ✅ `agent.ledger.entries()` | — | — |
| Routing hints (`routing_hint`) | ✅ `routing_hint={...}` | ✅ via `extra_body` | ✅ via `extra_body` |
| Pool filter (`auto`/`open`/`frontier`) | ✅ `pool=` | ✅ via `extra_body` | ✅ via `extra_body` |
| API base | `https://api.ainfera.ai` | `https://api.ainfera.ai/v1` | `https://api.ainfera.ai` |

**Wire format:** The Ainfera API serves both OpenAI-compatible
(`/v1/chat/completions`) and Anthropic-compatible (`/v1/messages`) endpoints.
The `ainfera` SDK uses the native `/v1/inference` surface which is
single-shot JSON only. For streaming, point an OpenAI or Anthropic SDK at
`api.ainfera.ai`.

## API versioning

The SDK targets API version `v1`. The API follows semantic versioning:

- **Major** — breaking changes to request/response shapes
- **Minor** — new optional fields, new endpoints
- **Patch** — bug fixes, no schema changes

SDK releases track API compatibility via `__version__`. The current SDK
version targets API `v1` and is backward-compatible with all `v1.x` API
revisions. See [CHANGELOG.md](./CHANGELOG.md) for the version history.

## Streaming

SSE streaming on `/v1/inference` returns `501 streaming_not_supported`.
Streaming is available via the OpenAI-compatible (`/v1/chat/completions`)
and Anthropic-compatible (`/v1/messages`) endpoints. Use the `openai` or
`anthropic` SDK pointed at `api.ainfera.ai`:

```python
from openai import OpenAI

client = OpenAI(
base_url="https://api.ainfera.ai/v1",
api_key="ainfera_...",
)
stream = client.chat.completions.create(
model="ainfera-inference",
messages=[{"role": "user", "content": "Write a haiku about routing."}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
```

## Tools (function calling)

Pass `tools` and `tool_choice` directly to `agent.inference()`. The API
forwards them to the selected backend and returns tool-use blocks in
`content_blocks` (Anthropic shape):

```python
response = agent.inference(
model="ainfera-inference",
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
tool_choice="auto",
)
# response.content_blocks may contain {"type": "tool_use", ...}
```

## Retries and error handling

The SDK does not auto-retry. Wrap calls in your own retry logic for
production resilience. The exception hierarchy maps HTTP status codes to
semantic types:

```python
import time
from ainfera import (
AinferaError,
APIError,
ModelUnavailable,
SpendPolicyExceeded,
WalletInsufficient,
)

def inference_with_retry(agent, *, model, messages, max_retries=3, **kwargs):
"""Retry on transient errors; surface semantic exceptions to caller."""
for attempt in range(max_retries):
try:
return agent.inference(model=model, messages=messages, **kwargs)
except (ModelUnavailable, SpendPolicyExceeded, WalletInsufficient):
raise # not transient — caller must fix
except APIError as exc:
if exc.status_code >= 500 and attempt < max_retries - 1:
time.sleep(2 ** attempt) # exponential backoff
continue
raise
```

| Exception | HTTP status | When |
|---|---|---|
| `WalletInsufficient` | 402 | Wallet balance below request cost |
| `SpendPolicyExceeded` | 403 | Agent's spend policy blocked the call |
| `ModelUnavailable` | 422 | Requested model not available from any provider |
| `APIError` | other 4xx/5xx | All other API errors |
| `AgentCardInvalid` | — | JWS signature verification failed |
| `AuditChainBroken` | — | Local hash-chain verification detected a break |

## Async usage

```python
import asyncio
from ainfera import AsyncAinferaClient

async def main():
client = AsyncAinferaClient(api_key="ainfera_...")
try:
agent = await client.agents.retrieve("agent_...")
response = await agent.inference(
model="ainfera-inference",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.content)

# Wallet is awaitable on async agents
wallet = await agent.wallet
print(f"Balance: ${wallet.balance_usd}")

# Verify audit chain locally
ok = await agent.audit_chain.verify()
print(f"Chain intact: {ok}")
finally:
await client.aclose()

asyncio.run(main())
```

## Receipt verification

Every inference response carries a `receipt_id` linking to an AuditChain
entry. The receipt is returned inline — there is no `GET /v1/receipts/{id}`
endpoint:

```python
response = agent.inference(
model="ainfera-inference",
messages=[{"role": "user", "content": "Hello"}],
)
# receipt_id links this call to its AuditChain entry
print(response.receipt_id)
print(response.inference_id)
print(response.cost_usd)
```

To verify the integrity of the audit trail (which includes this call's
receipt), walk the AuditChain locally:

```python
from ainfera import verify_chain

# Fetch all events and verify the hash chain offline
events = list(agent.audit_chain.events())
assert verify_chain(events) is True # raises AuditChainBroken if tampered
```

## Local verification

Local verification proves log integrity relative to the published key —
it confirms that the AuditChain events you received from the API have not
been tampered with since the control plane hashed them. The chain rule:

event_hash = sha256_hex(
(previous_hash or "") + canonical_json(payload)
)

This is a **tamper-evidence** check, not a **tamper-prevention** check.
It proves the chain is internally consistent and that no event has been
modified, inserted, or removed. It does not prove the events are truthful
— only that they are structurally intact relative to each other.

An Annex IV auditor can verify a chain without trusting Ainfera: hand
them the events plus the `verify_chain()` function and they can confirm
tamper-evidence themselves. No API key, no network call, no Ainfera trust
required.

## Routing hints

Pass `routing_hint` to influence how the routing brain selects a model:

```python
response = agent.inference(
model="ainfera-inference",
messages=[{"role": "user", "content": "Explain quantum computing."}],
routing_hint={
"quality_floor": "production", # frontier|production|good
"budget_cap_usd": 0.012,
"latency_cap_ms": 2000,
},
pool="auto", # auto (default) | open | frontier
)
```

## Concepts

- [Agent](https://ainfera.ai/concepts/agent)
- [AgentCard](https://ainfera.ai/concepts/agent-card)
- [Inference](https://ainfera.ai/concepts/inference)
- [Wallet](https://ainfera.ai/concepts/wallet)
- [AuditChain](https://ainfera.ai/concepts/audit-chain)
- [Agent](https://ainfera.ai/docs#auth)
- [AgentCard](https://ainfera.ai/docs#auth)
- [Inference](https://ainfera.ai/docs#chat)
- [Wallet](https://ainfera.ai/docs#caps)
- [AuditChain](https://ainfera.ai/docs#audit)

## Compose, don't invent

Expand Down
33 changes: 23 additions & 10 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,37 @@ and emit real events to the public audit chain.
## `quickstart.py`

The 60-second first-Inference example. Mints an Agent Card, runs one
signed Inference through `claude-haiku-4-5`, prints the audit URLs you
can verify.
signed Inference through `ainfera-inference` (the flagship route),
prints the audit URLs you can verify.

```bash
pip install --no-cache-dir 'ainfera==1.0.1'
pip install --no-cache-dir 'ainfera>=1.1.0'
python examples/quickstart.py
```

Output should end with `✅ Soft launch verified`. The minted Agent
Output should end with `✅ Quickstart verified`. The minted Agent
appears on `https://api.ainfera.ai/v1/audit/public` within seconds.

Set `GITHUB_USER=<your-handle>` before running to make the canonical
URI map to your GitHub identity instead of `anonymous`.

## Coming next (D6+)
## `python/examples/`

- Multi-provider routing example
- LangChain wrapper example
- MCP server agent example
- hermes-agent drop-in example
- OpenClaw skill example
In-depth examples covering individual API surfaces:

- `01_register_agent.py` — Register an agent via `client.agents.register()`
- `02_mint_card.py` — Fetch and verify a JWS-signed AgentCard
- `03_topup_wallet.py` — Top up an agent's wallet
- `04_inference.py` — Basic inference call
- `05_read_audit.py` — Read the audit chain
- `06_verify_chain.py` — Local hash-chain verification (offline, no trust)

## Using streaming, tools, and retries

See the [README](../README.md) for production patterns on:

- SSE streaming (via OpenAI/Anthropic SDK pointed at `api.ainfera.ai`)
- Tools / function calling (`tools=`, `tool_choice=`)
- Retry with exponential backoff
- Async usage (`AsyncAinferaClient`)
- Receipt verification and local AuditChain verification
33 changes: 33 additions & 0 deletions examples/async_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Async usage — AsyncAinferaClient for async/await callers."""

import asyncio

from ainfera import AsyncAinferaClient


async def main() -> None:
client = AsyncAinferaClient(api_key="ainfera_...")
try:
agent = await client.agents.retrieve("agent_...")

response = await agent.inference(
model="ainfera-inference",
messages=[{"role": "user", "content": "Hello"}],
)
print(f"content: {response.content}")
print(f"model_used: {response.model_used}")
print(f"cost: ${response.cost_usd}")

# Wallet is awaitable on async agents
wallet = await agent.wallet
print(f"balance: ${wallet.balance_usd}")

# Verify audit chain locally (offline, no trust required)
ok = await agent.audit_chain.verify()
print(f"chain intact: {ok}")
finally:
await client.aclose()


if __name__ == "__main__":
asyncio.run(main())
Loading