agent-forensics 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.
- agent_forensics-0.1.0/LICENSE +21 -0
- agent_forensics-0.1.0/PKG-INFO +186 -0
- agent_forensics-0.1.0/README.md +155 -0
- agent_forensics-0.1.0/agent_forensics/__init__.py +38 -0
- agent_forensics-0.1.0/agent_forensics/core.py +183 -0
- agent_forensics-0.1.0/agent_forensics/dashboard.py +267 -0
- agent_forensics-0.1.0/agent_forensics/integrations/__init__.py +0 -0
- agent_forensics-0.1.0/agent_forensics/integrations/crewai.py +108 -0
- agent_forensics-0.1.0/agent_forensics/integrations/langchain.py +162 -0
- agent_forensics-0.1.0/agent_forensics/integrations/openai_agents.py +180 -0
- agent_forensics-0.1.0/agent_forensics/report.py +423 -0
- agent_forensics-0.1.0/agent_forensics/store.py +126 -0
- agent_forensics-0.1.0/agent_forensics.egg-info/PKG-INFO +186 -0
- agent_forensics-0.1.0/agent_forensics.egg-info/SOURCES.txt +17 -0
- agent_forensics-0.1.0/agent_forensics.egg-info/dependency_links.txt +1 -0
- agent_forensics-0.1.0/agent_forensics.egg-info/requires.txt +18 -0
- agent_forensics-0.1.0/agent_forensics.egg-info/top_level.txt +1 -0
- agent_forensics-0.1.0/pyproject.toml +35 -0
- agent_forensics-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ilya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-forensics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Black box for AI agents — capture decisions, generate forensic reports for EU AI Act compliance
|
|
5
|
+
Author: ilya
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ilya/agent-forensics
|
|
8
|
+
Keywords: ai,agent,forensics,compliance,eu-ai-act,observability
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Provides-Extra: langchain
|
|
18
|
+
Requires-Dist: langchain-core>=0.3; extra == "langchain"
|
|
19
|
+
Provides-Extra: openai-agents
|
|
20
|
+
Requires-Dist: openai-agents>=0.1; extra == "openai-agents"
|
|
21
|
+
Provides-Extra: crewai
|
|
22
|
+
Requires-Dist: crewai>=0.80; extra == "crewai"
|
|
23
|
+
Provides-Extra: pdf
|
|
24
|
+
Requires-Dist: fpdf2>=2.8; extra == "pdf"
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: langchain-core>=0.3; extra == "all"
|
|
27
|
+
Requires-Dist: openai-agents>=0.1; extra == "all"
|
|
28
|
+
Requires-Dist: crewai>=0.80; extra == "all"
|
|
29
|
+
Requires-Dist: fpdf2>=2.8; extra == "all"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Agent Forensics
|
|
33
|
+
|
|
34
|
+
**Black box for AI agents.** Capture every decision, generate forensic reports for EU AI Act compliance.
|
|
35
|
+
|
|
36
|
+
When an AI agent makes a wrong purchase, leaks data, or fails silently — you need to know **why**. Agent Forensics records every decision point, tool call, and LLM interaction, then reconstructs the causal chain when things go wrong.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
## Why
|
|
41
|
+
|
|
42
|
+
- **EU AI Act** (Aug 2026): High-risk AI systems must provide decision traceability. Fines up to €35M or 7% of global revenue.
|
|
43
|
+
- **AI agents are already causing incidents**: Meta Sev-1 data leak (Mar 2026), unauthorized purchases, fabricated customer responses.
|
|
44
|
+
- **No existing tool** reconstructs the *why* behind agent failures. Monitoring tools watch in real-time. Forensics analyzes after the fact.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
### From PyPI
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install agent-forensics # Core only (manual recording)
|
|
52
|
+
pip install agent-forensics[langchain] # + LangChain integration
|
|
53
|
+
pip install agent-forensics[openai-agents] # + OpenAI Agents SDK
|
|
54
|
+
pip install agent-forensics[crewai] # + CrewAI
|
|
55
|
+
pip install agent-forensics[all] # Everything
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### From Source
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/ilflow4592/agent-forensics.git
|
|
62
|
+
cd agent-forensics
|
|
63
|
+
pip install -e ".[all]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
### Manual Recording (Framework-Agnostic)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from agent_forensics import Forensics
|
|
72
|
+
|
|
73
|
+
f = Forensics(session="order-123", agent="shopping-agent")
|
|
74
|
+
|
|
75
|
+
f.decision("search_products", input={"query": "mouse"}, reasoning="User requested product search")
|
|
76
|
+
f.tool_call("search_api", input={"q": "mouse"}, output={"results": [...]})
|
|
77
|
+
f.error("purchase_failed", output={"reason": "Out of stock"})
|
|
78
|
+
f.finish("Could not complete purchase due to stock unavailability.")
|
|
79
|
+
|
|
80
|
+
print(f.report()) # Markdown forensic report
|
|
81
|
+
f.save_pdf() # PDF report
|
|
82
|
+
f.dashboard(port=8080) # Web dashboard at http://localhost:8080
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### LangChain — One Line
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from agent_forensics import Forensics
|
|
89
|
+
|
|
90
|
+
f = Forensics(session="order-123")
|
|
91
|
+
agent.invoke({"input": "..."}, config={"callbacks": [f.langchain()]})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### OpenAI Agents SDK — One Line
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from agent_forensics import Forensics
|
|
98
|
+
from agents import Agent, Runner
|
|
99
|
+
|
|
100
|
+
f = Forensics(session="order-123")
|
|
101
|
+
agent = Agent(name="shopper", tools=[...], hooks=f.openai_agents())
|
|
102
|
+
result = await Runner.run(agent, "Buy a wireless mouse")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### CrewAI — Two Lines
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from agent_forensics import Forensics
|
|
109
|
+
|
|
110
|
+
f = Forensics(session="order-123")
|
|
111
|
+
hooks = f.crewai()
|
|
112
|
+
agent = Agent(role="shopper", step_callback=hooks.step_callback)
|
|
113
|
+
task = Task(description="...", agent=agent, callback=hooks.task_callback)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## What You Get
|
|
117
|
+
|
|
118
|
+
### Forensic Report
|
|
119
|
+
|
|
120
|
+
Every report includes:
|
|
121
|
+
|
|
122
|
+
- **Timeline** — Chronological record of all agent actions
|
|
123
|
+
- **Decision Chain** — Each decision with its reasoning
|
|
124
|
+
- **Incident Analysis** — Automatic error detection + root cause chain
|
|
125
|
+
- **Causal Chain** — `Decision → Tool Call → Result → Error` trace
|
|
126
|
+
- **Compliance Notes** — EU AI Act Article 14 (Human Oversight) support
|
|
127
|
+
|
|
128
|
+
### Web Dashboard
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python -c "from agent_forensics import Forensics; Forensics(db_path='forensics.db').dashboard()"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Dark-themed dashboard with session selector, color-coded timeline, and causal chain visualization.
|
|
135
|
+
|
|
136
|
+

