graphsight-langgraph 0.2.0__tar.gz → 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.
Files changed (21) hide show
  1. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/LICENSE +21 -21
  2. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/PKG-INFO +3 -3
  3. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/README.md +259 -259
  4. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph/__init__.py +34 -34
  5. graphsight_langgraph-0.3.0/graphsight_langgraph/_text.py +17 -0
  6. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph/capture.py +43 -43
  7. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph/github_ingest.py +310 -309
  8. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph/mapper.py +158 -158
  9. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph/schema.py +79 -79
  10. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph/tracer.py +271 -270
  11. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph.egg-info/PKG-INFO +3 -3
  12. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph.egg-info/SOURCES.txt +5 -1
  13. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/pyproject.toml +41 -41
  14. graphsight_langgraph-0.3.0/tests/test_capture.py +32 -0
  15. graphsight_langgraph-0.3.0/tests/test_mapper.py +81 -0
  16. graphsight_langgraph-0.3.0/tests/test_tracer.py +138 -0
  17. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph.egg-info/dependency_links.txt +0 -0
  18. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph.egg-info/entry_points.txt +0 -0
  19. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph.egg-info/requires.txt +0 -0
  20. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/graphsight_langgraph.egg-info/top_level.txt +0 -0
  21. {graphsight_langgraph-0.2.0 → graphsight_langgraph-0.3.0}/setup.cfg +0 -0
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Arush Karnatak
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arush Karnatak
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphsight-langgraph
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: X-ray for LangGraph retrieval: one callback handler captures every retriever call, score, and relational path — rendered as an interactive graph.
5
5
  Author: Arush Karnatak
6
6
  License: MIT
@@ -64,8 +64,8 @@ pip install graphsight # the local viewer (recommended)
64
64
  | Python | ≥ 3.10 |
65
65
  | `langchain-core` | ≥ 0.3 (verified against 1.5.0) |
66
66
  | `langgraph` | any recent version; only needed for the `[example]` extra |
67
- | Sync (`invoke` / `stream`) | verified |
68
- | Async (`ainvoke` / `astream`) | same callback events; not yet covered by the verification run |
67
+ | Sync (`invoke` / `stream`) | verified, covered by the test suite |
68
+ | Async (`ainvoke` / `astream`) | verified, covered by the test suite |
69
69
 
70
70
  ## Quickstart: trace a GitHub repo in 60 seconds
71
71
 
