langgraph-velesdb 4.2.0__py3-none-any.whl

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
+ """LangGraph memory tools for VelesDB — the ``why()`` knowledge-graph wedge.
2
+
3
+ Drop VelesDB's local-first agent memory into any LangGraph agent:
4
+
5
+ >>> from langgraph.prebuilt import create_react_agent
6
+ >>> from langgraph_velesdb import make_memory_tools
7
+ >>>
8
+ >>> agent = create_react_agent(llm, make_memory_tools("./agent_memory"))
9
+
10
+ ``make_memory_tools`` returns the full VelesDB memory tool set: ``remember``,
11
+ ``recall``, ``recall_where``, ``recall_fused``, ``relate``, ``forget``,
12
+ ``feedback``, and the differentiator ``why`` — which answers with the
13
+ best-matching memory *plus* the connected subgraph a vector recall is blind
14
+ to — plus ``save_working_context``/``load_working_context`` for cross-run
15
+ resumption. The store is on disk, so memory persists across agent runs.
16
+ """
17
+
18
+ from langgraph_velesdb.tools import make_memory_tools
19
+
20
+ __all__ = ["make_memory_tools"]
21
+ __version__ = "4.2.0"
File without changes
@@ -0,0 +1,245 @@
1
+ """LangGraph / LangChain tools backed by VelesDB's high-level ``MemoryService``.
2
+
3
+ A thin adapter: each tool is a small method on ``_MemoryToolkit``, bound to one
4
+ ``MemoryService`` (the core). No state or logic lives here beyond shaping
5
+ inputs/outputs for the tool layer — the engine does the work. Methods live on
6
+ a class (rather than as closures inside ``make_memory_tools``) so each tool's
7
+ own cyclomatic complexity is what a linter measures, instead of all ten
8
+ rolling up into one function.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Optional
14
+
15
+ from langchain_core.tools import BaseTool, StructuredTool # nosemgrep: ai.python.detect-langchain.detect-langchain # this package IS the LangChain connector
16
+
17
+ try:
18
+ from velesdb import MemoryService
19
+ except ImportError as exc: # pragma: no cover - import guard
20
+ raise ImportError(
21
+ "velesdb is required for langgraph-velesdb. Install with: pip install velesdb"
22
+ ) from exc
23
+
24
+
25
+ def make_memory_tools(
26
+ path: Optional[str] = None,
27
+ *,
28
+ service: Optional["MemoryService"] = None,
29
+ ) -> list[BaseTool]:
30
+ """Build the full VelesDB memory tool set for a LangGraph agent.
31
+
32
+ Returns ``remember`` / ``recall`` / ``recall_where`` / ``recall_fused`` /
33
+ ``relate`` / ``forget`` / ``feedback`` / ``why`` / ``save_working_context``
34
+ / ``load_working_context``.
35
+
36
+ Pass a ``path`` to open a local on-disk store (offline hash embedder), or a
37
+ pre-configured ``service`` (e.g. an Ollama-backed ``MemoryService``) to reuse
38
+ it. The store is on disk, so memory persists across agent runs.
39
+ """
40
+ toolkit = _MemoryToolkit(_resolve_service(path, service))
41
+ return [
42
+ StructuredTool.from_function(fn)
43
+ for fn in (
44
+ toolkit.remember,
45
+ toolkit.recall,
46
+ toolkit.recall_where,
47
+ toolkit.recall_fused,
48
+ toolkit.relate,
49
+ toolkit.forget,
50
+ toolkit.feedback,
51
+ toolkit.why,
52
+ toolkit.save_working_context,
53
+ toolkit.load_working_context,
54
+ )
55
+ ]
56
+
57
+
58
+ class _MemoryToolkit:
59
+ """Binds one ``MemoryService`` to the ten tool methods ``make_memory_tools``
60
+ wraps into ``StructuredTool``\\ s. Not part of the public API."""
61
+
62
+ def __init__(self, mem: "MemoryService") -> None:
63
+ self._mem = mem
64
+
65
+ def remember(
66
+ self,
67
+ fact: str,
68
+ links: Optional[list[list[Any]]] = None,
69
+ metadata: Optional[dict[str, Any]] = None,
70
+ ttl_seconds: Optional[int] = None,
71
+ ) -> int:
72
+ """Store a fact in long-term memory and return its id.
73
+
74
+ ``links`` optionally wires typed outgoing edges to existing memory ids
75
+ at write time — each item is ``[target_id, relation]`` (equivalent to
76
+ calling ``relate`` right after). ``metadata`` attaches key/value pairs
77
+ ``recall_where``/``recall_fused`` can later filter on (every fact also
78
+ gets an automatic ``_veles_date`` stamp — today's date as a
79
+ ``YYYYMMDD`` int — unless you set that key yourself). ``ttl_seconds``
80
+ makes the fact expire, and stop being recalled, after that many
81
+ seconds. All three are optional; a bare ``remember(fact)`` behaves
82
+ exactly as before.
83
+ """
84
+ return self._mem.remember(
85
+ fact,
86
+ links=_as_tuples(links),
87
+ metadata=metadata,
88
+ ttl_seconds=ttl_seconds,
89
+ )
90
+
91
+ def recall(self, query: str, k: int = 5) -> list[dict[str, Any]]:
92
+ """Recall memories semantically similar to the query (vector search).
93
+
94
+ Each hit's ``metadata`` is always ``None`` on this path — use
95
+ ``recall_where`` (or ``recall_fused``) when you need each memory's
96
+ metadata, including the automatic ``_veles_date`` stamp.
97
+ """
98
+ return self._mem.recall(query, k)
99
+
100
+ def recall_where(
101
+ self,
102
+ query: str,
103
+ filters: Optional[list[list[Any]]] = None,
104
+ k: int = 5,
105
+ ) -> list[dict[str, Any]]:
106
+ """Recall memories similar to the query, filtered by metadata, with
107
+ each hit's metadata attached.
108
+
109
+ ``filters`` is a list of ``[field, op, value]`` triples, ``op`` one of
110
+ ``"eq"``/``"ne"``/``"lt"``/``"le"``/``"gt"``/``"ge"`` — e.g.
111
+ ``[["_veles_date", "ge", 20260101]]`` keeps only facts stamped on or
112
+ after that date. Pass an empty list (the default) for no filtering.
113
+ Unlike plain ``recall``, every hit's ``metadata`` dict is returned,
114
+ including the automatic ``_veles_date`` stamp every fact gets at
115
+ write time.
116
+ """
117
+ return self._mem.recall_where(query, _as_tuples(filters) or [], k)
118
+
119
+ def recall_fused(
120
+ self,
121
+ query: str,
122
+ k: int = 5,
123
+ metadata_filter: Optional[dict[str, Any]] = None,
124
+ date_field: Optional[str] = None,
125
+ ) -> Any:
126
+ """Fused vector + graph recall: walks the graph from the top hit and
127
+ folds in any connected fact a plain ``recall`` would miss.
128
+
129
+ ``metadata_filter`` is an optional exact-match metadata filter. Pass
130
+ ``date_field="_veles_date"`` (the automatic stamp every fact gets) to
131
+ get back a dict with a chronological, date-prefixed timeline
132
+ (``dated_context``) instead of a plain list — useful for "what
133
+ happened, in order" questions. Omit ``date_field`` for the plain-list
134
+ form.
135
+ """
136
+ return self._mem.recall_fused(query, k, metadata_filter, date_field=date_field)
137
+
138
+ def relate(self, from_id: int, to_id: int, relation: str) -> int:
139
+ """Link two memories with a typed edge (``from_id`` -> ``to_id``)."""
140
+ return self._mem.relate(from_id, to_id, relation)
141
+
142
+ def forget(self, memory_id: int) -> bool:
143
+ """Delete a memory by id.
144
+
145
+ Returns ``True`` if a memory actually existed under ``memory_id`` and
146
+ was removed, ``False`` if there was nothing stored there (a stale id
147
+ or a typo) — a no-op, not an error.
148
+ """
149
+ return self._mem.forget(memory_id)
150
+
151
+ def feedback(self, memory_id: int, success: bool) -> Any:
152
+ """Reinforce or weaken a memory after using it, and return its
153
+ updated confidence in ``[0.0, 1.0]``.
154
+
155
+ Call this once you know whether a memory you recalled actually helped
156
+ answer the question (``success=True``) or was misleading/irrelevant
157
+ (``success=False``). This closes the self-improving loop: future
158
+ ``recall``/``recall_where``/``recall_fused`` calls rank
159
+ higher-confidence memories higher.
160
+
161
+ If the installed ``velesdb`` predates this method, returns
162
+ ``{"error": "..."}`` instead of raising, telling you to upgrade.
163
+ """
164
+ if not hasattr(self._mem, "feedback"):
165
+ return _unsupported("feedback")
166
+ return self._mem.feedback(memory_id, success)
167
+
168
+ def why(self, question: str, max_hops: int = 2) -> dict[str, Any]:
169
+ """Explain something: the best-matching memory plus the connected
170
+ subgraph reachable through typed links — context a plain recall misses."""
171
+ return self._mem.why(question, max_hops)
172
+
173
+ def save_working_context(
174
+ self,
175
+ project: str,
176
+ session: str,
177
+ working: dict[str, Any],
178
+ ) -> Any:
179
+ """Persist the current working state under ``project`` + ``session``
180
+ so a later run can resume exactly where this one left off.
181
+
182
+ ``working`` is free-form but conventionally shaped like
183
+ ``{"goal": str, "active_constraints": [...], "verified_facts": [...],
184
+ "open_hypotheses": [...], "decisions": [...], "exact_evidence": [...],
185
+ "pending_actions": [...]}`` — call this near the end of a session, or
186
+ whenever the plan materially changes. Saving again under the same
187
+ ``project`` + ``session`` replaces the previous state (not a merge).
188
+
189
+ If the installed ``velesdb`` predates this method, returns
190
+ ``{"error": "..."}`` instead of raising, telling you to upgrade.
191
+ """
192
+ if not hasattr(self._mem, "save_working_context"):
193
+ return _unsupported("save_working_context")
194
+ return self._mem.save_working_context(project, session, working)
195
+
196
+ def load_working_context(self, project: str, session: str) -> Any:
197
+ """Load the working context previously saved under ``project`` +
198
+ ``session`` by ``save_working_context``.
199
+
200
+ Call this at the very start of a new run, before doing anything else,
201
+ to resume a prior session instead of restarting from scratch. Returns
202
+ ``None`` when nothing was ever saved under that exact
203
+ ``project``/``session`` pair — not an error, just a fresh start.
204
+
205
+ If the installed ``velesdb`` predates this method, returns
206
+ ``{"error": "..."}`` instead of raising, telling you to upgrade.
207
+ """
208
+ if not hasattr(self._mem, "load_working_context"):
209
+ return _unsupported("load_working_context")
210
+ return self._mem.load_working_context(project, session)
211
+
212
+
213
+ def _resolve_service(
214
+ path: Optional[str], service: Optional["MemoryService"]
215
+ ) -> "MemoryService":
216
+ if service is not None:
217
+ return service
218
+ if path is None:
219
+ raise ValueError("make_memory_tools requires either `path` or `service`")
220
+ return MemoryService(path)
221
+
222
+
223
+ def _as_tuples(pairs: Optional[list[list[Any]]]) -> Optional[list[tuple[Any, ...]]]:
224
+ """``[[a, b, ...], ...]`` -> ``[(a, b, ...), ...]``, or ``None`` if falsy.
225
+
226
+ Shared by ``remember``'s ``links`` and ``recall_where``'s ``filters``: both
227
+ accept plain JSON-friendly lists of lists from the LLM and forward tuples
228
+ to the binding, which expects ``List[Tuple[...]]``.
229
+ """
230
+ if not pairs:
231
+ return None
232
+ return [tuple(p) for p in pairs]
233
+
234
+
235
+ def _unsupported(method: str) -> dict[str, str]:
236
+ """Actionable tool-error payload for a ``MemoryService`` method missing on
237
+ the installed ``velesdb`` (older than this package expects).
238
+
239
+ Returned as a normal tool result rather than raised, so a missing method
240
+ surfaces to the LLM as a tool error it can react to instead of an
241
+ uncaught ``AttributeError`` that would kill the whole LangGraph run.
242
+ """
243
+ return {
244
+ "error": f"{method} requires velesdb > 3.12.0 — upgrade with `pip install -U velesdb`"
245
+ }
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: langgraph-velesdb
3
+ Version: 4.2.0
4
+ Summary: LangGraph memory tools for VelesDB: drop the why() knowledge-graph memory wedge into any LangGraph agent.
5
+ Author-email: VelesDB Team <contact@wiscale.fr>
6
+ License: MIT
7
+ Project-URL: Homepage, https://velesdb.com
8
+ Project-URL: Repository, https://github.com/cyberlife-coder/VelesDB
9
+ Keywords: langgraph,langchain,velesdb,agent-memory,knowledge-graph,local-first,why,llm
10
+ Classifier: Development Status :: 4 - Beta
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: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: langchain-core<2.0.0,>=0.3.0
22
+ Requires-Dist: velesdb>=3.12.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest<9.0,>=7.0; extra == "dev"
25
+ Requires-Dist: langgraph>=0.2.0; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # langgraph-velesdb
29
+
30
+ Drop VelesDB's local-first agent memory — the **`why()` knowledge-graph wedge** —
31
+ into any [LangGraph](https://github.com/langchain-ai/langgraph) agent in three lines.
32
+
33
+ Most agent memory is vector recall: it finds text that *looks like* the query.
34
+ VelesDB **connects** memories with typed links, so `why()` answers with the
35
+ best-matching memory **plus the connected subgraph** — context that shares no
36
+ words with the question, which a plain vector recall is blind to. The store is on
37
+ disk, so memory persists across agent runs.
38
+
39
+ ![recall() finds the booking but misses the reason; why() reaches it through typed links, across a session restart](https://raw.githubusercontent.com/cyberlife-coder/VelesDB/develop/examples/agent_memory/why_across_sessions.gif)
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install langgraph-velesdb # pulls langchain-core + velesdb
45
+ ```
46
+
47
+ ## Use
48
+
49
+ ```python
50
+ from langgraph.prebuilt import create_react_agent
51
+ from langgraph_velesdb import make_memory_tools
52
+
53
+ # Offline by default (hash embedder); pass a configured MemoryService for Ollama.
54
+ agent = create_react_agent(llm, make_memory_tools("./agent_memory"))
55
+ ```
56
+
57
+ `make_memory_tools` returns ten tools the agent can call:
58
+
59
+ | Tool | What it does |
60
+ |------------------------|--------------|
61
+ | `remember` | store a fact (optionally with `links`, `metadata`, `ttl_seconds`), return its id |
62
+ | `recall` | semantic (vector) recall — no metadata |
63
+ | `recall_where` | filtered recall (metadata `[field, op, value]` triples) **with metadata attached**, e.g. the auto `_veles_date` stamp |
64
+ | `recall_fused` | fused vector + graph recall; pass `date_field="_veles_date"` for a dated timeline |
65
+ | `relate` | link two memories with a typed edge |
66
+ | `forget` | delete a memory by id (`True` if it existed, `False` if it was already gone) |
67
+ | `feedback` | reinforce or weaken a memory after using it, closing the self-improving recall loop |
68
+ | `why` | best-matching memory **+ its connected subgraph** (the wedge) |
69
+ | `save_working_context` | persist the current goal/constraints/decisions/pending actions under a project + session |
70
+ | `load_working_context` | resume a prior run's working context — call at the start of a session |
71
+
72
+ For a pre-configured backend (e.g. Ollama embeddings), build the service yourself
73
+ and pass it through:
74
+
75
+ ```python
76
+ from velesdb import MemoryService
77
+ tools = make_memory_tools(service=MemoryService("./agent_memory"))
78
+ ```
79
+
80
+ ### Cross-run resumption
81
+
82
+ `save_working_context` / `load_working_context` let an agent pick up a task
83
+ across separate runs instead of restarting from scratch:
84
+
85
+ `make_memory_tools` returns a *list* of tools, so index them by name first
86
+ (the same pattern LangGraph's `ToolNode`/`create_react_agent` use internally):
87
+
88
+ ```python
89
+ tools = {t.name: t for t in make_memory_tools("./agent_memory")}
90
+
91
+ tools["save_working_context"].invoke({
92
+ "project": "veles",
93
+ "session": "issue-1546",
94
+ "working": {
95
+ "goal": "ship the langgraph tool set",
96
+ "pending_actions": ["open the PR"],
97
+ },
98
+ })
99
+
100
+ # ... next run, same project + session:
101
+ tools["load_working_context"].invoke({"project": "veles", "session": "issue-1546"})
102
+ ```
103
+
104
+ ### Dated recall
105
+
106
+ Every `remember`-ed fact is auto-stamped with `_veles_date` (today, as a
107
+ `YYYYMMDD` int) unless you set that metadata key yourself. Point
108
+ `recall_fused` at it to get a chronological timeline instead of a ranked list:
109
+
110
+ ```python
111
+ tools = {t.name: t for t in make_memory_tools("./agent_memory")}
112
+
113
+ tools["recall_fused"].invoke({"query": "what changed this week", "date_field": "_veles_date"})
114
+ # -> {"memories": [...], "dated_context": "...", "now": "..."}
115
+ ```
116
+
117
+ ## Compatibility
118
+
119
+ This package requires `velesdb>=3.12.0`, the highest version published to
120
+ PyPI at the time of writing. `feedback`, `save_working_context`,
121
+ `load_working_context`, and the automatic `_veles_date` metadata stamp landed
122
+ in `velesdb`/`velesdb-memory` *after* the 3.12.0 release cut and are not yet
123
+ in a published wheel. On a plain 3.12.0 install those three tools detect the
124
+ missing binding method at call time and return an error payload instead of
125
+ raising, e.g.:
126
+
127
+ ```json
128
+ {"error": "feedback requires velesdb > 3.12.0 — upgrade with `pip install -U velesdb`"}
129
+ ```
130
+
131
+ so a single unsupported call surfaces to the agent as a normal tool result it
132
+ can react to, instead of an uncaught `AttributeError` killing the whole graph
133
+ run. `recall_where`/`recall_fused` still work, but their `metadata` stays
134
+ empty for auto-dating until you upgrade. `forget` works on 3.12.0 too, but
135
+ returns `None` instead of a `True`/`False` existed-or-not signal. The floor
136
+ will be bumped again once a `velesdb` release past 3.12.0 ships.
137
+
138
+ `list_working_contexts` (browse saved sessions for a project) is not exposed
139
+ here: it exists on the WASM and MCP surfaces but not yet on the `velesdb`
140
+ Python binding (`MemoryService`), and this package only ever calls the
141
+ binding — no memory logic is reimplemented in this MIT-licensed integration.
142
+ It will be added once the Python binding grows the method.
143
+
144
+ ## License
145
+
146
+ MIT — see [LICENSE](./LICENSE). Wraps [`velesdb`](https://pypi.org/project/velesdb/),
147
+ which is under the VelesDB Core License 1.0.
@@ -0,0 +1,8 @@
1
+ langgraph_velesdb/__init__.py,sha256=7kTqsXM0iReyVMAZ1aK2iFdffEaPxqzyJrq3_p_0jYU,907
2
+ langgraph_velesdb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ langgraph_velesdb/tools.py,sha256=6R9WBKE-L0b41gE2aC-a0aZFT5pwuZktiKSIsNAsLck,10314
4
+ langgraph_velesdb-4.2.0.dist-info/licenses/LICENSE,sha256=hu80gXApIu5PnZ5cS0G4aLlhs-SqMhOT4OsqHr8Z5Is,1298
5
+ langgraph_velesdb-4.2.0.dist-info/METADATA,sha256=oSGR3U_IQq2tOjuKi7gXf83GSeFE41tKHpYjD-36ReA,6437
6
+ langgraph_velesdb-4.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
7
+ langgraph_velesdb-4.2.0.dist-info/top_level.txt,sha256=Vhmq6JWJ2ybjnubG6qMA3sxvF_v4FvpjNPL1dEIQkdA,18
8
+ langgraph_velesdb-4.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Wiscale France
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.
22
+
23
+ ---
24
+
25
+ Note: This MIT license applies to the LangChain integration code only.
26
+ VelesDB Core itself is licensed under the Elastic License 2.0 (ELv2).
27
+ See https://github.com/cyberlife-coder/VelesDB for VelesDB Core licensing.
@@ -0,0 +1 @@
1
+ langgraph_velesdb