Metadata-Version: 2.5
Name: agentaudit-python
Version: 0.1.0
Summary: EU AI Act Article 12 audit trails for AI agents
Author: Sathya Prakash J
License: MIT
License-File: LICENSE
Keywords: agents,ai,audit,compliance,eu-ai-act,langchain,logging,observability
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: langchain-core>=0.3; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: twine>=6; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Description-Content-Type: text/markdown

# agentaudit — Python SDK

Tamper-evident audit logging for AI agents. See `docs/03_SDK_QUICKSTART.md`.

```python
import agentaudit

agentaudit.init(api_key="aa_live_sk_...", agent_id="my-support-agent")
agentaudit.set_session("sess_abc123")

agentaudit.log_event(
    event_type="decision",
    action="route_to_human",
    metadata={"reason": "low_confidence"},
    severity="warning",
)
```

Inputs and outputs are SHA-256 hashed, never sent raw (ADR-002). Pass
`hash_inputs=False` to `init()` to opt into raw logging; raw values then travel
under the reserved keys `metadata.raw_input` and `metadata.raw_output`, since
the event schema has no top-level field for them. `init(environment=...)` is
likewise recorded as `metadata.environment`.

## Delivery

Capture never blocks: `log_event` appends to an in-memory queue and returns
(~0.05 ms). A daemon thread owning its own asyncio loop and `httpx.AsyncClient`
drains that queue every `flush_interval` seconds, or as soon as `batch_size`
events are waiting — whichever comes first (ADR-005). One implementation serves
both sync and async callers; nothing touches your event loop if you have one.

- `flush(timeout=10.0)` forces a send and blocks until it completes.
- Failed batches are retried `max_retries` times with exponential backoff and
  jitter, then spilled to a JSONL file in the temp directory. The buffer is
  keyed to the API key and `agent_id`, so a restarted process finds its backlog.
- While the backend is down, new events queue behind the buffered ones so
  delivery order matches production order. Buffered events replay first.
- A repeated failure starts a cooldown between cycles, so a long outage does not
  cost a full retry budget every `flush_interval`. An explicit `flush()` ignores it.
- Permanent failures (`400`, `401`) are discarded rather than buffered — they
  would fail identically forever.
- An `atexit` hook flushes whatever is queued at process exit.

## Decorator and LangChain integration (Week 2 Day 5-6)

```python
@agentaudit.trace
def search_documents(query: str, top_k: int = 5) -> list:
    return vector_db.similarity_search(query, k=top_k)
```

Logs a `tool_call` event per invocation: `action` is the function's qualified
name, `input_hash`/`output_hash` are SHA-256 digests of the arguments and
return value (never raw), `metadata.duration_ms` is execution time. A raised
exception is logged as `severity="critical"` with `metadata.error` and always
still propagates — `@trace` never changes what the wrapped function does.
Works on sync and async functions.

```python
from agentaudit.integrations import LangChainHandler  # pip install agentaudit[langchain]

chain.invoke(inputs, config={"callbacks": [LangChainHandler()]})
```

Handles `on_llm_start`/`on_llm_end` (and `on_chat_model_start`, which chat
models call instead), `on_llm_error`, `on_tool_start`/`on_tool_end`,
`on_tool_error`, and `on_chain_error`. LLM calls log model name and token
usage **only** — no prompt or response content, hashed or otherwise, since a
callback wired into every LLM call in a chain has no per-call decision behind
it. Tool calls are hashed, matching the SDK's default elsewhere.

All three error paths log the identical shape: `event_type="error"`,
`severity="critical"`, `metadata={"error": {"type": ..., "message": ...}}`.
A chain error's `action` is the chain's name when LangChain provides one
(`config={"run_name": "..."}"`), else generic. A tool or LLM error's `action`
is the tool/model name. `on_tool_error` fires even when the surrounding chain
catches the exception and completes normally — a tool failure the chain
recovers from is still worth an audit trail, and `on_chain_error` alone would
miss it entirely.

Chain start/end events are not implemented — that's session/trace-grouping
territory, not an audit-trail gap, since every LLM call, tool call, and error
within a chain is already captured on its own.

Both reuse Day 1-2's hashing and Day 3-4's sender through `agentaudit.log_event`
— no new transport code.

**Implemented (Week 2 Day 1-6):** `init`, `log_event`, `set_session`, `status`,
`flush`, `shutdown`, `@trace`, `LangChainHandler`, input/output hashing, event
builder, bounded queue, batch sender, offline buffer, retries, atexit hook.
**Not yet:** PyPI packaging (Day 7).
