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.2.tar.gz (48.6 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.2-py3-none-any.whl (21.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: annexkit-0.1.2.tar.gz
  • Upload date:
  • Size: 48.6 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.2.tar.gz
Algorithm Hash digest
SHA256 8a8ceea07668974f69c5c6f232709ab195d69029312b56787109efbfa207b22a
MD5 59a137d1831cd40844a0129285a0d0fc
BLAKE2b-256 397d433e49c4020f9567e500334c3afff64af1678048d36043aaba56f6dd03f5

See more details on using hashes here.

File details

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

File metadata

  • Download URL: annexkit-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 21.0 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1363282c3a00fc9fa37b65bbdf97986b42baa8741ac0688b7aada7b23f487b53
MD5 93cad8c62d2190e1a00e99735c8e3cef
BLAKE2b-256 df57cc51ef158e4ae3d859d444a100dc55deeb6b5328bbc3fad5db461ad51157

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