langchain-sqlite-graph-memory 0.1.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.
- langchain_sqlite_graph_memory/__init__.py +5 -0
- langchain_sqlite_graph_memory/py.typed +0 -0
- langchain_sqlite_graph_memory/retrievers.py +146 -0
- langchain_sqlite_graph_memory-0.1.0.dist-info/METADATA +129 -0
- langchain_sqlite_graph_memory-0.1.0.dist-info/RECORD +7 -0
- langchain_sqlite_graph_memory-0.1.0.dist-info/WHEEL +4 -0
- langchain_sqlite_graph_memory-0.1.0.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""LangChain retriever over a sqlite-graph-memory index.
|
|
2
|
+
|
|
3
|
+
Паспорт (for the people who maintain this):
|
|
4
|
+
in: a query string; an index built by `sgm-index` (two files: _brain_e5.npy,
|
|
5
|
+
_brain_e5_meta.pkl) in `index_dir`.
|
|
6
|
+
out: list[Document], best first. page_content = the matched chunk; metadata =
|
|
7
|
+
path, title, date, score, rank, via_graph.
|
|
8
|
+
who calls: any LangChain / LangGraph app (`retriever.invoke(q)`, a tool, a chain).
|
|
9
|
+
what breaks it: sqlite-graph-memory changing the signatures of load_index,
|
|
10
|
+
query_sims, retrieve_candidates, expand_1hop, rerank_candidates. The test
|
|
11
|
+
suite pins that contract with fake models, so a drift fails CI, not a user.
|
|
12
|
+
рельса: none, no LLM call. Embedding + cross-encoder run locally.
|
|
13
|
+
|
|
14
|
+
It runs the SAME pipeline functions `sgm-ask` runs (imported from
|
|
15
|
+
sqlite_graph_memory.brain_ask), so recall through LangChain cannot drift away from
|
|
16
|
+
recall through the CLI or the MCP server.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, List, Literal, Optional, Union
|
|
23
|
+
|
|
24
|
+
from langchain_core.callbacks import CallbackManagerForRetrieverRun
|
|
25
|
+
from langchain_core.documents import Document
|
|
26
|
+
from langchain_core.retrievers import BaseRetriever
|
|
27
|
+
from pydantic import ConfigDict, Field, PrivateAttr
|
|
28
|
+
|
|
29
|
+
from sqlite_graph_memory import brain_ask as _sgm
|
|
30
|
+
|
|
31
|
+
GraphMode = Union[bool, Literal["auto"]]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SQLiteGraphMemoryRetriever(BaseRetriever):
|
|
35
|
+
"""Associative recall over a folder of markdown notes.
|
|
36
|
+
|
|
37
|
+
Vector search finds entry points, 1-hop ``[[wikilinks]]`` pull in linked notes,
|
|
38
|
+
a cross-encoder reranks the union. The index is two files on disk; no server.
|
|
39
|
+
|
|
40
|
+
Setup:
|
|
41
|
+
.. code-block:: bash
|
|
42
|
+
|
|
43
|
+
pip install "langchain-sqlite-graph-memory[lite]"
|
|
44
|
+
sgm-index /path/to/vault --out ./index
|
|
45
|
+
|
|
46
|
+
Instantiate:
|
|
47
|
+
.. code-block:: python
|
|
48
|
+
|
|
49
|
+
from langchain_sqlite_graph_memory import SQLiteGraphMemoryRetriever
|
|
50
|
+
|
|
51
|
+
retriever = SQLiteGraphMemoryRetriever(index_dir="./index", k=8)
|
|
52
|
+
|
|
53
|
+
Invoke:
|
|
54
|
+
.. code-block:: python
|
|
55
|
+
|
|
56
|
+
docs = retriever.invoke("why did we drop the graph database?")
|
|
57
|
+
for d in docs:
|
|
58
|
+
print(d.metadata["rank"], d.metadata["title"], d.metadata["via_graph"])
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
62
|
+
|
|
63
|
+
index_dir: Optional[str] = None
|
|
64
|
+
"""Directory with the `sgm-index` output. Default: env BRAIN_INDEX_DIR, then ./index."""
|
|
65
|
+
k: int = Field(default=_sgm.TOPN, ge=1)
|
|
66
|
+
"""How many documents to return."""
|
|
67
|
+
graph: GraphMode = "auto"
|
|
68
|
+
"""Wikilink expansion. True = always, False = never (plain vector + rerank),
|
|
69
|
+
"auto" = on, except for name/tool lookups, where it measured as noise
|
|
70
|
+
(sqlite_graph_memory.brain_ask.looks_like_entity)."""
|
|
71
|
+
fetch_k: int = Field(default=_sgm.TOPK_RETRIEVE, ge=1)
|
|
72
|
+
"""Unique notes taken from vector search before expansion and rerank."""
|
|
73
|
+
encoder: Any = None
|
|
74
|
+
"""Optional preloaded encoder with `.encode(...)`. Default: the SGM e5 model."""
|
|
75
|
+
reranker: Any = None
|
|
76
|
+
"""Optional preloaded cross-encoder with `.predict(pairs)`. Default: the SGM reranker."""
|
|
77
|
+
|
|
78
|
+
_emb: Any = PrivateAttr(default=None)
|
|
79
|
+
_meta: Any = PrivateAttr(default=None)
|
|
80
|
+
_by_base: Any = PrivateAttr(default=None)
|
|
81
|
+
|
|
82
|
+
# ------------------------------------------------------------------ loading
|
|
83
|
+
def _index_paths(self) -> tuple[Path, Path]:
|
|
84
|
+
base = Path(self.index_dir or os.getenv("BRAIN_INDEX_DIR", "index"))
|
|
85
|
+
return base / "_brain_e5.npy", base / "_brain_e5_meta.pkl"
|
|
86
|
+
|
|
87
|
+
def _ensure_loaded(self) -> None:
|
|
88
|
+
if self._meta is not None:
|
|
89
|
+
return
|
|
90
|
+
emb_path, meta_path = self._index_paths()
|
|
91
|
+
if not emb_path.exists() or not meta_path.exists():
|
|
92
|
+
raise FileNotFoundError(
|
|
93
|
+
"No sqlite-graph-memory index at %s. Build one first: "
|
|
94
|
+
"sgm-index <notes folder> (see https://github.com/tonydzi/sqlite-graph-memory)"
|
|
95
|
+
% emb_path.parent
|
|
96
|
+
)
|
|
97
|
+
self._emb, self._meta = _sgm.load_index(emb_path, meta_path)
|
|
98
|
+
self._by_base = _sgm.index_by_basename(self._meta)
|
|
99
|
+
if self.encoder is None:
|
|
100
|
+
self.encoder = _sgm.load_encoder()
|
|
101
|
+
if self.reranker is None:
|
|
102
|
+
self.reranker = _sgm.load_reranker()
|
|
103
|
+
|
|
104
|
+
def _use_graph(self, query: str) -> bool:
|
|
105
|
+
if self.graph == "auto":
|
|
106
|
+
return not _sgm.looks_like_entity(query)
|
|
107
|
+
return bool(self.graph)
|
|
108
|
+
|
|
109
|
+
# ------------------------------------------------------------------ retrieval
|
|
110
|
+
def _get_relevant_documents(
|
|
111
|
+
self,
|
|
112
|
+
query: str,
|
|
113
|
+
*,
|
|
114
|
+
run_manager: CallbackManagerForRetrieverRun,
|
|
115
|
+
**kwargs: Any,
|
|
116
|
+
) -> List[Document]:
|
|
117
|
+
k = int(kwargs.get("k", self.k))
|
|
118
|
+
self._ensure_loaded()
|
|
119
|
+
meta = self._meta
|
|
120
|
+
sims = _sgm.query_sims(self.encoder, self._emb, query)
|
|
121
|
+
base_order = _sgm.retrieve_candidates(sims, meta, topk=self.fetch_k)
|
|
122
|
+
added: List[int] = []
|
|
123
|
+
if base_order and self._use_graph(query):
|
|
124
|
+
added = _sgm.expand_1hop(sims, meta, base_order, by_base=self._by_base)
|
|
125
|
+
via_graph = set(added)
|
|
126
|
+
ranked = _sgm.rerank_candidates(
|
|
127
|
+
self.reranker, query, base_order + added, meta, topn=k
|
|
128
|
+
)
|
|
129
|
+
docs = []
|
|
130
|
+
for rank, (i, score) in enumerate(ranked, 1):
|
|
131
|
+
m = meta[i]
|
|
132
|
+
docs.append(
|
|
133
|
+
Document(
|
|
134
|
+
page_content=m.get("snippet", ""),
|
|
135
|
+
metadata={
|
|
136
|
+
"path": m.get("path"),
|
|
137
|
+
"title": m.get("title"),
|
|
138
|
+
"date": m.get("date"),
|
|
139
|
+
"score": float(score),
|
|
140
|
+
"rank": rank,
|
|
141
|
+
"via_graph": i in via_graph,
|
|
142
|
+
"source": m.get("path"),
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return docs
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: langchain-sqlite-graph-memory
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain retriever for sqlite-graph-memory: vector search + [[wikilink]] graph hop + cross-encoder rerank over your markdown notes. No server.
|
|
5
|
+
Project-URL: Homepage, https://github.com/tonydzi/langchain-sqlite-graph-memory
|
|
6
|
+
Project-URL: Repository, https://github.com/tonydzi/langchain-sqlite-graph-memory
|
|
7
|
+
Project-URL: Issues, https://github.com/tonydzi/langchain-sqlite-graph-memory/issues
|
|
8
|
+
Project-URL: Core library, https://github.com/tonydzi/sqlite-graph-memory
|
|
9
|
+
Author: Anton Dziatkovskii
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Palo Alto AI Research Lab
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: agent-memory,graph-rag,langchain,obsidian,rag,retriever,wikilinks
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Requires-Dist: langchain-core<2,>=1.0
|
|
43
|
+
Requires-Dist: sqlite-graph-memory>=0.3.6
|
|
44
|
+
Provides-Extra: embeddings
|
|
45
|
+
Requires-Dist: sqlite-graph-memory[embeddings]>=0.3.6; extra == 'embeddings'
|
|
46
|
+
Provides-Extra: lite
|
|
47
|
+
Requires-Dist: sqlite-graph-memory[lite]>=0.3.6; extra == 'lite'
|
|
48
|
+
Provides-Extra: test
|
|
49
|
+
Requires-Dist: langchain-tests<2,>=1.0; extra == 'test'
|
|
50
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# langchain-sqlite-graph-memory
|
|
54
|
+
|
|
55
|
+
A LangChain retriever over your own markdown notes, with the links between them. It runs
|
|
56
|
+
[sqlite-graph-memory](https://github.com/tonydzi/sqlite-graph-memory): vector search finds
|
|
57
|
+
the entry points, the `[[wikilinks]]` you already wrote are the graph, and a cross-encoder
|
|
58
|
+
reranks what the 1-hop expansion pulled in. Everything runs on your machine: no server, no
|
|
59
|
+
graph database, no LLM call at retrieval time.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install "langchain-sqlite-graph-memory[lite]" # ONNX models, starts in seconds
|
|
63
|
+
sgm-index /path/to/your/notes # builds ./index once
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from langchain_sqlite_graph_memory import SQLiteGraphMemoryRetriever
|
|
68
|
+
|
|
69
|
+
retriever = SQLiteGraphMemoryRetriever(index_dir="./index", k=8)
|
|
70
|
+
|
|
71
|
+
for doc in retriever.invoke("why did we drop the graph database?"):
|
|
72
|
+
print(doc.metadata["rank"], doc.metadata["title"], doc.metadata["via_graph"])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Each result is a `Document`. `page_content` holds the matched chunk of the note. `metadata`
|
|
76
|
+
holds `path`, `title`, `date`, `score` (cross-encoder), `rank`, and `via_graph`. `via_graph`
|
|
77
|
+
is `True` when the note came in only through a wikilink from a top vector hit, so you can
|
|
78
|
+
tell associative recall from direct recall.
|
|
79
|
+
|
|
80
|
+
## Options
|
|
81
|
+
|
|
82
|
+
| Argument | Default | What it does |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `index_dir` | `$BRAIN_INDEX_DIR`, else `./index` | Folder with the `sgm-index` output |
|
|
85
|
+
| `k` | 12 | Documents returned. `retriever.invoke(q, k=3)` overrides it per call |
|
|
86
|
+
| `graph` | `"auto"` | `True` always follows wikilinks, `False` never does. `"auto"` follows them except for name or tool lookups, where the hop measured as noise |
|
|
87
|
+
| `fetch_k` | 60 | Unique notes taken from vector search before expansion and rerank |
|
|
88
|
+
| `encoder`, `reranker` | SGM models | Pass your own preloaded models, or fakes in tests |
|
|
89
|
+
|
|
90
|
+
## In an agent
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from langchain_core.tools import create_retriever_tool
|
|
94
|
+
|
|
95
|
+
recall = create_retriever_tool(
|
|
96
|
+
retriever, "recall_notes",
|
|
97
|
+
"Search my notes and the notes they link to. Use for anything I wrote down before.",
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Scope, stated plainly
|
|
102
|
+
|
|
103
|
+
- **This adapter calls the core library and adds nothing to retrieval.** It calls the same
|
|
104
|
+
functions `sgm-ask` calls (`query_sims`, `retrieve_candidates`, `expand_1hop`,
|
|
105
|
+
`rerank_candidates`). A test pins this: the adapter's order must equal a direct call of
|
|
106
|
+
those functions. On a real vault of 29,474 notes (105,383 chunks), the adapter returned the same 12 notes in the
|
|
107
|
+
same order as `sgm-ask --graph`.
|
|
108
|
+
- **The index is two files on disk**, an embedding matrix (`.npy`) and note metadata
|
|
109
|
+
(`.pkl`), written by `sgm-index`. Wikilinks are read from the notes at query time.
|
|
110
|
+
SQLite stores the core library's session memory and A/B logs. The retriever needs only
|
|
111
|
+
the two index files.
|
|
112
|
+
- Embeddings: `intfloat/multilingual-e5-base`. Reranker:
|
|
113
|
+
`cross-encoder/mmarco-mMiniLMv2-L12-H384-v1`. Both work for many languages.
|
|
114
|
+
- Not implemented yet: a `VectorStore` (adding documents through LangChain) and a LangGraph
|
|
115
|
+
long-term memory store. Indexing goes through `sgm-index`.
|
|
116
|
+
|
|
117
|
+
## Tests
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install -e ".[test]"
|
|
121
|
+
pytest
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The suite includes LangChain's standard retriever tests (`langchain-tests`). It runs with
|
|
125
|
+
fake models on a four-note vault: no download, no GPU, a few seconds.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
langchain_sqlite_graph_memory/__init__.py,sha256=JW0teu2TVrBaK63pXEOh1AeDukNdvnVOwkgwuop5mFY,229
|
|
2
|
+
langchain_sqlite_graph_memory/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
langchain_sqlite_graph_memory/retrievers.py,sha256=rb7tAbxoJ4xwzPmlMZFctkD82v5kxrLRh6kUNNwohw8,5932
|
|
4
|
+
langchain_sqlite_graph_memory-0.1.0.dist-info/METADATA,sha256=f2W8XyaJZryVUUkHwgeQdgiB1hiB7GEQSPGFoDrOx-c,6089
|
|
5
|
+
langchain_sqlite_graph_memory-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
6
|
+
langchain_sqlite_graph_memory-0.1.0.dist-info/licenses/LICENSE,sha256=p88nZdrfMUiES3_MpUEwhGmqz2hQFpyMh0k0cqdpfU8,1082
|
|
7
|
+
langchain_sqlite_graph_memory-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Palo Alto AI Research Lab
|
|
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.
|