graphsight-langgraph 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,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.
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: graphsight-langgraph
3
+ Version: 0.1.0
4
+ Summary: Capture LangGraph agent runs as Graphsight traces — see exactly what your agent retrieved and why.
5
+ Author: Arush Karnatak
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/Kcodess2807/graphsight
8
+ Project-URL: Documentation, https://github.com/Kcodess2807/graphsight/tree/main/graphsight-langgraph
9
+ Keywords: langgraph,langchain,tracing,observability,rag,retrieval,agents
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Debuggers
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: langchain-core>=0.3
24
+ Provides-Extra: example
25
+ Requires-Dist: langgraph>=0.2; extra == "example"
26
+ Dynamic: license-file
27
+
28
+ # graphsight-langgraph
29
+
30
+ **See exactly why your LangGraph agent picked the context it picked.**
31
+
32
+ One callback handler captures a [LangGraph](https://github.com/langchain-ai/langgraph)
33
+ run — every node, every retriever call, per-document scores, and (for
34
+ graph-aware retrievers) the relational paths between retrieved entities — as a
35
+ framework-neutral `AgentTrace`. One function maps it to a JSON file the
36
+ [Graphsight Studio](../frontend) renders with **zero backend**.
37
+
38
+ ```
39
+ your LangGraph agent ──▶ LangGraphTracer ──▶ AgentTrace (v0.1) ──▶ to_tracestate() ──▶ Studio /memory/import
40
+ (callbacks) neutral JSON Studio JSON full canvas render
41
+ ```
42
+
43
+ This package is standalone. It does **not** require the TraceRAG engine,
44
+ database, or SaaS stack — its only dependency is `langchain-core`.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install -e . # the tracer (depends only on langchain-core)
50
+ pip install -e ".[example]" # + langgraph, to run the demo agent
51
+ ```
52
+
53
+ Python ≥ 3.10. Verified against `langchain-core 1.5.0` + current `langgraph`.
54
+
55
+ ## Quickstart
56
+
57
+ ```python
58
+ from graphsight_langgraph import LangGraphTracer, to_tracestate
59
+
60
+ tracer = LangGraphTracer()
61
+ result = graph.invoke(inputs, config={"callbacks": [tracer]})
62
+
63
+ trace = tracer.finish(
64
+ query="why is checkout failing?",
65
+ answer=result["answer"],
66
+ )
67
+
68
+ trace.to_dict() # framework-neutral AgentTrace (schema v0.1)
69
+ to_tracestate(trace) # Studio-ready JSON → drop into /memory/import
70
+ ```
71
+
72
+ That's the whole API surface: construct, pass as a callback, `finish()`, map.
73
+
74
+ ### ⚠ The one gotcha: config propagation
75
+
76
+ LangChain only propagates callbacks into runnables that receive the run's
77
+ config. Inside a LangGraph node, **pass the node's config through** to
78
+ sub-runnables, or the tracer will see the node but not what happened inside it:
79
+
80
+ ```python
81
+ def retrieve(state, config): # ① accept config
82
+ docs = retriever.invoke(state["q"], config=config) # ② pass it through
83
+ return {"docs": docs}
84
+ ```
85
+
86
+ If your trace shows node spans but no retrievals, this is why.
87
+
88
+ ## Try it in 60 seconds (offline, no API keys)
89
+
90
+ ```bash
91
+ python example/demo_agent.py
92
+ ```
93
+
94
+ This builds and runs a real two-node LangGraph agent (`retrieve → answer`)
95
+ over an in-memory "codebase memory" corpus — a PR, a service, a person, and a
96
+ ticket, connected by edges — and traces it:
97
+
98
+ ```
99
+ answer : The regression traces to PR #4821 in checkout-service, authored by Priya N. …
100
+ spans : 3 (retrieve, CodebaseMemoryRetriever, answer)
101
+ retrieved: 4 items · 4 edges · arm=graph
102
+ latency : 3.9ms
103
+ ```
104
+
105
+ Two files land in `example/out/`:
106
+
107
+ | File | What it is |
108
+ |---|---|
109
+ | `agent_trace.json` | the neutral `AgentTrace` — what any consumer would ingest |
110
+ | `trace_state.json` | Studio-ready — drag-and-drop (or paste) into `http://localhost:5173/memory/import` |
111
+
112
+ In the Studio you'll see the full canvas: four typed entity nodes with score
113
+ chips, the traced relational path (person → PR → service, PR → ticket), the
114
+ execution timeline with per-span timings, and the retriever's arm.
115
+
116
+ Tip: with [`graphsight`](../graphsight) installed, skip the manual
117
+ drop — `graphsight example/out/trace_state.json` serves the UI locally
118
+ and opens the trace directly.
119
+
120
+ ## Trace your own GitHub repo
121
+
122
+ The package ships a dead-simple ingest CLI — one command from repo to trace:
123
+
124
+ ```bash
125
+ graphsight-github-trace langchain-ai/langgraph "who fixed the recent streaming bugs?"
126
+ graphsight graphsight_out/trace_state.json
127
+ ```
128
+
129
+ It fetches recent PRs + issues (public repos need no token; `--token` /
130
+ `GITHUB_TOKEN` for private), builds a corpus with relational edges
131
+ (`person AUTHORED pr`, `pr RESOLVES issue`, `pr TOUCHES repo`), and runs a
132
+ traced two-node LangGraph agent over it. Retrieval is two-arm in miniature:
133
+ lexical seeds + 1-hop graph expansion along the edges — simple on purpose,
134
+ and the scores shown are exactly the lexical scores computed, nothing
135
+ dressed up as semantic. Requires the `example` extra (`langgraph`).
136
+
137
+ ## What gets captured
138
+
139
+ | Source in the run | Captured as |
140
+ |---|---|
141
+ | Each LangGraph node execution | `Span(kind="node")` with monotonic-clock timing |
142
+ | Framework internals (`RunnableSequence`, `ChannelWrite`, `__start__`, …) | **filtered out** — one span per user node, no noise |
143
+ | `on_retriever_end` documents | one `RetrievedItem` per doc: id, label, kind, score, content, source |
144
+ | `Document.metadata` score keys (`score`, `relevance_score`, `similarity`, …) | item scores |
145
+ | `Document.metadata["edges"]` = `[{source, target, relation, weight}]` | relational edges (deduplicated) |
146
+ | LLM / tool calls | plain spans in the execution timeline |
147
+ | Edges present in a retrieval? | `arm = "graph"`, else `"vector"` — auto-detected |
148
+
149
+ ### Honest degradation — stated plainly
150
+
151
+ - **Vector-only retriever** (no edges in metadata) → the Studio renders a flat
152
+ scored-retrieval view instead of relational path highlighting. Still useful;
153
+ just not graph-aware.
154
+ - **No score keys** → scores stay `None` and no score chips render.
155
+ **Nothing is ever fabricated.**
156
+ - The emitted `confidence.rationale` explicitly says scores came from *your*
157
+ retriever and Graphsight does not recompute them — an imported trace never
158
+ masquerades as an engine-computed one.
159
+
160
+ ## Schema (v0.1)
161
+
162
+ `AgentTrace` is the stable contract; future adapters (LlamaIndex, raw OTel)
163
+ emit the same shape.
164
+
165
+ ```jsonc
166
+ {
167
+ "schema_version": "0.1",
168
+ "framework": "langgraph",
169
+ "query": "…",
170
+ "spans": [
171
+ { "id": "…", "name": "retrieve", "kind": "node", // node | retriever | llm | tool | chain
172
+ "parent_id": null, "start_ms": 0.0, "end_ms": 0.3, "status": "ok" }
173
+ ],
174
+ "retrievals": [
175
+ {
176
+ "span_id": "…", "query": "…",
177
+ "arm": "graph", // "vector" | "graph" (auto: edges present?)
178
+ "items": [
179
+ { "id": "pr_4821", "label": "PR #4821", "kind": "pull_request",
180
+ "score": 0.94, "vector_score": null, "graph_score": null,
181
+ "content": "…", "source_uri": "https://…", "metadata": {} }
182
+ ],
183
+ "edges": [ // optional — the graph-aware view
184
+ { "source": "pr_4821", "target": "svc_checkout", "relation": "TOUCHES", "weight": 0.9 }
185
+ ]
186
+ }
187
+ ],
188
+ "answer": "…",
189
+ "latency_ms": 3.9
190
+ }
191
+ ```
192
+
193
+ `to_tracestate()` maps this onto the Studio's `TraceState`
194
+ ([frontend/src/types/trace.ts](../frontend/src/types/trace.ts)). Free-form
195
+ `kind` strings are normalized to Studio entity types
196
+ (`pull_request → PR`, `service → Service`, `person/author → Person`,
197
+ `ticket/issue/jira → Ticket`, … fallback `Document`). Node positions are
198
+ recomputed client-side by the Studio's layout engine, so emitters never deal
199
+ with coordinates.
200
+
201
+ ## Package layout
202
+
203
+ ```
204
+ graphsight-langgraph/
205
+ ├── graphsight_langgraph/
206
+ │ ├── schema.py # AgentTrace v0.1 — zero-dependency dataclasses
207
+ │ ├── tracer.py # LangGraphTracer (BaseCallbackHandler)
208
+ │ ├── mapper.py # to_tracestate() — AgentTrace → Studio TraceState
209
+ │ └── github_ingest.py # `graphsight-github-trace` CLI (repo → trace)
210
+ ├── example/
211
+ │ └── demo_agent.py # offline end-to-end demo (writes example/out/*.json)
212
+ └── pyproject.toml
213
+ ```
214
+
215
+ ## Status & roadmap
216
+
217
+ **v0.1** — verified end-to-end against `langchain-core 1.5.0` + current
218
+ `langgraph`: spans, scored items, edges, and arm detection all captured from a
219
+ real run and rendered in the Studio.
220
+
221
+ Known limits, honestly:
222
+
223
+ - Score-key coverage is the pragmatic short list above; real-world retrievers
224
+ vary, and unrecognized keys degrade to `None` (visible, not wrong).
225
+ - Edges are read from a documented metadata convention
226
+ (`metadata["edges"]`) — retrievers must opt in to the graph-aware view.
227
+ - Async (`ainvoke` / `astream`) uses the same callback events but hasn't been
228
+ exercised in the verification run yet.
229
+
230
+ Planned next, in order: **LlamaIndex adapter** emitting the same
231
+ `AgentTrace`, then a raw **OTel span** ingestor. The schema is the contract;
232
+ the adapters are thin.
@@ -0,0 +1,205 @@
1
+ # graphsight-langgraph
2
+
3
+ **See exactly why your LangGraph agent picked the context it picked.**
4
+
5
+ One callback handler captures a [LangGraph](https://github.com/langchain-ai/langgraph)
6
+ run — every node, every retriever call, per-document scores, and (for
7
+ graph-aware retrievers) the relational paths between retrieved entities — as a
8
+ framework-neutral `AgentTrace`. One function maps it to a JSON file the
9
+ [Graphsight Studio](../frontend) renders with **zero backend**.
10
+
11
+ ```
12
+ your LangGraph agent ──▶ LangGraphTracer ──▶ AgentTrace (v0.1) ──▶ to_tracestate() ──▶ Studio /memory/import
13
+ (callbacks) neutral JSON Studio JSON full canvas render
14
+ ```
15
+
16
+ This package is standalone. It does **not** require the TraceRAG engine,
17
+ database, or SaaS stack — its only dependency is `langchain-core`.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install -e . # the tracer (depends only on langchain-core)
23
+ pip install -e ".[example]" # + langgraph, to run the demo agent
24
+ ```
25
+
26
+ Python ≥ 3.10. Verified against `langchain-core 1.5.0` + current `langgraph`.
27
+
28
+ ## Quickstart
29
+
30
+ ```python
31
+ from graphsight_langgraph import LangGraphTracer, to_tracestate
32
+
33
+ tracer = LangGraphTracer()
34
+ result = graph.invoke(inputs, config={"callbacks": [tracer]})
35
+
36
+ trace = tracer.finish(
37
+ query="why is checkout failing?",
38
+ answer=result["answer"],
39
+ )
40
+
41
+ trace.to_dict() # framework-neutral AgentTrace (schema v0.1)
42
+ to_tracestate(trace) # Studio-ready JSON → drop into /memory/import
43
+ ```
44
+
45
+ That's the whole API surface: construct, pass as a callback, `finish()`, map.
46
+
47
+ ### ⚠ The one gotcha: config propagation
48
+
49
+ LangChain only propagates callbacks into runnables that receive the run's
50
+ config. Inside a LangGraph node, **pass the node's config through** to
51
+ sub-runnables, or the tracer will see the node but not what happened inside it:
52
+
53
+ ```python
54
+ def retrieve(state, config): # ① accept config
55
+ docs = retriever.invoke(state["q"], config=config) # ② pass it through
56
+ return {"docs": docs}
57
+ ```
58
+
59
+ If your trace shows node spans but no retrievals, this is why.
60
+
61
+ ## Try it in 60 seconds (offline, no API keys)
62
+
63
+ ```bash
64
+ python example/demo_agent.py
65
+ ```
66
+
67
+ This builds and runs a real two-node LangGraph agent (`retrieve → answer`)
68
+ over an in-memory "codebase memory" corpus — a PR, a service, a person, and a
69
+ ticket, connected by edges — and traces it:
70
+
71
+ ```
72
+ answer : The regression traces to PR #4821 in checkout-service, authored by Priya N. …
73
+ spans : 3 (retrieve, CodebaseMemoryRetriever, answer)
74
+ retrieved: 4 items · 4 edges · arm=graph
75
+ latency : 3.9ms
76
+ ```
77
+
78
+ Two files land in `example/out/`:
79
+
80
+ | File | What it is |
81
+ |---|---|
82
+ | `agent_trace.json` | the neutral `AgentTrace` — what any consumer would ingest |
83
+ | `trace_state.json` | Studio-ready — drag-and-drop (or paste) into `http://localhost:5173/memory/import` |
84
+
85
+ In the Studio you'll see the full canvas: four typed entity nodes with score
86
+ chips, the traced relational path (person → PR → service, PR → ticket), the
87
+ execution timeline with per-span timings, and the retriever's arm.
88
+
89
+ Tip: with [`graphsight`](../graphsight) installed, skip the manual
90
+ drop — `graphsight example/out/trace_state.json` serves the UI locally
91
+ and opens the trace directly.
92
+
93
+ ## Trace your own GitHub repo
94
+
95
+ The package ships a dead-simple ingest CLI — one command from repo to trace:
96
+
97
+ ```bash
98
+ graphsight-github-trace langchain-ai/langgraph "who fixed the recent streaming bugs?"
99
+ graphsight graphsight_out/trace_state.json
100
+ ```
101
+
102
+ It fetches recent PRs + issues (public repos need no token; `--token` /
103
+ `GITHUB_TOKEN` for private), builds a corpus with relational edges
104
+ (`person AUTHORED pr`, `pr RESOLVES issue`, `pr TOUCHES repo`), and runs a
105
+ traced two-node LangGraph agent over it. Retrieval is two-arm in miniature:
106
+ lexical seeds + 1-hop graph expansion along the edges — simple on purpose,
107
+ and the scores shown are exactly the lexical scores computed, nothing
108
+ dressed up as semantic. Requires the `example` extra (`langgraph`).
109
+
110
+ ## What gets captured
111
+
112
+ | Source in the run | Captured as |
113
+ |---|---|
114
+ | Each LangGraph node execution | `Span(kind="node")` with monotonic-clock timing |
115
+ | Framework internals (`RunnableSequence`, `ChannelWrite`, `__start__`, …) | **filtered out** — one span per user node, no noise |
116
+ | `on_retriever_end` documents | one `RetrievedItem` per doc: id, label, kind, score, content, source |
117
+ | `Document.metadata` score keys (`score`, `relevance_score`, `similarity`, …) | item scores |
118
+ | `Document.metadata["edges"]` = `[{source, target, relation, weight}]` | relational edges (deduplicated) |
119
+ | LLM / tool calls | plain spans in the execution timeline |
120
+ | Edges present in a retrieval? | `arm = "graph"`, else `"vector"` — auto-detected |
121
+
122
+ ### Honest degradation — stated plainly
123
+
124
+ - **Vector-only retriever** (no edges in metadata) → the Studio renders a flat
125
+ scored-retrieval view instead of relational path highlighting. Still useful;
126
+ just not graph-aware.
127
+ - **No score keys** → scores stay `None` and no score chips render.
128
+ **Nothing is ever fabricated.**
129
+ - The emitted `confidence.rationale` explicitly says scores came from *your*
130
+ retriever and Graphsight does not recompute them — an imported trace never
131
+ masquerades as an engine-computed one.
132
+
133
+ ## Schema (v0.1)
134
+
135
+ `AgentTrace` is the stable contract; future adapters (LlamaIndex, raw OTel)
136
+ emit the same shape.
137
+
138
+ ```jsonc
139
+ {
140
+ "schema_version": "0.1",
141
+ "framework": "langgraph",
142
+ "query": "…",
143
+ "spans": [
144
+ { "id": "…", "name": "retrieve", "kind": "node", // node | retriever | llm | tool | chain
145
+ "parent_id": null, "start_ms": 0.0, "end_ms": 0.3, "status": "ok" }
146
+ ],
147
+ "retrievals": [
148
+ {
149
+ "span_id": "…", "query": "…",
150
+ "arm": "graph", // "vector" | "graph" (auto: edges present?)
151
+ "items": [
152
+ { "id": "pr_4821", "label": "PR #4821", "kind": "pull_request",
153
+ "score": 0.94, "vector_score": null, "graph_score": null,
154
+ "content": "…", "source_uri": "https://…", "metadata": {} }
155
+ ],
156
+ "edges": [ // optional — the graph-aware view
157
+ { "source": "pr_4821", "target": "svc_checkout", "relation": "TOUCHES", "weight": 0.9 }
158
+ ]
159
+ }
160
+ ],
161
+ "answer": "…",
162
+ "latency_ms": 3.9
163
+ }
164
+ ```
165
+
166
+ `to_tracestate()` maps this onto the Studio's `TraceState`
167
+ ([frontend/src/types/trace.ts](../frontend/src/types/trace.ts)). Free-form
168
+ `kind` strings are normalized to Studio entity types
169
+ (`pull_request → PR`, `service → Service`, `person/author → Person`,
170
+ `ticket/issue/jira → Ticket`, … fallback `Document`). Node positions are
171
+ recomputed client-side by the Studio's layout engine, so emitters never deal
172
+ with coordinates.
173
+
174
+ ## Package layout
175
+
176
+ ```
177
+ graphsight-langgraph/
178
+ ├── graphsight_langgraph/
179
+ │ ├── schema.py # AgentTrace v0.1 — zero-dependency dataclasses
180
+ │ ├── tracer.py # LangGraphTracer (BaseCallbackHandler)
181
+ │ ├── mapper.py # to_tracestate() — AgentTrace → Studio TraceState
182
+ │ └── github_ingest.py # `graphsight-github-trace` CLI (repo → trace)
183
+ ├── example/
184
+ │ └── demo_agent.py # offline end-to-end demo (writes example/out/*.json)
185
+ └── pyproject.toml
186
+ ```
187
+
188
+ ## Status & roadmap
189
+
190
+ **v0.1** — verified end-to-end against `langchain-core 1.5.0` + current
191
+ `langgraph`: spans, scored items, edges, and arm detection all captured from a
192
+ real run and rendered in the Studio.
193
+
194
+ Known limits, honestly:
195
+
196
+ - Score-key coverage is the pragmatic short list above; real-world retrievers
197
+ vary, and unrecognized keys degrade to `None` (visible, not wrong).
198
+ - Edges are read from a documented metadata convention
199
+ (`metadata["edges"]`) — retrievers must opt in to the graph-aware view.
200
+ - Async (`ainvoke` / `astream`) uses the same callback events but hasn't been
201
+ exercised in the verification run yet.
202
+
203
+ Planned next, in order: **LlamaIndex adapter** emitting the same
204
+ `AgentTrace`, then a raw **OTel span** ingestor. The schema is the contract;
205
+ the adapters are thin.
@@ -0,0 +1,30 @@
1
+ """graphsight-langgraph — capture LangGraph runs as Graphsight traces.
2
+
3
+ from graphsight_langgraph import LangGraphTracer, to_tracestate
4
+
5
+ tracer = LangGraphTracer()
6
+ result = graph.invoke(inputs, config={"callbacks": [tracer]})
7
+ trace = tracer.finish(query="...", answer=result.get("answer"))
8
+ studio_json = to_tracestate(trace) # render in Graphsight Studio
9
+ """
10
+ from .mapper import to_tracestate
11
+ from .schema import (
12
+ SCHEMA_VERSION,
13
+ AgentTrace,
14
+ Retrieval,
15
+ RetrievedItem,
16
+ Span,
17
+ TraceEdge,
18
+ )
19
+ from .tracer import LangGraphTracer
20
+
21
+ __all__ = [
22
+ "SCHEMA_VERSION",
23
+ "AgentTrace",
24
+ "LangGraphTracer",
25
+ "Retrieval",
26
+ "RetrievedItem",
27
+ "Span",
28
+ "TraceEdge",
29
+ "to_tracestate",
30
+ ]