mentedb-langchain 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.
- mentedb_langchain-0.1.0/.gitignore +9 -0
- mentedb_langchain-0.1.0/PKG-INFO +8 -0
- mentedb_langchain-0.1.0/README.md +114 -0
- mentedb_langchain-0.1.0/mentedb_langchain/__init__.py +5 -0
- mentedb_langchain-0.1.0/mentedb_langchain/chat_history.py +58 -0
- mentedb_langchain-0.1.0/mentedb_langchain/memory.py +63 -0
- mentedb_langchain-0.1.0/mentedb_langchain/retriever.py +53 -0
- mentedb_langchain-0.1.0/pyproject.toml +14 -0
- mentedb_langchain-0.1.0/tests/test_memory.py +96 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# mentedb-langchain
|
|
2
|
+
|
|
3
|
+
MenteDB integration for LangChain and LangGraph. Gives your agents persistent, cognitive memory that goes beyond simple vector retrieval.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install mentedb-langchain
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Components
|
|
12
|
+
|
|
13
|
+
### MenteDBMemory
|
|
14
|
+
|
|
15
|
+
A LangChain compatible memory backend that stores conversation context in MenteDB. Unlike buffer or summary memory, MenteDBMemory uses hybrid search (vector similarity, tag filtering, temporal decay) to assemble the most relevant context for each turn.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from mentedb_langchain import MenteDBMemory
|
|
19
|
+
from langchain.chains import ConversationChain
|
|
20
|
+
from langchain_openai import ChatOpenAI
|
|
21
|
+
|
|
22
|
+
memory = MenteDBMemory(
|
|
23
|
+
data_dir="./agent-memory",
|
|
24
|
+
agent_id="my-agent",
|
|
25
|
+
token_budget=4096,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
chain = ConversationChain(
|
|
29
|
+
llm=ChatOpenAI(),
|
|
30
|
+
memory=memory,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
chain.predict(input="What database should I use for time series data?")
|
|
34
|
+
chain.predict(input="Tell me more about that recommendation")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### MenteDBRetriever
|
|
38
|
+
|
|
39
|
+
A LangChain compatible retriever that uses MenteDB hybrid search. Supports optional tag filtering and agent scoping to narrow results.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from mentedb_langchain import MenteDBRetriever
|
|
43
|
+
from langchain.chains import RetrievalQA
|
|
44
|
+
from langchain_openai import ChatOpenAI
|
|
45
|
+
|
|
46
|
+
retriever = MenteDBRetriever(
|
|
47
|
+
data_dir="./agent-memory",
|
|
48
|
+
k=10,
|
|
49
|
+
tags=["backend", "architecture"],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
chain = RetrievalQA.from_chain_type(
|
|
53
|
+
llm=ChatOpenAI(),
|
|
54
|
+
retriever=retriever,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
chain.invoke("What were the key decisions about our database migration?")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### MenteDBChatHistory
|
|
61
|
+
|
|
62
|
+
Persistent chat history with cognitive tracking. MenteDB stores messages alongside reasoning trajectories, knowledge gaps, and contradiction signals so the agent's memory improves over time.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from mentedb_langchain import MenteDBChatHistory
|
|
66
|
+
|
|
67
|
+
history = MenteDBChatHistory(
|
|
68
|
+
session_id="session-123",
|
|
69
|
+
data_dir="./agent-memory",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
history.add_user_message("What database should I use?")
|
|
73
|
+
history.add_ai_message("I recommend PostgreSQL for your use case.")
|
|
74
|
+
|
|
75
|
+
messages = history.messages
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage with LangGraph
|
|
79
|
+
|
|
80
|
+
MenteDB works naturally with LangGraph. Use `MenteDBMemory` as a checkpointer or context source within graph nodes:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from mentedb_langchain import MenteDBMemory
|
|
84
|
+
|
|
85
|
+
memory = MenteDBMemory(data_dir="./graph-memory", agent_id="planner")
|
|
86
|
+
|
|
87
|
+
def plan_node(state):
|
|
88
|
+
context = memory.load_memory_variables({"input": state["task"]})
|
|
89
|
+
# Use context to inform planning
|
|
90
|
+
return {**state, "context": context}
|
|
91
|
+
|
|
92
|
+
def reflect_node(state):
|
|
93
|
+
memory.save_context(
|
|
94
|
+
inputs={"input": state["task"]},
|
|
95
|
+
outputs={"output": state["result"]},
|
|
96
|
+
)
|
|
97
|
+
return state
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
All components accept `data_dir` to specify where MenteDB stores its data. For multi agent setups, use `agent_id` to isolate each agent's memory space.
|
|
103
|
+
|
|
104
|
+
| Parameter | Default | Description |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `data_dir` | `./mentedb-data` | Path to the MenteDB data directory |
|
|
107
|
+
| `agent_id` | `None` | Optional agent identifier for memory isolation |
|
|
108
|
+
| `token_budget` | `4096` | Maximum tokens for assembled context (MenteDBMemory) |
|
|
109
|
+
| `k` | `10` | Number of results to return (MenteDBRetriever) |
|
|
110
|
+
| `tags` | `None` | Tag filter for retrieval (MenteDBRetriever) |
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
Apache 2.0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""LangChain chat history backed by MenteDB."""
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from mentedb import MenteDB
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MenteDBChatHistory:
|
|
8
|
+
"""Stores chat message history in MenteDB with persistent recall.
|
|
9
|
+
|
|
10
|
+
Each message is stored as an episodic memory, enabling semantic
|
|
11
|
+
search across conversation history and cross-session continuity.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from mentedb_langchain import MenteDBChatHistory
|
|
15
|
+
|
|
16
|
+
history = MenteDBChatHistory(
|
|
17
|
+
session_id="session-123",
|
|
18
|
+
data_dir="./agent-memory",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
history.add_user_message("What database should I use?")
|
|
22
|
+
history.add_ai_message("I recommend PostgreSQL.")
|
|
23
|
+
|
|
24
|
+
messages = history.messages
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, session_id: str, data_dir: str = "./mentedb-data",
|
|
28
|
+
agent_id: Optional[str] = None):
|
|
29
|
+
self.session_id = session_id
|
|
30
|
+
self.data_dir = data_dir
|
|
31
|
+
self.agent_id = agent_id
|
|
32
|
+
self._db = MenteDB(data_dir)
|
|
33
|
+
self._messages: list = []
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def messages(self) -> list:
|
|
37
|
+
return self._messages
|
|
38
|
+
|
|
39
|
+
def add_user_message(self, message: str) -> None:
|
|
40
|
+
self._messages.append({"role": "user", "content": message})
|
|
41
|
+
self._db.store(
|
|
42
|
+
f"[{self.session_id}] User: {message}",
|
|
43
|
+
memory_type="episodic",
|
|
44
|
+
agent_id=self.agent_id,
|
|
45
|
+
tags=["chat_history", self.session_id],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def add_ai_message(self, message: str) -> None:
|
|
49
|
+
self._messages.append({"role": "assistant", "content": message})
|
|
50
|
+
self._db.store(
|
|
51
|
+
f"[{self.session_id}] Assistant: {message}",
|
|
52
|
+
memory_type="episodic",
|
|
53
|
+
agent_id=self.agent_id,
|
|
54
|
+
tags=["chat_history", self.session_id],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def clear(self) -> None:
|
|
58
|
+
self._messages.clear()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""LangChain memory backed by MenteDB."""
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from mentedb import MenteDB
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MenteDBMemory:
|
|
8
|
+
"""LangChain compatible memory that stores conversation context in MenteDB.
|
|
9
|
+
|
|
10
|
+
Usage with LangChain:
|
|
11
|
+
from mentedb_langchain import MenteDBMemory
|
|
12
|
+
|
|
13
|
+
memory = MenteDBMemory(data_dir="./agent-memory", agent_id="my-agent")
|
|
14
|
+
|
|
15
|
+
memory.save_context(
|
|
16
|
+
inputs={"input": "What database should I use?"},
|
|
17
|
+
outputs={"output": "I recommend PostgreSQL for your use case."}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
context = memory.load_memory_variables({"input": "Tell me more about that database"})
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
memory_key: str = "history"
|
|
24
|
+
|
|
25
|
+
def __init__(self, data_dir: str = "./mentedb-data", agent_id: Optional[str] = None,
|
|
26
|
+
token_budget: int = 4096, return_messages: bool = False):
|
|
27
|
+
self.data_dir = data_dir
|
|
28
|
+
self.agent_id = agent_id
|
|
29
|
+
self.token_budget = token_budget
|
|
30
|
+
self.return_messages = return_messages
|
|
31
|
+
self._db = MenteDB(data_dir)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def memory_variables(self) -> List[str]:
|
|
35
|
+
return [self.memory_key]
|
|
36
|
+
|
|
37
|
+
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
38
|
+
"""Load relevant memories based on the current input."""
|
|
39
|
+
query = inputs.get("input", "")
|
|
40
|
+
if not query:
|
|
41
|
+
return {self.memory_key: ""}
|
|
42
|
+
results = self._db.search_text(query, k=10)
|
|
43
|
+
if not results:
|
|
44
|
+
return {self.memory_key: ""}
|
|
45
|
+
texts = []
|
|
46
|
+
for r in results:
|
|
47
|
+
mem = self._db.get_memory(r.id)
|
|
48
|
+
if mem:
|
|
49
|
+
texts.append(mem["content"])
|
|
50
|
+
return {self.memory_key: "\n".join(texts)}
|
|
51
|
+
|
|
52
|
+
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
|
|
53
|
+
"""Save a conversation turn as episodic memory."""
|
|
54
|
+
input_text = inputs.get("input", "")
|
|
55
|
+
output_text = outputs.get("output", "")
|
|
56
|
+
content = f"User: {input_text}\nAssistant: {output_text}"
|
|
57
|
+
self._db.store(content, memory_type="episodic", agent_id=self.agent_id)
|
|
58
|
+
|
|
59
|
+
def clear(self) -> None:
|
|
60
|
+
"""Clear all memories by closing and reopening."""
|
|
61
|
+
self._db.close()
|
|
62
|
+
self._db = MenteDB(self.data_dir)
|
|
63
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""LangChain retriever backed by MenteDB vector search."""
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from mentedb import MenteDB
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MenteDBRetriever:
|
|
8
|
+
"""LangChain compatible retriever using MenteDB hybrid search.
|
|
9
|
+
|
|
10
|
+
Combines vector similarity, tag filtering, and temporal relevance
|
|
11
|
+
in a single retrieval call.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from mentedb_langchain import MenteDBRetriever
|
|
15
|
+
|
|
16
|
+
retriever = MenteDBRetriever(
|
|
17
|
+
data_dir="./agent-memory",
|
|
18
|
+
k=10,
|
|
19
|
+
tags=["backend", "architecture"],
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
docs = retriever.get_relevant_documents("database migration strategy")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, data_dir: str = "./mentedb-data", k: int = 10,
|
|
26
|
+
tags: Optional[List[str]] = None, agent_id: Optional[str] = None):
|
|
27
|
+
self.data_dir = data_dir
|
|
28
|
+
self.k = k
|
|
29
|
+
self.tags = tags
|
|
30
|
+
self.agent_id = agent_id
|
|
31
|
+
self._db = MenteDB(data_dir)
|
|
32
|
+
|
|
33
|
+
def get_relevant_documents(self, query: str) -> list:
|
|
34
|
+
"""Retrieve relevant memories as dicts with id, content, and score."""
|
|
35
|
+
results = self._db.search_text(query, k=self.k, tags=self.tags)
|
|
36
|
+
docs = []
|
|
37
|
+
for r in results:
|
|
38
|
+
mem = self._db.get_memory(r.id)
|
|
39
|
+
if mem:
|
|
40
|
+
docs.append({
|
|
41
|
+
"page_content": mem["content"],
|
|
42
|
+
"metadata": {
|
|
43
|
+
"id": r.id,
|
|
44
|
+
"score": r.score,
|
|
45
|
+
"memory_type": mem.get("memory_type", ""),
|
|
46
|
+
"tags": mem.get("tags", []),
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
return docs
|
|
50
|
+
|
|
51
|
+
async def aget_relevant_documents(self, query: str) -> list:
|
|
52
|
+
"""Async version of get_relevant_documents."""
|
|
53
|
+
return self.get_relevant_documents(query)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mentedb-langchain"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MenteDB integration for LangChain and LangGraph"
|
|
5
|
+
license = "Apache-2.0"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"mentedb>=0.7.0",
|
|
9
|
+
"langchain-core>=0.3",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Tests for mentedb_langchain integration."""
|
|
2
|
+
from mentedb_langchain.memory import MenteDBMemory
|
|
3
|
+
from mentedb_langchain.retriever import MenteDBRetriever
|
|
4
|
+
from mentedb_langchain.chat_history import MenteDBChatHistory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestMenteDBMemory:
|
|
8
|
+
def test_default_construction(self):
|
|
9
|
+
memory = MenteDBMemory()
|
|
10
|
+
assert memory.data_dir == "./mentedb-data"
|
|
11
|
+
assert memory.agent_id is None
|
|
12
|
+
assert memory.token_budget == 4096
|
|
13
|
+
assert memory.return_messages is False
|
|
14
|
+
|
|
15
|
+
def test_custom_construction(self):
|
|
16
|
+
memory = MenteDBMemory(
|
|
17
|
+
data_dir="./custom-dir",
|
|
18
|
+
agent_id="agent-1",
|
|
19
|
+
token_budget=8192,
|
|
20
|
+
return_messages=True,
|
|
21
|
+
)
|
|
22
|
+
assert memory.data_dir == "./custom-dir"
|
|
23
|
+
assert memory.agent_id == "agent-1"
|
|
24
|
+
assert memory.token_budget == 8192
|
|
25
|
+
assert memory.return_messages is True
|
|
26
|
+
|
|
27
|
+
def test_memory_variables(self):
|
|
28
|
+
memory = MenteDBMemory()
|
|
29
|
+
assert memory.memory_variables == ["history"]
|
|
30
|
+
|
|
31
|
+
def test_load_memory_variables(self):
|
|
32
|
+
memory = MenteDBMemory()
|
|
33
|
+
result = memory.load_memory_variables({"input": "test query"})
|
|
34
|
+
assert "history" in result
|
|
35
|
+
assert isinstance(result["history"], str)
|
|
36
|
+
|
|
37
|
+
def test_save_context(self):
|
|
38
|
+
memory = MenteDBMemory()
|
|
39
|
+
memory.save_context(
|
|
40
|
+
inputs={"input": "hello"},
|
|
41
|
+
outputs={"output": "world"},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def test_clear(self):
|
|
45
|
+
memory = MenteDBMemory()
|
|
46
|
+
memory.clear()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestMenteDBRetriever:
|
|
50
|
+
def test_default_construction(self):
|
|
51
|
+
retriever = MenteDBRetriever()
|
|
52
|
+
assert retriever.data_dir == "./mentedb-data"
|
|
53
|
+
assert retriever.k == 10
|
|
54
|
+
assert retriever.tags is None
|
|
55
|
+
assert retriever.agent_id is None
|
|
56
|
+
|
|
57
|
+
def test_custom_construction(self):
|
|
58
|
+
retriever = MenteDBRetriever(
|
|
59
|
+
data_dir="./custom-dir",
|
|
60
|
+
k=20,
|
|
61
|
+
tags=["backend"],
|
|
62
|
+
agent_id="agent-1",
|
|
63
|
+
)
|
|
64
|
+
assert retriever.k == 20
|
|
65
|
+
assert retriever.tags == ["backend"]
|
|
66
|
+
|
|
67
|
+
def test_get_relevant_documents(self):
|
|
68
|
+
retriever = MenteDBRetriever()
|
|
69
|
+
docs = retriever.get_relevant_documents("test query")
|
|
70
|
+
assert isinstance(docs, list)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestMenteDBChatHistory:
|
|
74
|
+
def test_default_construction(self):
|
|
75
|
+
history = MenteDBChatHistory(session_id="s1")
|
|
76
|
+
assert history.session_id == "s1"
|
|
77
|
+
assert history.data_dir == "./mentedb-data"
|
|
78
|
+
assert history.agent_id is None
|
|
79
|
+
|
|
80
|
+
def test_messages_empty(self):
|
|
81
|
+
history = MenteDBChatHistory(session_id="s1")
|
|
82
|
+
assert history.messages == []
|
|
83
|
+
|
|
84
|
+
def test_add_messages(self):
|
|
85
|
+
history = MenteDBChatHistory(session_id="s1")
|
|
86
|
+
history.add_user_message("hello")
|
|
87
|
+
history.add_ai_message("hi there")
|
|
88
|
+
assert len(history.messages) == 2
|
|
89
|
+
assert history.messages[0] == {"role": "user", "content": "hello"}
|
|
90
|
+
assert history.messages[1] == {"role": "assistant", "content": "hi there"}
|
|
91
|
+
|
|
92
|
+
def test_clear(self):
|
|
93
|
+
history = MenteDBChatHistory(session_id="s1")
|
|
94
|
+
history.add_user_message("hello")
|
|
95
|
+
history.clear()
|
|
96
|
+
assert history.messages == []
|