EU AI Act compliance pipeline for developers — Python SDK
Project description
annexkit — EU AI Act compliance pipeline for developers
One decorator. Audit-ready evidence. EU-hosted.
pip install annexkit and the SDK turns every LLM invocation in your
codebase into Article 12 audit log entries plus an Annex IV
technical-documentation feed for the EU AI Act
(Reg. 2024/1689).
from annexkit import track
@track(
system_id="customer-support-bot",
risk_tier="auto",
purpose="answer customer questions on shipping and returns",
)
def chat(user_msg: str, user_role: str = "customer") -> str:
return openai.chat.completions.create(...).choices[0].message.content
What gets recorded
Every call captures, by default:
| Field | What it holds |
|---|---|
system_id / deployment |
Stable identifiers you choose |
risk_tier / purpose |
AI Act metadata for Annex IV §1 + §4 |
started_at / ended_at / latency_ms |
Timing in UTC |
input_hash / output_hash |
SHA-256 hex (privacy-preserving) |
input_chars / output_chars |
Char count of serialised payloads |
model_provider / model_name / model_version |
When set explicitly |
sources[] |
Retrieval provenance for RAG |
user_role |
Article 13/14 oversight context |
error |
<module>.<class>: <message> on exception |
metadata |
Free-form dict |
sdk_version / sdk_lang |
Provenance |
Plaintext content is never logged by default. Hashes only — that's
the privacy-by-default invariant
(see ../CLAUDE.md non-negotiable #7).
Install
Pre-PyPI: AnnexKit is in active MVP development. The PyPI listing goes live at v1.0 (Day 7 of the MVP roadmap). For now install from source:
git clone https://github.com/annexkit/annexkit
cd annexkit/sdk
uv sync # or: pip install -e .
When the package lands on PyPI:
pip install annexkit # coming at v1.0
# or
uv add annexkit
Python ≥ 3.10. Only two runtime deps: httpx and pydantic.
Quickstart
1. Decorate sync functions
from annexkit import track
@track(system_id="loan-screener", purpose="pre-screen credit applications")
def screen(applicant: dict) -> str:
return llm.classify(applicant)
2. Decorate async functions
@track(system_id="async-classifier")
async def classify(ticket_id: str, body: str) -> str:
return await llm.acomplete(body)
The decorator auto-detects sync vs async — same API, no flag needed.
3. Multi-step blocks via the context manager
import annexkit
with annexkit.session(
system_id="policy-rag",
purpose="answer customer questions from policy KB",
) as span:
span.set_input(user_query)
docs = retriever.search(user_query)
for d in docs:
span.attach_source(uri=d.uri, hash=d.hash, version=d.version)
answer = llm.generate(user_query, docs)
span.set_output(answer)
span.set_model(provider="mistral", name="mistral-small-latest")
annexkit.session(...) is the right shape when you can't decorate a
single function (notebook cells, agent loops, orchestration scripts).
4. Configure via env or code
export ANNEXKIT_API_KEY=ak_xxxxx
export ANNEXKIT_COLLECTOR_URL=https://collector.annexkit.dev
export ANNEXKIT_DEPLOYMENT=prod
Or programmatically:
import annexkit
annexkit.configure(api_key="ak_xxxxx", deployment="staging")
| Env var | Default | Purpose |
|---|---|---|
ANNEXKIT_API_KEY |
unset | When set, switches to HTTP exporter |
ANNEXKIT_COLLECTOR_URL |
https://collector.annexkit.dev |
Collector endpoint |
ANNEXKIT_EXPORTER |
auto |
auto/stdout/http/noop |
ANNEXKIT_DISABLED |
0 |
Set 1 to disable tracking globally |
ANNEXKIT_DEPLOYMENT |
prod |
Default deployment label for spans |
5. Stdout in dev, HTTP in prod
With no API key, AnnexKit prints one JSON span per line to stderr —
perfect for testing, log shipping, or jq exploration:
{"system_id":"customer-support-bot","trace_id":"...","input_hash":"2cf24dba...",...}
Set ANNEXKIT_API_KEY and the same span is POSTed to the collector
instead.
Examples
Runnable examples in examples/:
cd sdk
uv sync
uv run python examples/basic_chatbot.py
uv run python examples/async_handler.py
uv run python examples/with_session.py
Each prints span JSON on stderr — no API keys required.
Custom exporters
Subclass annexkit.exporters.Exporter and pass an instance:
from annexkit.exporters import Exporter
from annexkit.schema import Span
class MyExporter(Exporter):
def export(self, span: Span) -> None:
my_observability_platform.publish(span.model_dump())
import annexkit
annexkit.configure(exporter=MyExporter())
The base class never raises from export() — your subclass shouldn't
either. Catching and logging exporter failures is the contract.
Public API
from annexkit import (
track, # decorator
session, # context manager
configure, # runtime override
flush, shutdown, # lifecycle
Span, Source, # data types
SpanHandle, # session yield type
__version__,
)
from annexkit.exporters import Exporter, StdoutExporter, HttpExporter
Testing your code with AnnexKit
The SDK ships a CollectingExporter pattern in
tests/conftest.py you can copy into your own test
suite to assert on emitted spans without hitting the network:
from annexkit.exporters.base import Exporter
class CollectingExporter(Exporter):
def __init__(self) -> None:
self.spans = []
def export(self, span):
self.spans.append(span)
Then:
def test_my_handler():
exporter = CollectingExporter()
annexkit.configure(exporter=exporter)
my_handler("input")
assert exporter.spans[0].system_id == "my-handler"
Status
v0.1.0 (Day 2 of MVP) — sync + async decorator, session manager,
stdout + HTTP exporters, config via env or code, 40+ unit tests.
The collector ingest endpoint (POST /api/v1/spans) lands in Day 3 of
the MVP — until then the HTTP exporter has nothing real to talk to.
Track progress in ../README.md.
See CHANGELOG.md for version history.
License
MIT — see LICENSE.
Disclaimer
AnnexKit is not a law firm. The Annex IV documents and risk classifications it produces are technical evidence; interpretation is the responsibility of your legal team or external counsel.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file annexkit-0.1.0.tar.gz.
File metadata
- Download URL: annexkit-0.1.0.tar.gz
- Upload date:
- Size: 46.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c55ce740f7c4840bb1672ab1344e3faa4808bfe554937c099b848862756011b
|
|
| MD5 |
66822ca00623b57b7cdb7fe5a96c20a5
|
|
| BLAKE2b-256 |
d968dc1ef882ef37a63fd3c8498021c2f51f462bfc905e59f7cbfae77b17603c
|
File details
Details for the file annexkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: annexkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f55f9722c3c7b4357f362431dabe55d2a2bf89f589bb7486454355f53b6253c8
|
|
| MD5 |
b3a68f31b396eb6c7740598b775b56e3
|
|
| BLAKE2b-256 |
391b2bb1cbf6aa2b09239c6e1241c0882351f27b9ffb2413fd74228287155ed6
|