ragradar 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,38 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ dist/
7
+ build/
8
+ *.so
9
+
10
+ # uv
11
+ .uv/
12
+ uv.lock
13
+
14
+ # ragradar runtime — never commit user run data
15
+ .ragradar/
16
+
17
+ # environment
18
+ .env
19
+ *.env
20
+ .env.*
21
+
22
+ # IDE
23
+ .vscode/
24
+ .idea/
25
+ *.swp
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
30
+
31
+ # test output
32
+ .pytest_cache/
33
+ htmlcov/
34
+ .coverage
35
+
36
+ # example output
37
+ examples/rag_pipeline/output/
38
+ .claude/
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragradar
3
+ Version: 0.1.0
4
+ Summary: RAG observability: capture, evaluate, and explain pipeline runs (umbrella package + analyst CLI)
5
+ Project-URL: Homepage, https://github.com/pleokarthik/RAGRadar
6
+ Project-URL: Repository, https://github.com/pleokarthik/RAGRadar
7
+ Project-URL: Issues, https://github.com/pleokarthik/RAGRadar/issues
8
+ Author-email: Leo Karthik Paramasivan <pleokarthik@gmail.com>
9
+ License-Expression: MIT
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: click>=8.0
18
+ Requires-Dist: ragradar-capture<0.2.0,>=0.1.0
19
+ Requires-Dist: ragradar-core<0.2.0,>=0.1.0
20
+ Requires-Dist: ragradar-evaluate<0.2.0,>=0.1.0
21
+ Requires-Dist: rich>=13.0
22
+ Provides-Extra: semantic
23
+ Requires-Dist: sentence-transformers>=3.0; extra == 'semantic'
24
+ Requires-Dist: sqlite-vec>=0.1; extra == 'semantic'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ragradar
28
+
29
+ Analyst CLI for the ragradar observability system. Reads the local store at
30
+ `~/.ragradar/runs.db` that `ragradar-capture` writes; browses sessions, searches
31
+ runs, and explains exactly what went into a run's context window.
32
+
33
+ ```
34
+ pip install ragradar
35
+ ```
36
+
37
+ Runs are addressed as `sNrN` (session 2, run 3 → `s2r3`) — the id every
38
+ capture call returns. Commands that take a `<target>` accept an exact
39
+ id, nothing (= latest run), or a quoted text hint (searched; multiple
40
+ matches show a pick list).
41
+
42
+ ## ragradar list — sessions and runs
43
+
44
+ ```bash
45
+ ragradar list # sessions, newest first
46
+ ragradar list s2 # runs inside session 2
47
+ ```
48
+
49
+ ```
50
+ Sessions
51
+ | ID | Runs | Pipeline | Created | Title |
52
+ |----+------+-------------+------------+-------|
53
+ | s2 | 3 | rag_example | 2026-07-02 | |
54
+ | s1 | 1 | quickstart | 2026-07-02 | |
55
+ ```
56
+
57
+ ## ragradar find — search runs by query text
58
+
59
+ ```bash
60
+ ragradar find "reranking" # token match (FTS5)
61
+ ragradar find "score scale" --exact # phrase match
62
+ ragradar find "RRF" --session s2 # scope to a session
63
+ ragradar find "RRF" --pipeline rag_example
64
+ ragradar find "RRF" --from 2026-07-01 --to 2026-07-02
65
+ ragradar find "RRF" --today
66
+ ragradar find --recent 5 # latest N runs, no hint
67
+ ```
68
+
69
+ ```
70
+ Search results (2)
71
+ | Run | Date | Session | Query |
72
+ |-------+------------+---------+----------------------------------|
73
+ | s2 r3 | 2026-07-02 | | what is RRF and how does it ... |
74
+ ```
75
+
76
+ ## ragradar explain — the seven analysis factors
77
+
78
+ ```bash
79
+ ragradar explain # latest run
80
+ ragradar explain s2r3 # specific run
81
+ ragradar explain s2r3 --full
82
+ ragradar explain s2r3 --html # snapshot to ~/.ragradar/reports/s2r3.html
83
+ ```
84
+
85
+ Renders every factor the captured data supports, silently skipping the
86
+ rest: token usage, chunk scores, duplicates, truncation, dropped
87
+ history, cache hits, and the final prompt. Runs scored by
88
+ `ragradar-evaluate` also show an Evaluation Scores panel (risk score, policy
89
+ violations, RAGAS metrics).
90
+
91
+ ```
92
+ +------------- Token Usage --------------+
93
+ | Total: 1138/4096 (27.8%) |
94
+ | Chunks: 625 |
95
+ | Headroom: 196 |
96
+ +----------------------------------------+
97
+ +------------- Duplicates ---------------+
98
+ | 1 duplicate (25%): 1 window |
99
+ +----------------------------------------+
100
+ ```
101
+
102
+ ## ragradar diff — compare two runs
103
+
104
+ ```bash
105
+ ragradar diff s2r1 s2r3
106
+ ```
107
+
108
+ Side-by-side query delta, chunks added/removed, per-chunk score deltas,
109
+ token budget deltas, history and truncation changes. Ambiguous targets
110
+ are rejected — use exact ids here.
111
+
112
+ ## ragradar budget — token waterfall only
113
+
114
+ ```bash
115
+ ragradar budget s2r3
116
+ ```
117
+
118
+ ```
119
+ +------------- Token Usage --------------+
120
+ | Total: 1138/4096 (27.8%) |
121
+ | Chunks: 625 History: 13 |
122
+ | System: 500 Headroom: 196 |
123
+ +----------------------------------------+
124
+ ```
125
+
126
+ ## ragradar session rename
127
+
128
+ ```bash
129
+ ragradar session rename s2 "RRF investigation"
130
+ ```
131
+
132
+ ```
133
+ Session 2 renamed to "RRF investigation".
134
+ ```
135
+
136
+ ## Notes
137
+
138
+ - Read-mostly by design: the only data this CLI writes is a session
139
+ title. (Opening the store may create/migrate `runs.db` via `ragradar-core`
140
+ — that's environment setup, not run data.)
141
+ - No LLM anywhere in the navigation path; search is SQLite FTS5.
142
+ - Optional semantic search: `pip install ragradar[semantic]`.
@@ -0,0 +1,116 @@
1
+ # ragradar
2
+
3
+ Analyst CLI for the ragradar observability system. Reads the local store at
4
+ `~/.ragradar/runs.db` that `ragradar-capture` writes; browses sessions, searches
5
+ runs, and explains exactly what went into a run's context window.
6
+
7
+ ```
8
+ pip install ragradar
9
+ ```
10
+
11
+ Runs are addressed as `sNrN` (session 2, run 3 → `s2r3`) — the id every
12
+ capture call returns. Commands that take a `<target>` accept an exact
13
+ id, nothing (= latest run), or a quoted text hint (searched; multiple
14
+ matches show a pick list).
15
+
16
+ ## ragradar list — sessions and runs
17
+
18
+ ```bash
19
+ ragradar list # sessions, newest first
20
+ ragradar list s2 # runs inside session 2
21
+ ```
22
+
23
+ ```
24
+ Sessions
25
+ | ID | Runs | Pipeline | Created | Title |
26
+ |----+------+-------------+------------+-------|
27
+ | s2 | 3 | rag_example | 2026-07-02 | |
28
+ | s1 | 1 | quickstart | 2026-07-02 | |
29
+ ```
30
+
31
+ ## ragradar find — search runs by query text
32
+
33
+ ```bash
34
+ ragradar find "reranking" # token match (FTS5)
35
+ ragradar find "score scale" --exact # phrase match
36
+ ragradar find "RRF" --session s2 # scope to a session
37
+ ragradar find "RRF" --pipeline rag_example
38
+ ragradar find "RRF" --from 2026-07-01 --to 2026-07-02
39
+ ragradar find "RRF" --today
40
+ ragradar find --recent 5 # latest N runs, no hint
41
+ ```
42
+
43
+ ```
44
+ Search results (2)
45
+ | Run | Date | Session | Query |
46
+ |-------+------------+---------+----------------------------------|
47
+ | s2 r3 | 2026-07-02 | | what is RRF and how does it ... |
48
+ ```
49
+
50
+ ## ragradar explain — the seven analysis factors
51
+
52
+ ```bash
53
+ ragradar explain # latest run
54
+ ragradar explain s2r3 # specific run
55
+ ragradar explain s2r3 --full
56
+ ragradar explain s2r3 --html # snapshot to ~/.ragradar/reports/s2r3.html
57
+ ```
58
+
59
+ Renders every factor the captured data supports, silently skipping the
60
+ rest: token usage, chunk scores, duplicates, truncation, dropped
61
+ history, cache hits, and the final prompt. Runs scored by
62
+ `ragradar-evaluate` also show an Evaluation Scores panel (risk score, policy
63
+ violations, RAGAS metrics).
64
+
65
+ ```
66
+ +------------- Token Usage --------------+
67
+ | Total: 1138/4096 (27.8%) |
68
+ | Chunks: 625 |
69
+ | Headroom: 196 |
70
+ +----------------------------------------+
71
+ +------------- Duplicates ---------------+
72
+ | 1 duplicate (25%): 1 window |
73
+ +----------------------------------------+
74
+ ```
75
+
76
+ ## ragradar diff — compare two runs
77
+
78
+ ```bash
79
+ ragradar diff s2r1 s2r3
80
+ ```
81
+
82
+ Side-by-side query delta, chunks added/removed, per-chunk score deltas,
83
+ token budget deltas, history and truncation changes. Ambiguous targets
84
+ are rejected — use exact ids here.
85
+
86
+ ## ragradar budget — token waterfall only
87
+
88
+ ```bash
89
+ ragradar budget s2r3
90
+ ```
91
+
92
+ ```
93
+ +------------- Token Usage --------------+
94
+ | Total: 1138/4096 (27.8%) |
95
+ | Chunks: 625 History: 13 |
96
+ | System: 500 Headroom: 196 |
97
+ +----------------------------------------+
98
+ ```
99
+
100
+ ## ragradar session rename
101
+
102
+ ```bash
103
+ ragradar session rename s2 "RRF investigation"
104
+ ```
105
+
106
+ ```
107
+ Session 2 renamed to "RRF investigation".
108
+ ```
109
+
110
+ ## Notes
111
+
112
+ - Read-mostly by design: the only data this CLI writes is a session
113
+ title. (Opening the store may create/migrate `runs.db` via `ragradar-core`
114
+ — that's environment setup, not run data.)
115
+ - No LLM anywhere in the navigation path; search is SQLite FTS5.
116
+ - Optional semantic search: `pip install ragradar[semantic]`.
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "ragradar"
3
+ version = "0.1.0"
4
+ description = "RAG observability: capture, evaluate, and explain pipeline runs (umbrella package + analyst CLI)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ authors = [
9
+ { name = "Leo Karthik Paramasivan", email = "pleokarthik@gmail.com" },
10
+ ]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
18
+ ]
19
+ dependencies = [
20
+ "ragradar-core>=0.1.0,<0.2.0",
21
+ "ragradar-capture>=0.1.0,<0.2.0",
22
+ "ragradar-evaluate>=0.1.0,<0.2.0",
23
+ "rich>=13.0",
24
+ "click>=8.0",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/pleokarthik/RAGRadar"
29
+ Repository = "https://github.com/pleokarthik/RAGRadar"
30
+ Issues = "https://github.com/pleokarthik/RAGRadar/issues"
31
+
32
+ [project.optional-dependencies]
33
+ semantic = [
34
+ "sentence-transformers>=3.0",
35
+ "sqlite-vec>=0.1",
36
+ ]
37
+
38
+ [project.scripts]
39
+ ragradar = "ragradar.cli:main"
40
+
41
+ [tool.uv.sources]
42
+ ragradar-core = { workspace = true }
43
+ ragradar-capture = { workspace = true }
44
+ ragradar-evaluate = { workspace = true }
45
+
46
+ [build-system]
47
+ requires = ["hatchling"]
48
+ build-backend = "hatchling.build"
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/ragradar"]
@@ -0,0 +1,74 @@
1
+ """ragradar — the single public import surface.
2
+
3
+ Users only ever write ``import ragradar``: capture entry points
4
+ (capture/start and the staged proxies), evaluation entry points
5
+ (check/evaluate/available_metrics), and the schema dataclasses are all
6
+ re-exported here. The underlying distributions (ragradar-core,
7
+ ragradar-capture, ragradar-evaluate) stay separately installable so a
8
+ production pipeline can depend on ragradar-capture alone without
9
+ pulling the evaluation stack (scipy/ragas) — but importing their
10
+ modules directly is an internal concern, not the public API.
11
+ """
12
+
13
+ from ragradar_capture import (
14
+ Capture,
15
+ cache,
16
+ capture,
17
+ chunks,
18
+ commit,
19
+ context,
20
+ history,
21
+ response,
22
+ set_strict,
23
+ start,
24
+ tool_call,
25
+ )
26
+ from ragradar_core.schema import (
27
+ CacheEvent,
28
+ ChunkRecord,
29
+ RunRecord,
30
+ TokenBudget,
31
+ TokenUsage,
32
+ ToolCallRecord,
33
+ Turn,
34
+ )
35
+ from ragradar_evaluate import (
36
+ CheckResult,
37
+ EvalResult,
38
+ InputQualityPolicy,
39
+ MetricInfo,
40
+ available_metrics,
41
+ check,
42
+ evaluate,
43
+ )
44
+
45
+ __all__ = [
46
+ # Capture
47
+ "Capture",
48
+ "start",
49
+ "capture",
50
+ "set_strict",
51
+ "chunks",
52
+ "context",
53
+ "history",
54
+ "response",
55
+ "cache",
56
+ "tool_call",
57
+ "commit",
58
+ # Evaluation
59
+ "check",
60
+ "evaluate",
61
+ "available_metrics",
62
+ "CheckResult",
63
+ "EvalResult",
64
+ "MetricInfo",
65
+ "InputQualityPolicy",
66
+ # Schema dataclasses (advanced path; primitives coerce everywhere)
67
+ "ChunkRecord",
68
+ "TokenBudget",
69
+ "TokenUsage",
70
+ "Turn",
71
+ "CacheEvent",
72
+ "ToolCallRecord",
73
+ "RunRecord",
74
+ ]
@@ -0,0 +1,231 @@
1
+ import re
2
+ from datetime import date
3
+
4
+ import click
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+
8
+ from ragradar import store
9
+ from ragradar.explain import loader
10
+ from ragradar.explain.renderer import html as html_renderer
11
+ from ragradar.explain.renderer import terminal as terminal_renderer
12
+
13
+ console = Console()
14
+
15
+ _SESSION_RE = re.compile(r"^s(\d+)$", re.IGNORECASE)
16
+
17
+
18
+ def _parse_session_id(value: str) -> int:
19
+ m = _SESSION_RE.match(value)
20
+ if m:
21
+ return int(m.group(1))
22
+ return int(value)
23
+
24
+
25
+ def _disambiguate(results: list[dict]) -> dict | None:
26
+ console.print("\n [bold]Multiple matches:[/bold]\n")
27
+ for i, r in enumerate(results, 1):
28
+ title = r.get("session_title") or ""
29
+ query_preview = r["query"][:60]
30
+ console.print(
31
+ f" {i} s{r['session_id']} r{r['run_seq']} "
32
+ f"{r['created_at'][:10]} {title} "
33
+ f'— "{query_preview}"'
34
+ )
35
+ console.print()
36
+ try:
37
+ choice = click.prompt(
38
+ " Pick (number) or press Enter to cancel",
39
+ default="",
40
+ show_default=False,
41
+ )
42
+ if not choice:
43
+ return None
44
+ idx = int(choice) - 1
45
+ if 0 <= idx < len(results):
46
+ r = results[idx]
47
+ return store.get_run(r["session_id"], r["run_seq"])
48
+ except (ValueError, KeyboardInterrupt, EOFError):
49
+ pass
50
+ return None
51
+
52
+
53
+ def _resolve_and_load(target: str | None = None):
54
+ result = store.resolve_target(target)
55
+ if result is None:
56
+ console.print("No runs found.")
57
+ return None, None
58
+ if isinstance(result, list):
59
+ run_row = _disambiguate(result)
60
+ if run_row is None:
61
+ return None, None
62
+ else:
63
+ run_row = result
64
+ record = loader.load_run_record(run_row)
65
+ return run_row, record
66
+
67
+
68
+ @click.group()
69
+ def main():
70
+ """ragradar — analyst CLI for the ragradar observability system."""
71
+
72
+
73
+ @main.command("list")
74
+ @click.argument("session_id", required=False)
75
+ def list_cmd(session_id):
76
+ """List sessions, or runs within a session."""
77
+ if session_id is not None:
78
+ sid = _parse_session_id(session_id)
79
+ runs = store.list_runs(sid)
80
+ if not runs:
81
+ console.print(f"No runs found in session {sid}.")
82
+ return
83
+ tbl = Table(title=f"Session {sid} — Runs")
84
+ tbl.add_column("Run", style="cyan")
85
+ tbl.add_column("Date")
86
+ tbl.add_column("Query")
87
+ for r in runs:
88
+ tbl.add_row(
89
+ f"s{r['session_id']} r{r['run_seq']}",
90
+ r["created_at"][:10],
91
+ r["query"][:80],
92
+ )
93
+ console.print(tbl)
94
+ else:
95
+ sessions = store.list_sessions()
96
+ if not sessions:
97
+ console.print("No sessions found.")
98
+ return
99
+ tbl = Table(title="Sessions")
100
+ tbl.add_column("ID", style="cyan")
101
+ tbl.add_column("Runs", justify="right")
102
+ tbl.add_column("Pipeline")
103
+ tbl.add_column("Created")
104
+ tbl.add_column("Title")
105
+ for s in sessions:
106
+ tbl.add_row(
107
+ f"s{s['session_id']}",
108
+ str(s["run_count"]),
109
+ s["pipeline"] or "",
110
+ s["created_at"][:10],
111
+ s["title"] or "",
112
+ )
113
+ console.print(tbl)
114
+
115
+
116
+ @main.command()
117
+ @click.argument("hint", required=False)
118
+ @click.option("--exact", is_flag=True)
119
+ @click.option("--from", "from_dt", default=None)
120
+ @click.option("--to", "to_dt", default=None)
121
+ @click.option("--today", is_flag=True)
122
+ @click.option("--session", "session_filter", default=None)
123
+ @click.option("--pipeline", default=None)
124
+ @click.option("--recent", default=None, type=int)
125
+ def find(hint, exact, from_dt, to_dt, today, session_filter, pipeline, recent):
126
+ """Search runs by query text."""
127
+ if today:
128
+ today_str = date.today().isoformat()
129
+ if from_dt is None:
130
+ from_dt = today_str
131
+ if to_dt is None:
132
+ to_dt = today_str + "T23:59:59.999999Z"
133
+
134
+ sid = None
135
+ if session_filter is not None:
136
+ sid = _parse_session_id(session_filter)
137
+
138
+ results = store.search_runs(
139
+ hint=hint,
140
+ exact=exact,
141
+ session_id=sid,
142
+ pipeline=pipeline,
143
+ from_dt=from_dt,
144
+ to_dt=to_dt,
145
+ recent_n=recent,
146
+ )
147
+
148
+ if not results:
149
+ console.print("No matching runs found.")
150
+ return
151
+
152
+ tbl = Table(title=f"Search results ({len(results)})")
153
+ tbl.add_column("Run", style="cyan")
154
+ tbl.add_column("Date")
155
+ tbl.add_column("Session")
156
+ tbl.add_column("Query")
157
+ for r in results:
158
+ tbl.add_row(
159
+ f"s{r['session_id']} r{r['run_seq']}",
160
+ r["created_at"][:10],
161
+ r.get("session_title") or "",
162
+ r["query"][:80],
163
+ )
164
+ console.print(tbl)
165
+
166
+
167
+ @main.command()
168
+ @click.argument("target", required=False)
169
+ @click.option("--full", is_flag=True)
170
+ @click.option("--html", "to_html", is_flag=True)
171
+ def explain(target, full, to_html):
172
+ """Explain a run — all analysis factors."""
173
+ run_row, record = _resolve_and_load(target)
174
+ if record is None:
175
+ return
176
+
177
+ if to_html:
178
+ run_id = f"s{run_row['session_id']}r{run_row['run_seq']}"
179
+ path = html_renderer.render(record, run_id)
180
+ console.print(f"Report written to {path}")
181
+ else:
182
+ terminal_renderer.render(record, full=full, run_row=run_row)
183
+
184
+
185
+ @main.command()
186
+ @click.argument("target_a")
187
+ @click.argument("target_b")
188
+ def diff(target_a, target_b):
189
+ """Compare two runs side by side."""
190
+ row_a = store.resolve_target(target_a)
191
+ row_b = store.resolve_target(target_b)
192
+
193
+ if row_a is None or row_b is None:
194
+ console.print("Could not resolve both targets.")
195
+ return
196
+ if isinstance(row_a, list) or isinstance(row_b, list):
197
+ console.print("Ambiguous target — use exact run ID (e.g. s2r3).")
198
+ return
199
+
200
+ rec_a = loader.load_run_record(row_a)
201
+ rec_b = loader.load_run_record(row_b)
202
+
203
+ id_a = f"s{row_a['session_id']}r{row_a['run_seq']}"
204
+ id_b = f"s{row_b['session_id']}r{row_b['run_seq']}"
205
+
206
+ terminal_renderer.render_diff(rec_a, rec_b, id_a, id_b)
207
+
208
+
209
+ @main.command()
210
+ @click.argument("target")
211
+ def budget(target):
212
+ """Token waterfall only."""
213
+ run_row, record = _resolve_and_load(target)
214
+ if record is None:
215
+ return
216
+ terminal_renderer.render_budget(record)
217
+
218
+
219
+ @main.group()
220
+ def session():
221
+ """Session management commands."""
222
+
223
+
224
+ @session.command()
225
+ @click.argument("session_id")
226
+ @click.argument("title")
227
+ def rename(session_id, title):
228
+ """Rename a session."""
229
+ sid = _parse_session_id(session_id)
230
+ store.rename_session(sid, title)
231
+ console.print(f'Session {sid} renamed to "{title}".')
File without changes
@@ -0,0 +1,19 @@
1
+ from ragradar_core.schema import RunRecord
2
+
3
+
4
+ def analyze(record: RunRecord) -> dict | None:
5
+ if not record.cache_events:
6
+ return None
7
+
8
+ hits = [e for e in record.cache_events if e.hit]
9
+ misses = [e for e in record.cache_events if not e.hit]
10
+ total = len(record.cache_events)
11
+
12
+ return {
13
+ "total_events": total,
14
+ "hits": len(hits),
15
+ "misses": len(misses),
16
+ "hit_ratio": len(hits) / total if total else 0.0,
17
+ "hit_chunks": [e.chunk_id for e in hits],
18
+ "miss_chunks": [e.chunk_id for e in misses],
19
+ }