Skip to main content

EU AI Act compliance pipeline for developers — Python SDK

Project description

annexkit

EU AI Act compliance pipeline — Python SDK.

PyPI version Python versions License: MIT GitHub stars

One decorator on your LLM call. Audit-ready Annex IV technical documentation under Reg. (EU) 2024/1689 out the other end.

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(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": user_msg}],
    ).choices[0].message.content

That decorator:

  • emits a span on every call (Article 12 logging),
  • ships it to the collector — or your own exporter,
  • feeds the system's Annex IV technical documentation, generated on demand at the collector.

Inputs and outputs are SHA-256 hashed before they leave your host. Plaintext logging is opt-in.


Install

pip install annexkit

Python ≥ 3.10. Two runtime deps: httpx and pydantic.

Quickstart

1. Decorate

from annexkit import track

@track(system_id="loan-screener", purpose="pre-screen credit applications")
def screen(applicant: dict) -> str:
    return llm.classify(applicant)

Sync or async — the decorator auto-detects. Same API, no flag.

2. Two modes — stdout in dev, HTTP in prod

Without an API key, AnnexKit prints one JSON span per call to stderr. Perfect for local dev, log shipping, or jq exploration:

{"system_id":"loan-screener","trace_id":"...","input_hash":"2cf24dba...","latency_ms":523,...}

With ANNEXKIT_API_KEY set, the same span is POSTed to the collector:

export ANNEXKIT_API_KEY=ak_xxxxx
export ANNEXKIT_COLLECTOR_URL=https://collector.annexkit.dev

Or programmatically:

import annexkit
annexkit.configure(api_key="ak_xxxxx", deployment="staging")

3. Async — same API

@track(system_id="async-classifier")
async def classify(ticket_id: str, body: str) -> str:
    return await llm.acomplete(body)

4. Multi-step via 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.

What gets recorded

Field What it holds
system_id / deployment Stable identifiers you choose
risk_tier / purpose AI Act metadata for Annex IV
started_at / ended_at / latency_ms Timing in UTC
input_hash / output_hash SHA-256 hex (privacy-preserving)
input_chars / output_chars Char counts of 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 for adapter authors
sdk_version / sdk_lang Provenance

Plaintext payloads are never logged by default — privacy-by-default is wired into the SDK, not a config flag.

Configuration

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 to 1 to disable tracking globally
ANNEXKIT_DEPLOYMENT prod Default deployment label for spans

Custom exporters

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())

Subclasses must never raise from export() — catch and log inside. That's the contract; the base class will never raise either.

Public API

from annexkit import (
    track,             # decorator (sync + async)
    session,           # context manager
    configure,         # runtime override
    flush, shutdown,   # lifecycle helpers
    Span, Source,      # data types
    SpanHandle,        # session yield type
    __version__,
)
from annexkit.exporters import Exporter, StdoutExporter, HttpExporter

Testing your code with AnnexKit

A common pattern: a CollectingExporter that captures spans into a list, so your tests can assert on what was emitted without hitting the network.

from annexkit.exporters import Exporter

class CollectingExporter(Exporter):
    def __init__(self) -> None:
        self.spans = []
    def export(self, span):
        self.spans.append(span)
def test_my_handler():
    exporter = CollectingExporter()
    annexkit.configure(exporter=exporter)
    my_handler("input")
    assert exporter.spans[0].system_id == "my-handler"

Status

v0.1.x — sync + async decorator, session manager, stdout + HTTP exporters, config via env or code. 48 unit tests with httpx.MockTransport for hermetic HTTP coverage (zero real network in CI).

The hosted collector at annexkit.dev is in early access. The self-host path (docker compose up against the repo) is available today under AGPL-3.0.

Roadmap (v0.2)

  • Mistral advisor for ambiguous declarations (hard guardrail: never declassifies a system the rules engine flagged)
  • LangChain + LlamaIndex auto-instrumentation (annexkit.auto_instrument(["langchain"]))
  • TypeScript / JavaScript SDK (@annexkit/node) — same wire format, same env-var conventions
  • Async HTTP exporter (httpx.AsyncClient)
  • Span batching + retry on transient failure
  • OpenTelemetry GenAI semconv compliance — emit OTel-native spans alongside the AnnexKit-specific extensions

Full CHANGELOG.

Links

License

MIT — see LICENSE.

The hosted collector and trust-center frontend are AGPL-3.0; the SDK is MIT so you can drop it into any codebase without copyleft concerns. Same dual-licence arrangement Sentry, PostHog, and MinIO use.

Disclaimer

AnnexKit is not a law firm; AnnexKit non è uno studio legale. The Annex IV documents and risk classifications it produces are technical artefacts; legal interpretation is the responsibility of your legal team or external counsel.

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

annexkit-0.1.1.tar.gz (47.8 kB view details)

Uploaded Source

Built Distribution

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

annexkit-0.1.1-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file annexkit-0.1.1.tar.gz.

File metadata

  • Download URL: annexkit-0.1.1.tar.gz
  • Upload date:
  • Size: 47.8 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

Hashes for annexkit-0.1.1.tar.gz
Algorithm Hash digest
SHA256 e771ba70ec4ad3dbaf3e6cda97b499fc4576a50892280d941983fa1ec7a7b980
MD5 5f0dff5f0ca77b71ee66cb7bc0a39317
BLAKE2b-256 87217bb21a6c78a5900ef6339e8680fd5774ce4918745a87b8d29a60b4498a74

See more details on using hashes here.

File details

Details for the file annexkit-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: annexkit-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 20.7 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

Hashes for annexkit-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 499522da87fb4830d9e05d6771397dcf2efd5a235860fbd3a1ef56775fbd379a
MD5 82e1f3804030b085016868139322798f
BLAKE2b-256 e4e2e30d58cb38d9f7f5a3c6f0d953cb10c15de8a009e55fb99add44d2c92196

See more details on using hashes here.

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