Skip to main content

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; only disclarion_meta changes, 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/prepend disclosure_text; disclarion_meta["disclosed"] reflects whether that happened.
  • off — pure logging, no disclosure signal at all: disclarion_meta omits is_first_message/disclosed entirely. 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. Unsupported response types raise a ValueError immediately for the same reason: both are programming/config errors to fix, not a runtime condition to swallow.

OpenAI (ChatCompletion), Anthropic (Message), and Gemini (GenerateContentResponse) responses are all supported — see disclarion/adapters/.

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.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for disclarion 0.2.3
File Size Uploaded
disclarion-0.2.3.tar.gz 28.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for disclarion 0.2.3
File Interpreter ABI Platform
disclarion-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 51.5 kB

Release files / disclarion-0.2.3.tar.gz

Download URL disclarion-0.2.3.tar.gz
Size 28.1 kB
Tags Source
SHA-256 checksum
How to use checksums
16d8d45ecd2dc72af7efe6c43f01230dab4378e888a68cf9999aa78868927d67
BLAKE2b-256 checksum
How to use checksums
bcf2c79ed88a5e1dabf0609c1193818ef4f8bc3e7efadfb9e3c166e82ddac462
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.2.3-py3-none-any.whl

Download URL disclarion-0.2.3-py3-none-any.whl
Size 23.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8a150bb927e720da9af44962f3d24e14559c367a845a87794001737929ab0ac1
BLAKE2b-256 checksum
How to use checksums
5d7d18c852878c5f0da495bbe102d16181ec0c3687e76e6a04562b554e4dbd10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release history Release notifications | RSS feed

0.8.0

2 release files

0.7.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.4

2 release files

This release

0.2.3 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page