zizkadb-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,67 @@
1
+ # Environment
2
+ .env
3
+ *.env
4
+ .env.local
5
+ *.env.local
6
+ .env.staging
7
+ infra/.env.staging
8
+
9
+ # Python
10
+ __pycache__/
11
+ *.py[cod]
12
+ *.pyo
13
+ .venv/
14
+ venv/
15
+ env/
16
+ *.egg-info/
17
+ dist/
18
+ build/
19
+ .pytest_cache/
20
+
21
+ # Node
22
+ node_modules/
23
+ .next/
24
+ .turbo/
25
+ *.tsbuildinfo
26
+ *.log
27
+
28
+ # macOS
29
+ .DS_Store
30
+ # iCloud duplicate filenames (e.g. "file 2.py")
31
+ * 2.*
32
+ * 2/
33
+
34
+ # Docker volumes
35
+ postgres_data/
36
+ qdrant_data/
37
+ redis_data/
38
+
39
+ # IDE
40
+ .idea/
41
+ .vscode/
42
+ .cursor/mcp.json
43
+ .mcp.json
44
+ *.swp
45
+
46
+ # Postgres backups (created by infra/backup-postgres.sh)
47
+ infra/backups/
48
+
49
+ # Local native stack (Qdrant binary, storage)
50
+ .local/
51
+
52
+ # Qdrant data when started from repo root by mistake
53
+ storage/
54
+ .qdrant-initialized
55
+
56
+ # Extra local venvs / caches
57
+ .venv-phase0/
58
+ .venv-ci/
59
+ .ruff_cache/
60
+ core/.venv/
61
+
62
+ # Profiling artifacts
63
+ *.profraw
64
+ default.profraw
65
+
66
+ # Agents
67
+ .agents/
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.5
2
+ Name: zizkadb-langgraph
3
+ Version: 0.1.0
4
+ Summary: LangGraph middleware for ZizkaDB — automatic causal logging per graph node
5
+ License: AGPL-3.0-or-later
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: zizkadb-sdk>=0.3.0
8
+ Provides-Extra: langgraph
9
+ Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # zizkadb-langgraph
13
+
14
+ LangGraph middleware for [ZizkaDB](https://db.zizka.ai) — logs `graph_node` / `graph_node_done` with automatic `parent_id` via graph state.
15
+
16
+ ```bash
17
+ pip install zizkadb-langgraph
18
+ ```
19
+
20
+ ```python
21
+ from zizkadb import ZizkaDB
22
+ from zizkadb_langgraph import ZizkaDBLangGraphMiddleware, wrap_node
23
+
24
+ async with ZizkaDB(host="http://localhost:8000") as db:
25
+ async with db.track(agent="research-bot") as ctx:
26
+ mw = ZizkaDBLangGraphMiddleware(db, agent="research-bot", session_id=ctx.session_id)
27
+
28
+ async def retrieve(state):
29
+ return {"docs": ["a", "b"]}
30
+
31
+ graph.add_node("retrieve", wrap_node(mw, "retrieve", retrieve))
32
+ ```
33
+
34
+ See `examples/golden-path/langgraph-rag-bot/` for the full RAG support-bot reference.
@@ -0,0 +1,23 @@
1
+ # zizkadb-langgraph
2
+
3
+ LangGraph middleware for [ZizkaDB](https://db.zizka.ai) — logs `graph_node` / `graph_node_done` with automatic `parent_id` via graph state.
4
+
5
+ ```bash
6
+ pip install zizkadb-langgraph
7
+ ```
8
+
9
+ ```python
10
+ from zizkadb import ZizkaDB
11
+ from zizkadb_langgraph import ZizkaDBLangGraphMiddleware, wrap_node
12
+
13
+ async with ZizkaDB(host="http://localhost:8000") as db:
14
+ async with db.track(agent="research-bot") as ctx:
15
+ mw = ZizkaDBLangGraphMiddleware(db, agent="research-bot", session_id=ctx.session_id)
16
+
17
+ async def retrieve(state):
18
+ return {"docs": ["a", "b"]}
19
+
20
+ graph.add_node("retrieve", wrap_node(mw, "retrieve", retrieve))
21
+ ```
22
+
23
+ See `examples/golden-path/langgraph-rag-bot/` for the full RAG support-bot reference.
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "zizkadb-langgraph"
3
+ version = "0.1.0"
4
+ description = "LangGraph middleware for ZizkaDB — automatic causal logging per graph node"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "AGPL-3.0-or-later" }
8
+ dependencies = [
9
+ "zizkadb-sdk>=0.3.0",
10
+ ]
11
+
12
+ [project.optional-dependencies]
13
+ langgraph = ["langgraph>=0.2.0"]
14
+
15
+ [project.entry-points."zizkadb.integrations"]
16
+ langgraph = "zizkadb_langgraph.middleware"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["zizkadb_langgraph"]
@@ -0,0 +1,41 @@
1
+ """LangGraph middleware keeps graph state lineage in sync."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import AsyncMock, MagicMock
6
+
7
+ import pytest
8
+
9
+ from zizkadb_langgraph.middleware import STATE_LAST_EVENT_KEY, ZizkaDBLangGraphMiddleware, wrap_node
10
+
11
+
12
+ def _make_db() -> MagicMock:
13
+ counter = iter(range(1, 100))
14
+ db = MagicMock()
15
+
16
+ async def _log(**kwargs):
17
+ result = MagicMock()
18
+ result.event_id = f"evt-{next(counter)}"
19
+ return result
20
+
21
+ db.log = AsyncMock(side_effect=_log)
22
+ return db
23
+
24
+
25
+ @pytest.mark.asyncio
26
+ async def test_wrap_node_updates_caller_state_after_done():
27
+ db = _make_db()
28
+ mw = ZizkaDBLangGraphMiddleware(db, agent="bot", session_id="sess-1")
29
+ state = {STATE_LAST_EVENT_KEY: "evt-root"}
30
+
31
+ async def node(state: dict) -> dict:
32
+ return {"docs": [1]}
33
+
34
+ wrapped = wrap_node(mw, "retrieve", node)
35
+ await wrapped(state)
36
+
37
+ assert state[STATE_LAST_EVENT_KEY] == "evt-2"
38
+ calls = db.log.call_args_list
39
+ assert len(calls) == 2
40
+ assert calls[0].kwargs["parent_id"] == "evt-root"
41
+ assert calls[1].kwargs["parent_id"] == "evt-1"
@@ -0,0 +1,5 @@
1
+ """LangGraph integration for ZizkaDB."""
2
+
3
+ from zizkadb_langgraph.middleware import ZizkaDBLangGraphMiddleware, wrap_node
4
+
5
+ __all__ = ["ZizkaDBLangGraphMiddleware", "wrap_node"]
@@ -0,0 +1,78 @@
1
+ """LangGraph node wrapper — logs graph_node events with automatic parent_id."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable, Awaitable
6
+
7
+ from zizkadb import ZizkaDB
8
+
9
+ STATE_LAST_EVENT_KEY = "zizkadb_last_event_id"
10
+
11
+
12
+ class ZizkaDBLangGraphMiddleware:
13
+ """Attach to a LangGraph state dict key for cross-node lineage."""
14
+
15
+ def __init__(self, db: ZizkaDB, agent: str, session_id: str | None = None) -> None:
16
+ self.db = db
17
+ self.agent = agent
18
+ self.session_id = session_id
19
+
20
+ async def log_node(
21
+ self,
22
+ state: dict[str, Any],
23
+ node_name: str,
24
+ *,
25
+ input_summary: dict[str, Any] | None = None,
26
+ output_summary: dict[str, Any] | None = None,
27
+ phase: str = "enter",
28
+ ) -> None:
29
+ parent_id = state.get(STATE_LAST_EVENT_KEY)
30
+ event = "graph_node" if phase == "enter" else "graph_node_done"
31
+ data: dict[str, Any] = {"node": node_name, "phase": phase}
32
+ if input_summary:
33
+ data["input"] = input_summary
34
+ if output_summary:
35
+ data["output"] = output_summary
36
+ result = await self.db.log(
37
+ agent=self.agent,
38
+ event=event,
39
+ data=data,
40
+ parent_id=parent_id,
41
+ session_id=self.session_id or state.get("session_id"),
42
+ )
43
+ state[STATE_LAST_EVENT_KEY] = result.event_id
44
+
45
+
46
+ def wrap_node(
47
+ middleware: ZizkaDBLangGraphMiddleware,
48
+ node_name: str,
49
+ fn: Callable[..., Awaitable[dict[str, Any]]],
50
+ ) -> Callable[..., Awaitable[dict[str, Any]]]:
51
+ """Wrap an async LangGraph node to log enter/exit with causal links."""
52
+
53
+ async def wrapped(state: dict[str, Any], *args: Any, **kwargs: Any) -> dict[str, Any]:
54
+ await middleware.log_node(
55
+ state,
56
+ node_name,
57
+ input_summary={"keys": list(state.keys())[:20]},
58
+ phase="enter",
59
+ )
60
+ out = await fn(state, *args, **kwargs)
61
+ if isinstance(out, dict):
62
+ merged = {**state, **out}
63
+ else:
64
+ merged = dict(state)
65
+ await middleware.log_node(
66
+ merged,
67
+ node_name,
68
+ output_summary={"keys": list(merged.keys())[:20]},
69
+ phase="done",
70
+ )
71
+ # Propagate the done-phase event id back to the caller's state dict.
72
+ # log_node updates `merged` (a copy), so without this the next node reads
73
+ # a stale zizkadb_last_event_id and forks the causal chain.
74
+ state[STATE_LAST_EVENT_KEY] = merged[STATE_LAST_EVENT_KEY]
75
+ return out if isinstance(out, dict) else state
76
+
77
+ wrapped.__name__ = getattr(fn, "__name__", node_name)
78
+ return wrapped