@@ -1,259 +1,259 @@
1
- # graphsight-langgraph
2
-
3
- [![PyPI](https://img.shields.io/pypi/v/graphsight-langgraph.svg)](https://pypi.org/project/graphsight-langgraph/)
4
- [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/graphsight-langgraph/)
5
- [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/Kcodess2807/graphsight/blob/main/graphsight-langgraph/LICENSE)
6
-
7
- **See exactly why your LangGraph agent picked the context it picked.**
8
-
9
- Retrieval in an agent is a black box: documents go in, an answer comes out,
10
- and when the answer is wrong you're left guessing which context misled it.
11
- This package opens the box. One callback handler records a
12
- [LangGraph](https://github.com/langchain-ai/langgraph) run — every node,
13
- every retriever call, per-document scores, and (for graph-aware retrievers)
14
- the relational paths between retrieved entities — and one command renders it
15
- as an interactive graph in your browser.
16
-
17
- ```
18
- your LangGraph agent ──▶ LangGraphTracer ──▶ AgentTrace (v0.1) ──▶ graphsight viewer
19
- (callbacks) neutral JSON interactive graph
20
- ```
21
-
22
- Only dependency: `langchain-core`. No engine, no backend, no account —
23
- nothing leaves your machine.
24
-
25
- ## Installation
26
-
27
- ```bash
28
- pip install graphsight-langgraph # the tracer
29
- pip install "graphsight-langgraph[example]" # + langgraph, for the GitHub CLI
30
- pip install graphsight # the local viewer (recommended)
31
- ```
32
-
33
- ### Compatibility
34
-
35
- | | |
36
- |---|---|
37
- | Python | ≥ 3.10 |
38
- | `langchain-core` | ≥ 0.3 (verified against 1.5.0) |
39
- | `langgraph` | any recent version; only needed for the `[example]` extra |
40
- | Sync (`invoke` / `stream`) | verified |
41
- | Async (`ainvoke` / `astream`) | same callback events; not yet covered by the verification run |
42
-
43
- ## Quickstart: trace a GitHub repo in 60 seconds
44
-
45
- No setup, no API keys — public repositories don't need a token:
46
-
47
- ```bash
48
- graphsight-github-trace langchain-ai/langgraph "who fixed the recent streaming bugs?"
49
- graphsight graphsight_out/trace_state.json
50
- ```
51
-
52
- Your browser opens on a live graph of that repository's recent activity:
53
- the PRs that matched the question, the people who authored them, the issues
54
- they resolve — every node clickable, scores shown, execution timeline
55
- included.
56
-
57
- ### `graphsight-github-trace` reference
58
-
59
- ```
60
- graphsight-github-trace REPO [QUESTION] [options]
61
- ```
62
-
63
- | Argument | Default | Description |
64
- |---|---|---|
65
- | `REPO` | — | `owner/name`, e.g. `langchain-ai/langgraph`. |
66
- | `QUESTION` | *"What changed recently in \<repo\>, and who drove it?"* | The question the traced retrieval answers. |
67
- | `--token` | `$GITHUB_TOKEN` | GitHub token. Required for private repositories; raises the rate limit on public ones. |
68
- | `--prs` | `25` | Recent pull requests to fetch. |
69
- | `--issues` | `25` | Recent issues to fetch. |
70
- | `--commits` | `25` | Recent commits to fetch — solo repos with no PRs/issues still produce a full graph. |
71
- | `--top` | `10` | Items the retrieval keeps. |
72
- | `--out` | `graphsight_out/` | Output directory for `agent_trace.json` and `trace_state.json`. |
73
-
74
- Method note: the CLI builds a corpus with relational edges
75
- (`person AUTHORED pr/commit`, `pr RESOLVES issue`, `pr TOUCHES repo`) and
76
- ranks by **lexical overlap with 1-hop graph expansion** — deliberately
77
- simple, and reported as such. The scores you see are exactly the scores
78
- computed; nothing is presented as semantic similarity.
79
-
80
- Ask it *who / what / when* questions ("who touched auth recently?", "which
81
- issue needed two attempts?") — that's what commit and PR history can answer.
82
- *How-does-it-work* questions need semantic retrieval over code and docs,
83
- which this deliberately simple demo does not pretend to do.
84
-
85
- ## Tracing your own agent
86
-
87
- The complete integration:
88
-
89
- ```python
90
- from graphsight_langgraph import LangGraphTracer, capture
91
-
92
- tracer = LangGraphTracer()
93
- result = graph.invoke(inputs, config={"callbacks": [tracer]})
94
-
95
- capture(tracer, query="why is checkout failing?", answer=result["answer"])
96
- # -> .graphsight/20260725T071558_why-is-checkout-failing.json
97
- ```
98
-
99
- `capture()` finishes the trace and appends it to a local history directory
100
- (`./.graphsight/` by default, `$GRAPHSIGHT_DIR` to override) — browse every
101
- run with `graphsight .graphsight/`. For manual control use
102
- `tracer.finish()` + `to_tracestate()` / `save_trace()`; `trace.to_dict()`
103
- gives the framework-neutral `AgentTrace` for your own tooling.
104
-
105
- ### Retrieved vs. used
106
-
107
- When you pass `answer=`, each retrieved item gets an `answer_overlap`
108
- score — the lexical overlap between the item's content and the final
109
- answer. In the viewer, items that surfaced in the answer render
110
- highlighted; items retrieved but unused render dimmed with the label
111
- *"retrieved, unused."* That splits the two classic retrieval failures at a
112
- glance:
113
-
114
- - **right doc retrieved, ignored by the model** → dimmed node with a high
115
- retrieval score
116
- - **wrong doc trusted** → highlighted node that shouldn't be
117
-
118
- The overlap is a lexical heuristic (labeled as such, threshold 0.2) — it is
119
- never presented as a model-computed relevance judgment. No `answer=` → no
120
- usage claims; everything renders plain.
121
-
122
- ### Configuration propagation (read this once)
123
-
124
- LangChain only propagates callbacks into runnables that receive the run's
125
- config. Inside a LangGraph node, pass the node's config through to
126
- sub-runnables, or the tracer will record the node but not what happened
127
- inside it:
128
-
129
- ```python
130
- def retrieve(state, config): # 1. accept config
131
- docs = retriever.invoke(state["q"], config=config) # 2. pass it through
132
- return {"docs": docs}
133
- ```
134
-
135
- **Symptom if you skip this:** the trace shows node spans but zero
136
- retrievals.
137
-
138
- ### API reference
139
-
140
- | Name | Description |
141
- |---|---|
142
- | `LangGraphTracer()` | `BaseCallbackHandler` subclass. Pass via `config={"callbacks": [tracer]}` to `invoke` / `stream`. Reusable within a single run; create a fresh instance per run. |
143
- | `tracer.finish(query=None, answer=None) -> AgentTrace` | Assembles the trace after the run: closes dangling spans, computes total latency and per-item `answer_overlap`. `query` falls back to the first retriever query seen. |
144
- | `capture(tracer, query=None, answer=None, dir=None) -> Path` | `finish()` + save to the history directory in one call. |
145
- | `save_trace(trace, dir=None) -> Path` | Writes a finished trace to the history directory (`./.graphsight/` or `$GRAPHSIGHT_DIR`). |
146
- | `to_tracestate(trace) -> dict` | Maps an `AgentTrace` to the viewer's JSON contract. Serialize with `json.dump`. |
147
- | `trace.to_dict() -> dict` | The framework-neutral `AgentTrace` (schema v0.1) for your own tooling. |
148
- | `AgentTrace`, `Span`, `Retrieval`, `RetrievedItem`, `TraceEdge` | Plain dataclasses defining the schema; importable for custom emitters. |
149
-
150
- ## What gets captured
151
-
152
- | Source in the run | Captured as |
153
- |---|---|
154
- | Each LangGraph node execution | `Span(kind="node")` with monotonic-clock timing |
155
- | Framework internals (`RunnableSequence`, `ChannelWrite`, `__start__`, …) | filtered out — one span per user node |
156
- | `on_retriever_end` documents | one `RetrievedItem` per doc: id, label, kind, score, content, source |
157
- | `Document.metadata` score keys (`score`, `relevance_score`, `similarity`, `_score`, `vector_score`) | item scores |
158
- | `Document.metadata["edges"]` | relational edges, deduplicated per retrieval |
159
- | LLM / tool calls | plain spans in the execution timeline |
160
- | Edges present in a retrieval | `arm = "graph"`, else `"vector"` — detected automatically |
161
- | The final answer (when passed to `finish`/`capture`) | per-item `answer_overlap` — the retrieved-vs-used signal |
162
-
163
- ### Making your retriever graph-aware
164
-
165
- The tracer reads two optional metadata conventions from the `Document`
166
- objects your retriever returns:
167
-
168
- ```python
169
- Document(
170
- page_content="PR #4821 'fix: idempotent refund path' merged by Priya N. ...",
171
- metadata={
172
- "id": "pr_4821", # stable node id (falls back to a content hash)
173
- "label": "PR #4821", # display name
174
- "kind": "pull_request", # normalized to a viewer entity type, see below
175
- "score": 0.94, # any recognized score key
176
- "source": "https://github.com/acme/platform/pull/4821",
177
- "edges": [ # optional — enables the relational view
178
- {"source": "pr_4821", "target": "svc_checkout",
179
- "relation": "TOUCHES", "weight": 0.9},
180
- ],
181
- },
182
- )
183
- ```
184
-
185
- `kind` values are normalized (`pull_request → PR`, `service → Service`,
186
- `person`/`author` → `Person`, `ticket`/`issue`/`jira` → `Ticket`,
187
- `repo → Repo`, `library → Library`, `team → Team`, `tool → Tool`; anything
188
- else renders as `Document`).
189
-
190
- ### Degradation behavior
191
-
192
- - **No edges in metadata** → a flat scored-retrieval view instead of
193
- relational path highlighting. Still useful; not graph-aware.
194
- - **No recognized score keys** → scores stay `None`; no score chips render.
195
- Nothing is ever fabricated.
196
- - The emitted `confidence.rationale` states that scores came from your
197
- retriever and were not recomputed — an imported trace never masquerades
198
- as an engine-computed one.
199
-
200
- ## Schema (v0.2)
201
-
202
- `AgentTrace` is the stable contract. Future adapters (LlamaIndex, raw
203
- OpenTelemetry) emit the same shape and render in the same viewer. v0.2 adds
204
- `RetrievedItem.answer_overlap` (additive — v0.1 traces stay valid).
205
-
206
- ```jsonc
207
- {
208
- "schema_version": "0.2",
209
- "framework": "langgraph",
210
- "query": "…",
211
- "spans": [
212
- { "id": "…", "name": "retrieve", "kind": "node", // node | retriever | llm | tool
213
- "parent_id": null, "start_ms": 0.0, "end_ms": 0.3, "status": "ok" }
214
- ],
215
- "retrievals": [
216
- {
217
- "span_id": "…", "query": "…",
218
- "arm": "graph", // "vector" | "graph", auto-detected
219
- "items": [
220
- { "id": "pr_4821", "label": "PR #4821", "kind": "pull_request",
221
- "score": 0.94, "vector_score": null, "graph_score": null,
222
- "answer_overlap": 0.41, // null when no answer given
223
- "content": "…", "source_uri": "https://…", "metadata": {} }
224
- ],
225
- "edges": [ // optional — the relational view
226
- { "source": "pr_4821", "target": "svc_checkout", "relation": "TOUCHES", "weight": 0.9 }
227
- ]
228
- }
229
- ],
230
- "answer": "…",
231
- "latency_ms": 3.9
232
- }
233
- ```
234
-
235
- ## Troubleshooting
236
-
237
- | Symptom | Cause / fix |
238
- |---|---|
239
- | Node spans but no retrievals | Config not propagated into the node's sub-runnables — see [Configuration propagation](#configuration-propagation-read-this-once). |
240
- | Items without scores | Your retriever doesn't write a recognized score key to `Document.metadata` — add one (`score` is simplest). |
241
- | Flat graph, no edges | Your retriever doesn't emit `metadata["edges"]` — see [Making your retriever graph-aware](#making-your-retriever-graph-aware). |
242
- | `GitHub API 403` from the CLI | Rate limit (60 requests/hour unauthenticated) — pass `--token` or set `GITHUB_TOKEN`. |
243
- | Garbled output on Windows consoles | Fixed in ≥ 0.1.1; upgrade. |
244
-
245
- ## Roadmap
246
-
247
- In order: a **LlamaIndex adapter** emitting the same `AgentTrace`, then a
248
- raw **OpenTelemetry span** ingestor. The schema is the contract; adapters
249
- stay thin.
250
-
251
- ## Links
252
-
253
- - Source & issue tracker: [github.com/Kcodess2807/graphsight](https://github.com/Kcodess2807/graphsight)
254
- - The viewer: [graphsight on PyPI](https://pypi.org/project/graphsight/)
255
- - Beta test script: [BETA.md](https://github.com/Kcodess2807/graphsight/blob/main/BETA.md)
256
-
257
- ## License
258
-
259
- MIT © Arush Karnatak
1
+ # graphsight-langgraph
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/graphsight-langgraph.svg)](https://pypi.org/project/graphsight-langgraph/)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/graphsight-langgraph/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/Kcodess2807/graphsight/blob/main/graphsight-langgraph/LICENSE)
6
+
7
+ **See exactly why your LangGraph agent picked the context it picked.**
8
+
9
+ Retrieval in an agent is a black box: documents go in, an answer comes out,
10
+ and when the answer is wrong you're left guessing which context misled it.
11
+ This package opens the box. One callback handler records a
12
+ [LangGraph](https://github.com/langchain-ai/langgraph) run — every node,
13
+ every retriever call, per-document scores, and (for graph-aware retrievers)
14
+ the relational paths between retrieved entities — and one command renders it
15
+ as an interactive graph in your browser.
16
+
17
+ ```
18
+ your LangGraph agent ──▶ LangGraphTracer ──▶ AgentTrace (v0.1) ──▶ graphsight viewer
19
+ (callbacks) neutral JSON interactive graph
20
+ ```
21
+
22
+ Only dependency: `langchain-core`. No engine, no backend, no account —
23
+ nothing leaves your machine.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install graphsight-langgraph # the tracer
29
+ pip install "graphsight-langgraph[example]" # + langgraph, for the GitHub CLI
30
+ pip install graphsight # the local viewer (recommended)
31
+ ```
32
+
33
+ ### Compatibility
34
+
35
+ | | |
36
+ |---|---|
37
+ | Python | ≥ 3.10 |
38
+ | `langchain-core` | ≥ 0.3 (verified against 1.5.0) |
39
+ | `langgraph` | any recent version; only needed for the `[example]` extra |
40
+ | Sync (`invoke` / `stream`) | verified, covered by the test suite |
41
+ | Async (`ainvoke` / `astream`) | verified, covered by the test suite |
42
+
43
+ ## Quickstart: trace a GitHub repo in 60 seconds
44
+
45
+ No setup, no API keys — public repositories don't need a token:
46
+
47
+ ```bash
48
+ graphsight-github-trace langchain-ai/langgraph "who fixed the recent streaming bugs?"
49
+ graphsight graphsight_out/trace_state.json
50
+ ```
51
+
52
+ Your browser opens on a live graph of that repository's recent activity:
53
+ the PRs that matched the question, the people who authored them, the issues
54
+ they resolve — every node clickable, scores shown, execution timeline
55
+ included.
56
+
57
+ ### `graphsight-github-trace` reference
58
+
59
+ ```
60
+ graphsight-github-trace REPO [QUESTION] [options]
61
+ ```
62
+
63
+ | Argument | Default | Description |
64
+ |---|---|---|
65
+ | `REPO` | — | `owner/name`, e.g. `langchain-ai/langgraph`. |
66
+ | `QUESTION` | *"What changed recently in \<repo\>, and who drove it?"* | The question the traced retrieval answers. |
67
+ | `--token` | `$GITHUB_TOKEN` | GitHub token. Required for private repositories; raises the rate limit on public ones. |
68
+ | `--prs` | `25` | Recent pull requests to fetch. |
69
+ | `--issues` | `25` | Recent issues to fetch. |
70
+ | `--commits` | `25` | Recent commits to fetch — solo repos with no PRs/issues still produce a full graph. |
71
+ | `--top` | `10` | Items the retrieval keeps. |
72
+ | `--out` | `graphsight_out/` | Output directory for `agent_trace.json` and `trace_state.json`. |
73
+
74
+ Method note: the CLI builds a corpus with relational edges
75
+ (`person AUTHORED pr/commit`, `pr RESOLVES issue`, `pr TOUCHES repo`) and
76
+ ranks by **lexical overlap with 1-hop graph expansion** — deliberately
77
+ simple, and reported as such. The scores you see are exactly the scores
78
+ computed; nothing is presented as semantic similarity.
79
+
80
+ Ask it *who / what / when* questions ("who touched auth recently?", "which
81
+ issue needed two attempts?") — that's what commit and PR history can answer.
82
+ *How-does-it-work* questions need semantic retrieval over code and docs,
83
+ which this deliberately simple demo does not pretend to do.
84
+
85
+ ## Tracing your own agent
86
+
87
+ The complete integration:
88
+
89
+ ```python
90
+ from graphsight_langgraph import LangGraphTracer, capture
91
+
92
+ tracer = LangGraphTracer()
93
+ result = graph.invoke(inputs, config={"callbacks": [tracer]})
94
+
95
+ capture(tracer, query="why is checkout failing?", answer=result["answer"])
96
+ # -> .graphsight/20260725T071558_why-is-checkout-failing.json
97
+ ```
98
+
99
+ `capture()` finishes the trace and appends it to a local history directory
100
+ (`./.graphsight/` by default, `$GRAPHSIGHT_DIR` to override) — browse every
101
+ run with `graphsight .graphsight/`. For manual control use
102
+ `tracer.finish()` + `to_tracestate()` / `save_trace()`; `trace.to_dict()`
103
+ gives the framework-neutral `AgentTrace` for your own tooling.
104
+
105
+ ### Retrieved vs. used
106
+
107
+ When you pass `answer=`, each retrieved item gets an `answer_overlap`
108
+ score — the lexical overlap between the item's content and the final
109
+ answer. In the viewer, items that surfaced in the answer render
110
+ highlighted; items retrieved but unused render dimmed with the label
111
+ *"retrieved, unused."* That splits the two classic retrieval failures at a
112
+ glance:
113
+
114
+ - **right doc retrieved, ignored by the model** → dimmed node with a high
115
+ retrieval score
116
+ - **wrong doc trusted** → highlighted node that shouldn't be
117
+
118
+ The overlap is a lexical heuristic (labeled as such, threshold 0.2) — it is
119
+ never presented as a model-computed relevance judgment. No `answer=` → no
120
+ usage claims; everything renders plain.
121
+
122
+ ### Configuration propagation (read this once)
123
+
124
+ LangChain only propagates callbacks into runnables that receive the run's
125
+ config. Inside a LangGraph node, pass the node's config through to
126
+ sub-runnables, or the tracer will record the node but not what happened
127
+ inside it:
128
+
129
+ ```python
130
+ def retrieve(state, config): # 1. accept config
131
+ docs = retriever.invoke(state["q"], config=config) # 2. pass it through
132
+ return {"docs": docs}
133
+ ```
134
+
135
+ **Symptom if you skip this:** the trace shows node spans but zero
136
+ retrievals.
137
+
138
+ ### API reference
139
+
140
+ | Name | Description |
141
+ |---|---|
142
+ | `LangGraphTracer()` | `BaseCallbackHandler` subclass. Pass via `config={"callbacks": [tracer]}` to `invoke` / `stream`. Reusable within a single run; create a fresh instance per run. |
143
+ | `tracer.finish(query=None, answer=None) -> AgentTrace` | Assembles the trace after the run: closes dangling spans, computes total latency and per-item `answer_overlap`. `query` falls back to the first retriever query seen. |
144
+ | `capture(tracer, query=None, answer=None, dir=None) -> Path` | `finish()` + save to the history directory in one call. |
145
+ | `save_trace(trace, dir=None) -> Path` | Writes a finished trace to the history directory (`./.graphsight/` or `$GRAPHSIGHT_DIR`). |
146
+ | `to_tracestate(trace) -> dict` | Maps an `AgentTrace` to the viewer's JSON contract. Serialize with `json.dump`. |
147
+ | `trace.to_dict() -> dict` | The framework-neutral `AgentTrace` (schema v0.1) for your own tooling. |
148
+ | `AgentTrace`, `Span`, `Retrieval`, `RetrievedItem`, `TraceEdge` | Plain dataclasses defining the schema; importable for custom emitters. |
149
+
150
+ ## What gets captured
151
+
152
+ | Source in the run | Captured as |
153
+ |---|---|
154
+ | Each LangGraph node execution | `Span(kind="node")` with monotonic-clock timing |
155
+ | Framework internals (`RunnableSequence`, `ChannelWrite`, `__start__`, …) | filtered out — one span per user node |
156
+ | `on_retriever_end` documents | one `RetrievedItem` per doc: id, label, kind, score, content, source |
157
+ | `Document.metadata` score keys (`score`, `relevance_score`, `similarity`, `_score`, `vector_score`) | item scores |
158
+ | `Document.metadata["edges"]` | relational edges, deduplicated per retrieval |
159
+ | LLM / tool calls | plain spans in the execution timeline |
160
+ | Edges present in a retrieval | `arm = "graph"`, else `"vector"` — detected automatically |
161
+ | The final answer (when passed to `finish`/`capture`) | per-item `answer_overlap` — the retrieved-vs-used signal |
162
+
163
+ ### Making your retriever graph-aware
164
+
165
+ The tracer reads two optional metadata conventions from the `Document`
166
+ objects your retriever returns:
167
+
168
+ ```python
169
+ Document(
170
+ page_content="PR #4821 'fix: idempotent refund path' merged by Priya N. ...",
171
+ metadata={
172
+ "id": "pr_4821", # stable node id (falls back to a content hash)
173
+ "label": "PR #4821", # display name
174
+ "kind": "pull_request", # normalized to a viewer entity type, see below
175
+ "score": 0.94, # any recognized score key
176
+ "source": "https://github.com/acme/platform/pull/4821",
177
+ "edges": [ # optional — enables the relational view
178
+ {"source": "pr_4821", "target": "svc_checkout",
179
+ "relation": "TOUCHES", "weight": 0.9},
180
+ ],
181
+ },
182
+ )
183
+ ```
184
+
185
+ `kind` values are normalized (`pull_request → PR`, `service → Service`,
186
+ `person`/`author` → `Person`, `ticket`/`issue`/`jira` → `Ticket`,
187
+ `repo → Repo`, `library → Library`, `team → Team`, `tool → Tool`; anything
188
+ else renders as `Document`).
189
+
190
+ ### Degradation behavior
191
+
192
+ - **No edges in metadata** → a flat scored-retrieval view instead of
193
+ relational path highlighting. Still useful; not graph-aware.
194
+ - **No recognized score keys** → scores stay `None`; no score chips render.
195
+ Nothing is ever fabricated.
196
+ - The emitted `confidence.rationale` states that scores came from your
197
+ retriever and were not recomputed — an imported trace never masquerades
198
+ as an engine-computed one.
199
+
200
+ ## Schema (v0.2)
201
+
202
+ `AgentTrace` is the stable contract. Future adapters (LlamaIndex, raw
203
+ OpenTelemetry) emit the same shape and render in the same viewer. v0.2 adds
204
+ `RetrievedItem.answer_overlap` (additive — v0.1 traces stay valid).
205
+
206
+ ```jsonc
207
+ {
208
+ "schema_version": "0.2",
209
+ "framework": "langgraph",
210
+ "query": "…",
211
+ "spans": [
212
+ { "id": "…", "name": "retrieve", "kind": "node", // node | retriever | llm | tool
213
+ "parent_id": null, "start_ms": 0.0, "end_ms": 0.3, "status": "ok" }
214
+ ],
215
+ "retrievals": [
216
+ {
217
+ "span_id": "…", "query": "…",
218
+ "arm": "graph", // "vector" | "graph", auto-detected
219
+ "items": [
220
+ { "id": "pr_4821", "label": "PR #4821", "kind": "pull_request",
221
+ "score": 0.94, "vector_score": null, "graph_score": null,
222
+ "answer_overlap": 0.41, // null when no answer given
223
+ "content": "…", "source_uri": "https://…", "metadata": {} }
224
+ ],
225
+ "edges": [ // optional — the relational view
226
+ { "source": "pr_4821", "target": "svc_checkout", "relation": "TOUCHES", "weight": 0.9 }
227
+ ]
228
+ }
229
+ ],
230
+ "answer": "…",
231
+ "latency_ms": 3.9
232
+ }
233
+ ```
234
+
235
+ ## Troubleshooting
236
+
237
+ | Symptom | Cause / fix |
238
+ |---|---|
239
+ | Node spans but no retrievals | Config not propagated into the node's sub-runnables — see [Configuration propagation](#configuration-propagation-read-this-once). |
240
+ | Items without scores | Your retriever doesn't write a recognized score key to `Document.metadata` — add one (`score` is simplest). |
241
+ | Flat graph, no edges | Your retriever doesn't emit `metadata["edges"]` — see [Making your retriever graph-aware](#making-your-retriever-graph-aware). |
242
+ | `GitHub API 403` from the CLI | Rate limit (60 requests/hour unauthenticated) — pass `--token` or set `GITHUB_TOKEN`. |
243
+ | Garbled output on Windows consoles | Fixed in ≥ 0.1.1; upgrade. |
244
+
245
+ ## Roadmap
246
+
247
+ In order: a **LlamaIndex adapter** emitting the same `AgentTrace`, then a
248
+ raw **OpenTelemetry span** ingestor. The schema is the contract; adapters
249
+ stay thin.
250
+
251
+ ## Links
252
+
253
+ - Source & issue tracker: [github.com/Kcodess2807/graphsight](https://github.com/Kcodess2807/graphsight)
254
+ - The viewer: [graphsight on PyPI](https://pypi.org/project/graphsight/)
255
+ - Beta test script: [BETA.md](https://github.com/Kcodess2807/graphsight/blob/main/BETA.md)
256
+
257
+ ## License
258
+
259
+ MIT © Arush Karnatak
@@ -1,34 +1,34 @@
1
- """graphsight-langgraph — capture LangGraph runs as Graphsight traces.
2
-
3
- from graphsight_langgraph import LangGraphTracer, capture
4
-
5
- tracer = LangGraphTracer()
6
- result = graph.invoke(inputs, config={"callbacks": [tracer]})
7
- capture(tracer, query="...", answer=result.get("answer")) # -> .graphsight/
8
-
9
- Then browse every run: `graphsight .graphsight/`
10
- """
11
- from .capture import capture, save_trace
12
- from .mapper import to_tracestate
13
- from .schema import (
14
- SCHEMA_VERSION,
15
- AgentTrace,
16
- Retrieval,
17
- RetrievedItem,
18
- Span,
19
- TraceEdge,
20
- )
21
- from .tracer import LangGraphTracer
22
-
23
- __all__ = [
24
- "SCHEMA_VERSION",
25
- "AgentTrace",
26
- "LangGraphTracer",
27
- "Retrieval",
28
- "RetrievedItem",
29
- "Span",
30
- "TraceEdge",
31
- "capture",
32
- "save_trace",
33
- "to_tracestate",
34
- ]
1
+ """graphsight-langgraph — capture LangGraph runs as Graphsight traces.
2
+
3
+ from graphsight_langgraph import LangGraphTracer, capture
4
+
5
+ tracer = LangGraphTracer()
6
+ result = graph.invoke(inputs, config={"callbacks": [tracer]})
7
+ capture(tracer, query="...", answer=result.get("answer")) # -> .graphsight/
8
+
9
+ Then browse every run: `graphsight .graphsight/`
10
+ """
11
+ from .capture import capture, save_trace
12
+ from .mapper import to_tracestate
13
+ from .schema import (
14
+ SCHEMA_VERSION,
15
+ AgentTrace,
16
+ Retrieval,
17
+ RetrievedItem,
18
+ Span,
19
+ TraceEdge,
20
+ )
21
+ from .tracer import LangGraphTracer
22
+
23
+ __all__ = [
24
+ "SCHEMA_VERSION",
25
+ "AgentTrace",
26
+ "LangGraphTracer",
27
+ "Retrieval",
28
+ "RetrievedItem",
29
+ "Span",
30
+ "TraceEdge",
31
+ "capture",
32
+ "save_trace",
33
+ "to_tracestate",
34
+ ]