ai-audit-logger 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-audit-logger
3
+ Version: 0.1.0
4
+ Summary: Drop-in audit logging for LLM API calls (OpenAI, Anthropic)
5
+ Project-URL: Source, https://github.com/elliottj06/ai-audit-logger
6
+ Project-URL: Bug Tracker, https://github.com/elliottj06/ai-audit-logger/issues
7
+ Keywords: llm openai anthropic audit logging ai compliance observability
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: System :: Logging
13
+ Classifier: Intended Audience :: Developers
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: httpx>=0.24.0
17
+ Provides-Extra: openai
18
+ Requires-Dist: openai>=1.0.0; extra == "openai"
19
+ Provides-Extra: anthropic
20
+ Requires-Dist: anthropic>=0.20.0; extra == "anthropic"
21
+ Provides-Extra: all
22
+ Requires-Dist: openai>=1.0.0; extra == "all"
23
+ Requires-Dist: anthropic>=0.20.0; extra == "all"
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: keywords
28
+ Dynamic: project-url
29
+ Dynamic: provides-extra
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
33
+
34
+ # AI Audit Logger
35
+
36
+ ![CI](https://github.com/elliottj06/ai-audit-logger/actions/workflows/ci.yml/badge.svg)
37
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
38
+ ![License](https://img.shields.io/badge/license-MIT-green)
39
+
40
+ A self-hosted audit logger for LLM API calls. One line of code captures every OpenAI and Anthropic call — prompts, responses, tokens, cost, latency — and stores it in a queryable database.
41
+
42
+ Built for teams that need visibility into their AI usage for **debugging**, **cost control**, and **compliance** (EU AI Act, SOC 2).
43
+
44
+ ![AI Audit Logger web dashboard showing total calls, cost, latency, token usage, per-model charts and a filterable table of recent calls](docs/images/dashboard.png)
45
+
46
+ ---
47
+
48
+ ## How it works
49
+
50
+ ```
51
+ Your App → SDK (wrap your client) → Ingest API → Postgres
52
+ ↓
53
+ CLI / Dashboard / Export
54
+ ```
55
+
56
+ The SDK is a thin wrapper around your existing OpenAI or Anthropic client. It intercepts every call, records it asynchronously (never blocks your app), and ships it to the server.
57
+
58
+ ---
59
+
60
+ ## Quickstart
61
+
62
+ ### 1. Start the server
63
+
64
+ ```bash
65
+ git clone https://github.com/elliottj06/ai-audit-logger
66
+ cd ai-audit-logger
67
+ docker compose up -d
68
+ ```
69
+
70
+ ### 2. Wrap your client — one line change
71
+
72
+ **OpenAI:**
73
+ ```python
74
+ from openai import OpenAI
75
+ from ai_audit_logger import wrap
76
+
77
+ client = wrap(OpenAI())
78
+
79
+ # Every call now logged automatically — no other changes needed
80
+ response = client.chat.completions.create(
81
+ model="gpt-4o",
82
+ messages=[{"role": "user", "content": "Hello"}]
83
+ )
84
+ ```
85
+
86
+ **Anthropic:**
87
+ ```python
88
+ from anthropic import Anthropic
89
+ from ai_audit_logger import wrap
90
+
91
+ client = wrap(Anthropic())
92
+
93
+ response = client.messages.create(
94
+ model="claude-sonnet-4-6",
95
+ max_tokens=1024,
96
+ messages=[{"role": "user", "content": "Hello"}]
97
+ )
98
+ ```
99
+
100
+ ### 3. Query your logs
101
+
102
+ ```bash
103
+ pip install -r cli/requirements.txt
104
+
105
+ python cli/main.py tail # recent calls
106
+ python cli/main.py stats # cost + usage summary
107
+ ```
108
+
109
+ **`tail` output:**
110
+ ```
111
+ Showing 3 of 3 entries
112
+
113
+ Timestamp Provider Model Tokens Cost Latency
114
+ ------------------- ----------- ---------------------------- ------------ ------------- ---------
115
+ 2026-11-04 14:23:01 openai gpt-4o 18→32 $0.00057 843ms
116
+ 2026-11-04 14:23:02 anthropic claude-sonnet-4-6 20→41 $0.00068 1204ms
117
+ 2026-11-04 14:23:03 openai gpt-4o-mini 0→0 n/a 120ms ERROR
118
+ ```
119
+
120
+ **`stats` output:**
121
+
122
+ ![Output of the stats command: total calls, total cost, tokens in and out, average latency, and a per-model breakdown of calls and cost](docs/images/cli-stats.png)
123
+
124
+ ### 4. Open the dashboard
125
+
126
+ With the server running, open **http://localhost:8000** for the web dashboard: summary cards, calls and cost by model, and a filterable table of recent calls (click any row for full detail).
127
+
128
+ ---
129
+
130
+ ## Try it without API keys
131
+
132
+ Run the included example to send simulated log entries and see the CLI in action:
133
+
134
+ ```bash
135
+ python examples/basic_usage.py
136
+ python cli/main.py tail
137
+ python cli/main.py stats
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Configuration
143
+
144
+ ```python
145
+ from ai_audit_logger import configure
146
+
147
+ configure(
148
+ server_url="http://localhost:8000", # your server
149
+ app_name="my-chatbot", # tag logs by app
150
+ user_id="user-123", # tie logs to a user
151
+ session_id="session-abc", # group calls into sessions
152
+ redact_prompts=False, # True = omit prompt text from logs
153
+ enabled=True,
154
+ )
155
+ ```
156
+
157
+ | Env var | Default | Description |
158
+ |---|---|---|
159
+ | `AUDIT_SERVER_URL` | `http://localhost:8000` | Ingest server URL |
160
+ | `AUDIT_APP_NAME` | (none) | Tag logs with your app name |
161
+ | `AUDIT_ENABLED` | `true` | Set `false` to disable without code changes |
162
+ | `AUDIT_REDACT_PROMPTS` | `false` | Omit prompt/response text (privacy mode) |
163
+
164
+ ---
165
+
166
+ ## API
167
+
168
+ | Endpoint | Method | Description |
169
+ |---|---|---|
170
+ | `/api/v1/log` | POST | Ingest a log entry |
171
+ | `/api/v1/logs` | GET | Query logs (filter by provider, model, user, app, session) |
172
+ | `/api/v1/logs/stats` | GET | Aggregated stats — total calls, cost, tokens, latency |
173
+ | `/health` | GET | Health check |
174
+
175
+ ---
176
+
177
+ ## Project structure
178
+
179
+ ```
180
+ ai-audit-logger/
181
+ ├── server/ # FastAPI ingest + query API
182
+ │ ├── main.py
183
+ │ ├── models.py # SQLAlchemy LogEntry model
184
+ │ ├── db.py # Postgres connection
185
+ │ ├── routes/
186
+ │ │ ├── ingest.py # POST /api/v1/log
187
+ │ │ └── query.py # GET /api/v1/logs + /stats
188
+ │ └── static/index.html # Web dashboard (served at /)
189
+ ├── sdk/python/ # Python SDK
190
+ │ └── ai_audit_logger/
191
+ │ ├── __init__.py # wrap() + configure()
192
+ │ ├── wrapper.py # OpenAI + Anthropic wrappers
193
+ │ ├── client.py # Fire-and-forget HTTP sender
194
+ │ └── config.py # AuditConfig dataclass
195
+ ├── cli/
196
+ │ └── main.py # tail / stats / get commands
197
+ ├── examples/
198
+ │ └── basic_usage.py # Try it without API keys
199
+ └── docker-compose.yml
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Roadmap
205
+
206
+ - [ ] TypeScript / Node.js SDK
207
+ - [x] Web dashboard with cost charts
208
+ - [ ] LangChain integration
209
+ - [ ] Publish to PyPI (`pip install ai-audit-logger`)
210
+ - [ ] Tamper-evident log hashing for compliance exports
211
+ - [ ] Slack / email alerts on cost spikes
212
+
213
+ ---
214
+
215
+ ## License
216
+
217
+ MIT
@@ -0,0 +1,184 @@
1
+ # AI Audit Logger
2
+
3
+ ![CI](https://github.com/elliottj06/ai-audit-logger/actions/workflows/ci.yml/badge.svg)
4
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
5
+ ![License](https://img.shields.io/badge/license-MIT-green)
6
+
7
+ A self-hosted audit logger for LLM API calls. One line of code captures every OpenAI and Anthropic call — prompts, responses, tokens, cost, latency — and stores it in a queryable database.
8
+
9
+ Built for teams that need visibility into their AI usage for **debugging**, **cost control**, and **compliance** (EU AI Act, SOC 2).
10
+
11
+ ![AI Audit Logger web dashboard showing total calls, cost, latency, token usage, per-model charts and a filterable table of recent calls](docs/images/dashboard.png)
12
+
13
+ ---
14
+
15
+ ## How it works
16
+
17
+ ```
18
+ Your App → SDK (wrap your client) → Ingest API → Postgres
19
+ ↓
20
+ CLI / Dashboard / Export
21
+ ```
22
+
23
+ The SDK is a thin wrapper around your existing OpenAI or Anthropic client. It intercepts every call, records it asynchronously (never blocks your app), and ships it to the server.
24
+
25
+ ---
26
+
27
+ ## Quickstart
28
+
29
+ ### 1. Start the server
30
+
31
+ ```bash
32
+ git clone https://github.com/elliottj06/ai-audit-logger
33
+ cd ai-audit-logger
34
+ docker compose up -d
35
+ ```
36
+
37
+ ### 2. Wrap your client — one line change
38
+
39
+ **OpenAI:**
40
+ ```python
41
+ from openai import OpenAI
42
+ from ai_audit_logger import wrap
43
+
44
+ client = wrap(OpenAI())
45
+
46
+ # Every call now logged automatically — no other changes needed
47
+ response = client.chat.completions.create(
48
+ model="gpt-4o",
49
+ messages=[{"role": "user", "content": "Hello"}]
50
+ )
51
+ ```
52
+
53
+ **Anthropic:**
54
+ ```python
55
+ from anthropic import Anthropic
56
+ from ai_audit_logger import wrap
57
+
58
+ client = wrap(Anthropic())
59
+
60
+ response = client.messages.create(
61
+ model="claude-sonnet-4-6",
62
+ max_tokens=1024,
63
+ messages=[{"role": "user", "content": "Hello"}]
64
+ )
65
+ ```
66
+
67
+ ### 3. Query your logs
68
+
69
+ ```bash
70
+ pip install -r cli/requirements.txt
71
+
72
+ python cli/main.py tail # recent calls
73
+ python cli/main.py stats # cost + usage summary
74
+ ```
75
+
76
+ **`tail` output:**
77
+ ```
78
+ Showing 3 of 3 entries
79
+
80
+ Timestamp Provider Model Tokens Cost Latency
81
+ ------------------- ----------- ---------------------------- ------------ ------------- ---------
82
+ 2026-11-04 14:23:01 openai gpt-4o 18→32 $0.00057 843ms
83
+ 2026-11-04 14:23:02 anthropic claude-sonnet-4-6 20→41 $0.00068 1204ms
84
+ 2026-11-04 14:23:03 openai gpt-4o-mini 0→0 n/a 120ms ERROR
85
+ ```
86
+
87
+ **`stats` output:**
88
+
89
+ ![Output of the stats command: total calls, total cost, tokens in and out, average latency, and a per-model breakdown of calls and cost](docs/images/cli-stats.png)
90
+
91
+ ### 4. Open the dashboard
92
+
93
+ With the server running, open **http://localhost:8000** for the web dashboard: summary cards, calls and cost by model, and a filterable table of recent calls (click any row for full detail).
94
+
95
+ ---
96
+
97
+ ## Try it without API keys
98
+
99
+ Run the included example to send simulated log entries and see the CLI in action:
100
+
101
+ ```bash
102
+ python examples/basic_usage.py
103
+ python cli/main.py tail
104
+ python cli/main.py stats
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Configuration
110
+
111
+ ```python
112
+ from ai_audit_logger import configure
113
+
114
+ configure(
115
+ server_url="http://localhost:8000", # your server
116
+ app_name="my-chatbot", # tag logs by app
117
+ user_id="user-123", # tie logs to a user
118
+ session_id="session-abc", # group calls into sessions
119
+ redact_prompts=False, # True = omit prompt text from logs
120
+ enabled=True,
121
+ )
122
+ ```
123
+
124
+ | Env var | Default | Description |
125
+ |---|---|---|
126
+ | `AUDIT_SERVER_URL` | `http://localhost:8000` | Ingest server URL |
127
+ | `AUDIT_APP_NAME` | (none) | Tag logs with your app name |
128
+ | `AUDIT_ENABLED` | `true` | Set `false` to disable without code changes |
129
+ | `AUDIT_REDACT_PROMPTS` | `false` | Omit prompt/response text (privacy mode) |
130
+
131
+ ---
132
+
133
+ ## API
134
+
135
+ | Endpoint | Method | Description |
136
+ |---|---|---|
137
+ | `/api/v1/log` | POST | Ingest a log entry |
138
+ | `/api/v1/logs` | GET | Query logs (filter by provider, model, user, app, session) |
139
+ | `/api/v1/logs/stats` | GET | Aggregated stats — total calls, cost, tokens, latency |
140
+ | `/health` | GET | Health check |
141
+
142
+ ---
143
+
144
+ ## Project structure
145
+
146
+ ```
147
+ ai-audit-logger/
148
+ ├── server/ # FastAPI ingest + query API
149
+ │ ├── main.py
150
+ │ ├── models.py # SQLAlchemy LogEntry model
151
+ │ ├── db.py # Postgres connection
152
+ │ ├── routes/
153
+ │ │ ├── ingest.py # POST /api/v1/log
154
+ │ │ └── query.py # GET /api/v1/logs + /stats
155
+ │ └── static/index.html # Web dashboard (served at /)
156
+ ├── sdk/python/ # Python SDK
157
+ │ └── ai_audit_logger/
158
+ │ ├── __init__.py # wrap() + configure()
159
+ │ ├── wrapper.py # OpenAI + Anthropic wrappers
160
+ │ ├── client.py # Fire-and-forget HTTP sender
161
+ │ └── config.py # AuditConfig dataclass
162
+ ├── cli/
163
+ │ └── main.py # tail / stats / get commands
164
+ ├── examples/
165
+ │ └── basic_usage.py # Try it without API keys
166
+ └── docker-compose.yml
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Roadmap
172
+
173
+ - [ ] TypeScript / Node.js SDK
174
+ - [x] Web dashboard with cost charts
175
+ - [ ] LangChain integration
176
+ - [ ] Publish to PyPI (`pip install ai-audit-logger`)
177
+ - [ ] Tamper-evident log hashing for compliance exports
178
+ - [ ] Slack / email alerts on cost spikes
179
+
180
+ ---
181
+
182
+ ## License
183
+
184
+ MIT
@@ -0,0 +1,53 @@
1
+ from .config import AuditConfig
2
+ from .client import AuditClient
3
+ from .wrapper import WrappedOpenAI, WrappedAnthropic
4
+
5
+ _default_config = AuditConfig()
6
+ _default_client = AuditClient(_default_config)
7
+
8
+
9
+ def configure(
10
+ server_url: str = None,
11
+ app_name: str = None,
12
+ user_id: str = None,
13
+ session_id: str = None,
14
+ redact_prompts: bool = False,
15
+ enabled: bool = True,
16
+ ):
17
+ """Configure global defaults for the audit logger."""
18
+ global _default_config, _default_client
19
+ _default_config = AuditConfig(
20
+ server_url=server_url or _default_config.server_url,
21
+ app_name=app_name,
22
+ user_id=user_id,
23
+ session_id=session_id,
24
+ redact_prompts=redact_prompts,
25
+ enabled=enabled,
26
+ )
27
+ _default_client = AuditClient(_default_config)
28
+
29
+
30
+ def wrap(client, config: AuditConfig = None):
31
+ """
32
+ Wrap an OpenAI or Anthropic client to automatically log all LLM calls.
33
+
34
+ Usage:
35
+ from openai import OpenAI
36
+ from ai_audit_logger import wrap
37
+
38
+ client = wrap(OpenAI())
39
+ # All calls now logged automatically
40
+ """
41
+ cfg = config or _default_config
42
+ audit = AuditClient(cfg)
43
+ class_name = type(client).__name__
44
+
45
+ if "OpenAI" in class_name:
46
+ return WrappedOpenAI(client, audit, cfg)
47
+ elif "Anthropic" in class_name:
48
+ return WrappedAnthropic(client, audit, cfg)
49
+ else:
50
+ raise ValueError(f"Unsupported client type: {class_name}. Expected OpenAI or Anthropic.")
51
+
52
+
53
+ __all__ = ["wrap", "configure", "AuditConfig", "AuditClient"]
@@ -0,0 +1,29 @@
1
+ import httpx
2
+ import threading
3
+ from .config import AuditConfig
4
+
5
+
6
+ class AuditClient:
7
+ def __init__(self, config: AuditConfig):
8
+ self.config = config
9
+ self._http = httpx.Client(timeout=config.timeout)
10
+
11
+ def log(self, **kwargs):
12
+ if not self.config.enabled:
13
+ return
14
+ # Fire-and-forget — never blocks the calling application
15
+ t = threading.Thread(target=self._send, kwargs=kwargs, daemon=True)
16
+ t.start()
17
+
18
+ def _send(self, **kwargs):
19
+ try:
20
+ self._http.post(
21
+ f"{self.config.server_url}/api/v1/log",
22
+ json=kwargs,
23
+ timeout=self.config.timeout,
24
+ )
25
+ except Exception:
26
+ pass # Silently drop — logging must never crash the app
27
+
28
+ def close(self):
29
+ self._http.close()
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional
3
+ import os
4
+
5
+
6
+ @dataclass
7
+ class AuditConfig:
8
+ server_url: str = field(default_factory=lambda: os.getenv("AUDIT_SERVER_URL", "http://localhost:8000"))
9
+ app_name: Optional[str] = field(default_factory=lambda: os.getenv("AUDIT_APP_NAME"))
10
+ user_id: Optional[str] = None
11
+ session_id: Optional[str] = None
12
+ enabled: bool = field(default_factory=lambda: os.getenv("AUDIT_ENABLED", "true").lower() == "true")
13
+ redact_prompts: bool = field(default_factory=lambda: os.getenv("AUDIT_REDACT_PROMPTS", "false").lower() == "true")
14
+ timeout: float = 2.0
@@ -0,0 +1,153 @@
1
+ import time
2
+ import json
3
+ from typing import Optional
4
+ from .config import AuditConfig
5
+ from .client import AuditClient
6
+
7
+ # Cost per 1M tokens (input, output) in USD
8
+ COST_TABLE = {
9
+ "gpt-4o-mini": (0.15, 0.60),
10
+ "gpt-4o": (5.00, 15.00),
11
+ "gpt-4-turbo": (10.00, 30.00),
12
+ "gpt-3.5-turbo": (0.50, 1.50),
13
+ "claude-haiku-4-5": (0.80, 4.00),
14
+ "claude-sonnet-4-6": (3.00, 15.00),
15
+ "claude-opus-4-6": (15.00, 75.00),
16
+ "claude-3-5-sonnet": (3.00, 15.00),
17
+ "claude-3-5-haiku": (0.80, 4.00),
18
+ }
19
+
20
+
21
+ def _calc_cost(model: str, tokens_in: int, tokens_out: int) -> Optional[float]:
22
+ model_lower = model.lower()
23
+ for key, (cost_in, cost_out) in COST_TABLE.items():
24
+ if key in model_lower:
25
+ return (tokens_in * cost_in + tokens_out * cost_out) / 1_000_000
26
+ return None
27
+
28
+
29
+ # ── OpenAI wrappers ───────────────────────────────────────────────────────────
30
+
31
+ class WrappedOpenAI:
32
+ def __init__(self, client, audit: AuditClient, config: AuditConfig):
33
+ self._client = client
34
+ self.chat = WrappedChat(client.chat, audit, config)
35
+
36
+ def __getattr__(self, name):
37
+ return getattr(self._client, name)
38
+
39
+
40
+ class WrappedChat:
41
+ def __init__(self, chat, audit: AuditClient, config: AuditConfig):
42
+ self._chat = chat
43
+ self.completions = WrappedCompletions(chat.completions, audit, config)
44
+
45
+ def __getattr__(self, name):
46
+ return getattr(self._chat, name)
47
+
48
+
49
+ class WrappedCompletions:
50
+ def __init__(self, completions, audit: AuditClient, config: AuditConfig):
51
+ self._completions = completions
52
+ self._audit = audit
53
+ self._config = config
54
+
55
+ def create(self, **kwargs):
56
+ start = time.time()
57
+ error = None
58
+ response = None
59
+ try:
60
+ response = self._completions.create(**kwargs)
61
+ return response
62
+ except Exception as e:
63
+ error = str(e)
64
+ raise
65
+ finally:
66
+ latency_ms = (time.time() - start) * 1000
67
+ model = kwargs.get("model", "unknown")
68
+ tokens_in = tokens_out = None
69
+ response_text = None
70
+
71
+ if response:
72
+ if hasattr(response, "usage") and response.usage:
73
+ tokens_in = response.usage.prompt_tokens
74
+ tokens_out = response.usage.completion_tokens
75
+ if hasattr(response, "choices") and response.choices:
76
+ response_text = response.choices[0].message.content
77
+
78
+ self._audit.log(
79
+ provider="openai",
80
+ model=model,
81
+ prompt=None if self._config.redact_prompts else json.dumps(kwargs.get("messages", [])),
82
+ response=response_text,
83
+ tokens_in=tokens_in,
84
+ tokens_out=tokens_out,
85
+ latency_ms=latency_ms,
86
+ cost_usd=_calc_cost(model, tokens_in or 0, tokens_out or 0) if tokens_in else None,
87
+ session_id=self._config.session_id,
88
+ user_id=self._config.user_id,
89
+ app_name=self._config.app_name,
90
+ error=error,
91
+ )
92
+
93
+ def __getattr__(self, name):
94
+ return getattr(self._completions, name)
95
+
96
+
97
+ # ── Anthropic wrappers ────────────────────────────────────────────────────────
98
+
99
+ class WrappedAnthropic:
100
+ def __init__(self, client, audit: AuditClient, config: AuditConfig):
101
+ self._client = client
102
+ self.messages = WrappedAnthropicMessages(client.messages, audit, config)
103
+
104
+ def __getattr__(self, name):
105
+ return getattr(self._client, name)
106
+
107
+
108
+ class WrappedAnthropicMessages:
109
+ def __init__(self, messages, audit: AuditClient, config: AuditConfig):
110
+ self._messages = messages
111
+ self._audit = audit
112
+ self._config = config
113
+
114
+ def create(self, **kwargs):
115
+ start = time.time()
116
+ error = None
117
+ response = None
118
+ try:
119
+ response = self._messages.create(**kwargs)
120
+ return response
121
+ except Exception as e:
122
+ error = str(e)
123
+ raise
124
+ finally:
125
+ latency_ms = (time.time() - start) * 1000
126
+ model = kwargs.get("model", "unknown")
127
+ tokens_in = tokens_out = None
128
+ response_text = None
129
+
130
+ if response:
131
+ if hasattr(response, "usage"):
132
+ tokens_in = response.usage.input_tokens
133
+ tokens_out = response.usage.output_tokens
134
+ if hasattr(response, "content") and response.content:
135
+ response_text = response.content[0].text
136
+
137
+ self._audit.log(
138
+ provider="anthropic",
139
+ model=model,
140
+ prompt=None if self._config.redact_prompts else json.dumps(kwargs.get("messages", [])),
141
+ response=response_text,
142
+ tokens_in=tokens_in,
143
+ tokens_out=tokens_out,
144
+ latency_ms=latency_ms,
145
+ cost_usd=_calc_cost(model, tokens_in or 0, tokens_out or 0) if tokens_in else None,
146
+ session_id=self._config.session_id,
147
+ user_id=self._config.user_id,
148
+ app_name=self._config.app_name,
149
+ error=error,
150
+ )
151
+
152
+ def __getattr__(self, name):
153
+ return getattr(self._messages, name)
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-audit-logger
3
+ Version: 0.1.0
4
+ Summary: Drop-in audit logging for LLM API calls (OpenAI, Anthropic)
5
+ Project-URL: Source, https://github.com/elliottj06/ai-audit-logger
6
+ Project-URL: Bug Tracker, https://github.com/elliottj06/ai-audit-logger/issues
7
+ Keywords: llm openai anthropic audit logging ai compliance observability
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: System :: Logging
13
+ Classifier: Intended Audience :: Developers
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: httpx>=0.24.0
17
+ Provides-Extra: openai
18
+ Requires-Dist: openai>=1.0.0; extra == "openai"
19
+ Provides-Extra: anthropic
20
+ Requires-Dist: anthropic>=0.20.0; extra == "anthropic"
21
+ Provides-Extra: all
22
+ Requires-Dist: openai>=1.0.0; extra == "all"
23
+ Requires-Dist: anthropic>=0.20.0; extra == "all"
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: keywords
28
+ Dynamic: project-url
29
+ Dynamic: provides-extra
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
33
+
34
+ # AI Audit Logger
35
+
36
+ ![CI](https://github.com/elliottj06/ai-audit-logger/actions/workflows/ci.yml/badge.svg)
37
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
38
+ ![License](https://img.shields.io/badge/license-MIT-green)
39
+
40
+ A self-hosted audit logger for LLM API calls. One line of code captures every OpenAI and Anthropic call — prompts, responses, tokens, cost, latency — and stores it in a queryable database.
41
+
42
+ Built for teams that need visibility into their AI usage for **debugging**, **cost control**, and **compliance** (EU AI Act, SOC 2).
43
+
44
+ ![AI Audit Logger web dashboard showing total calls, cost, latency, token usage, per-model charts and a filterable table of recent calls](docs/images/dashboard.png)
45
+
46
+ ---
47
+
48
+ ## How it works
49
+
50
+ ```
51
+ Your App → SDK (wrap your client) → Ingest API → Postgres
52
+ ↓
53
+ CLI / Dashboard / Export
54
+ ```
55
+
56
+ The SDK is a thin wrapper around your existing OpenAI or Anthropic client. It intercepts every call, records it asynchronously (never blocks your app), and ships it to the server.
57
+
58
+ ---
59
+
60
+ ## Quickstart
61
+
62
+ ### 1. Start the server
63
+
64
+ ```bash
65
+ git clone https://github.com/elliottj06/ai-audit-logger
66
+ cd ai-audit-logger
67
+ docker compose up -d
68
+ ```
69
+
70
+ ### 2. Wrap your client — one line change
71
+
72
+ **OpenAI:**
73
+ ```python
74
+ from openai import OpenAI
75
+ from ai_audit_logger import wrap
76
+
77
+ client = wrap(OpenAI())
78
+
79
+ # Every call now logged automatically — no other changes needed
80
+ response = client.chat.completions.create(
81
+ model="gpt-4o",
82
+ messages=[{"role": "user", "content": "Hello"}]
83
+ )
84
+ ```
85
+
86
+ **Anthropic:**
87
+ ```python
88
+ from anthropic import Anthropic
89
+ from ai_audit_logger import wrap
90
+
91
+ client = wrap(Anthropic())
92
+
93
+ response = client.messages.create(
94
+ model="claude-sonnet-4-6",
95
+ max_tokens=1024,
96
+ messages=[{"role": "user", "content": "Hello"}]
97
+ )
98
+ ```
99
+
100
+ ### 3. Query your logs
101
+
102
+ ```bash
103
+ pip install -r cli/requirements.txt
104
+
105
+ python cli/main.py tail # recent calls
106
+ python cli/main.py stats # cost + usage summary
107
+ ```
108
+
109
+ **`tail` output:**
110
+ ```
111
+ Showing 3 of 3 entries
112
+
113
+ Timestamp Provider Model Tokens Cost Latency
114
+ ------------------- ----------- ---------------------------- ------------ ------------- ---------
115
+ 2026-11-04 14:23:01 openai gpt-4o 18→32 $0.00057 843ms
116
+ 2026-11-04 14:23:02 anthropic claude-sonnet-4-6 20→41 $0.00068 1204ms
117
+ 2026-11-04 14:23:03 openai gpt-4o-mini 0→0 n/a 120ms ERROR
118
+ ```
119
+
120
+ **`stats` output:**
121
+
122
+ ![Output of the stats command: total calls, total cost, tokens in and out, average latency, and a per-model breakdown of calls and cost](docs/images/cli-stats.png)
123
+
124
+ ### 4. Open the dashboard
125
+
126
+ With the server running, open **http://localhost:8000** for the web dashboard: summary cards, calls and cost by model, and a filterable table of recent calls (click any row for full detail).
127
+
128
+ ---
129
+
130
+ ## Try it without API keys
131
+
132
+ Run the included example to send simulated log entries and see the CLI in action:
133
+
134
+ ```bash
135
+ python examples/basic_usage.py
136
+ python cli/main.py tail
137
+ python cli/main.py stats
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Configuration
143
+
144
+ ```python
145
+ from ai_audit_logger import configure
146
+
147
+ configure(
148
+ server_url="http://localhost:8000", # your server
149
+ app_name="my-chatbot", # tag logs by app
150
+ user_id="user-123", # tie logs to a user
151
+ session_id="session-abc", # group calls into sessions
152
+ redact_prompts=False, # True = omit prompt text from logs
153
+ enabled=True,
154
+ )
155
+ ```
156
+
157
+ | Env var | Default | Description |
158
+ |---|---|---|
159
+ | `AUDIT_SERVER_URL` | `http://localhost:8000` | Ingest server URL |
160
+ | `AUDIT_APP_NAME` | (none) | Tag logs with your app name |
161
+ | `AUDIT_ENABLED` | `true` | Set `false` to disable without code changes |
162
+ | `AUDIT_REDACT_PROMPTS` | `false` | Omit prompt/response text (privacy mode) |
163
+
164
+ ---
165
+
166
+ ## API
167
+
168
+ | Endpoint | Method | Description |
169
+ |---|---|---|
170
+ | `/api/v1/log` | POST | Ingest a log entry |
171
+ | `/api/v1/logs` | GET | Query logs (filter by provider, model, user, app, session) |
172
+ | `/api/v1/logs/stats` | GET | Aggregated stats — total calls, cost, tokens, latency |
173
+ | `/health` | GET | Health check |
174
+
175
+ ---
176
+
177
+ ## Project structure
178
+
179
+ ```
180
+ ai-audit-logger/
181
+ ├── server/ # FastAPI ingest + query API
182
+ │ ├── main.py
183
+ │ ├── models.py # SQLAlchemy LogEntry model
184
+ │ ├── db.py # Postgres connection
185
+ │ ├── routes/
186
+ │ │ ├── ingest.py # POST /api/v1/log
187
+ │ │ └── query.py # GET /api/v1/logs + /stats
188
+ │ └── static/index.html # Web dashboard (served at /)
189
+ ├── sdk/python/ # Python SDK
190
+ │ └── ai_audit_logger/
191
+ │ ├── __init__.py # wrap() + configure()
192
+ │ ├── wrapper.py # OpenAI + Anthropic wrappers
193
+ │ ├── client.py # Fire-and-forget HTTP sender
194
+ │ └── config.py # AuditConfig dataclass
195
+ ├── cli/
196
+ │ └── main.py # tail / stats / get commands
197
+ ├── examples/
198
+ │ └── basic_usage.py # Try it without API keys
199
+ └── docker-compose.yml
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Roadmap
205
+
206
+ - [ ] TypeScript / Node.js SDK
207
+ - [x] Web dashboard with cost charts
208
+ - [ ] LangChain integration
209
+ - [ ] Publish to PyPI (`pip install ai-audit-logger`)
210
+ - [ ] Tamper-evident log hashing for compliance exports
211
+ - [ ] Slack / email alerts on cost spikes
212
+
213
+ ---
214
+
215
+ ## License
216
+
217
+ MIT
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ ai_audit_logger/__init__.py
5
+ ai_audit_logger/client.py
6
+ ai_audit_logger/config.py
7
+ ai_audit_logger/wrapper.py
8
+ ai_audit_logger.egg-info/PKG-INFO
9
+ ai_audit_logger.egg-info/SOURCES.txt
10
+ ai_audit_logger.egg-info/dependency_links.txt
11
+ ai_audit_logger.egg-info/requires.txt
12
+ ai_audit_logger.egg-info/top_level.txt
@@ -0,0 +1,11 @@
1
+ httpx>=0.24.0
2
+
3
+ [all]
4
+ openai>=1.0.0
5
+ anthropic>=0.20.0
6
+
7
+ [anthropic]
8
+ anthropic>=0.20.0
9
+
10
+ [openai]
11
+ openai>=1.0.0
@@ -0,0 +1 @@
1
+ ai_audit_logger
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,38 @@
1
+ from setuptools import setup, find_packages
2
+ import os
3
+
4
+ try:
5
+ readme = open(os.path.join(os.path.dirname(__file__), "../../README.md"), encoding="utf-8").read()
6
+ except FileNotFoundError:
7
+ readme = "Drop-in audit logging for LLM API calls (OpenAI, Anthropic)"
8
+
9
+ setup(
10
+ name="ai-audit-logger",
11
+ version="0.1.0",
12
+ description="Drop-in audit logging for LLM API calls (OpenAI, Anthropic)",
13
+ long_description=readme,
14
+ long_description_content_type="text/markdown",
15
+ packages=find_packages(),
16
+ python_requires=">=3.9",
17
+ install_requires=[
18
+ "httpx>=0.24.0",
19
+ ],
20
+ extras_require={
21
+ "openai": ["openai>=1.0.0"],
22
+ "anthropic": ["anthropic>=0.20.0"],
23
+ "all": ["openai>=1.0.0", "anthropic>=0.20.0"],
24
+ },
25
+ classifiers=[
26
+ "Programming Language :: Python :: 3",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ "Topic :: System :: Logging",
31
+ "Intended Audience :: Developers",
32
+ ],
33
+ keywords="llm openai anthropic audit logging ai compliance observability",
34
+ project_urls={
35
+ "Source": "https://github.com/elliottj06/ai-audit-logger",
36
+ "Bug Tracker": "https://github.com/elliottj06/ai-audit-logger/issues",
37
+ },
38
+ )