agentlens-xray 0.3.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.
- agentlens_xray-0.3.0/.gitignore +42 -0
- agentlens_xray-0.3.0/LICENSE +21 -0
- agentlens_xray-0.3.0/PKG-INFO +304 -0
- agentlens_xray-0.3.0/README.md +251 -0
- agentlens_xray-0.3.0/examples/basic_agent.py +66 -0
- agentlens_xray-0.3.0/examples/langchain_agent.py +89 -0
- agentlens_xray-0.3.0/examples/raw_openai_client.py +78 -0
- agentlens_xray-0.3.0/examples/trip_planner_agent.py +347 -0
- agentlens_xray-0.3.0/frontend/.gitignore +24 -0
- agentlens_xray-0.3.0/frontend/README.md +73 -0
- agentlens_xray-0.3.0/frontend/eslint.config.js +23 -0
- agentlens_xray-0.3.0/frontend/index.html +16 -0
- agentlens_xray-0.3.0/frontend/package-lock.json +3895 -0
- agentlens_xray-0.3.0/frontend/package.json +38 -0
- agentlens_xray-0.3.0/frontend/public/favicon.svg +9 -0
- agentlens_xray-0.3.0/frontend/src/App.tsx +21 -0
- agentlens_xray-0.3.0/frontend/src/api/client.ts +78 -0
- agentlens_xray-0.3.0/frontend/src/components/DurationBar.tsx +17 -0
- agentlens_xray-0.3.0/frontend/src/components/JsonViewer.tsx +34 -0
- agentlens_xray-0.3.0/frontend/src/components/Layout.tsx +27 -0
- agentlens_xray-0.3.0/frontend/src/components/Modal.tsx +43 -0
- agentlens_xray-0.3.0/frontend/src/components/ReplaySpanDiff.tsx +93 -0
- agentlens_xray-0.3.0/frontend/src/components/ReplayView.tsx +163 -0
- agentlens_xray-0.3.0/frontend/src/components/SpanDetailPanel.tsx +96 -0
- agentlens_xray-0.3.0/frontend/src/components/SpanEditor.tsx +145 -0
- agentlens_xray-0.3.0/frontend/src/components/SpanKindIcon.tsx +14 -0
- agentlens_xray-0.3.0/frontend/src/components/SpanTimeline.tsx +41 -0
- agentlens_xray-0.3.0/frontend/src/components/SpanTimelineItem.tsx +64 -0
- agentlens_xray-0.3.0/frontend/src/components/StatusBadge.tsx +18 -0
- agentlens_xray-0.3.0/frontend/src/components/TraceDetail.tsx +163 -0
- agentlens_xray-0.3.0/frontend/src/components/TraceList.tsx +89 -0
- agentlens_xray-0.3.0/frontend/src/components/TraceListRow.tsx +37 -0
- agentlens_xray-0.3.0/frontend/src/context/TraceDetailContext.tsx +49 -0
- agentlens_xray-0.3.0/frontend/src/hooks/useKeyboardNav.ts +56 -0
- agentlens_xray-0.3.0/frontend/src/hooks/useReplay.ts +29 -0
- agentlens_xray-0.3.0/frontend/src/hooks/useTrace.ts +29 -0
- agentlens_xray-0.3.0/frontend/src/hooks/useTraces.ts +39 -0
- agentlens_xray-0.3.0/frontend/src/index.css +26 -0
- agentlens_xray-0.3.0/frontend/src/lib/format.ts +37 -0
- agentlens_xray-0.3.0/frontend/src/lib/spans.ts +48 -0
- agentlens_xray-0.3.0/frontend/src/main.tsx +10 -0
- agentlens_xray-0.3.0/frontend/src/types/index.ts +76 -0
- agentlens_xray-0.3.0/frontend/tsconfig.app.json +28 -0
- agentlens_xray-0.3.0/frontend/tsconfig.json +7 -0
- agentlens_xray-0.3.0/frontend/tsconfig.node.json +26 -0
- agentlens_xray-0.3.0/frontend/vite.config.ts +24 -0
- agentlens_xray-0.3.0/pyproject.toml +89 -0
- agentlens_xray-0.3.0/src/agentlens/__init__.py +35 -0
- agentlens_xray-0.3.0/src/agentlens/cli.py +69 -0
- agentlens_xray-0.3.0/src/agentlens/integrations/__init__.py +34 -0
- agentlens_xray-0.3.0/src/agentlens/integrations/_base.py +141 -0
- agentlens_xray-0.3.0/src/agentlens/integrations/clients.py +253 -0
- agentlens_xray-0.3.0/src/agentlens/integrations/crewai.py +85 -0
- agentlens_xray-0.3.0/src/agentlens/integrations/langchain.py +320 -0
- agentlens_xray-0.3.0/src/agentlens/integrations/openai_agents.py +177 -0
- agentlens_xray-0.3.0/src/agentlens/py.typed +0 -0
- agentlens_xray-0.3.0/src/agentlens/replay/__init__.py +0 -0
- agentlens_xray-0.3.0/src/agentlens/replay/context.py +84 -0
- agentlens_xray-0.3.0/src/agentlens/replay/engine.py +76 -0
- agentlens_xray-0.3.0/src/agentlens/replay/live.py +113 -0
- agentlens_xray-0.3.0/src/agentlens/replay/span_replay.py +303 -0
- agentlens_xray-0.3.0/src/agentlens/sdk/__init__.py +0 -0
- agentlens_xray-0.3.0/src/agentlens/sdk/decorators.py +241 -0
- agentlens_xray-0.3.0/src/agentlens/sdk/models.py +105 -0
- agentlens_xray-0.3.0/src/agentlens/sdk/recorder.py +227 -0
- agentlens_xray-0.3.0/src/agentlens/sdk/tracer.py +289 -0
- agentlens_xray-0.3.0/src/agentlens/server/__init__.py +0 -0
- agentlens_xray-0.3.0/src/agentlens/server/app.py +49 -0
- agentlens_xray-0.3.0/src/agentlens/server/db.py +221 -0
- agentlens_xray-0.3.0/src/agentlens/server/routes/__init__.py +0 -0
- agentlens_xray-0.3.0/src/agentlens/server/routes/replay.py +22 -0
- agentlens_xray-0.3.0/src/agentlens/server/routes/traces.py +35 -0
- agentlens_xray-0.3.0/src/agentlens/server/static/assets/index-1on5bs8e.js +31 -0
- agentlens_xray-0.3.0/src/agentlens/server/static/assets/index-D3nyU__9.css +2 -0
- agentlens_xray-0.3.0/src/agentlens/server/static/favicon.svg +9 -0
- agentlens_xray-0.3.0/src/agentlens/server/static/index.html +17 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
*.swp
|
|
18
|
+
*.swo
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
# Node
|
|
25
|
+
frontend/node_modules/
|
|
26
|
+
frontend/dist/
|
|
27
|
+
|
|
28
|
+
# Built frontend (served by FastAPI)
|
|
29
|
+
src/agentlens/server/static/
|
|
30
|
+
|
|
31
|
+
# AgentLens data
|
|
32
|
+
.agentlens/
|
|
33
|
+
|
|
34
|
+
# Test
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
htmlcov/
|
|
38
|
+
.coverage
|
|
39
|
+
|
|
40
|
+
# Claude Code
|
|
41
|
+
.claude/
|
|
42
|
+
CLAUDE.md
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Saswat Ray
|
|
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,304 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentlens-xray
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Local-first AI agent debugger with fork & replay
|
|
5
|
+
Project-URL: Homepage, https://github.com/BugsBunnyWanders/agentlens
|
|
6
|
+
Project-URL: Repository, https://github.com/BugsBunnyWanders/agentlens
|
|
7
|
+
Project-URL: Issues, https://github.com/BugsBunnyWanders/agentlens/issues
|
|
8
|
+
Author-email: Saswat Ray <saswatray2505@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,debugging,llm,observability,replay,tracing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: aiosqlite>=0.19
|
|
25
|
+
Requires-Dist: click>=8.0
|
|
26
|
+
Requires-Dist: fastapi>=0.104
|
|
27
|
+
Requires-Dist: pydantic>=2.0
|
|
28
|
+
Requires-Dist: uvicorn[standard]>=0.24
|
|
29
|
+
Provides-Extra: all-integrations
|
|
30
|
+
Requires-Dist: anthropic>=0.20.0; extra == 'all-integrations'
|
|
31
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'all-integrations'
|
|
32
|
+
Requires-Dist: openai>=1.0.0; extra == 'all-integrations'
|
|
33
|
+
Provides-Extra: anthropic
|
|
34
|
+
Requires-Dist: anthropic>=0.20.0; extra == 'anthropic'
|
|
35
|
+
Provides-Extra: crewai
|
|
36
|
+
Requires-Dist: crewai>=0.30.0; extra == 'crewai'
|
|
37
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'crewai'
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: build; extra == 'dev'
|
|
40
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
41
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
45
|
+
Requires-Dist: twine; extra == 'dev'
|
|
46
|
+
Provides-Extra: langchain
|
|
47
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
|
|
48
|
+
Provides-Extra: openai
|
|
49
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
50
|
+
Provides-Extra: openai-agents
|
|
51
|
+
Requires-Dist: openai-agents>=0.1.0; extra == 'openai-agents'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# AgentLens
|
|
55
|
+
|
|
56
|
+
**Local-first AI agent debugger with fork & replay.**
|
|
57
|
+
|
|
58
|
+
AgentLens captures execution traces from multi-step AI agent workflows, visualizes them in a local web UI, and lets you fork a trace at any step, edit its output, and **re-execute downstream steps with real API calls** to see what would have happened differently.
|
|
59
|
+
|
|
60
|
+
Think of it as **Chrome DevTools for AI agents** — the Network tab + time-travel debugging, but for LLM agent workflows.
|
|
61
|
+
|
|
62
|
+
> **Install:** `pip install agentlens-xray` — the import name is `agentlens`.
|
|
63
|
+
|
|
64
|
+
## The Problem
|
|
65
|
+
|
|
66
|
+
AI agents are non-deterministic. When a multi-step agent fails at step 7 of 12, developers currently have no way to:
|
|
67
|
+
|
|
68
|
+
1. See exactly what happened at each step (tool calls, LLM reasoning, intermediate state)
|
|
69
|
+
2. Reproduce the failure deterministically
|
|
70
|
+
3. Test a fix by replaying from the failure point without re-running the entire chain
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install agentlens-xray
|
|
76
|
+
python examples/basic_agent.py # creates a sample trace (no API keys needed)
|
|
77
|
+
agentlens serve # opens the UI at localhost:7600
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For a real agent with OpenAI:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
export OPENAI_API_KEY="sk-..."
|
|
84
|
+
python examples/trip_planner_agent.py
|
|
85
|
+
agentlens serve
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
### 1. Instrument Your Code
|
|
91
|
+
|
|
92
|
+
Add decorators to your existing agent functions — zero logic changes:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import agentlens
|
|
96
|
+
|
|
97
|
+
@agentlens.trace(name="my_agent")
|
|
98
|
+
async def my_agent(query: str):
|
|
99
|
+
data = await fetch_data(query)
|
|
100
|
+
result = await analyze(data)
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
@agentlens.wrap_tool(name="fetch_data")
|
|
104
|
+
async def fetch_data(query: str) -> dict:
|
|
105
|
+
return await api.search(query)
|
|
106
|
+
|
|
107
|
+
@agentlens.wrap_llm(name="analyze", model="gpt-4o-mini")
|
|
108
|
+
async def analyze(data: dict) -> str:
|
|
109
|
+
response = await openai.chat.completions.create(...)
|
|
110
|
+
return response.choices[0].message.content
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
There's also a context manager API:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
async with agentlens.start_trace_async("my_agent") as t:
|
|
117
|
+
with t.span("fetch_data", kind="tool") as s:
|
|
118
|
+
data = await fetch(...)
|
|
119
|
+
s.record_output(data)
|
|
120
|
+
with t.span("analyze", kind="llm", model="gpt-4o") as s:
|
|
121
|
+
s.record_input({"messages": [...]})
|
|
122
|
+
result = await llm.complete(...)
|
|
123
|
+
s.record_output(result)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. View Traces
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
agentlens serve # Web UI at localhost:7600
|
|
130
|
+
agentlens serve --port 8080 # Custom port
|
|
131
|
+
agentlens traces # List recent traces in terminal
|
|
132
|
+
agentlens traces --last 5 # Show last 5
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 3. Fork & Replay
|
|
136
|
+
|
|
137
|
+
In the web UI:
|
|
138
|
+
|
|
139
|
+
1. Click a trace to see its span timeline
|
|
140
|
+
2. Select a span and click **Fork & Replay**
|
|
141
|
+
3. Edit the span's output in the code editor
|
|
142
|
+
4. Choose a **replay mode**
|
|
143
|
+
5. Click **Replay from here** to create a forked trace
|
|
144
|
+
6. View the side-by-side comparison with diff highlighting
|
|
145
|
+
|
|
146
|
+
### Replay Modes
|
|
147
|
+
|
|
148
|
+
| Mode | What happens | Cost | Use case |
|
|
149
|
+
|------|-------------|------|----------|
|
|
150
|
+
| **Deterministic** | Only the edited span changes. Downstream spans are marked stale. | Free | Quick data annotation, bookmarking bugs |
|
|
151
|
+
| **Live** | All downstream spans re-execute with real API calls. | Token costs | "What would the LLM say if the tool returned different data?" |
|
|
152
|
+
| **Hybrid** | LLM spans re-execute live, tool spans return recorded data. | Lower token costs | Test LLM behavior with changed context, no tool side effects |
|
|
153
|
+
|
|
154
|
+
**Example:** Your weather tool returned "sunny" but the real weather is a blizzard. Fork the weather span, change it to blizzard, select **Live** mode. The LLM re-generates the itinerary accounting for severe weather — with real API calls, producing genuinely different output.
|
|
155
|
+
|
|
156
|
+
## Framework Integrations
|
|
157
|
+
|
|
158
|
+
AgentLens works with popular frameworks out of the box — no decorators needed.
|
|
159
|
+
|
|
160
|
+
### OpenAI / Anthropic SDK (wrap your client)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pip install agentlens-xray[openai] # or agentlens-xray[anthropic]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from openai import OpenAI
|
|
168
|
+
from agentlens import wrap_openai
|
|
169
|
+
|
|
170
|
+
client = wrap_openai(OpenAI())
|
|
171
|
+
# All chat.completions.create() calls are now traced automatically
|
|
172
|
+
response = client.chat.completions.create(model="gpt-4o", messages=[...])
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### LangChain / LangGraph
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pip install agentlens-xray[langchain]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from agentlens.integrations.langchain import AgentLensCallbackHandler
|
|
183
|
+
|
|
184
|
+
with AgentLensCallbackHandler(trace_name="my_agent") as handler:
|
|
185
|
+
chain.invoke(input, config={"callbacks": [handler]})
|
|
186
|
+
# Full trace with LLM, tool, retrieval, and chain spans
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### OpenAI Agents SDK
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pip install agentlens-xray[openai-agents]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from agentlens.integrations.openai_agents import install_agentlens_tracing
|
|
197
|
+
|
|
198
|
+
install_agentlens_tracing() # One line — all agent runs traced automatically
|
|
199
|
+
result = await Runner.run(agent, input="Process this refund")
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### CrewAI
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pip install agentlens-xray[crewai]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from agentlens.integrations.crewai import CrewAIHandler
|
|
210
|
+
|
|
211
|
+
handler = CrewAIHandler(trace_name="my_crew")
|
|
212
|
+
crew = Crew(agents=[...], tasks=[...], callbacks=[handler])
|
|
213
|
+
crew.kickoff() # Crew, agent, and task spans captured
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Features
|
|
217
|
+
|
|
218
|
+
- **Zero-config tracing** — `pip install agentlens-xray` and add decorators or use framework integrations
|
|
219
|
+
- **Framework integrations** — LangChain, LangGraph, OpenAI Agents SDK, CrewAI, raw SDKs
|
|
220
|
+
- **Live replay** — re-execute downstream spans with real API calls after editing a span's output
|
|
221
|
+
- **Hybrid replay** — LLM calls go live, tool calls use recorded data (no side effects)
|
|
222
|
+
- **Async-first** — non-blocking trace capture, works in both sync and async code
|
|
223
|
+
- **Local-first** — no cloud accounts, no telemetry, everything stays on your machine
|
|
224
|
+
- **Keyboard navigable** — arrow keys / j/k to browse spans, `e` to edit
|
|
225
|
+
|
|
226
|
+
## Architecture
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
Your Agent Code
|
|
230
|
+
--> @trace, @wrap_tool, @wrap_llm decorators capture spans
|
|
231
|
+
--> Async queue --> SQLite (~/.agentlens/traces.db)
|
|
232
|
+
--> FastAPI serves JSON API + bundled React frontend
|
|
233
|
+
--> localhost:7600
|
|
234
|
+
|
|
235
|
+
Fork & Replay (Live mode):
|
|
236
|
+
--> Load original trace, apply mutations
|
|
237
|
+
--> Re-import user's function, set ReplayContext
|
|
238
|
+
--> Decorators intercept each span:
|
|
239
|
+
Before mutation: execute normally
|
|
240
|
+
At mutation: return edited output
|
|
241
|
+
After mutation: execute live (real API calls)
|
|
242
|
+
--> Save new trace for side-by-side comparison
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Web UI Pages
|
|
246
|
+
|
|
247
|
+
- **Trace List** — all captured traces with status, duration, token count, cost
|
|
248
|
+
- **Trace Detail** — span timeline (left) + selected span I/O (right), keyboard navigable
|
|
249
|
+
- **Replay Comparison** — side-by-side original vs forked trace, with RE-EXECUTED / EDITED / STALE badges
|
|
250
|
+
|
|
251
|
+
## Configuration
|
|
252
|
+
|
|
253
|
+
| Variable | Default | Description |
|
|
254
|
+
|----------|---------|-------------|
|
|
255
|
+
| `AGENTLENS_DB_PATH` | `~/.agentlens/traces.db` | SQLite database path |
|
|
256
|
+
| `AGENTLENS_PORT` | `7600` | Default server port |
|
|
257
|
+
| `AGENTLENS_ENABLED` | `true` | Set `false` to disable tracing (decorators become no-ops) |
|
|
258
|
+
|
|
259
|
+
## Development
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Install with dev dependencies
|
|
263
|
+
pip install -e ".[dev]"
|
|
264
|
+
|
|
265
|
+
# Frontend development (hot reload)
|
|
266
|
+
cd frontend && npm install && npm run dev
|
|
267
|
+
|
|
268
|
+
# Build frontend for production
|
|
269
|
+
cd frontend && npm run build
|
|
270
|
+
|
|
271
|
+
# Run tests
|
|
272
|
+
pytest
|
|
273
|
+
|
|
274
|
+
# Type checking
|
|
275
|
+
mypy src/agentlens
|
|
276
|
+
cd frontend && npx tsc --noEmit
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Project Structure
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
src/agentlens/
|
|
283
|
+
sdk/ # Tracing SDK (decorators, context management, SQLite writer)
|
|
284
|
+
server/ # FastAPI API + static file serving
|
|
285
|
+
replay/
|
|
286
|
+
engine.py # Replay dispatcher (deterministic vs live)
|
|
287
|
+
live.py # Live replay engine (re-executes user functions)
|
|
288
|
+
context.py # ReplayContext (decorators check this at runtime)
|
|
289
|
+
cli.py # CLI entry point
|
|
290
|
+
|
|
291
|
+
frontend/ # React + TypeScript + Tailwind UI (Vite)
|
|
292
|
+
examples/ # Working examples (basic + OpenAI trip planner)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Tech Stack
|
|
296
|
+
|
|
297
|
+
- **SDK**: Python 3.10+, Pydantic v2, aiosqlite, contextvars
|
|
298
|
+
- **Server**: FastAPI, Uvicorn
|
|
299
|
+
- **Frontend**: React 18, TypeScript, Tailwind CSS, CodeMirror, Vite
|
|
300
|
+
- **Storage**: SQLite (WAL mode, zero-config)
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# AgentLens
|
|
2
|
+
|
|
3
|
+
**Local-first AI agent debugger with fork & replay.**
|
|
4
|
+
|
|
5
|
+
AgentLens captures execution traces from multi-step AI agent workflows, visualizes them in a local web UI, and lets you fork a trace at any step, edit its output, and **re-execute downstream steps with real API calls** to see what would have happened differently.
|
|
6
|
+
|
|
7
|
+
Think of it as **Chrome DevTools for AI agents** — the Network tab + time-travel debugging, but for LLM agent workflows.
|
|
8
|
+
|
|
9
|
+
> **Install:** `pip install agentlens-xray` — the import name is `agentlens`.
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
AI agents are non-deterministic. When a multi-step agent fails at step 7 of 12, developers currently have no way to:
|
|
14
|
+
|
|
15
|
+
1. See exactly what happened at each step (tool calls, LLM reasoning, intermediate state)
|
|
16
|
+
2. Reproduce the failure deterministically
|
|
17
|
+
3. Test a fix by replaying from the failure point without re-running the entire chain
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install agentlens-xray
|
|
23
|
+
python examples/basic_agent.py # creates a sample trace (no API keys needed)
|
|
24
|
+
agentlens serve # opens the UI at localhost:7600
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For a real agent with OpenAI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export OPENAI_API_KEY="sk-..."
|
|
31
|
+
python examples/trip_planner_agent.py
|
|
32
|
+
agentlens serve
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### 1. Instrument Your Code
|
|
38
|
+
|
|
39
|
+
Add decorators to your existing agent functions — zero logic changes:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import agentlens
|
|
43
|
+
|
|
44
|
+
@agentlens.trace(name="my_agent")
|
|
45
|
+
async def my_agent(query: str):
|
|
46
|
+
data = await fetch_data(query)
|
|
47
|
+
result = await analyze(data)
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
@agentlens.wrap_tool(name="fetch_data")
|
|
51
|
+
async def fetch_data(query: str) -> dict:
|
|
52
|
+
return await api.search(query)
|
|
53
|
+
|
|
54
|
+
@agentlens.wrap_llm(name="analyze", model="gpt-4o-mini")
|
|
55
|
+
async def analyze(data: dict) -> str:
|
|
56
|
+
response = await openai.chat.completions.create(...)
|
|
57
|
+
return response.choices[0].message.content
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
There's also a context manager API:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
async with agentlens.start_trace_async("my_agent") as t:
|
|
64
|
+
with t.span("fetch_data", kind="tool") as s:
|
|
65
|
+
data = await fetch(...)
|
|
66
|
+
s.record_output(data)
|
|
67
|
+
with t.span("analyze", kind="llm", model="gpt-4o") as s:
|
|
68
|
+
s.record_input({"messages": [...]})
|
|
69
|
+
result = await llm.complete(...)
|
|
70
|
+
s.record_output(result)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. View Traces
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
agentlens serve # Web UI at localhost:7600
|
|
77
|
+
agentlens serve --port 8080 # Custom port
|
|
78
|
+
agentlens traces # List recent traces in terminal
|
|
79
|
+
agentlens traces --last 5 # Show last 5
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Fork & Replay
|
|
83
|
+
|
|
84
|
+
In the web UI:
|
|
85
|
+
|
|
86
|
+
1. Click a trace to see its span timeline
|
|
87
|
+
2. Select a span and click **Fork & Replay**
|
|
88
|
+
3. Edit the span's output in the code editor
|
|
89
|
+
4. Choose a **replay mode**
|
|
90
|
+
5. Click **Replay from here** to create a forked trace
|
|
91
|
+
6. View the side-by-side comparison with diff highlighting
|
|
92
|
+
|
|
93
|
+
### Replay Modes
|
|
94
|
+
|
|
95
|
+
| Mode | What happens | Cost | Use case |
|
|
96
|
+
|------|-------------|------|----------|
|
|
97
|
+
| **Deterministic** | Only the edited span changes. Downstream spans are marked stale. | Free | Quick data annotation, bookmarking bugs |
|
|
98
|
+
| **Live** | All downstream spans re-execute with real API calls. | Token costs | "What would the LLM say if the tool returned different data?" |
|
|
99
|
+
| **Hybrid** | LLM spans re-execute live, tool spans return recorded data. | Lower token costs | Test LLM behavior with changed context, no tool side effects |
|
|
100
|
+
|
|
101
|
+
**Example:** Your weather tool returned "sunny" but the real weather is a blizzard. Fork the weather span, change it to blizzard, select **Live** mode. The LLM re-generates the itinerary accounting for severe weather — with real API calls, producing genuinely different output.
|
|
102
|
+
|
|
103
|
+
## Framework Integrations
|
|
104
|
+
|
|
105
|
+
AgentLens works with popular frameworks out of the box — no decorators needed.
|
|
106
|
+
|
|
107
|
+
### OpenAI / Anthropic SDK (wrap your client)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install agentlens-xray[openai] # or agentlens-xray[anthropic]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from openai import OpenAI
|
|
115
|
+
from agentlens import wrap_openai
|
|
116
|
+
|
|
117
|
+
client = wrap_openai(OpenAI())
|
|
118
|
+
# All chat.completions.create() calls are now traced automatically
|
|
119
|
+
response = client.chat.completions.create(model="gpt-4o", messages=[...])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### LangChain / LangGraph
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install agentlens-xray[langchain]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from agentlens.integrations.langchain import AgentLensCallbackHandler
|
|
130
|
+
|
|
131
|
+
with AgentLensCallbackHandler(trace_name="my_agent") as handler:
|
|
132
|
+
chain.invoke(input, config={"callbacks": [handler]})
|
|
133
|
+
# Full trace with LLM, tool, retrieval, and chain spans
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### OpenAI Agents SDK
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pip install agentlens-xray[openai-agents]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from agentlens.integrations.openai_agents import install_agentlens_tracing
|
|
144
|
+
|
|
145
|
+
install_agentlens_tracing() # One line — all agent runs traced automatically
|
|
146
|
+
result = await Runner.run(agent, input="Process this refund")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### CrewAI
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install agentlens-xray[crewai]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from agentlens.integrations.crewai import CrewAIHandler
|
|
157
|
+
|
|
158
|
+
handler = CrewAIHandler(trace_name="my_crew")
|
|
159
|
+
crew = Crew(agents=[...], tasks=[...], callbacks=[handler])
|
|
160
|
+
crew.kickoff() # Crew, agent, and task spans captured
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Features
|
|
164
|
+
|
|
165
|
+
- **Zero-config tracing** — `pip install agentlens-xray` and add decorators or use framework integrations
|
|
166
|
+
- **Framework integrations** — LangChain, LangGraph, OpenAI Agents SDK, CrewAI, raw SDKs
|
|
167
|
+
- **Live replay** — re-execute downstream spans with real API calls after editing a span's output
|
|
168
|
+
- **Hybrid replay** — LLM calls go live, tool calls use recorded data (no side effects)
|
|
169
|
+
- **Async-first** — non-blocking trace capture, works in both sync and async code
|
|
170
|
+
- **Local-first** — no cloud accounts, no telemetry, everything stays on your machine
|
|
171
|
+
- **Keyboard navigable** — arrow keys / j/k to browse spans, `e` to edit
|
|
172
|
+
|
|
173
|
+
## Architecture
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
Your Agent Code
|
|
177
|
+
--> @trace, @wrap_tool, @wrap_llm decorators capture spans
|
|
178
|
+
--> Async queue --> SQLite (~/.agentlens/traces.db)
|
|
179
|
+
--> FastAPI serves JSON API + bundled React frontend
|
|
180
|
+
--> localhost:7600
|
|
181
|
+
|
|
182
|
+
Fork & Replay (Live mode):
|
|
183
|
+
--> Load original trace, apply mutations
|
|
184
|
+
--> Re-import user's function, set ReplayContext
|
|
185
|
+
--> Decorators intercept each span:
|
|
186
|
+
Before mutation: execute normally
|
|
187
|
+
At mutation: return edited output
|
|
188
|
+
After mutation: execute live (real API calls)
|
|
189
|
+
--> Save new trace for side-by-side comparison
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Web UI Pages
|
|
193
|
+
|
|
194
|
+
- **Trace List** — all captured traces with status, duration, token count, cost
|
|
195
|
+
- **Trace Detail** — span timeline (left) + selected span I/O (right), keyboard navigable
|
|
196
|
+
- **Replay Comparison** — side-by-side original vs forked trace, with RE-EXECUTED / EDITED / STALE badges
|
|
197
|
+
|
|
198
|
+
## Configuration
|
|
199
|
+
|
|
200
|
+
| Variable | Default | Description |
|
|
201
|
+
|----------|---------|-------------|
|
|
202
|
+
| `AGENTLENS_DB_PATH` | `~/.agentlens/traces.db` | SQLite database path |
|
|
203
|
+
| `AGENTLENS_PORT` | `7600` | Default server port |
|
|
204
|
+
| `AGENTLENS_ENABLED` | `true` | Set `false` to disable tracing (decorators become no-ops) |
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Install with dev dependencies
|
|
210
|
+
pip install -e ".[dev]"
|
|
211
|
+
|
|
212
|
+
# Frontend development (hot reload)
|
|
213
|
+
cd frontend && npm install && npm run dev
|
|
214
|
+
|
|
215
|
+
# Build frontend for production
|
|
216
|
+
cd frontend && npm run build
|
|
217
|
+
|
|
218
|
+
# Run tests
|
|
219
|
+
pytest
|
|
220
|
+
|
|
221
|
+
# Type checking
|
|
222
|
+
mypy src/agentlens
|
|
223
|
+
cd frontend && npx tsc --noEmit
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Project Structure
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
src/agentlens/
|
|
230
|
+
sdk/ # Tracing SDK (decorators, context management, SQLite writer)
|
|
231
|
+
server/ # FastAPI API + static file serving
|
|
232
|
+
replay/
|
|
233
|
+
engine.py # Replay dispatcher (deterministic vs live)
|
|
234
|
+
live.py # Live replay engine (re-executes user functions)
|
|
235
|
+
context.py # ReplayContext (decorators check this at runtime)
|
|
236
|
+
cli.py # CLI entry point
|
|
237
|
+
|
|
238
|
+
frontend/ # React + TypeScript + Tailwind UI (Vite)
|
|
239
|
+
examples/ # Working examples (basic + OpenAI trip planner)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Tech Stack
|
|
243
|
+
|
|
244
|
+
- **SDK**: Python 3.10+, Pydantic v2, aiosqlite, contextvars
|
|
245
|
+
- **Server**: FastAPI, Uvicorn
|
|
246
|
+
- **Frontend**: React 18, TypeScript, Tailwind CSS, CodeMirror, Vite
|
|
247
|
+
- **Storage**: SQLite (WAL mode, zero-config)
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Basic agent example — demonstrates AgentLens tracing with simulated tool/LLM calls.
|
|
2
|
+
|
|
3
|
+
No API keys required. Run this, then `agentlens serve` to view the trace.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
import agentlens
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@agentlens.trace(name="weather_agent")
|
|
12
|
+
async def weather_agent(city: str) -> str:
|
|
13
|
+
weather = await get_weather(city)
|
|
14
|
+
forecast = await get_forecast(city)
|
|
15
|
+
recommendation = await get_recommendation(weather, forecast)
|
|
16
|
+
return recommendation
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@agentlens.wrap_tool(name="get_weather")
|
|
20
|
+
async def get_weather(city: str) -> dict:
|
|
21
|
+
"""Simulated weather API call."""
|
|
22
|
+
await asyncio.sleep(0.1)
|
|
23
|
+
return {"city": city, "temp_f": 72, "condition": "sunny", "humidity": 45}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@agentlens.wrap_tool(name="get_forecast")
|
|
27
|
+
async def get_forecast(city: str) -> dict:
|
|
28
|
+
"""Simulated forecast API call."""
|
|
29
|
+
await asyncio.sleep(0.15)
|
|
30
|
+
return {
|
|
31
|
+
"city": city,
|
|
32
|
+
"next_3_days": [
|
|
33
|
+
{"day": "Tomorrow", "high": 75, "low": 60, "condition": "partly cloudy"},
|
|
34
|
+
{"day": "Day 2", "high": 78, "low": 62, "condition": "sunny"},
|
|
35
|
+
{"day": "Day 3", "high": 70, "low": 55, "condition": "rainy"},
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@agentlens.wrap_llm(name="get_recommendation", model="simulated-llm")
|
|
41
|
+
async def get_recommendation(weather: dict, forecast: dict) -> str:
|
|
42
|
+
"""Simulated LLM call that generates a recommendation."""
|
|
43
|
+
await asyncio.sleep(0.2)
|
|
44
|
+
city = weather["city"]
|
|
45
|
+
temp = weather["temp_f"]
|
|
46
|
+
condition = weather["condition"]
|
|
47
|
+
return (
|
|
48
|
+
f"It's currently {condition} and {temp}F in {city}. "
|
|
49
|
+
f"Great day for outdoor activities! "
|
|
50
|
+
f"Note: rain expected in {forecast['next_3_days'][2]['day'].lower()}, "
|
|
51
|
+
f"so plan accordingly."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
print("Running weather agent...")
|
|
57
|
+
result = await weather_agent("San Francisco")
|
|
58
|
+
print(f"\nAgent result: {result}")
|
|
59
|
+
print("\nTrace saved! Run 'agentlens serve' to view it in the UI.")
|
|
60
|
+
|
|
61
|
+
# Give the recorder time to flush
|
|
62
|
+
await asyncio.sleep(0.5)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
asyncio.run(main())
|