langchain-xache 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_xache-0.1.0.dist-info/METADATA +199 -0
- langchain_xache-0.1.0.dist-info/RECORD +12 -0
- langchain_xache-0.1.0.dist-info/WHEEL +5 -0
- langchain_xache-0.1.0.dist-info/top_level.txt +1 -0
- xache_langchain/__init__.py +59 -0
- xache_langchain/_async_utils.py +56 -0
- xache_langchain/chat_history.py +194 -0
- xache_langchain/collective.py +254 -0
- xache_langchain/extraction.py +229 -0
- xache_langchain/memory.py +181 -0
- xache_langchain/reputation.py +237 -0
- xache_langchain/retriever.py +221 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-xache
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain integration for Xache Protocol - verifiable AI agent memory
|
|
5
|
+
Author-email: Xache Protocol <dev@xache.xyz>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://xache.xyz
|
|
8
|
+
Project-URL: Documentation, https://docs.xache.xyz
|
|
9
|
+
Project-URL: Repository, https://github.com/oliveskin/xache
|
|
10
|
+
Keywords: langchain,ai,agents,memory,blockchain,receipts,reputation,erc8004,x402
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: xache>=0.1.0
|
|
24
|
+
Requires-Dist: langchain>=0.1.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
29
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
31
|
+
|
|
32
|
+
# langchain-xache
|
|
33
|
+
|
|
34
|
+
LangChain integration for [Xache Protocol](https://xache.xyz) - verifiable AI agent memory with cryptographic receipts, collective intelligence, and portable ERC-8004 reputation.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install langchain-xache
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### One-Line Memory Replacement
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Before (standard LangChain)
|
|
48
|
+
from langchain.memory import ConversationBufferMemory
|
|
49
|
+
memory = ConversationBufferMemory()
|
|
50
|
+
|
|
51
|
+
# After (with Xache - one line change!)
|
|
52
|
+
from xache_langchain import XacheMemory
|
|
53
|
+
memory = XacheMemory(
|
|
54
|
+
wallet_address="0x...",
|
|
55
|
+
private_key="0x..."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Everything else stays the same
|
|
59
|
+
agent = initialize_agent(tools, llm, memory=memory)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
### Memory Storage
|
|
65
|
+
Persistent memory that survives across sessions with cryptographic receipts:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from xache_langchain import XacheMemory
|
|
69
|
+
|
|
70
|
+
memory = XacheMemory(
|
|
71
|
+
wallet_address="0xYourWallet",
|
|
72
|
+
private_key="0xYourPrivateKey",
|
|
73
|
+
api_url="https://api.xache.xyz", # optional
|
|
74
|
+
chain="base" # or "solana"
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Retrieval (RAG)
|
|
79
|
+
Semantic search for retrieval-augmented generation:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from xache_langchain import XacheRetriever
|
|
83
|
+
from langchain.chains import RetrievalQA
|
|
84
|
+
|
|
85
|
+
retriever = XacheRetriever(
|
|
86
|
+
wallet_address="0x...",
|
|
87
|
+
private_key="0x...",
|
|
88
|
+
k=5 # number of documents
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
qa = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Collective Intelligence
|
|
95
|
+
Query and contribute to shared knowledge:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from xache_langchain import XacheCollectiveContributeTool, XacheCollectiveQueryTool
|
|
99
|
+
|
|
100
|
+
# Add to your agent's tools
|
|
101
|
+
contribute = XacheCollectiveContributeTool(
|
|
102
|
+
wallet_address="0x...",
|
|
103
|
+
private_key="0x..."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
query = XacheCollectiveQueryTool(
|
|
107
|
+
wallet_address="0x...",
|
|
108
|
+
private_key="0x..."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
tools = [contribute, query, ...]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Memory Extraction
|
|
115
|
+
Auto-extract memories from conversations:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from xache_langchain import XacheExtractor
|
|
119
|
+
|
|
120
|
+
extractor = XacheExtractor(
|
|
121
|
+
wallet_address="0x...",
|
|
122
|
+
private_key="0x...",
|
|
123
|
+
mode="xache-managed" # or "api-key" with your LLM key
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
result = extractor.extract(
|
|
127
|
+
trace="User asked about quantum computing...",
|
|
128
|
+
auto_store=True # automatically store extracted memories
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
print(f"Extracted {len(result.memories)} memories")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Reputation
|
|
135
|
+
Check and verify agent reputation:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from xache_langchain import XacheReputationTool, XacheReputationChecker
|
|
139
|
+
|
|
140
|
+
# As a tool for your agent
|
|
141
|
+
rep_tool = XacheReputationTool(
|
|
142
|
+
wallet_address="0x...",
|
|
143
|
+
private_key="0x..."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Or check other agents
|
|
147
|
+
checker = XacheReputationChecker(
|
|
148
|
+
wallet_address="0x...",
|
|
149
|
+
private_key="0x..."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
other_rep = checker.check("did:agent:evm:0xOtherAgent...")
|
|
153
|
+
if other_rep.score >= 0.5:
|
|
154
|
+
print("Agent is trustworthy")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Chat History
|
|
158
|
+
|
|
159
|
+
For more control over message history:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from xache_langchain import XacheChatMessageHistory
|
|
163
|
+
from langchain.memory import ConversationBufferMemory
|
|
164
|
+
|
|
165
|
+
history = XacheChatMessageHistory(
|
|
166
|
+
wallet_address="0x...",
|
|
167
|
+
private_key="0x...",
|
|
168
|
+
session_id="unique-session-id"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
memory = ConversationBufferMemory(chat_memory=history)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Pricing
|
|
175
|
+
|
|
176
|
+
All operations use x402 micropayments (auto-handled):
|
|
177
|
+
|
|
178
|
+
| Operation | Price |
|
|
179
|
+
|-----------|-------|
|
|
180
|
+
| Memory Store | $0.002 |
|
|
181
|
+
| Memory Retrieve | $0.003 |
|
|
182
|
+
| Collective Contribute | $0.002 |
|
|
183
|
+
| Collective Query | $0.011 |
|
|
184
|
+
| Extraction (managed) | $0.011 |
|
|
185
|
+
|
|
186
|
+
## ERC-8004 Portable Reputation
|
|
187
|
+
|
|
188
|
+
Xache supports [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) for portable, on-chain reputation. Enable it to make your agent's reputation verifiable across platforms.
|
|
189
|
+
|
|
190
|
+
## Resources
|
|
191
|
+
|
|
192
|
+
- [Documentation](https://docs.xache.xyz)
|
|
193
|
+
- [API Reference](https://docs.xache.xyz/api)
|
|
194
|
+
- [GitHub](https://github.com/oliveskin/xache)
|
|
195
|
+
- [Discord](https://discord.gg/xache)
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
xache_langchain/__init__.py,sha256=OYzpiCnA75ToCTVmQcqut7kGygD9lQhjrUTiFZU5S88,1560
|
|
2
|
+
xache_langchain/_async_utils.py,sha256=Srhe4rJ1S4W6E0sDfgsTqMdfMFr0Je9scJ3cFNa8fWA,1699
|
|
3
|
+
xache_langchain/chat_history.py,sha256=MS6w8fyowWlLvvtZ0RBQWNygBytM3cnasZ6C0KwL4sE,6730
|
|
4
|
+
xache_langchain/collective.py,sha256=OyZKvfOx20ETK236P36fqAZ6ahLyos6FYs81IOKjti0,8092
|
|
5
|
+
xache_langchain/extraction.py,sha256=tFJtL3Sk1XK7UUSmlLw3eMsPA9Lh5kI-G8EhQ1Uiuf0,7407
|
|
6
|
+
xache_langchain/memory.py,sha256=71hxXcUtsyaAo67_xBmjel43EvhgDiCENcD6AvJxMBo,6196
|
|
7
|
+
xache_langchain/reputation.py,sha256=b1GVceH1QPqB7rjvTj295QllxiJ5W0rQ5rUoKJ5Np7A,7389
|
|
8
|
+
xache_langchain/retriever.py,sha256=qw7CSP7zR6g7mn9DUYHbId9S9K564kKXqKR1WnnHkP0,7607
|
|
9
|
+
langchain_xache-0.1.0.dist-info/METADATA,sha256=YXS1nXk1NsOstVUNgauJtq-Z_laum0LNEpy7_-U7Q-w,5308
|
|
10
|
+
langchain_xache-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
langchain_xache-0.1.0.dist-info/top_level.txt,sha256=in69PSq9agqGIAyShkm5ZVg9n0ks76QlD1tGaws9efA,16
|
|
12
|
+
langchain_xache-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xache_langchain
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain integration for Xache Protocol
|
|
3
|
+
Drop-in memory, retrieval, and collective intelligence with verifiable receipts
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
```python
|
|
7
|
+
# One-line memory replacement
|
|
8
|
+
from xache_langchain import XacheMemory
|
|
9
|
+
|
|
10
|
+
memory = XacheMemory(
|
|
11
|
+
wallet_address="0x...",
|
|
12
|
+
private_key="0x..."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Everything else stays the same
|
|
16
|
+
agent = initialize_agent(tools, llm, memory=memory)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
All Xache features:
|
|
20
|
+
- Memory: Persistent, verifiable memory storage
|
|
21
|
+
- Retrieval: Semantic search for RAG pipelines
|
|
22
|
+
- Collective: Share and learn from agent community
|
|
23
|
+
- Extraction: Auto-extract memories from conversations
|
|
24
|
+
- Reputation: Portable ERC-8004 on-chain reputation
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from .memory import XacheMemory, XacheConversationBufferMemory
|
|
28
|
+
from .chat_history import XacheChatMessageHistory
|
|
29
|
+
from .retriever import XacheRetriever
|
|
30
|
+
from .extraction import XacheExtractor, ExtractionResult
|
|
31
|
+
from .collective import (
|
|
32
|
+
XacheCollectiveContributeTool,
|
|
33
|
+
XacheCollectiveQueryTool,
|
|
34
|
+
)
|
|
35
|
+
from .reputation import (
|
|
36
|
+
XacheReputationTool,
|
|
37
|
+
XacheReputationChecker,
|
|
38
|
+
ReputationResult,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__version__ = "0.1.0"
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Memory
|
|
44
|
+
"XacheMemory",
|
|
45
|
+
"XacheConversationBufferMemory",
|
|
46
|
+
"XacheChatMessageHistory",
|
|
47
|
+
# Retrieval
|
|
48
|
+
"XacheRetriever",
|
|
49
|
+
# Extraction
|
|
50
|
+
"XacheExtractor",
|
|
51
|
+
"ExtractionResult",
|
|
52
|
+
# Collective Intelligence
|
|
53
|
+
"XacheCollectiveContributeTool",
|
|
54
|
+
"XacheCollectiveQueryTool",
|
|
55
|
+
# Reputation
|
|
56
|
+
"XacheReputationTool",
|
|
57
|
+
"XacheReputationChecker",
|
|
58
|
+
"ReputationResult",
|
|
59
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async utilities for running coroutines in any context.
|
|
3
|
+
Handles Jupyter notebooks, async frameworks, and sync contexts.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
from typing import TypeVar, Coroutine, Any
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_sync(coro: Coroutine[Any, Any, T]) -> T:
|
|
13
|
+
"""
|
|
14
|
+
Run an async coroutine synchronously in any context.
|
|
15
|
+
|
|
16
|
+
Works correctly in:
|
|
17
|
+
- Regular sync Python scripts
|
|
18
|
+
- Jupyter notebooks (where event loop is already running)
|
|
19
|
+
- Async frameworks (FastAPI, etc.)
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
coro: The coroutine to run
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The result of the coroutine
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
# Check if there's already a running event loop
|
|
29
|
+
loop = asyncio.get_running_loop()
|
|
30
|
+
except RuntimeError:
|
|
31
|
+
# No running loop - we can safely use asyncio.run()
|
|
32
|
+
return asyncio.run(coro)
|
|
33
|
+
|
|
34
|
+
# There's a running loop - we need to handle this carefully
|
|
35
|
+
# This happens in Jupyter notebooks, async frameworks, etc.
|
|
36
|
+
try:
|
|
37
|
+
# Try using nest_asyncio for Jupyter compatibility
|
|
38
|
+
import nest_asyncio
|
|
39
|
+
nest_asyncio.apply()
|
|
40
|
+
return loop.run_until_complete(coro)
|
|
41
|
+
except ImportError:
|
|
42
|
+
# nest_asyncio not installed - use thread pool as fallback
|
|
43
|
+
import concurrent.futures
|
|
44
|
+
|
|
45
|
+
def _run_in_thread():
|
|
46
|
+
# Create a new event loop for this thread
|
|
47
|
+
new_loop = asyncio.new_event_loop()
|
|
48
|
+
asyncio.set_event_loop(new_loop)
|
|
49
|
+
try:
|
|
50
|
+
return new_loop.run_until_complete(coro)
|
|
51
|
+
finally:
|
|
52
|
+
new_loop.close()
|
|
53
|
+
|
|
54
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
55
|
+
future = pool.submit(_run_in_thread)
|
|
56
|
+
return future.result()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xache Chat Message History for LangChain
|
|
3
|
+
Persistent chat history with verifiable receipts
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
from langchain.schema import BaseMessage, HumanMessage, AIMessage, SystemMessage
|
|
9
|
+
from langchain.memory.chat_message_histories.base import BaseChatMessageHistory
|
|
10
|
+
|
|
11
|
+
from xache import XacheClient
|
|
12
|
+
from ._async_utils import run_sync
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class XacheChatMessageHistory(BaseChatMessageHistory):
|
|
16
|
+
"""
|
|
17
|
+
Chat message history backed by Xache Protocol.
|
|
18
|
+
|
|
19
|
+
Stores chat messages with cryptographic receipts and optional
|
|
20
|
+
reputation tracking.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
```python
|
|
24
|
+
from xache_langchain import XacheChatMessageHistory
|
|
25
|
+
from langchain.memory import ConversationBufferMemory
|
|
26
|
+
|
|
27
|
+
history = XacheChatMessageHistory(
|
|
28
|
+
wallet_address="0x...",
|
|
29
|
+
private_key="0x...",
|
|
30
|
+
session_id="unique-session-id"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
memory = ConversationBufferMemory(chat_memory=history)
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
wallet_address: str,
|
|
40
|
+
private_key: str,
|
|
41
|
+
session_id: str,
|
|
42
|
+
api_url: Optional[str] = None,
|
|
43
|
+
chain: str = "base",
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Initialize Xache chat history.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
wallet_address: Wallet address for authentication
|
|
50
|
+
private_key: Private key for signing
|
|
51
|
+
session_id: Unique session identifier
|
|
52
|
+
api_url: Xache API URL
|
|
53
|
+
chain: Blockchain (base, solana)
|
|
54
|
+
"""
|
|
55
|
+
self.wallet_address = wallet_address
|
|
56
|
+
self.private_key = private_key
|
|
57
|
+
self.session_id = session_id
|
|
58
|
+
self.api_url = api_url or os.environ.get("XACHE_API_URL", "https://api.xache.xyz")
|
|
59
|
+
self.chain = chain
|
|
60
|
+
|
|
61
|
+
# Build DID
|
|
62
|
+
chain_prefix = "sol" if chain == "solana" else "evm"
|
|
63
|
+
self.did = f"did:agent:{chain_prefix}:{wallet_address.lower()}"
|
|
64
|
+
|
|
65
|
+
self._client = XacheClient(
|
|
66
|
+
api_url=api_url,
|
|
67
|
+
did=self.did,
|
|
68
|
+
private_key=private_key,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def messages(self) -> List[BaseMessage]:
|
|
73
|
+
"""Retrieve all messages from Xache"""
|
|
74
|
+
|
|
75
|
+
async def _get_messages():
|
|
76
|
+
async with self._client as client:
|
|
77
|
+
# Retrieve memories for this session (filtered by context)
|
|
78
|
+
memories = await client.memory.list(
|
|
79
|
+
limit=1000,
|
|
80
|
+
context=f"chat:session:{self.session_id}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
messages = []
|
|
84
|
+
result_memories = memories.get("memories", []) if isinstance(memories, dict) else memories
|
|
85
|
+
for mem in sorted(result_memories, key=lambda x: x.get("created_at", "")):
|
|
86
|
+
content = mem.get("content", "")
|
|
87
|
+
metadata = mem.get("metadata", {})
|
|
88
|
+
role = metadata.get("role", "human")
|
|
89
|
+
|
|
90
|
+
if role == "human":
|
|
91
|
+
messages.append(HumanMessage(content=content))
|
|
92
|
+
elif role == "ai":
|
|
93
|
+
messages.append(AIMessage(content=content))
|
|
94
|
+
elif role == "system":
|
|
95
|
+
messages.append(SystemMessage(content=content))
|
|
96
|
+
|
|
97
|
+
return messages
|
|
98
|
+
|
|
99
|
+
return run_sync(_get_messages())
|
|
100
|
+
|
|
101
|
+
def add_message(self, message: BaseMessage) -> None:
|
|
102
|
+
"""Add a message to the history"""
|
|
103
|
+
|
|
104
|
+
# Determine role from message type
|
|
105
|
+
if isinstance(message, HumanMessage):
|
|
106
|
+
role = "human"
|
|
107
|
+
elif isinstance(message, AIMessage):
|
|
108
|
+
role = "ai"
|
|
109
|
+
elif isinstance(message, SystemMessage):
|
|
110
|
+
role = "system"
|
|
111
|
+
else:
|
|
112
|
+
role = "unknown"
|
|
113
|
+
|
|
114
|
+
async def _add():
|
|
115
|
+
async with self._client as client:
|
|
116
|
+
await client.memory.store(
|
|
117
|
+
content=message.content,
|
|
118
|
+
context=f"chat:session:{self.session_id}",
|
|
119
|
+
metadata={
|
|
120
|
+
"role": role,
|
|
121
|
+
"session_id": self.session_id,
|
|
122
|
+
"source": "langchain",
|
|
123
|
+
"message_type": message.__class__.__name__,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
run_sync(_add())
|
|
128
|
+
|
|
129
|
+
def add_user_message(self, message: str) -> None:
|
|
130
|
+
"""Add a user message"""
|
|
131
|
+
self.add_message(HumanMessage(content=message))
|
|
132
|
+
|
|
133
|
+
def add_ai_message(self, message: str) -> None:
|
|
134
|
+
"""Add an AI message"""
|
|
135
|
+
self.add_message(AIMessage(content=message))
|
|
136
|
+
|
|
137
|
+
def clear(self) -> None:
|
|
138
|
+
"""Clear message history (soft delete) using bulk deletion"""
|
|
139
|
+
self.bulk_delete()
|
|
140
|
+
|
|
141
|
+
def bulk_delete(self, storage_keys: Optional[List[str]] = None) -> dict:
|
|
142
|
+
"""
|
|
143
|
+
Delete multiple messages from history.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
storage_keys: List of storage keys to delete. If None, deletes all messages
|
|
147
|
+
in this session.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
dict with 'deleted' count and 'errors' list
|
|
151
|
+
"""
|
|
152
|
+
import asyncio
|
|
153
|
+
|
|
154
|
+
async def _bulk_delete():
|
|
155
|
+
async with self._client as client:
|
|
156
|
+
# Get storage keys if not provided
|
|
157
|
+
keys_to_delete = storage_keys
|
|
158
|
+
if keys_to_delete is None:
|
|
159
|
+
result = await client.memory.list(
|
|
160
|
+
limit=1000,
|
|
161
|
+
context=f"chat:session:{self.session_id}"
|
|
162
|
+
)
|
|
163
|
+
memories = result.get("memories", []) if isinstance(result, dict) else result
|
|
164
|
+
keys_to_delete = [
|
|
165
|
+
mem.get("storage_key") for mem in memories
|
|
166
|
+
if mem.get("storage_key")
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
if not keys_to_delete:
|
|
170
|
+
return {"deleted": 0, "errors": []}
|
|
171
|
+
|
|
172
|
+
# Delete in parallel using asyncio.gather with return_exceptions
|
|
173
|
+
async def safe_delete(key):
|
|
174
|
+
try:
|
|
175
|
+
await client.memory.delete(key)
|
|
176
|
+
return {"key": key, "success": True}
|
|
177
|
+
except Exception as e:
|
|
178
|
+
return {"key": key, "success": False, "error": str(e)}
|
|
179
|
+
|
|
180
|
+
results = await asyncio.gather(
|
|
181
|
+
*[safe_delete(key) for key in keys_to_delete],
|
|
182
|
+
return_exceptions=True
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
deleted = sum(1 for r in results if isinstance(r, dict) and r.get("success"))
|
|
186
|
+
errors = [
|
|
187
|
+
r for r in results
|
|
188
|
+
if isinstance(r, dict) and not r.get("success")
|
|
189
|
+
or isinstance(r, Exception)
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
return {"deleted": deleted, "errors": errors}
|
|
193
|
+
|
|
194
|
+
return run_sync(_bulk_delete())
|