|
|
137
|
+
|
|
138
|
+

|
|
139
|
+
|
|
140
|
+
### Output Formats
|
|
141
|
+
|
|
142
|
+
- **Markdown** — `f.save_markdown()`
|
|
143
|
+
- **PDF** — `f.save_pdf()` (requires `pip install agent-forensics[pdf]`)
|
|
144
|
+
- **Web Dashboard** — `f.dashboard(port=8080)`
|
|
145
|
+
- **Raw Events** — `f.events()` returns `list[Event]`
|
|
146
|
+
|
|
147
|
+
## Architecture
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Your Agent (any framework)
|
|
151
|
+
│
|
|
152
|
+
│ Callback / Hook (1 line of code)
|
|
153
|
+
▼
|
|
154
|
+
┌──────────────────────┐
|
|
155
|
+
│ Forensics Collector │ Captures every LLM call, tool use, decision
|
|
156
|
+
├──────────────────────┤
|
|
157
|
+
│ Event Store (SQLite) │ Immutable event log
|
|
158
|
+
├──────────────────────┤
|
|
159
|
+
│ Report Generator │ Markdown / PDF / Dashboard
|
|
160
|
+
└──────────────────────┘
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Supported Frameworks
|
|
164
|
+
|
|
165
|
+
| Framework | Integration | Method |
|
|
166
|
+
|-----------|------------|--------|
|
|
167
|
+
| **Any** (manual) | `f.decision()`, `f.tool_call()`, `f.error()` | Direct API |
|
|
168
|
+
| **LangChain / LangGraph** | `f.langchain()` | Callback Handler |
|
|
169
|
+
| **OpenAI Agents SDK** | `f.openai_agents()` | AgentHooks |
|
|
170
|
+
| **CrewAI** | `f.crewai()` | step_callback / task_callback |
|
|
171
|
+
|
|
172
|
+
## Event Types
|
|
173
|
+
|
|
174
|
+
| Type | When | Why It Matters |
|
|
175
|
+
|------|------|---------------|
|
|
176
|
+
| `decision` | Agent decides what to do next | Core of forensics — the *why* |
|
|
177
|
+
| `tool_call_start` | Tool execution begins | What tool, what input |
|
|
178
|
+
| `tool_call_end` | Tool returns result | What came back |
|
|
179
|
+
| `llm_call_start` | LLM request sent | What was asked |
|
|
180
|
+
| `llm_call_end` | LLM response received | What was answered |
|
|
181
|
+
| `error` | Something went wrong | Incident detection |
|
|
182
|
+
| `final_decision` | Agent produces final output | End of decision chain |
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Agent Forensics
|
|
2
|
+
|
|
3
|
+
**Black box for AI agents.** Capture every decision, generate forensic reports for EU AI Act compliance.
|
|
4
|
+
|
|
5
|
+
When an AI agent makes a wrong purchase, leaks data, or fails silently — you need to know **why**. Agent Forensics records every decision point, tool call, and LLM interaction, then reconstructs the causal chain when things go wrong.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
- **EU AI Act** (Aug 2026): High-risk AI systems must provide decision traceability. Fines up to €35M or 7% of global revenue.
|
|
12
|
+
- **AI agents are already causing incidents**: Meta Sev-1 data leak (Mar 2026), unauthorized purchases, fabricated customer responses.
|
|
13
|
+
- **No existing tool** reconstructs the *why* behind agent failures. Monitoring tools watch in real-time. Forensics analyzes after the fact.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
### From PyPI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install agent-forensics # Core only (manual recording)
|
|
21
|
+
pip install agent-forensics[langchain] # + LangChain integration
|
|
22
|
+
pip install agent-forensics[openai-agents] # + OpenAI Agents SDK
|
|
23
|
+
pip install agent-forensics[crewai] # + CrewAI
|
|
24
|
+
pip install agent-forensics[all] # Everything
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### From Source
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/ilflow4592/agent-forensics.git
|
|
31
|
+
cd agent-forensics
|
|
32
|
+
pip install -e ".[all]"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Manual Recording (Framework-Agnostic)
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from agent_forensics import Forensics
|
|
41
|
+
|
|
42
|
+
f = Forensics(session="order-123", agent="shopping-agent")
|
|
43
|
+
|
|
44
|
+
f.decision("search_products", input={"query": "mouse"}, reasoning="User requested product search")
|
|
45
|
+
f.tool_call("search_api", input={"q": "mouse"}, output={"results": [...]})
|
|
46
|
+
f.error("purchase_failed", output={"reason": "Out of stock"})
|
|
47
|
+
f.finish("Could not complete purchase due to stock unavailability.")
|
|
48
|
+
|
|
49
|
+
print(f.report()) # Markdown forensic report
|
|
50
|
+
f.save_pdf() # PDF report
|
|
51
|
+
f.dashboard(port=8080) # Web dashboard at http://localhost:8080
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### LangChain — One Line
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from agent_forensics import Forensics
|
|
58
|
+
|
|
59
|
+
f = Forensics(session="order-123")
|
|
60
|
+
agent.invoke({"input": "..."}, config={"callbacks": [f.langchain()]})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### OpenAI Agents SDK — One Line
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from agent_forensics import Forensics
|
|
67
|
+
from agents import Agent, Runner
|
|
68
|
+
|
|
69
|
+
f = Forensics(session="order-123")
|
|
70
|
+
agent = Agent(name="shopper", tools=[...], hooks=f.openai_agents())
|
|
71
|
+
result = await Runner.run(agent, "Buy a wireless mouse")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### CrewAI — Two Lines
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from agent_forensics import Forensics
|
|
78
|
+
|
|
79
|
+
f = Forensics(session="order-123")
|
|
80
|
+
hooks = f.crewai()
|
|
81
|
+
agent = Agent(role="shopper", step_callback=hooks.step_callback)
|
|
82
|
+
task = Task(description="...", agent=agent, callback=hooks.task_callback)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## What You Get
|
|
86
|
+
|
|
87
|
+
### Forensic Report
|
|
88
|
+
|
|
89
|
+
Every report includes:
|
|
90
|
+
|
|
91
|
+
- **Timeline** — Chronological record of all agent actions
|
|
92
|
+
- **Decision Chain** — Each decision with its reasoning
|
|
93
|
+
- **Incident Analysis** — Automatic error detection + root cause chain
|
|
94
|
+
- **Causal Chain** — `Decision → Tool Call → Result → Error` trace
|
|
95
|
+
- **Compliance Notes** — EU AI Act Article 14 (Human Oversight) support
|
|
96
|
+
|
|
97
|
+
### Web Dashboard
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python -c "from agent_forensics import Forensics; Forensics(db_path='forensics.db').dashboard()"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Dark-themed dashboard with session selector, color-coded timeline, and causal chain visualization.
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+

