cortexdb-langchain 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.
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""CortexDB integration for LangChain.
|
|
2
|
+
|
|
3
|
+
Provides chat-message history, retrievers, and tools that connect LangChain
|
|
4
|
+
applications to CortexDB's long-term memory system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from cortexdb_langchain.memory import CortexDBChatMessageHistory
|
|
8
|
+
from cortexdb_langchain.retriever import CortexDBRetriever
|
|
9
|
+
from cortexdb_langchain.tools import (
|
|
10
|
+
CortexDBForgetTool,
|
|
11
|
+
CortexDBSearchTool,
|
|
12
|
+
CortexDBStoreTool,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CortexDBChatMessageHistory",
|
|
17
|
+
"CortexDBRetriever",
|
|
18
|
+
"CortexDBForgetTool",
|
|
19
|
+
"CortexDBSearchTool",
|
|
20
|
+
"CortexDBStoreTool",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Legacy BaseMemory backend — only present on langchain-core < 1.0.
|
|
24
|
+
try:
|
|
25
|
+
from cortexdb_langchain.memory import CortexDBMemory # noqa: F401
|
|
26
|
+
|
|
27
|
+
__all__.append("CortexDBMemory")
|
|
28
|
+
except ImportError:
|
|
29
|
+
pass
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""LangChain memory backed by CortexDB.
|
|
2
|
+
|
|
3
|
+
Primary, version-agnostic primitive: :class:`CortexDBChatMessageHistory`, a
|
|
4
|
+
``BaseChatMessageHistory`` (available in langchain-core 0.3 **and** 1.x). Use it
|
|
5
|
+
with ``RunnableWithMessageHistory`` to give a chain/agent long-term memory:
|
|
6
|
+
messages are persisted to CortexDB and relevant context is recalled back.
|
|
7
|
+
|
|
8
|
+
The legacy :class:`CortexDBMemory` (built on the pre-1.0 ``BaseMemory``) is kept
|
|
9
|
+
only when that base class is importable (langchain-core < 1.0), since 1.x removed
|
|
10
|
+
the ``BaseMemory`` framework entirely.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
from langchain_core.chat_history import BaseChatMessageHistory
|
|
18
|
+
from langchain_core.messages import AIMessage, BaseMessage
|
|
19
|
+
|
|
20
|
+
from cortexdb import Cortex
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CortexDBChatMessageHistory(BaseChatMessageHistory):
|
|
24
|
+
"""Chat-message history backed by CortexDB (langchain-core 0.3 + 1.x).
|
|
25
|
+
|
|
26
|
+
- ``add_message`` / ``add_messages`` persist each message via ``experience``.
|
|
27
|
+
- ``messages`` recalls relevant long-term context for ``recall_query`` and
|
|
28
|
+
surfaces it as a single ``AIMessage`` (CortexDB is semantic memory, not a
|
|
29
|
+
literal per-session log). Set ``recall_query`` to the current user input.
|
|
30
|
+
- ``clear`` forgets the scope.
|
|
31
|
+
|
|
32
|
+
Example::
|
|
33
|
+
|
|
34
|
+
from cortexdb import Cortex
|
|
35
|
+
from cortexdb_langchain import CortexDBChatMessageHistory
|
|
36
|
+
|
|
37
|
+
client = Cortex("https://api-v1.cortexdb.ai", actor="user:app", bearer="v4.public...")
|
|
38
|
+
history = CortexDBChatMessageHistory(client=client, scope="user:my-app")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
client: Cortex,
|
|
44
|
+
scope: str = "user:default",
|
|
45
|
+
recall_query: str = "",
|
|
46
|
+
) -> None:
|
|
47
|
+
self._client = client
|
|
48
|
+
self.scope = scope
|
|
49
|
+
self.recall_query = recall_query
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def messages(self) -> list[BaseMessage]:
|
|
53
|
+
query = self.recall_query
|
|
54
|
+
if not query:
|
|
55
|
+
return []
|
|
56
|
+
result = self._client.recall(self.scope, query=str(query))
|
|
57
|
+
block = result.get("context_block", "")
|
|
58
|
+
return [AIMessage(content=block)] if block else []
|
|
59
|
+
|
|
60
|
+
def add_message(self, message: BaseMessage) -> None:
|
|
61
|
+
self._client.experience(
|
|
62
|
+
self.scope, text=f"{message.type}: {message.content}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def add_messages(self, messages: list[BaseMessage]) -> None:
|
|
66
|
+
for message in messages:
|
|
67
|
+
self.add_message(message)
|
|
68
|
+
|
|
69
|
+
def clear(self) -> None:
|
|
70
|
+
self._client.forget(
|
|
71
|
+
self.scope,
|
|
72
|
+
confirm_all=True,
|
|
73
|
+
cascade="redact_events",
|
|
74
|
+
reason="LangChain memory clear requested",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ── Legacy BaseMemory backend (langchain-core < 1.0 only) ────────────────────
|
|
79
|
+
# langchain-core 1.x removed BaseMemory and the classic `memory=` chain hook, so
|
|
80
|
+
# we only define this when the base class is importable.
|
|
81
|
+
try:
|
|
82
|
+
from langchain_core.memory import BaseMemory as _BaseMemory
|
|
83
|
+
except ImportError: # langchain-core >= 1.0
|
|
84
|
+
_BaseMemory = None
|
|
85
|
+
|
|
86
|
+
if _BaseMemory is not None:
|
|
87
|
+
from pydantic import PrivateAttr
|
|
88
|
+
|
|
89
|
+
class CortexDBMemory(_BaseMemory): # type: ignore[misc,valid-type]
|
|
90
|
+
"""Deprecated: classic BaseMemory backend (langchain-core < 1.0).
|
|
91
|
+
|
|
92
|
+
Prefer :class:`CortexDBChatMessageHistory` with
|
|
93
|
+
``RunnableWithMessageHistory`` on langchain-core 1.x.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
memory_key: str = "cortex_context"
|
|
97
|
+
input_key: Optional[str] = None
|
|
98
|
+
output_key: Optional[str] = None
|
|
99
|
+
scope: str = "user:default"
|
|
100
|
+
|
|
101
|
+
_client: Cortex = PrivateAttr()
|
|
102
|
+
|
|
103
|
+
def __init__(self, client: Cortex, **kwargs: Any) -> None:
|
|
104
|
+
super().__init__(**kwargs)
|
|
105
|
+
self._client = client
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def memory_variables(self) -> list[str]:
|
|
109
|
+
return [self.memory_key]
|
|
110
|
+
|
|
111
|
+
def load_memory_variables(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
112
|
+
if self.input_key is not None:
|
|
113
|
+
query = inputs.get(self.input_key, "")
|
|
114
|
+
else:
|
|
115
|
+
query = next(iter(inputs.values()), "") if inputs else ""
|
|
116
|
+
if not query:
|
|
117
|
+
return {self.memory_key: ""}
|
|
118
|
+
result = self._client.recall(self.scope, query=str(query))
|
|
119
|
+
return {self.memory_key: result.get("context_block", "")}
|
|
120
|
+
|
|
121
|
+
def save_context(
|
|
122
|
+
self, inputs: dict[str, Any], outputs: dict[str, str]
|
|
123
|
+
) -> None:
|
|
124
|
+
if self.output_key is not None:
|
|
125
|
+
output_text = outputs.get(self.output_key, "")
|
|
126
|
+
else:
|
|
127
|
+
output_text = next(iter(outputs.values()), "") if outputs else ""
|
|
128
|
+
if self.input_key is not None:
|
|
129
|
+
input_text = inputs.get(self.input_key, "")
|
|
130
|
+
else:
|
|
131
|
+
input_text = next(iter(inputs.values()), "") if inputs else ""
|
|
132
|
+
content = f"User: {input_text}\nAssistant: {output_text}"
|
|
133
|
+
self._client.experience(self.scope, text=content)
|
|
134
|
+
|
|
135
|
+
def clear(self) -> None:
|
|
136
|
+
self._client.forget(
|
|
137
|
+
self.scope,
|
|
138
|
+
confirm_all=True,
|
|
139
|
+
cascade="redact_events",
|
|
140
|
+
reason="LangChain memory clear requested",
|
|
141
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""LangChain retriever backed by CortexDB.
|
|
2
|
+
|
|
3
|
+
Provides a standard LangChain retriever interface for semantic search
|
|
4
|
+
over CortexDB's memory store, compatible with retrieval chains and
|
|
5
|
+
retrieval-augmented generation (RAG) pipelines.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
from langchain_core.callbacks import CallbackManagerForRetrieverRun
|
|
13
|
+
from langchain_core.documents import Document
|
|
14
|
+
from langchain_core.retrievers import BaseRetriever
|
|
15
|
+
from pydantic import Field, PrivateAttr
|
|
16
|
+
|
|
17
|
+
from cortexdb import Cortex
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CortexDBRetriever(BaseRetriever):
|
|
21
|
+
"""LangChain retriever that queries CortexDB's memory store.
|
|
22
|
+
|
|
23
|
+
Wraps CortexDB's ``recall`` API as a standard LangChain retriever,
|
|
24
|
+
returning results as LangChain ``Document`` objects.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
client: An initialized CortexDB client instance.
|
|
28
|
+
scope: The scope path for memory isolation (hierarchical string).
|
|
29
|
+
|
|
30
|
+
Example::
|
|
31
|
+
|
|
32
|
+
from cortexdb import Cortex
|
|
33
|
+
from cortexdb_langchain import CortexDBRetriever
|
|
34
|
+
|
|
35
|
+
client = Cortex(
|
|
36
|
+
"https://api-v1.cortexdb.ai",
|
|
37
|
+
actor="user:app",
|
|
38
|
+
bearer="v4.public...",
|
|
39
|
+
)
|
|
40
|
+
retriever = CortexDBRetriever(client=client, scope="user:my-app")
|
|
41
|
+
|
|
42
|
+
docs = retriever.invoke("What happened in the last sprint?")
|
|
43
|
+
for doc in docs:
|
|
44
|
+
print(doc.page_content)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
scope: str = "user:default"
|
|
48
|
+
|
|
49
|
+
_client: Cortex = PrivateAttr()
|
|
50
|
+
|
|
51
|
+
def __init__(self, client: Cortex, **kwargs: Any) -> None:
|
|
52
|
+
super().__init__(**kwargs)
|
|
53
|
+
self._client = client
|
|
54
|
+
|
|
55
|
+
def _get_relevant_documents(
|
|
56
|
+
self,
|
|
57
|
+
query: str,
|
|
58
|
+
*,
|
|
59
|
+
run_manager: CallbackManagerForRetrieverRun,
|
|
60
|
+
) -> list[Document]:
|
|
61
|
+
result = self._client.recall(self.scope, query=query)
|
|
62
|
+
|
|
63
|
+
metadata = {
|
|
64
|
+
"source": "cortexdb",
|
|
65
|
+
"scope": self.scope,
|
|
66
|
+
"pack_id": result.get("pack_id"),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return [
|
|
70
|
+
Document(
|
|
71
|
+
page_content=result.get("context_block", ""),
|
|
72
|
+
metadata=metadata,
|
|
73
|
+
)
|
|
74
|
+
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""LangChain tools for interacting with CortexDB.
|
|
2
|
+
|
|
3
|
+
Provides search, store, and forget tools that allow LangChain agents
|
|
4
|
+
to interact with CortexDB's memory system as part of their tool-use
|
|
5
|
+
capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Optional, Type
|
|
11
|
+
|
|
12
|
+
from langchain_core.callbacks import CallbackManagerForToolRun
|
|
13
|
+
from langchain_core.tools import BaseTool
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
from cortexdb import Cortex
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _SearchInput(BaseModel):
|
|
20
|
+
query: str = Field(description="The search query to find relevant memories.")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CortexDBSearchTool(BaseTool):
|
|
24
|
+
"""LangChain tool for searching memories in CortexDB."""
|
|
25
|
+
|
|
26
|
+
name: str = "cortexdb_search"
|
|
27
|
+
description: str = (
|
|
28
|
+
"Search CortexDB for relevant memories and past context. "
|
|
29
|
+
"Use this when you need to recall information from previous "
|
|
30
|
+
"conversations or stored knowledge."
|
|
31
|
+
)
|
|
32
|
+
args_schema: Type[BaseModel] = _SearchInput
|
|
33
|
+
scope: str = "user:default"
|
|
34
|
+
|
|
35
|
+
_client: Cortex
|
|
36
|
+
|
|
37
|
+
def __init__(self, client: Cortex, **kwargs: Any) -> None:
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
self._client = client
|
|
40
|
+
|
|
41
|
+
def _run(
|
|
42
|
+
self,
|
|
43
|
+
query: str,
|
|
44
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
45
|
+
) -> str:
|
|
46
|
+
result = self._client.recall(self.scope, query=query)
|
|
47
|
+
|
|
48
|
+
context = result.get("context_block", "")
|
|
49
|
+
if not context:
|
|
50
|
+
return "No relevant memories found."
|
|
51
|
+
|
|
52
|
+
return context
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _StoreInput(BaseModel):
|
|
56
|
+
content: str = Field(description="The content to store as a memory.")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CortexDBStoreTool(BaseTool):
|
|
60
|
+
"""LangChain tool for storing memories in CortexDB."""
|
|
61
|
+
|
|
62
|
+
name: str = "cortexdb_store"
|
|
63
|
+
description: str = (
|
|
64
|
+
"Store information in CortexDB's long-term memory. "
|
|
65
|
+
"Use this to save important facts, decisions, or context "
|
|
66
|
+
"that should be remembered for future interactions."
|
|
67
|
+
)
|
|
68
|
+
args_schema: Type[BaseModel] = _StoreInput
|
|
69
|
+
scope: str = "user:default"
|
|
70
|
+
|
|
71
|
+
_client: Cortex
|
|
72
|
+
|
|
73
|
+
def __init__(self, client: Cortex, **kwargs: Any) -> None:
|
|
74
|
+
super().__init__(**kwargs)
|
|
75
|
+
self._client = client
|
|
76
|
+
|
|
77
|
+
def _run(
|
|
78
|
+
self,
|
|
79
|
+
content: str,
|
|
80
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
81
|
+
) -> str:
|
|
82
|
+
resp = self._client.experience(self.scope, text=content)
|
|
83
|
+
return f"Memory stored successfully (event_id: {resp.get('event_id')})."
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _ForgetInput(BaseModel):
|
|
87
|
+
query: str = Field(description="Query identifying the memories to forget.")
|
|
88
|
+
reason: str = Field(description="The reason for forgetting these memories.")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CortexDBForgetTool(BaseTool):
|
|
92
|
+
"""LangChain tool for forgetting memories in CortexDB."""
|
|
93
|
+
|
|
94
|
+
name: str = "cortexdb_forget"
|
|
95
|
+
description: str = (
|
|
96
|
+
"Forget or remove memories from CortexDB. "
|
|
97
|
+
"Use this when information should no longer be retained, "
|
|
98
|
+
"such as for privacy compliance or data correction."
|
|
99
|
+
)
|
|
100
|
+
args_schema: Type[BaseModel] = _ForgetInput
|
|
101
|
+
scope: str = "user:default"
|
|
102
|
+
|
|
103
|
+
_client: Cortex
|
|
104
|
+
|
|
105
|
+
def __init__(self, client: Cortex, **kwargs: Any) -> None:
|
|
106
|
+
super().__init__(**kwargs)
|
|
107
|
+
self._client = client
|
|
108
|
+
|
|
109
|
+
def _run(
|
|
110
|
+
self,
|
|
111
|
+
query: str,
|
|
112
|
+
reason: str,
|
|
113
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
114
|
+
) -> str:
|
|
115
|
+
resp = self._client.forget(
|
|
116
|
+
self.scope,
|
|
117
|
+
confirm_all=True,
|
|
118
|
+
cascade="redact_events",
|
|
119
|
+
reason=reason,
|
|
120
|
+
)
|
|
121
|
+
return f"Forget request accepted (audit_id: {resp.get('audit_id')})."
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cortexdb-langchain
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain integration for CortexDB — long-term memory for AI systems
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: cortexdbai>=0.1.0
|
|
8
|
+
Requires-Dist: langchain-core>=0.3
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# cortexdb-langchain
|
|
15
|
+
|
|
16
|
+
LangChain integration for CortexDB long-term memory.
|
|
17
|
+
|
|
18
|
+
> **LLM provider note (audit BLK-2):** the canonical quickstart uses
|
|
19
|
+
> `from langchain_openai import ChatOpenAI` for the example agent. CortexDB
|
|
20
|
+
> itself is LLM-agnostic — the LangChain example just needs *some* model to
|
|
21
|
+
> drive the agent. Swap with one line:
|
|
22
|
+
>
|
|
23
|
+
> ```python
|
|
24
|
+
> # OpenAI (default in the docs)
|
|
25
|
+
> from langchain_openai import ChatOpenAI
|
|
26
|
+
> llm = ChatOpenAI(model="gpt-4o")
|
|
27
|
+
>
|
|
28
|
+
> # Anthropic
|
|
29
|
+
> from langchain_anthropic import ChatAnthropic
|
|
30
|
+
> llm = ChatAnthropic(model="claude-sonnet-4-6")
|
|
31
|
+
>
|
|
32
|
+
> # Google
|
|
33
|
+
> from langchain_google_genai import ChatGoogleGenerativeAI
|
|
34
|
+
> llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro")
|
|
35
|
+
>
|
|
36
|
+
> # Local / Ollama (no API key)
|
|
37
|
+
> from langchain_community.chat_models import ChatOllama
|
|
38
|
+
> llm = ChatOllama(model="llama3.1")
|
|
39
|
+
> ```
|
|
40
|
+
>
|
|
41
|
+
> Nothing in `cortexdb-langchain` reads `OPENAI_API_KEY`. The retriever and
|
|
42
|
+
> memory classes only talk to the CortexDB v1 surface.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install cortexdb-langchain
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API base URL
|
|
51
|
+
|
|
52
|
+
The SDK and integration default to `https://api-v1.cortexdb.ai` (audit FRI-8).
|
|
53
|
+
Override with `CORTEXDB_API_URL` if you self-host.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
cortexdb_langchain/__init__.py,sha256=k7DwyE8EJUq-fy-pLozMENjFOtWW_pOxL1KfttzawA4,803
|
|
2
|
+
cortexdb_langchain/memory.py,sha256=wVzgfTKR8sP3NHhDByVh_htqtfeUVP-FSooSzJEC7qk,5356
|
|
3
|
+
cortexdb_langchain/retriever.py,sha256=GImZOZvUEezJqnetPctRvTbZXqxazxp-DbXhLtiYP5s,2180
|
|
4
|
+
cortexdb_langchain/tools.py,sha256=_mjDRQUNm4_ISZD7hI1cSjm6ibMI1IvJhdl8ZjcPG5I,3607
|
|
5
|
+
cortexdb_langchain-0.1.0.dist-info/METADATA,sha256=Vr4iddBlO-_AHFuSJ9_ae8oroJAFxkb0OVRLDXLfiog,1595
|
|
6
|
+
cortexdb_langchain-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
cortexdb_langchain-0.1.0.dist-info/RECORD,,
|