disclarion
AI disclosure logging SDK. Wraps your LLM provider responses and reports standardized disclosure/labeling metadata to Disclarion.
Install
pip install disclarion
Usage
from disclarion import Disclarion
import openai
client = openai.OpenAI()
dc = Disclarion(api_key="dcl_live_...")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "hello"}],
)
dc.track(response, session_id="session-123")
track() never modifies response by default — it attaches a
disclarion_meta dict to it instead, and your own UI decides what to show:
tracked = dc.track(response, session_id="session-123")
tracked.disclarion_meta
# {"ai_generated": True, "is_first_message": True, "disclosed": False, "logged_at": "..."}
is_first_message is what a host-app disclosure widget should key off of —
show a one-time blocking notice on True, keep a small persistent badge up
for the rest of the session on False. A ready React and vanilla-JS
implementation of exactly that pattern ships inside every install — find it
with:
python -c "import disclarion; print(disclarion.widget_dir())"
This is the disclosure_mode="modal_once" default, set on the client, not
per call:
modal_once(default) — never touches response content; onlydisclarion_metachanges, as above.inline_every_message— the SDK's original behavior, kept for backward compatibility. Despite the name, it still only rewrites the response's own content on a session's first message (never later ones) to append/prependdisclosure_text;disclarion_meta["disclosed"]reflects whether that happened.off— pure logging, no disclosure signal at all:disclarion_metaomitsis_first_message/disclosedentirely. Use this if your product already has its own unrelated AI-disclosure UI.
dc = Disclarion(api_key="dcl_live_...", disclosure_mode="inline_every_message")
Regardless of disclosure_mode, every track() call is still logged to
Disclarion's audit trail (interaction_logs) exactly the same way — the UI
mode only changes what happens to response and disclarion_meta, never
what gets recorded for compliance.
track() makes one fast synchronous attempt (that's what makes
is_first_message available immediately) and never retries inline — a
slow or erroring Disclarion backend can never turn into multi-second
latency on your own request path. Anything worth retrying (a network
error, a 5xx, a transient 429) is handed to a background thread with
a bounded queue (up to 1000 pending logs, oldest dropped first if your
process is generating logs faster than Disclarion can accept them) that
retries with capped, jittered exponential backoff, honoring a 429's
Retry-After header when present. The queue is flushed on normal
process exit, SIGINT, and SIGTERM (bounded to a couple of seconds, so
shutdown is never blocked indefinitely by a backend that's still down).
A 429 that means "you've exceeded your monthly plan quota" is handled
differently from a transient rate limit: retrying it is pointless until
next period, so the SDK opens a circuit breaker instead — every track()
call skips the network entirely (sub-millisecond) until an hourly (then
exponentially longer, capped at 24h) cooldown elapses and one probe
request checks whether it's resolved. One logging.warning marks each
state change (opened/resolved), not one per call, so a month-long outage
doesn't spam your logs. Call dc.get_stats() any time for the queue size,
circuit state, and delivery counters.
An invalid or revoked API key is different from all of the above: the
backend is reachable and is explicitly rejecting it, which is a setup
mistake you need to see, so that raises disclarion.AuthenticationError
immediately — no retry, no circuit-breaking, no silent fallback. An
inactive subscription (trial ended, payment failed) is treated the same
way: it raises disclarion.SubscriptionInactiveError immediately, for
the same reason (new in 0.3.0 — before this, a 402 fell through the
generic failure path above and was silently swallowed at DEBUG level,
which meant a lapsed subscription could leave a real, permanent gap in
your audit trail with no visible symptom; this is an intentional breaking
change, see CHANGELOG.md). Unsupported response types raise a
ValueError immediately for the same reason: all three are programming/
account-state errors to fix, not a runtime condition to swallow.
OpenAI (ChatCompletion), Anthropic (Message), and Gemini
(GenerateContentResponse) responses are all supported — see
disclarion/adapters/.
Jurisdiction and interaction type
track() also takes jurisdiction (default "EU") and interaction_type
(default "chat"):
dc.track(response, session_id="session-123", jurisdiction="US-CA", interaction_type="chat")
These feed Disclarion's backend Obligation Engine, which decides which
obligations (e.g. disclosure_modal, audit_log) actually apply to this
pair — the SDK has no built-in notion of "EU Article 50" or any other
specific regulation, it only acts on whatever required_actions the
backend returns. The defaults match the only rule that exists today, so
every existing caller sees identical behavior without passing these. Any
jurisdiction/interaction type combination without a configured rule is
still logged in full (the audit trail is never gated by obligations), it
just comes back with no obligations — disclarion_meta omits
is_first_message/disclosed for that call, the same shape as
disclosure_mode="off".
Check what a pair currently requires without logging anything, with the same API key:
curl "https://api.disclarion.com/v1/obligations?jurisdiction=EU&interaction_type=chat" \
-H "Authorization: Bearer dcl_live_..."
# {"data": {"jurisdiction": "EU", "interaction_type": "chat", "required_actions": ["disclosure_modal", "audit_log"]}, "error": null}
There's no SDK wrapper for this endpoint yet — it's a plain side-effect-free
GET, same auth as everything else.
Using this with an async framework (FastAPI, aiohttp, Starlette, ...)
track() is synchronous under the hood (it uses requests) and blocks for
up to FAST_PATH_TIMEOUT_SECONDS (currently 1.5s) even on a healthy
backend. Called directly inside an async def handler, that blocks the
whole worker's event loop for that long — stalling every other concurrent
request on the same worker, not just the one that called track(). Get it
off the event loop:
# FastAPI / Starlette
from starlette.concurrency import run_in_threadpool
tracked = await run_in_threadpool(dc.track, response, session_id=session_id)
# Any asyncio app, no Starlette dependency
import asyncio
tracked = await asyncio.to_thread(dc.track, response, session_id=session_id)
This applies even if the backend you're calling is your own service — a
synchronous track() call made from inside an async handler can, in the
worst case, block that same process against itself.
Testing without a live backend
To exercise _send_log() end to end without hitting the real
api.disclarion.com:
python scripts/mock_server.py # terminal 1 - fake /v1/logs endpoint
python scripts/manual_test.py # terminal 2 - sends a fake ChatCompletion
The mock server prints every received payload and the Authorization
header so you can confirm the normalized log shape is correct. See
scripts/README.md for details, or tests/ for the automated unit tests
(pytest) which mock the HTTP layer instead of needing a running server.
Metadata
Release files for disclarion 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| disclarion-0.3.0.tar.gz | 33.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| disclarion-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.5 kB
Release files / disclarion-0.3.0.tar.gz
| Download URL | disclarion-0.3.0.tar.gz |
|---|---|
| Size | 33.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99402d4ee3654036fa5bed869b44540bf6d21e6771cccfe2a3604b724ef34a08
|
|
BLAKE2b-256 checksum How to use checksums |
52f2ac28239cfb54774d626745b889daba46a33f8ede8a79b43e74518ea09e55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / disclarion-0.3.0-py3-none-any.whl
| Download URL | disclarion-0.3.0-py3-none-any.whl |
|---|---|
| Size | 26.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6e7504f91915165531cec387eb5105e79640488e86c801da3f5f4c322f6c21b0
|
|
BLAKE2b-256 checksum How to use checksums |
583ce9f43c9a6c7ce5bbf07ac4b31c7fdf57af47d41e08f388180f61b3d81f23
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|