floe-ai 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.
floe_ai-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dhruv Vootkuri and Kevin Geng
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.
floe_ai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,408 @@
1
+ Metadata-Version: 2.4
2
+ Name: floe-ai
3
+ Version: 0.1.0
4
+ Summary: Optimize & observe your agent infrastructure.
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: opentelemetry-api>=1.40.0
10
+ Requires-Dist: opentelemetry-sdk>=1.40.0
11
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.53.3
12
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.53.3
13
+ Requires-Dist: openai>=2.0.0
14
+ Requires-Dist: litellm>=1.80.0
15
+ Requires-Dist: click>=8.1
16
+ Requires-Dist: blessed>=1.20
17
+ Requires-Dist: fastapi>=0.115
18
+ Requires-Dist: uvicorn>=0.30
19
+ Requires-Dist: pydantic>=2.7
20
+ Requires-Dist: python-dotenv>=1.0
21
+ Provides-Extra: demo
22
+ Requires-Dist: langchain>=1.2; extra == "demo"
23
+ Requires-Dist: langchain-openai>=1.1; extra == "demo"
24
+ Requires-Dist: langchain-core>=1.2; extra == "demo"
25
+ Requires-Dist: langgraph>=1.1; extra == "demo"
26
+ Dynamic: license-file
27
+
28
+ # Replay
29
+
30
+ **Git for agent execution.** Capture an AI agent run as a tree of spans, then fork any step, change the inputs, and rerun from that point forward to see what would have happened — in seconds, instead of re-running the whole agent from scratch.
31
+
32
+ When a multi-step agent fails, you don't want to rerun the world. You want to fork the trace at the broken step, apply a fix, and observe the new downstream behavior. Replay does exactly that.
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ### Requirements
39
+
40
+ | | |
41
+ |---|---|
42
+ | **Python** | 3.10 or newer (`python3 --version` to check) |
43
+ | **OS** | macOS, Linux, or Windows |
44
+ | **OpenAI API key** | Only for *capturing* new traces and for replays/pressure tests — browsing traces already on disk needs no key |
45
+ | **Node.js** | Not required. Only for the marketing site in `app/` — the Replay Console dashboard is pure Python |
46
+
47
+ If you don't have Python 3.10+: [python.org/downloads](https://www.python.org/downloads/), or `brew install python@3.11` on macOS.
48
+
49
+ ### Install
50
+
51
+ ```bash
52
+ git clone https://github.com/dhruv-vootkuri/Replay.git
53
+ cd Replay
54
+
55
+ python3 -m venv venv
56
+ source venv/bin/activate # Windows: venv\Scripts\activate
57
+
58
+ pip install -e ".[demo]"
59
+ ```
60
+
61
+ That's it. `pip install -e ".[demo]"` installs the engine, the web console, and the LangChain packages needed to run the included demo agent, and puts a `replay` command on your PATH.
62
+
63
+ Leave off `[demo]` (`pip install -e .`) if you only want to explore and replay traces and won't be capturing new ones from a LangChain agent.
64
+
65
+ > Prefer the exact versions this was built against? `pip install -r requirements.txt` instead, then `pip install -e . --no-deps`.
66
+
67
+ ### Verify it worked
68
+
69
+ The repo ships with real captured traces, so you can confirm everything works before writing a line of code or spending a cent:
70
+
71
+ ```bash
72
+ replay list
73
+ ```
74
+
75
+ ```
76
+ 4 trace(s) found:
77
+
78
+ a7f3521dceaf7e37...
79
+ 18 spans • 3 LLM calls • 5 tool calls • 5.3s
80
+ ...
81
+ ```
82
+
83
+ Then open the dashboard:
84
+
85
+ ```bash
86
+ replay serve # → http://localhost:7823
87
+ ```
88
+
89
+ Browse the traces, open any span, and read the replay log — all without an API key.
90
+
91
+ ### Add your API key
92
+
93
+ Needed once you want to capture new traces or run replays and pressure tests (both make real model calls):
94
+
95
+ ```bash
96
+ cp .env.example .env
97
+ # edit .env and set OPENAI_API_KEY=sk-...
98
+ ```
99
+
100
+ Or export it directly:
101
+
102
+ ```bash
103
+ export OPENAI_API_KEY=sk-... # Windows: set OPENAI_API_KEY=sk-...
104
+ ```
105
+
106
+ ### Capture your first trace
107
+
108
+ ```bash
109
+ python demo_agent.py # runs the included trip-planner agent
110
+ replay list # your new trace is at the bottom
111
+ replay explore <trace_id> # arrow keys to navigate, Enter to fork
112
+ ```
113
+
114
+ ### Troubleshooting
115
+
116
+ | Symptom | Fix |
117
+ |---|---|
118
+ | `replay: command not found` | The venv isn't active. `source venv/bin/activate` (Windows: `venv\Scripts\activate`) |
119
+ | `ModuleNotFoundError: No module named 'replay'` | Same cause — activate the venv, or re-run `pip install -e .` |
120
+ | `ModuleNotFoundError: langchain` when running `demo_agent.py` | Installed without the extra. Run `pip install -e ".[demo]"` |
121
+ | `AuthenticationError` / 401 from OpenAI | `OPENAI_API_KEY` isn't set or is invalid. See "Add your API key" above |
122
+ | `Address already in use` on `replay serve` | Another process has the port. Use `replay serve --port 8080` |
123
+ | `No traces found` | You're not in the repo root, or pointing elsewhere. `cd` to the repo, or pass `--dir path/to/traces` |
124
+
125
+ ---
126
+
127
+ ## Features
128
+
129
+ ### One-line instrumentation
130
+
131
+ ```python
132
+ import replay
133
+ replay.init()
134
+ ```
135
+
136
+ That's the whole setup. Replay attaches to OpenTelemetry and auto-instruments anything OTel knows about.
137
+
138
+ ### Framework-agnostic capture
139
+
140
+ Built on OpenTelemetry, so it captures runs from anything OTel can see:
141
+ - **OpenAI** Python SDK (auto-instrumented)
142
+ - **LangChain / LangGraph** (auto-instrumented, including `create_agent`)
143
+ - **LlamaIndex**, raw OTel spans, and any other OTel-instrumented framework
144
+ - Tool calls, LLM calls, agent workflows, custom spans
145
+
146
+ ### Fork-and-replay engine
147
+
148
+ Pick any span in any captured trace and fork it:
149
+
150
+ 1. Ancestors of the fork point are kept as **cached** (not re-executed)
151
+ 2. The fork span gets your **attribute overrides** applied — your changes are ground truth, no re-execution of that step
152
+ 3. **Downstream LLM calls** are re-run with updated context (new tool results threaded in via `tool_call_id`, new messages rebuilt from `replay.messages_json`)
153
+ 4. **Downstream tool calls** consult the tool registry to decide whether to run for real, use an alternative, accept a manually provided output, or skip
154
+ 5. The replay is saved as `{original_trace_id}.replay.{replay_trace_id}.json` so you can diff it against the original
155
+
156
+ ### Tool registry with pause/resume
157
+
158
+ Declare your tools so replay knows how to handle them when they're hit downstream of a fork:
159
+
160
+ ```python
161
+ @replay.tool(safe=True) # safe to run for real during replay
162
+ def get_weather(city: str): ...
163
+
164
+ @replay.tool(safe=False) # has real-world side effects — replay will pause
165
+ def send_email(to: str, body: str): ...
166
+
167
+ @replay.tool(replay_fn=mock_db) # provide an alternative impl just for replay
168
+ def query_prod_db(sql: str): ...
169
+ ```
170
+
171
+ When a tool span is hit during replay, the engine consults this decision hierarchy:
172
+
173
+ 1. **Saved CLI preference** → run automatically with the user's prior choice
174
+ 2. **No preference** → pause and prompt the user:
175
+ - **run** — execute the real tool (only if `safe=True`)
176
+ - **alternative** — call the registered `replay_fn` (only if defined)
177
+ - **provide** — manually type the expected output
178
+ - **skip** — reuse the cached output from the original trace
179
+ - **stop** — abort the replay
180
+
181
+ Decisions can be saved to `.replay/tool_preferences.json` for future runs.
182
+
183
+ ### Interactive terminal explorer
184
+
185
+ ```bash
186
+ replay explore <trace_id>
187
+ ```
188
+
189
+ A `blessed`-based TUI that renders the trace as a navigable tree. Arrow keys to move, Enter to fork at the highlighted span. Inputs are shown inline; you edit them in place and the replay runs against your edits. Forkable spans (LLM and tool) are marked with a green diamond.
190
+
191
+ ### Replay Console — the live dashboard
192
+
193
+ ```bash
194
+ replay serve # opens http://localhost:7823
195
+ replay serve --port 8080
196
+ ```
197
+
198
+ Everything the CLI does, live in a browser. Five views over the same traces directory:
199
+
200
+ | View | What's in it |
201
+ |---|---|
202
+ | **Overview** | Traces, spans, LLM calls, tool calls, tokens, replays and pressure-check pass rate at a glance, plus span-mix and tool-usage charts and the most recent activity |
203
+ | **Traces** | The trace list and a span tree per trace — open any span for its inputs, output, model and tokens, and fork it in place. Also shows every replay that trace has produced. |
204
+ | **Pressure tests** | Pick a system prompt, edit it, and replay it against every stored trace at once (see below) |
205
+ | **Replay log** | Every replay ever played — what changed, what the new answer was, and a full span-by-span diff against the original |
206
+ | **Tools** | The tool registry: which tools can run for real during a replay, and their saved preferences (editable) |
207
+
208
+ The console loads `.replay/tool_sources.py` the same way `explore` does, so replays started from the browser can run your tools for real.
209
+
210
+ ### Pressure tests — one prompt against every trace
211
+
212
+ The workflow this exists for: you fork one trace, edit the system prompt, like the new answer — and now you need to know whether that edit breaks anything the agent has *already* been asked.
213
+
214
+ ```bash
215
+ # from the console: Traces → any trace → "Pressure test this prompt against all traces"
216
+ # or from a fork result: "Pressure test this prompt against all traces"
217
+
218
+ # from the CLI:
219
+ replay prompts # see the distinct system prompts
220
+ replay pressure --set-prompt "You are a terse assistant. Always use the tools."
221
+ replay pressure --from-prompt 0 --trace a7f35 --contains Tokyo
222
+ replay pressure-log # every run
223
+ replay pressure-show <run_id> # one run's results
224
+ ```
225
+
226
+ Each selected trace is forked at its **entry LLM call** with the candidate prompt swapped in (or added, if the trace had none), then the whole agent loop is replayed — real model calls, real tool calls — and graded against the original run:
227
+
228
+ | Check | Fails when |
229
+ |---|---|
230
+ | **Produced an answer** | the replay ended without a final answer |
231
+ | **No errored spans** | any replayed span came back with an error status |
232
+ | **Tool usage preserved** | the agent stopped calling a tool the original called — the classic way a prompt edit quietly turns a grounded agent into a guessing one |
233
+ | **Grounded facts survive** | a tool result that was quoted in the original answer is missing from the new one |
234
+ | **Must contain / must not contain / matches pattern** | your own assertions on the new answer |
235
+ | **Answer still similar** *(warning)* | word overlap with the original answer drops below a threshold |
236
+ | **No extra reasoning steps** *(warning)* | the replay takes more LLM calls than the original, past a tolerance |
237
+
238
+ Nothing here asks a model to judge a model — every check is something you could verify by hand. Blocking checks fail the run; warnings flag it without failing. Runs stream results live, can be cancelled mid-flight, and are stored under `traces/pressure/` so the history survives a restart. Every replay they produce also lands in the replay log, tagged with the run it came from.
239
+
240
+ ### Auto-explore on exit
241
+
242
+ Set the `REPLAY` env var and the explorer opens automatically when your script finishes:
243
+
244
+ ```bash
245
+ REPLAY=1 python my_agent.py
246
+ ```
247
+
248
+ No code changes needed beyond `replay.init()`.
249
+
250
+ ### Programmatic exploration
251
+
252
+ Open the explorer from inside your own process — useful when you want registered tools to actually execute during fork (CLI invocations are a separate process and can't reach back into your tool functions):
253
+
254
+ ```python
255
+ import replay
256
+ replay.init()
257
+ run_my_agent()
258
+ replay.explore() # open the latest trace
259
+ replay.explore("f6caa") # specific trace by partial ID
260
+ ```
261
+
262
+ ### Run-and-explore in one command
263
+
264
+ ```bash
265
+ replay run my_agent.py
266
+ ```
267
+
268
+ Executes your script in the current process (so `@replay.tool` registrations stay live), captures the trace, and drops you straight into the explorer when it finishes.
269
+
270
+ ### Diff replays against originals
271
+
272
+ ```bash
273
+ replay diff <replay_id>
274
+ ```
275
+
276
+ Side-by-side `before` / `after` comparison for every changed span: forked attributes, re-run LLM outputs, and which spans were cached vs. re-executed.
277
+
278
+ ### Span enrichment
279
+
280
+ At export time, replay adds three attributes to every span so the engine never has to reconstruct data from flattened OTel attributes:
281
+
282
+ - `replay.messages_json` — full messages array on LLM spans
283
+ - `replay.tool_call_map_json` — `tool_call_id` → `tool_name` mapping
284
+ - `replay.tool_result` — unwrapped tool result (LangChain wraps results in JSON blobs; this is the clean version)
285
+
286
+ ### Saved tool sources
287
+
288
+ The first time you run an agent script, replay snapshots your `@replay.tool` function definitions to `.replay/tool_sources.py`. That means you can `replay explore` later **without re-running the agent**, and the explorer can still execute your tools for real.
289
+
290
+ If your tool implementations change, refresh the snapshot:
291
+
292
+ ```bash
293
+ replay explore <trace_id> --reload-tools my_agent.py
294
+ ```
295
+
296
+ ---
297
+
298
+ ## CLI reference
299
+
300
+ | Command | What it does |
301
+ |---|---|
302
+ | `replay list` | List all captured traces with span counts and durations |
303
+ | `replay show <trace_id>` | Render a trace as an indented timeline with inputs/outputs/tokens |
304
+ | `replay ids <trace_id>` | Same as `show`, but prints span IDs (useful before forking) |
305
+ | `replay fork <trace_id> <span_id> --set attr=value [--set ...]` | Fork at a span, override attributes, rerun downstream |
306
+ | `replay diff <replay_id>` | Compare a replay against its original trace |
307
+ | `replay explore <trace_id>` | Interactive TUI for navigating and forking |
308
+ | `replay run <script.py>` | Run an agent script and explore the resulting trace |
309
+ | `replay serve [--port N]` | Start the Replay Console — the web dashboard |
310
+ | `replay replays [--trace <id>]` | The replay log — every replay ever played, newest first |
311
+ | `replay prompts` | Every distinct system prompt across your traces, and which traces use it |
312
+ | `replay pressure --set-prompt "…"` | Pressure test a candidate system prompt against every trace |
313
+ | `replay pressure-log` | List every pressure run |
314
+ | `replay pressure-show <run_id>` | Full per-trace results of one pressure run |
315
+
316
+ All `<trace_id>`, `<span_id>`, `<replay_id>` and `<run_id>` arguments accept partial prefixes.
317
+
318
+ ---
319
+
320
+ ## Quickstart
321
+
322
+ Assumes you've done the [Install](#install) above and your venv is active.
323
+
324
+ ```python
325
+ # my_agent.py
326
+ import replay
327
+ from langchain.agents import create_agent
328
+ # ...
329
+
330
+ replay.init() # one line
331
+
332
+ @replay.tool(safe=True)
333
+ def get_weather(city: str) -> str:
334
+ return f"It's sunny in {city}."
335
+
336
+ agent = create_agent(model="gpt-4", tools=[get_weather])
337
+ agent.invoke({"messages": [{"role": "user", "content": "Weather in Paris?"}]})
338
+ ```
339
+
340
+ ```bash
341
+ export OPENAI_API_KEY=sk-... # or put it in .env
342
+ python my_agent.py
343
+ replay list # see the captured trace
344
+ replay explore <trace_id> # fork at any step
345
+ replay serve # or browse everything at localhost:7823
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Architecture
351
+
352
+ ```
353
+ replay/
354
+ __init__.py # replay.init(), replay.explore()
355
+ tools.py # @replay.tool decorator
356
+ cli.py # CLI commands (list, show, ids, fork, diff, explore,
357
+ # run, serve, replays, prompts, pressure*)
358
+ core/
359
+ setup.py # TracerProvider + OTel auto-instrumentation
360
+ engine.py # the replay engine — fork, cascade, downstream rerun
361
+ loader.py # reads traces from disk
362
+ enrichment.py # adds replay.* attributes at export time
363
+ tool_registry.py # tool declarations, preferences, pause decisions
364
+ spans.py # shared span classification/extraction (one source of truth
365
+ # for the CLI, server, replay log and pressure suite)
366
+ replay_log.py # indexes every played replay + builds original-vs-replay diffs
367
+ pressure.py # pressure suite — checks, run storage, threaded runner
368
+ span.py # span helpers
369
+ trace.py # trace helpers
370
+ tracer.py # tracer wrappers
371
+ exporters/
372
+ json_exporter.py # writes spans to local JSON files
373
+ server/
374
+ app.py # FastAPI API behind the Replay Console
375
+ static/ # dashboard frontend (index.html, app.js, style.css)
376
+ traces/ # captured traces (JSON)
377
+ *.replay.*.json # every replay played, the replay log reads these
378
+ pressure/*.json # one file per pressure run — config, results, verdict
379
+ .replay/
380
+ tool_preferences.json # saved CLI tool decisions
381
+ tool_sources.py # snapshotted @replay.tool sources
382
+ ```
383
+
384
+ Key design decisions:
385
+ - **Enrichment at export time, not via span processor** — some OTel implementations don't allow setting attributes after a span ends; the exporter is the only safe place
386
+ - **`tool_call_id_map` is built from all spans before the fork point**, not just ancestors, because in LangGraph the decision LLM span is a sibling of the tool spans, not an ancestor
387
+ - **Span type detection uses attributes, not span names**, so the engine works for any OTel-instrumented framework rather than being LangChain-specific
388
+ - **A pressure test is just a fork with a policy** — it reuses the same engine path as a manual system-prompt fork, so anything the console can replay by hand, the suite can replay across every trace
389
+ - **Pressure checks are deterministic** — no LLM-as-judge. Every verdict traces back to something you could check yourself, which is what makes a red result actionable
390
+ - **The replay files are the log** — nothing separate to keep in sync; `replay_log.py` indexes the `*.replay.*.json` the engine already writes
391
+
392
+ ---
393
+
394
+ ## Tech stack
395
+
396
+ Python 3.11 · OpenTelemetry (api, sdk) · `opentelemetry-instrumentation-openai` · `opentelemetry-instrumentation-langchain` · OpenAI Python SDK · LangChain 1.2 · `click` (CLI) · `blessed` (TUI) · `fastapi` + `uvicorn` (web UI)
397
+
398
+ ---
399
+
400
+ ## Status & limitations
401
+
402
+ - Tool **"run for real"** during replay only works when the explorer runs in the same process as your `@replay.tool` registrations (use `replay.explore()` programmatically, or `replay run my_agent.py`)
403
+ - JSON file storage — fine for local dev; production usage will want a real database
404
+ - Tested primarily with LangChain/LangGraph + OpenAI; other OTel-instrumented frameworks should work but have less coverage
405
+ - Streaming LLM responses are not yet handled
406
+ - Installs from source (`pip install -e .`) — not published to PyPI yet
407
+ - A pressure run costs one full agent replay per trace — real model calls, real spend. Start with a few traces before pointing it at everything.
408
+ - Pressure runs execute in the server/CLI process with a thread pool; there is no queue or worker, so a very large trace set is better split across runs