|
|
108
|
+
|
|
109
|
+
### Output Formats
|
|
110
|
+
|
|
111
|
+
- **Markdown** — `f.save_markdown()`
|
|
112
|
+
- **PDF** — `f.save_pdf()` (requires `pip install agent-forensics[pdf]`)
|
|
113
|
+
- **Web Dashboard** — `f.dashboard(port=8080)`
|
|
114
|
+
- **Raw Events** — `f.events()` returns `list[Event]`
|
|
115
|
+
|
|
116
|
+
## Architecture
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Your Agent (any framework)
|
|
120
|
+
│
|
|
121
|
+
│ Callback / Hook (1 line of code)
|
|
122
|
+
▼
|
|
123
|
+
┌──────────────────────┐
|
|
124
|
+
│ Forensics Collector │ Captures every LLM call, tool use, decision
|
|
125
|
+
├──────────────────────┤
|
|
126
|
+
│ Event Store (SQLite) │ Immutable event log
|
|
127
|
+
├──────────────────────┤
|
|
128
|
+
│ Report Generator │ Markdown / PDF / Dashboard
|
|
129
|
+
└──────────────────────┘
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Supported Frameworks
|
|
133
|
+
|
|
134
|
+
| Framework | Integration | Method |
|
|
135
|
+
|-----------|------------|--------|
|
|
136
|
+
| **Any** (manual) | `f.decision()`, `f.tool_call()`, `f.error()` | Direct API |
|
|
137
|
+
| **LangChain / LangGraph** | `f.langchain()` | Callback Handler |
|
|
138
|
+
| **OpenAI Agents SDK** | `f.openai_agents()` | AgentHooks |
|
|
139
|
+
| **CrewAI** | `f.crewai()` | step_callback / task_callback |
|
|
140
|
+
|
|
141
|
+
## Event Types
|
|
142
|
+
|
|
143
|
+
| Type | When | Why It Matters |
|
|
144
|
+
|------|------|---------------|
|
|
145
|
+
| `decision` | Agent decides what to do next | Core of forensics — the *why* |
|
|
146
|
+
| `tool_call_start` | Tool execution begins | What tool, what input |
|
|
147
|
+
| `tool_call_end` | Tool returns result | What came back |
|
|
148
|
+
| `llm_call_start` | LLM request sent | What was asked |
|
|
149
|
+
| `llm_call_end` | LLM response received | What was answered |
|
|
150
|
+
| `error` | Something went wrong | Incident detection |
|
|
151
|
+
| `final_decision` | Agent produces final output | End of decision chain |
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Forensics — Black box for AI agents.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from agent_forensics import Forensics
|
|
6
|
+
|
|
7
|
+
# 1. Initialize
|
|
8
|
+
f = Forensics(session="order-123", agent="shopping-agent")
|
|
9
|
+
|
|
10
|
+
# 2a. Manual recording (framework-agnostic)
|
|
11
|
+
f.decision("search_products", input={"query": "mouse"}, reasoning="User request")
|
|
12
|
+
f.tool_call("search_api", input={"q": "mouse"}, output={"results": [...]})
|
|
13
|
+
f.error("purchase_failed", output={"reason": "Out of stock"})
|
|
14
|
+
|
|
15
|
+
# 2b. LangChain auto-recording
|
|
16
|
+
agent.invoke(..., config={"callbacks": [f.langchain()]})
|
|
17
|
+
|
|
18
|
+
# 2c. OpenAI Agents SDK auto-recording
|
|
19
|
+
agent = Agent(name="...", hooks=f.openai_agents())
|
|
20
|
+
|
|
21
|
+
# 2d. CrewAI auto-recording
|
|
22
|
+
hooks = f.crewai()
|
|
23
|
+
agent = Agent(role="...", step_callback=hooks.step_callback)
|
|
24
|
+
|
|
25
|
+
# 3. Report
|
|
26
|
+
print(f.report()) # Markdown
|
|
27
|
+
f.save_markdown() # forensics-report-order-123.md
|
|
28
|
+
f.save_pdf() # forensics-report-order-123.pdf
|
|
29
|
+
|
|
30
|
+
# 4. Dashboard
|
|
31
|
+
f.dashboard(port=8080) # http://localhost:8080
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from .core import Forensics
|
|
35
|
+
from .store import Event, EventStore
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0"
|
|
38
|
+
__all__ = ["Forensics", "Event", "EventStore"]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Forensics — Main interface for AI agent forensics.
|
|
3
|
+
|
|
4
|
+
Provides all functionality through a single class.
|
|
5
|
+
Framework-agnostic, with integrations for LangChain/CrewAI/OpenAI and more.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .store import EventStore, Event, now
|
|
9
|
+
from .report import generate_report, save_report, save_pdf
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Forensics:
|
|
13
|
+
"""AI Agent Forensics — Black box + report generator."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
session: str = "default",
|
|
18
|
+
agent: str = "default-agent",
|
|
19
|
+
db_path: str = "forensics.db",
|
|
20
|
+
):
|
|
21
|
+
self.session = session
|
|
22
|
+
self.agent = agent
|
|
23
|
+
self.store = EventStore(db_path)
|
|
24
|
+
|
|
25
|
+
# -- Manual Recording API --
|
|
26
|
+
|
|
27
|
+
def decision(self, action: str, *, input: dict = None, reasoning: str = "") -> str:
|
|
28
|
+
"""Record when the agent makes a decision."""
|
|
29
|
+
return self.store.save(Event(
|
|
30
|
+
timestamp=now(),
|
|
31
|
+
event_type="decision",
|
|
32
|
+
agent_id=self.agent,
|
|
33
|
+
action=action,
|
|
34
|
+
input_data=input or {},
|
|
35
|
+
output_data={},
|
|
36
|
+
reasoning=reasoning,
|
|
37
|
+
session_id=self.session,
|
|
38
|
+
))
|
|
39
|
+
|
|
40
|
+
def tool_call(self, action: str, *, input: dict = None, output: dict = None, reasoning: str = "") -> str:
|
|
41
|
+
"""Record a tool call."""
|
|
42
|
+
# start
|
|
43
|
+
self.store.save(Event(
|
|
44
|
+
timestamp=now(),
|
|
45
|
+
event_type="tool_call_start",
|
|
46
|
+
agent_id=self.agent,
|
|
47
|
+
action=f"tool:{action}",
|
|
48
|
+
input_data=input or {},
|
|
49
|
+
output_data={},
|
|
50
|
+
reasoning=reasoning or f"Calling tool: {action}",
|
|
51
|
+
session_id=self.session,
|
|
52
|
+
))
|
|
53
|
+
# end
|
|
54
|
+
return self.store.save(Event(
|
|
55
|
+
timestamp=now(),
|
|
56
|
+
event_type="tool_call_end",
|
|
57
|
+
agent_id=self.agent,
|
|
58
|
+
action="tool_result",
|
|
59
|
+
input_data={},
|
|
60
|
+
output_data=output or {},
|
|
61
|
+
reasoning="Tool execution completed",
|
|
62
|
+
session_id=self.session,
|
|
63
|
+
))
|
|
64
|
+
|
|
65
|
+
def llm_call(self, *, input: dict = None, output: str = "", reasoning: str = "") -> str:
|
|
66
|
+
"""Record an LLM call."""
|
|
67
|
+
self.store.save(Event(
|
|
68
|
+
timestamp=now(),
|
|
69
|
+
event_type="llm_call_start",
|
|
70
|
+
agent_id=self.agent,
|
|
71
|
+
action="llm_call",
|
|
72
|
+
input_data=input or {},
|
|
73
|
+
output_data={},
|
|
74
|
+
reasoning=reasoning or "LLM call",
|
|
75
|
+
session_id=self.session,
|
|
76
|
+
))
|
|
77
|
+
return self.store.save(Event(
|
|
78
|
+
timestamp=now(),
|
|
79
|
+
event_type="llm_call_end",
|
|
80
|
+
agent_id=self.agent,
|
|
81
|
+
action="llm_response",
|
|
82
|
+
input_data={},
|
|
83
|
+
output_data={"response": output},
|
|
84
|
+
reasoning="LLM response",
|
|
85
|
+
session_id=self.session,
|
|
86
|
+
))
|
|
87
|
+
|
|
88
|
+
def error(self, action: str, *, output: dict = None, reasoning: str = "") -> str:
|
|
89
|
+
"""Record an error/incident."""
|
|
90
|
+
return self.store.save(Event(
|
|
91
|
+
timestamp=now(),
|
|
92
|
+
event_type="error",
|
|
93
|
+
agent_id=self.agent,
|
|
94
|
+
action=action,
|
|
95
|
+
input_data={},
|
|
96
|
+
output_data=output or {},
|
|
97
|
+
reasoning=reasoning or f"Error occurred: {action}",
|
|
98
|
+
session_id=self.session,
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
def finish(self, output: str = "", *, reasoning: str = "") -> str:
|
|
102
|
+
"""Record the agent's final result."""
|
|
103
|
+
return self.store.save(Event(
|
|
104
|
+
timestamp=now(),
|
|
105
|
+
event_type="final_decision",
|
|
106
|
+
agent_id=self.agent,
|
|
107
|
+
action="agent_finish",
|
|
108
|
+
input_data={},
|
|
109
|
+
output_data={"response": output},
|
|
110
|
+
reasoning=reasoning or "Agent determined final answer",
|
|
111
|
+
session_id=self.session,
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
def record(self, event_type: str, action: str, *, input: dict = None, output: dict = None, reasoning: str = "") -> str:
|
|
115
|
+
"""Record a generic event."""
|
|
116
|
+
return self.store.save(Event(
|
|
117
|
+
timestamp=now(),
|
|
118
|
+
event_type=event_type,
|
|
119
|
+
agent_id=self.agent,
|
|
120
|
+
action=action,
|
|
121
|
+
input_data=input or {},
|
|
122
|
+
output_data=output or {},
|
|
123
|
+
reasoning=reasoning,
|
|
124
|
+
session_id=self.session,
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
# -- Report API --
|
|
128
|
+
|
|
129
|
+
def report(self) -> str:
|
|
130
|
+
"""Return the Markdown forensics report as a string."""
|
|
131
|
+
return generate_report(self.store, self.session)
|
|
132
|
+
|
|
133
|
+
def save_markdown(self, path: str = None) -> str:
|
|
134
|
+
"""Save the Markdown report to a file."""
|
|
135
|
+
return save_report(self.store, self.session, output_dir=path or ".")
|
|
136
|
+
|
|
137
|
+
def save_pdf(self, path: str = None) -> str:
|
|
138
|
+
"""Save the PDF report to a file."""
|
|
139
|
+
return save_pdf(self.store, self.session, output_dir=path or ".")
|
|
140
|
+
|
|
141
|
+
def events(self) -> list[Event]:
|
|
142
|
+
"""Return all events for the current session."""
|
|
143
|
+
return self.store.get_session_events(self.session)
|
|
144
|
+
|
|
145
|
+
def sessions(self) -> list[str]:
|
|
146
|
+
"""Return a list of all sessions."""
|
|
147
|
+
return self.store.get_all_sessions()
|
|
148
|
+
|
|
149
|
+
# -- Framework Integrations --
|
|
150
|
+
|
|
151
|
+
def langchain(self):
|
|
152
|
+
"""Return a LangChain callback handler. agent.invoke(..., config={"callbacks": [f.langchain()]})"""
|
|
153
|
+
from .integrations.langchain import ForensicsCollector
|
|
154
|
+
return ForensicsCollector(
|
|
155
|
+
store=self.store,
|
|
156
|
+
session_id=self.session,
|
|
157
|
+
agent_id=self.agent,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def openai_agents(self):
|
|
161
|
+
"""Return OpenAI Agents SDK hooks. Agent(hooks=f.openai_agents())"""
|
|
162
|
+
from .integrations.openai_agents import ForensicsAgentHooks
|
|
163
|
+
return ForensicsAgentHooks(
|
|
164
|
+
store=self.store,
|
|
165
|
+
session_id=self.session,
|
|
166
|
+
agent_id=self.agent,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def crewai(self):
|
|
170
|
+
"""Return CrewAI callback collection. Agent(step_callback=hooks.step_callback)"""
|
|
171
|
+
from .integrations.crewai import ForensicsCrewAIHooks
|
|
172
|
+
return ForensicsCrewAIHooks(
|
|
173
|
+
store=self.store,
|
|
174
|
+
session_id=self.session,
|
|
175
|
+
agent_id=self.agent,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# -- Dashboard --
|
|
179
|
+
|
|
180
|
+
def dashboard(self, port: int = 8080):
|
|
181
|
+
"""Launch the web dashboard."""
|
|
182
|
+
from .dashboard import run_dashboard
|
|
183
|
+
run_dashboard(self.store, port=port)
|