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,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xache Reputation for LangChain
|
|
3
|
+
Portable, verifiable agent reputation with ERC-8004 support
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional, Dict, Any
|
|
7
|
+
from langchain.tools import BaseTool
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from xache import XacheClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ReputationResult(BaseModel):
|
|
14
|
+
"""Reputation query result"""
|
|
15
|
+
score: float = Field(description="Reputation score (0-1)")
|
|
16
|
+
level: str = Field(description="Reputation level")
|
|
17
|
+
total_contributions: int = Field(default=0)
|
|
18
|
+
total_payments: int = Field(default=0)
|
|
19
|
+
erc8004_enabled: bool = Field(default=False)
|
|
20
|
+
erc8004_agent_id: Optional[str] = Field(default=None)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class XacheReputationTool(BaseTool):
|
|
24
|
+
"""
|
|
25
|
+
LangChain tool for checking Xache reputation.
|
|
26
|
+
|
|
27
|
+
Use this tool to check your agent's reputation score and status.
|
|
28
|
+
High reputation unlocks benefits like lower prices and higher trust.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
from xache_langchain import XacheReputationTool
|
|
33
|
+
|
|
34
|
+
rep_tool = XacheReputationTool(
|
|
35
|
+
wallet_address="0x...",
|
|
36
|
+
private_key="0x..."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Use in agent
|
|
40
|
+
tools = [rep_tool, ...]
|
|
41
|
+
```
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str = "xache_check_reputation"
|
|
45
|
+
description: str = (
|
|
46
|
+
"Check your current reputation score and status. "
|
|
47
|
+
"Returns your score (0-1), level, and ERC-8004 on-chain status. "
|
|
48
|
+
"Higher reputation means lower costs and more trust from other agents."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Xache configuration
|
|
52
|
+
api_url: str = "https://api.xache.xyz"
|
|
53
|
+
wallet_address: str
|
|
54
|
+
private_key: str
|
|
55
|
+
chain: str = "base"
|
|
56
|
+
|
|
57
|
+
_client: Optional[XacheClient] = None
|
|
58
|
+
|
|
59
|
+
class Config:
|
|
60
|
+
arbitrary_types_allowed = True
|
|
61
|
+
underscore_attrs_are_private = True
|
|
62
|
+
|
|
63
|
+
def __init__(self, **kwargs):
|
|
64
|
+
super().__init__(**kwargs)
|
|
65
|
+
chain_prefix = "sol" if self.chain == "solana" else "evm"
|
|
66
|
+
did = f"did:agent:{chain_prefix}:{self.wallet_address.lower()}"
|
|
67
|
+
self._client = XacheClient(
|
|
68
|
+
api_url=self.api_url,
|
|
69
|
+
did=did,
|
|
70
|
+
private_key=self.private_key,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def _run(self) -> str:
|
|
74
|
+
"""Check reputation"""
|
|
75
|
+
import asyncio
|
|
76
|
+
|
|
77
|
+
async def _check():
|
|
78
|
+
async with self._client as client:
|
|
79
|
+
result = await client.reputation.get_score()
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
loop = asyncio.get_event_loop()
|
|
84
|
+
if loop.is_running():
|
|
85
|
+
import concurrent.futures
|
|
86
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
87
|
+
result = pool.submit(asyncio.run, _check()).result()
|
|
88
|
+
else:
|
|
89
|
+
result = loop.run_until_complete(_check())
|
|
90
|
+
except RuntimeError:
|
|
91
|
+
result = asyncio.run(_check())
|
|
92
|
+
|
|
93
|
+
score = result.get("score", 0)
|
|
94
|
+
level = self._get_level(score)
|
|
95
|
+
|
|
96
|
+
output = f"Reputation Score: {score:.2f}/1.00 ({level})\n"
|
|
97
|
+
|
|
98
|
+
if result.get("erc8004AgentId"):
|
|
99
|
+
output += f"ERC-8004 Status: Enabled (Agent ID: {result['erc8004AgentId']})\n"
|
|
100
|
+
output += "Your reputation is verifiable on-chain!"
|
|
101
|
+
else:
|
|
102
|
+
output += "ERC-8004 Status: Not enabled\n"
|
|
103
|
+
output += "Enable ERC-8004 to make your reputation portable and verifiable."
|
|
104
|
+
|
|
105
|
+
return output
|
|
106
|
+
|
|
107
|
+
async def _arun(self) -> str:
|
|
108
|
+
"""Async check reputation"""
|
|
109
|
+
async with self._client as client:
|
|
110
|
+
result = await client.reputation.get_score()
|
|
111
|
+
|
|
112
|
+
score = result.get("score", 0)
|
|
113
|
+
level = self._get_level(score)
|
|
114
|
+
|
|
115
|
+
output = f"Reputation Score: {score:.2f}/1.00 ({level})\n"
|
|
116
|
+
|
|
117
|
+
if result.get("erc8004AgentId"):
|
|
118
|
+
output += f"ERC-8004 Status: Enabled (Agent ID: {result['erc8004AgentId']})\n"
|
|
119
|
+
else:
|
|
120
|
+
output += "ERC-8004 Status: Not enabled\n"
|
|
121
|
+
|
|
122
|
+
return output
|
|
123
|
+
|
|
124
|
+
def _get_level(self, score: float) -> str:
|
|
125
|
+
"""Get reputation level from score"""
|
|
126
|
+
if score >= 0.9:
|
|
127
|
+
return "Elite"
|
|
128
|
+
elif score >= 0.7:
|
|
129
|
+
return "Trusted"
|
|
130
|
+
elif score >= 0.5:
|
|
131
|
+
return "Established"
|
|
132
|
+
elif score >= 0.3:
|
|
133
|
+
return "Developing"
|
|
134
|
+
else:
|
|
135
|
+
return "New"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class XacheReputationChecker:
|
|
139
|
+
"""
|
|
140
|
+
Utility class for checking reputation of any agent.
|
|
141
|
+
|
|
142
|
+
Useful for verifying other agents before interacting.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
```python
|
|
146
|
+
from xache_langchain import XacheReputationChecker
|
|
147
|
+
|
|
148
|
+
checker = XacheReputationChecker(
|
|
149
|
+
wallet_address="0x...",
|
|
150
|
+
private_key="0x..."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Check another agent's reputation
|
|
154
|
+
other_rep = checker.check("did:agent:evm:0xOtherAgent...")
|
|
155
|
+
if other_rep.score >= 0.5:
|
|
156
|
+
print("Agent is trustworthy")
|
|
157
|
+
```
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
wallet_address: str,
|
|
163
|
+
private_key: str,
|
|
164
|
+
api_url: str = "https://api.xache.xyz",
|
|
165
|
+
chain: str = "base",
|
|
166
|
+
):
|
|
167
|
+
self.api_url = api_url
|
|
168
|
+
self.chain = chain
|
|
169
|
+
|
|
170
|
+
chain_prefix = "sol" if chain == "solana" else "evm"
|
|
171
|
+
self.did = f"did:agent:{chain_prefix}:{wallet_address.lower()}"
|
|
172
|
+
|
|
173
|
+
self._client = XacheClient(
|
|
174
|
+
api_url=api_url,
|
|
175
|
+
did=self.did,
|
|
176
|
+
private_key=private_key,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def check(self, agent_did: str) -> ReputationResult:
|
|
180
|
+
"""Check an agent's reputation"""
|
|
181
|
+
import asyncio
|
|
182
|
+
|
|
183
|
+
async def _check():
|
|
184
|
+
async with self._client as client:
|
|
185
|
+
result = await client.reputation.get_score(agent_did=agent_did)
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
loop = asyncio.get_event_loop()
|
|
190
|
+
if loop.is_running():
|
|
191
|
+
import concurrent.futures
|
|
192
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
193
|
+
result = pool.submit(asyncio.run, _check()).result()
|
|
194
|
+
else:
|
|
195
|
+
result = loop.run_until_complete(_check())
|
|
196
|
+
except RuntimeError:
|
|
197
|
+
result = asyncio.run(_check())
|
|
198
|
+
|
|
199
|
+
score = result.get("score", 0)
|
|
200
|
+
|
|
201
|
+
return ReputationResult(
|
|
202
|
+
score=score,
|
|
203
|
+
level=self._get_level(score),
|
|
204
|
+
total_contributions=result.get("totalContributions", 0),
|
|
205
|
+
total_payments=result.get("totalPayments", 0),
|
|
206
|
+
erc8004_enabled=bool(result.get("erc8004AgentId")),
|
|
207
|
+
erc8004_agent_id=result.get("erc8004AgentId"),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def acheck(self, agent_did: str) -> ReputationResult:
|
|
211
|
+
"""Async check an agent's reputation"""
|
|
212
|
+
async with self._client as client:
|
|
213
|
+
result = await client.reputation.get_score(agent_did=agent_did)
|
|
214
|
+
|
|
215
|
+
score = result.get("score", 0)
|
|
216
|
+
|
|
217
|
+
return ReputationResult(
|
|
218
|
+
score=score,
|
|
219
|
+
level=self._get_level(score),
|
|
220
|
+
total_contributions=result.get("totalContributions", 0),
|
|
221
|
+
total_payments=result.get("totalPayments", 0),
|
|
222
|
+
erc8004_enabled=bool(result.get("erc8004AgentId")),
|
|
223
|
+
erc8004_agent_id=result.get("erc8004AgentId"),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _get_level(self, score: float) -> str:
|
|
227
|
+
"""Get reputation level from score"""
|
|
228
|
+
if score >= 0.9:
|
|
229
|
+
return "Elite"
|
|
230
|
+
elif score >= 0.7:
|
|
231
|
+
return "Trusted"
|
|
232
|
+
elif score >= 0.5:
|
|
233
|
+
return "Established"
|
|
234
|
+
elif score >= 0.3:
|
|
235
|
+
return "Developing"
|
|
236
|
+
else:
|
|
237
|
+
return "New"
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xache Retriever for LangChain
|
|
3
|
+
Semantic memory retrieval for RAG pipelines
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import List, Optional, Dict, Any
|
|
8
|
+
from langchain.schema import BaseRetriever, Document
|
|
9
|
+
from langchain.callbacks.manager import CallbackManagerForRetrieverRun
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
|
|
12
|
+
from xache import XacheClient
|
|
13
|
+
from ._async_utils import run_sync
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class XacheRetriever(BaseRetriever):
|
|
17
|
+
"""
|
|
18
|
+
LangChain retriever backed by Xache Protocol.
|
|
19
|
+
|
|
20
|
+
Retrieves semantically relevant memories for RAG pipelines.
|
|
21
|
+
Each retrieval is paid via x402 micropayments.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
```python
|
|
25
|
+
from xache_langchain import XacheRetriever
|
|
26
|
+
from langchain.chains import RetrievalQA
|
|
27
|
+
|
|
28
|
+
retriever = XacheRetriever(
|
|
29
|
+
wallet_address="0x...",
|
|
30
|
+
private_key="0x...",
|
|
31
|
+
k=5
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
qa = RetrievalQA.from_chain_type(
|
|
35
|
+
llm=llm,
|
|
36
|
+
retriever=retriever
|
|
37
|
+
)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For collective intelligence retrieval:
|
|
41
|
+
```python
|
|
42
|
+
retriever = XacheRetriever(
|
|
43
|
+
wallet_address="0x...",
|
|
44
|
+
private_key="0x...",
|
|
45
|
+
collective_id="research-insights", # Query collective
|
|
46
|
+
k=10
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Xache configuration
|
|
52
|
+
api_url: str = Field(
|
|
53
|
+
default_factory=lambda: os.environ.get("XACHE_API_URL", "https://api.xache.xyz")
|
|
54
|
+
)
|
|
55
|
+
wallet_address: str = Field(...)
|
|
56
|
+
private_key: str = Field(...)
|
|
57
|
+
chain: str = Field(default="base")
|
|
58
|
+
|
|
59
|
+
# Retrieval configuration
|
|
60
|
+
k: int = Field(default=5, description="Number of documents to retrieve")
|
|
61
|
+
collective_id: Optional[str] = Field(
|
|
62
|
+
default=None,
|
|
63
|
+
description="Collective ID for collective intelligence queries"
|
|
64
|
+
)
|
|
65
|
+
include_metadata: bool = Field(default=True)
|
|
66
|
+
min_relevance: float = Field(
|
|
67
|
+
default=0.0,
|
|
68
|
+
description="Minimum relevance score (0-1)"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Internal
|
|
72
|
+
_client: Optional[XacheClient] = None
|
|
73
|
+
|
|
74
|
+
class Config:
|
|
75
|
+
arbitrary_types_allowed = True
|
|
76
|
+
underscore_attrs_are_private = True
|
|
77
|
+
|
|
78
|
+
def __init__(self, **kwargs):
|
|
79
|
+
super().__init__(**kwargs)
|
|
80
|
+
self._init_client()
|
|
81
|
+
|
|
82
|
+
def _init_client(self):
|
|
83
|
+
"""Initialize Xache client"""
|
|
84
|
+
chain_prefix = "sol" if self.chain == "solana" else "evm"
|
|
85
|
+
did = f"did:agent:{chain_prefix}:{self.wallet_address.lower()}"
|
|
86
|
+
|
|
87
|
+
self._client = XacheClient(
|
|
88
|
+
api_url=self.api_url,
|
|
89
|
+
did=did,
|
|
90
|
+
private_key=self.private_key,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _get_relevant_documents(
|
|
94
|
+
self,
|
|
95
|
+
query: str,
|
|
96
|
+
*,
|
|
97
|
+
run_manager: CallbackManagerForRetrieverRun,
|
|
98
|
+
) -> List[Document]:
|
|
99
|
+
"""Retrieve relevant documents from Xache"""
|
|
100
|
+
|
|
101
|
+
async def _retrieve():
|
|
102
|
+
async with self._client as client:
|
|
103
|
+
if self.collective_id:
|
|
104
|
+
# Query collective intelligence
|
|
105
|
+
result = await client.collective.query(
|
|
106
|
+
collective_id=self.collective_id,
|
|
107
|
+
query=query,
|
|
108
|
+
limit=self.k
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
documents = []
|
|
112
|
+
for item in result.get("results", []):
|
|
113
|
+
content = item.get("content", "")
|
|
114
|
+
metadata = {
|
|
115
|
+
"source": "xache_collective",
|
|
116
|
+
"collective_id": self.collective_id,
|
|
117
|
+
"contributor_did": item.get("contributor_did"),
|
|
118
|
+
"relevance": item.get("relevance", 0),
|
|
119
|
+
"receipt_id": item.get("receipt_id"),
|
|
120
|
+
}
|
|
121
|
+
if self.include_metadata:
|
|
122
|
+
metadata.update(item.get("metadata", {}))
|
|
123
|
+
|
|
124
|
+
if item.get("relevance", 1) >= self.min_relevance:
|
|
125
|
+
documents.append(Document(
|
|
126
|
+
page_content=content,
|
|
127
|
+
metadata=metadata
|
|
128
|
+
))
|
|
129
|
+
|
|
130
|
+
return documents
|
|
131
|
+
else:
|
|
132
|
+
# Query personal memories
|
|
133
|
+
result = await client.memory.retrieve(
|
|
134
|
+
query=query,
|
|
135
|
+
limit=self.k
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
documents = []
|
|
139
|
+
# Fix: Correctly normalize result - API returns {"memories": [...]}
|
|
140
|
+
memories = result.get("memories", []) if isinstance(result, dict) else []
|
|
141
|
+
|
|
142
|
+
for mem in memories:
|
|
143
|
+
content = mem.get("content", "")
|
|
144
|
+
metadata = {
|
|
145
|
+
"source": "xache_memory",
|
|
146
|
+
"storage_key": mem.get("storage_key"),
|
|
147
|
+
"created_at": mem.get("created_at"),
|
|
148
|
+
"receipt_id": mem.get("receipt_id"),
|
|
149
|
+
}
|
|
150
|
+
if self.include_metadata:
|
|
151
|
+
metadata.update(mem.get("metadata", {}))
|
|
152
|
+
|
|
153
|
+
documents.append(Document(
|
|
154
|
+
page_content=content,
|
|
155
|
+
metadata=metadata
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
return documents
|
|
159
|
+
|
|
160
|
+
return run_sync(_retrieve())
|
|
161
|
+
|
|
162
|
+
async def _aget_relevant_documents(
|
|
163
|
+
self,
|
|
164
|
+
query: str,
|
|
165
|
+
*,
|
|
166
|
+
run_manager: CallbackManagerForRetrieverRun,
|
|
167
|
+
) -> List[Document]:
|
|
168
|
+
"""Async retrieve relevant documents"""
|
|
169
|
+
async with self._client as client:
|
|
170
|
+
if self.collective_id:
|
|
171
|
+
result = await client.collective.query(
|
|
172
|
+
collective_id=self.collective_id,
|
|
173
|
+
query=query,
|
|
174
|
+
limit=self.k
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
documents = []
|
|
178
|
+
for item in result.get("results", []):
|
|
179
|
+
content = item.get("content", "")
|
|
180
|
+
metadata = {
|
|
181
|
+
"source": "xache_collective",
|
|
182
|
+
"collective_id": self.collective_id,
|
|
183
|
+
"contributor_did": item.get("contributor_did"),
|
|
184
|
+
"relevance": item.get("relevance", 0),
|
|
185
|
+
}
|
|
186
|
+
if self.include_metadata:
|
|
187
|
+
metadata.update(item.get("metadata", {}))
|
|
188
|
+
|
|
189
|
+
if item.get("relevance", 1) >= self.min_relevance:
|
|
190
|
+
documents.append(Document(
|
|
191
|
+
page_content=content,
|
|
192
|
+
metadata=metadata
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
return documents
|
|
196
|
+
else:
|
|
197
|
+
result = await client.memory.retrieve(
|
|
198
|
+
query=query,
|
|
199
|
+
limit=self.k
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
documents = []
|
|
203
|
+
# Fix: Correctly normalize result - API returns {"memories": [...]}
|
|
204
|
+
memories = result.get("memories", []) if isinstance(result, dict) else []
|
|
205
|
+
|
|
206
|
+
for mem in memories:
|
|
207
|
+
content = mem.get("content", "")
|
|
208
|
+
metadata = {
|
|
209
|
+
"source": "xache_memory",
|
|
210
|
+
"storage_key": mem.get("storage_key"),
|
|
211
|
+
"created_at": mem.get("created_at"),
|
|
212
|
+
}
|
|
213
|
+
if self.include_metadata:
|
|
214
|
+
metadata.update(mem.get("metadata", {}))
|
|
215
|
+
|
|
216
|
+
documents.append(Document(
|
|
217
|
+
page_content=content,
|
|
218
|
+
metadata=metadata
|
|
219
|
+
))
|
|
220
|
+
|
|
221
|
+
return documents
|