@xache/langchain 0.1.0
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.
- package/README.md +177 -0
- package/dist/index.d.mts +457 -0
- package/dist/index.d.ts +457 -0
- package/dist/index.js +567 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +533 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +68 -0
- package/src/chat_history.ts +232 -0
- package/src/collective.ts +195 -0
- package/src/extraction.ts +172 -0
- package/src/index.ts +59 -0
- package/src/memory.ts +121 -0
- package/src/reputation.ts +195 -0
- package/src/retriever.ts +100 -0
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xache/langchain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LangChain.js integration for Xache Protocol - verifiable AI agent memory",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"test": "vitest",
|
|
27
|
+
"lint": "eslint src --ext .ts",
|
|
28
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"langchain",
|
|
32
|
+
"xache",
|
|
33
|
+
"ai",
|
|
34
|
+
"agents",
|
|
35
|
+
"memory",
|
|
36
|
+
"blockchain",
|
|
37
|
+
"receipts",
|
|
38
|
+
"reputation",
|
|
39
|
+
"erc8004",
|
|
40
|
+
"x402"
|
|
41
|
+
],
|
|
42
|
+
"author": "Xache Protocol",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/oliveskin/xache"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://xache.xyz",
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@langchain/core": ">=0.1.0",
|
|
51
|
+
"langchain": ">=0.1.0"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@xache/sdk": "workspace:*",
|
|
55
|
+
"zod": "^3.22.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@langchain/core": "^0.1.0",
|
|
59
|
+
"@types/node": "^20.10.0",
|
|
60
|
+
"langchain": "^0.1.0",
|
|
61
|
+
"tsup": "^8.0.0",
|
|
62
|
+
"typescript": "^5.3.0",
|
|
63
|
+
"vitest": "^1.0.0"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=18.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xache Chat Message History for LangChain.js
|
|
3
|
+
* Persistent message storage with cryptographic receipts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseListChatMessageHistory } from '@langchain/core/chat_history';
|
|
7
|
+
import {
|
|
8
|
+
BaseMessage,
|
|
9
|
+
HumanMessage,
|
|
10
|
+
AIMessage,
|
|
11
|
+
SystemMessage,
|
|
12
|
+
} from '@langchain/core/messages';
|
|
13
|
+
import { XacheClient, DID } from '@xache/sdk';
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
|
|
16
|
+
export interface XacheChatMessageHistoryConfig {
|
|
17
|
+
/** Wallet address for authentication */
|
|
18
|
+
walletAddress: string;
|
|
19
|
+
/** Private key for signing */
|
|
20
|
+
privateKey: string;
|
|
21
|
+
/** Session ID to group messages */
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/** API URL (defaults to https://api.xache.xyz) */
|
|
24
|
+
apiUrl?: string;
|
|
25
|
+
/** Chain: 'base' or 'solana' */
|
|
26
|
+
chain?: 'base' | 'solana';
|
|
27
|
+
/** Maximum messages to load (default: 1000, set to -1 for unlimited) */
|
|
28
|
+
maxMessages?: number;
|
|
29
|
+
/** Page size for loading messages (default: 100) */
|
|
30
|
+
pageSize?: number;
|
|
31
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
32
|
+
timeout?: number;
|
|
33
|
+
/** Enable debug logging */
|
|
34
|
+
debug?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Chat message history backed by Xache Protocol.
|
|
39
|
+
*
|
|
40
|
+
* Messages are stored with cryptographic receipts and can persist
|
|
41
|
+
* across sessions.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* import { XacheChatMessageHistory } from '@xache/langchain';
|
|
46
|
+
*
|
|
47
|
+
* const history = new XacheChatMessageHistory({
|
|
48
|
+
* walletAddress: '0x...',
|
|
49
|
+
* privateKey: '0x...',
|
|
50
|
+
* sessionId: 'unique-session-id',
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* await history.addUserMessage('Hello!');
|
|
54
|
+
* await history.addAIMessage('Hi there!');
|
|
55
|
+
*
|
|
56
|
+
* const messages = await history.getMessages();
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class XacheChatMessageHistory extends BaseListChatMessageHistory {
|
|
60
|
+
lc_namespace = ['xache', 'chat_history'];
|
|
61
|
+
|
|
62
|
+
private client: XacheClient;
|
|
63
|
+
private sessionId: string;
|
|
64
|
+
private messages: BaseMessage[] = [];
|
|
65
|
+
private initialized = false;
|
|
66
|
+
private maxMessages: number;
|
|
67
|
+
private pageSize: number;
|
|
68
|
+
|
|
69
|
+
constructor(config: XacheChatMessageHistoryConfig) {
|
|
70
|
+
super();
|
|
71
|
+
|
|
72
|
+
const chainPrefix = config.chain === 'solana' ? 'sol' : 'evm';
|
|
73
|
+
const did = `did:agent:${chainPrefix}:${config.walletAddress.toLowerCase()}` as DID;
|
|
74
|
+
|
|
75
|
+
this.client = new XacheClient({
|
|
76
|
+
apiUrl: config.apiUrl || 'https://api.xache.xyz',
|
|
77
|
+
did,
|
|
78
|
+
privateKey: config.privateKey,
|
|
79
|
+
timeout: config.timeout,
|
|
80
|
+
debug: config.debug,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Use UUID for collision-resistant session IDs
|
|
84
|
+
this.sessionId = config.sessionId || `langchain-${randomUUID()}`;
|
|
85
|
+
this.maxMessages = config.maxMessages ?? 1000;
|
|
86
|
+
this.pageSize = config.pageSize ?? 100;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get all messages in the history
|
|
91
|
+
*/
|
|
92
|
+
async getMessages(): Promise<BaseMessage[]> {
|
|
93
|
+
if (!this.initialized) {
|
|
94
|
+
await this.loadMessages();
|
|
95
|
+
}
|
|
96
|
+
return this.messages;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Add a message to the history
|
|
101
|
+
*/
|
|
102
|
+
async addMessage(message: BaseMessage): Promise<void> {
|
|
103
|
+
this.messages.push(message);
|
|
104
|
+
|
|
105
|
+
const role = this.getMessageRole(message);
|
|
106
|
+
const content =
|
|
107
|
+
typeof message.content === 'string'
|
|
108
|
+
? message.content
|
|
109
|
+
: JSON.stringify(message.content);
|
|
110
|
+
|
|
111
|
+
await this.client.memory.store({
|
|
112
|
+
data: {
|
|
113
|
+
role,
|
|
114
|
+
content,
|
|
115
|
+
sessionId: this.sessionId,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
},
|
|
118
|
+
storageTier: 'hot',
|
|
119
|
+
context: `chat:${this.sessionId}`,
|
|
120
|
+
tags: ['chat', 'message', role],
|
|
121
|
+
metadata: {
|
|
122
|
+
sessionId: this.sessionId,
|
|
123
|
+
role,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Add a user message
|
|
130
|
+
*/
|
|
131
|
+
async addUserMessage(message: string): Promise<void> {
|
|
132
|
+
await this.addMessage(new HumanMessage(message));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Add an AI message
|
|
137
|
+
*/
|
|
138
|
+
async addAIMessage(message: string): Promise<void> {
|
|
139
|
+
await this.addMessage(new AIMessage(message));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Clear all messages from history
|
|
144
|
+
*/
|
|
145
|
+
async clear(): Promise<void> {
|
|
146
|
+
this.messages = [];
|
|
147
|
+
// Note: Xache doesn't support deletion - messages remain for audit trail
|
|
148
|
+
// We only clear the local cache
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Load messages from Xache storage with pagination support
|
|
153
|
+
*/
|
|
154
|
+
private async loadMessages(): Promise<void> {
|
|
155
|
+
try {
|
|
156
|
+
const allMemories: Array<{ storage_key: string; created_at?: string }> = [];
|
|
157
|
+
let offset = 0;
|
|
158
|
+
const effectiveMax = this.maxMessages === -1 ? Infinity : this.maxMessages;
|
|
159
|
+
|
|
160
|
+
// Paginate through all messages
|
|
161
|
+
while (allMemories.length < effectiveMax) {
|
|
162
|
+
const result = await this.client.memory.list({
|
|
163
|
+
context: `chat:${this.sessionId}`,
|
|
164
|
+
limit: Math.min(this.pageSize, effectiveMax - allMemories.length),
|
|
165
|
+
offset,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const memories = Array.isArray(result?.memories) ? result.memories : [];
|
|
169
|
+
if (memories.length === 0) break;
|
|
170
|
+
|
|
171
|
+
allMemories.push(...memories);
|
|
172
|
+
offset += memories.length;
|
|
173
|
+
|
|
174
|
+
// If we got less than pageSize, we've reached the end
|
|
175
|
+
if (memories.length < this.pageSize) break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Sort by timestamp
|
|
179
|
+
const sortedMemories = [...allMemories].sort((a, b) => {
|
|
180
|
+
const tsA = new Date(a.created_at || 0).getTime();
|
|
181
|
+
const tsB = new Date(b.created_at || 0).getTime();
|
|
182
|
+
return tsA - tsB;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// For each memory, we need to retrieve the full content
|
|
186
|
+
this.messages = [];
|
|
187
|
+
for (const m of sortedMemories) {
|
|
188
|
+
try {
|
|
189
|
+
const full = await this.client.memory.retrieve({
|
|
190
|
+
storageKey: m.storage_key,
|
|
191
|
+
});
|
|
192
|
+
const data = full.data as { role?: string; content?: string };
|
|
193
|
+
if (data && data.content) {
|
|
194
|
+
switch (data.role) {
|
|
195
|
+
case 'human':
|
|
196
|
+
case 'user':
|
|
197
|
+
this.messages.push(new HumanMessage(data.content));
|
|
198
|
+
break;
|
|
199
|
+
case 'ai':
|
|
200
|
+
case 'assistant':
|
|
201
|
+
this.messages.push(new AIMessage(data.content));
|
|
202
|
+
break;
|
|
203
|
+
case 'system':
|
|
204
|
+
this.messages.push(new SystemMessage(data.content));
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
this.messages.push(new HumanMessage(data.content));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
// Skip malformed messages but log in debug
|
|
212
|
+
console.debug?.(`[XacheChatMessageHistory] Failed to parse message: ${(error as Error).message}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.initialized = true;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
// If retrieval fails (e.g., no memories yet), start fresh
|
|
219
|
+
// This is expected for new sessions
|
|
220
|
+
console.debug?.(`[XacheChatMessageHistory] No existing messages found: ${(error as Error).message}`);
|
|
221
|
+
this.messages = [];
|
|
222
|
+
this.initialized = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private getMessageRole(message: BaseMessage): string {
|
|
227
|
+
if (message instanceof HumanMessage) return 'human';
|
|
228
|
+
if (message instanceof AIMessage) return 'ai';
|
|
229
|
+
if (message instanceof SystemMessage) return 'system';
|
|
230
|
+
return 'unknown';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xache Collective Intelligence for LangChain.js
|
|
3
|
+
* Share and learn from collective knowledge pools
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { XacheClient, DID } from '@xache/sdk';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a hash for the pattern (simple implementation)
|
|
12
|
+
*/
|
|
13
|
+
async function generatePatternHash(pattern: string): Promise<string> {
|
|
14
|
+
const encoder = new TextEncoder();
|
|
15
|
+
const data = encoder.encode(pattern);
|
|
16
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
17
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
18
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CollectiveToolConfig {
|
|
22
|
+
/** Wallet address for authentication */
|
|
23
|
+
walletAddress: string;
|
|
24
|
+
/** Private key for signing */
|
|
25
|
+
privateKey: string;
|
|
26
|
+
/** API URL (defaults to https://api.xache.xyz) */
|
|
27
|
+
apiUrl?: string;
|
|
28
|
+
/** Chain: 'base' or 'solana' */
|
|
29
|
+
chain?: 'base' | 'solana';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create an Xache client for collective tools
|
|
34
|
+
*/
|
|
35
|
+
function createClient(config: CollectiveToolConfig): XacheClient {
|
|
36
|
+
const chainPrefix = config.chain === 'solana' ? 'sol' : 'evm';
|
|
37
|
+
const did = `did:agent:${chainPrefix}:${config.walletAddress.toLowerCase()}` as DID;
|
|
38
|
+
|
|
39
|
+
return new XacheClient({
|
|
40
|
+
apiUrl: config.apiUrl || 'https://api.xache.xyz',
|
|
41
|
+
did,
|
|
42
|
+
privateKey: config.privateKey,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a tool for contributing to collective intelligence.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { createCollectiveContributeTool } from '@xache/langchain';
|
|
52
|
+
*
|
|
53
|
+
* const contributeTool = createCollectiveContributeTool({
|
|
54
|
+
* walletAddress: '0x...',
|
|
55
|
+
* privateKey: '0x...',
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* const tools = [contributeTool, ...];
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function createCollectiveContributeTool(
|
|
62
|
+
config: CollectiveToolConfig
|
|
63
|
+
): DynamicStructuredTool {
|
|
64
|
+
const client = createClient(config);
|
|
65
|
+
|
|
66
|
+
return new DynamicStructuredTool({
|
|
67
|
+
name: 'xache_collective_contribute',
|
|
68
|
+
description:
|
|
69
|
+
'Contribute an insight or learning to the collective intelligence pool. ' +
|
|
70
|
+
'Use this when you discover something valuable that could help other agents. ' +
|
|
71
|
+
"You'll earn reputation for quality contributions.",
|
|
72
|
+
schema: z.object({
|
|
73
|
+
insight: z.string().describe('The insight or learning to contribute'),
|
|
74
|
+
domain: z.string().describe('Domain/topic of the insight'),
|
|
75
|
+
evidence: z.string().optional().describe('Supporting evidence'),
|
|
76
|
+
tags: z.array(z.string()).optional().describe('Tags for categorization'),
|
|
77
|
+
}),
|
|
78
|
+
func: async ({ insight, domain, evidence, tags }) => {
|
|
79
|
+
// Generate required fields for the SDK
|
|
80
|
+
const pattern = insight;
|
|
81
|
+
const patternHash = await generatePatternHash(pattern);
|
|
82
|
+
const tagsArray = tags || ['general'];
|
|
83
|
+
|
|
84
|
+
const result = await client.collective.contribute({
|
|
85
|
+
domain,
|
|
86
|
+
pattern,
|
|
87
|
+
patternHash,
|
|
88
|
+
tags: tagsArray,
|
|
89
|
+
metrics: {
|
|
90
|
+
successRate: 0.8, // Default metrics for new contributions
|
|
91
|
+
sampleSize: 1,
|
|
92
|
+
confidence: 0.7,
|
|
93
|
+
},
|
|
94
|
+
encryptedContentRef: evidence || '', // Store evidence as encrypted ref
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const heuristicId = result.heuristicId || 'unknown';
|
|
98
|
+
const receiptId = result.receiptId || 'unknown';
|
|
99
|
+
|
|
100
|
+
return `Contributed insight to '${domain}'. Heuristic ID: ${heuristicId}, Receipt: ${receiptId}`;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a tool for querying collective intelligence.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* import { createCollectiveQueryTool } from '@xache/langchain';
|
|
111
|
+
*
|
|
112
|
+
* const queryTool = createCollectiveQueryTool({
|
|
113
|
+
* walletAddress: '0x...',
|
|
114
|
+
* privateKey: '0x...',
|
|
115
|
+
* });
|
|
116
|
+
*
|
|
117
|
+
* const tools = [queryTool, ...];
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function createCollectiveQueryTool(
|
|
121
|
+
config: CollectiveToolConfig
|
|
122
|
+
): DynamicStructuredTool {
|
|
123
|
+
const client = createClient(config);
|
|
124
|
+
|
|
125
|
+
return new DynamicStructuredTool({
|
|
126
|
+
name: 'xache_collective_query',
|
|
127
|
+
description:
|
|
128
|
+
'Query the collective intelligence pool to learn from other agents. ' +
|
|
129
|
+
'Use this when you need insights or knowledge from the community. ' +
|
|
130
|
+
'Returns relevant contributions from other agents.',
|
|
131
|
+
schema: z.object({
|
|
132
|
+
query: z.string().describe('What to search for in the collective'),
|
|
133
|
+
domain: z.string().optional().describe('Filter by domain'),
|
|
134
|
+
limit: z.number().optional().default(5).describe('Number of results'),
|
|
135
|
+
}),
|
|
136
|
+
func: async ({ query, domain, limit }) => {
|
|
137
|
+
const result = await client.collective.query({
|
|
138
|
+
queryText: query,
|
|
139
|
+
domain,
|
|
140
|
+
limit: limit || 5,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const results = result.matches || [];
|
|
144
|
+
|
|
145
|
+
if (results.length === 0) {
|
|
146
|
+
return 'No relevant insights found in the collective.';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let output = `Found ${results.length} insights:\n`;
|
|
150
|
+
|
|
151
|
+
results.forEach((item, i: number) => {
|
|
152
|
+
const pattern = (item.pattern || '').slice(0, 200);
|
|
153
|
+
output += `\n${i + 1}. ${pattern}`;
|
|
154
|
+
if (item.domain) {
|
|
155
|
+
output += ` [Domain: ${item.domain}]`;
|
|
156
|
+
}
|
|
157
|
+
if (item.relevanceScore) {
|
|
158
|
+
output += ` (Relevance: ${item.relevanceScore.toFixed(2)})`;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return output;
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Class-based collective contribute tool (alternative API)
|
|
169
|
+
*/
|
|
170
|
+
export class XacheCollectiveContributeTool {
|
|
171
|
+
private tool: DynamicStructuredTool;
|
|
172
|
+
|
|
173
|
+
constructor(config: CollectiveToolConfig) {
|
|
174
|
+
this.tool = createCollectiveContributeTool(config);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
asTool(): DynamicStructuredTool {
|
|
178
|
+
return this.tool;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Class-based collective query tool (alternative API)
|
|
184
|
+
*/
|
|
185
|
+
export class XacheCollectiveQueryTool {
|
|
186
|
+
private tool: DynamicStructuredTool;
|
|
187
|
+
|
|
188
|
+
constructor(config: CollectiveToolConfig) {
|
|
189
|
+
this.tool = createCollectiveQueryTool(config);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
asTool(): DynamicStructuredTool {
|
|
193
|
+
return this.tool;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xache Memory Extraction for LangChain.js
|
|
3
|
+
* Auto-extract memories from conversations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { XacheClient, DID } from '@xache/sdk';
|
|
7
|
+
import type { ExtractMemoriesResponse } from '@xache/sdk';
|
|
8
|
+
|
|
9
|
+
export interface ExtractedMemory {
|
|
10
|
+
/** Memory content */
|
|
11
|
+
content: string;
|
|
12
|
+
/** Suggested context/category */
|
|
13
|
+
context: string;
|
|
14
|
+
/** Suggested tags */
|
|
15
|
+
tags: string[];
|
|
16
|
+
/** Confidence score */
|
|
17
|
+
confidence: number;
|
|
18
|
+
/** Memory ID if stored */
|
|
19
|
+
memoryId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExtractionResult {
|
|
23
|
+
/** Extracted memories */
|
|
24
|
+
memories: ExtractedMemory[];
|
|
25
|
+
/** Number of memories extracted */
|
|
26
|
+
count: number;
|
|
27
|
+
/** Total cost in USD */
|
|
28
|
+
cost: number;
|
|
29
|
+
/** Receipt ID for the operation */
|
|
30
|
+
receiptId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface XacheExtractorConfig {
|
|
34
|
+
/** Wallet address for authentication */
|
|
35
|
+
walletAddress: string;
|
|
36
|
+
/** Private key for signing */
|
|
37
|
+
privateKey: string;
|
|
38
|
+
/** Extraction mode: 'xache-managed' uses Xache's LLM, 'api-key' uses your own */
|
|
39
|
+
mode?: 'xache-managed' | 'api-key';
|
|
40
|
+
/** Your LLM API key (required if mode is 'api-key') */
|
|
41
|
+
llmApiKey?: string;
|
|
42
|
+
/** LLM provider for api-key mode */
|
|
43
|
+
llmProvider?: 'anthropic' | 'openai';
|
|
44
|
+
/** API URL (defaults to https://api.xache.xyz) */
|
|
45
|
+
apiUrl?: string;
|
|
46
|
+
/** Chain: 'base' or 'solana' */
|
|
47
|
+
chain?: 'base' | 'solana';
|
|
48
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
49
|
+
timeout?: number;
|
|
50
|
+
/** Enable debug logging */
|
|
51
|
+
debug?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract memories from conversation traces.
|
|
56
|
+
*
|
|
57
|
+
* Can auto-store extracted memories to Xache for later retrieval.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import { XacheExtractor } from '@xache/langchain';
|
|
62
|
+
*
|
|
63
|
+
* const extractor = new XacheExtractor({
|
|
64
|
+
* walletAddress: '0x...',
|
|
65
|
+
* privateKey: '0x...',
|
|
66
|
+
* mode: 'xache-managed',
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* const result = await extractor.extract(
|
|
70
|
+
* 'User: What is the capital of France?\nAI: Paris is the capital.',
|
|
71
|
+
* { autoStore: true }
|
|
72
|
+
* );
|
|
73
|
+
*
|
|
74
|
+
* console.log(`Extracted ${result.count} memories`);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export class XacheExtractor {
|
|
78
|
+
private client: XacheClient;
|
|
79
|
+
private mode: 'xache-managed' | 'api-key';
|
|
80
|
+
private llmApiKey?: string;
|
|
81
|
+
private llmProvider: 'anthropic' | 'openai';
|
|
82
|
+
|
|
83
|
+
constructor(config: XacheExtractorConfig) {
|
|
84
|
+
// Validate api-key mode requires llmApiKey
|
|
85
|
+
const mode = config.mode || 'xache-managed';
|
|
86
|
+
if (mode === 'api-key' && !config.llmApiKey) {
|
|
87
|
+
throw new Error('llmApiKey is required when mode is "api-key"');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const chainPrefix = config.chain === 'solana' ? 'sol' : 'evm';
|
|
91
|
+
const did = `did:agent:${chainPrefix}:${config.walletAddress.toLowerCase()}` as DID;
|
|
92
|
+
|
|
93
|
+
this.client = new XacheClient({
|
|
94
|
+
apiUrl: config.apiUrl || 'https://api.xache.xyz',
|
|
95
|
+
did,
|
|
96
|
+
privateKey: config.privateKey,
|
|
97
|
+
timeout: config.timeout,
|
|
98
|
+
debug: config.debug,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.mode = mode;
|
|
102
|
+
this.llmApiKey = config.llmApiKey;
|
|
103
|
+
this.llmProvider = config.llmProvider || 'anthropic';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract memories from a conversation trace
|
|
108
|
+
*/
|
|
109
|
+
async extract(
|
|
110
|
+
trace: string,
|
|
111
|
+
options?: {
|
|
112
|
+
/** Automatically store extracted memories */
|
|
113
|
+
autoStore?: boolean;
|
|
114
|
+
/** Custom instructions for extraction */
|
|
115
|
+
instructions?: string;
|
|
116
|
+
/** Minimum confidence threshold (0-1) */
|
|
117
|
+
minConfidence?: number;
|
|
118
|
+
}
|
|
119
|
+
): Promise<ExtractionResult> {
|
|
120
|
+
const llmConfig =
|
|
121
|
+
this.mode === 'xache-managed'
|
|
122
|
+
? { type: 'xache-managed' as const, provider: 'anthropic' as const }
|
|
123
|
+
: {
|
|
124
|
+
type: 'api-key' as const,
|
|
125
|
+
provider: this.llmProvider || ('anthropic' as const),
|
|
126
|
+
apiKey: this.llmApiKey || '',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result: ExtractMemoriesResponse = await this.client.extraction.extract({
|
|
130
|
+
trace,
|
|
131
|
+
llmConfig,
|
|
132
|
+
options: {
|
|
133
|
+
autoStore: options?.autoStore,
|
|
134
|
+
contextHint: options?.instructions,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const minConfidence = options?.minConfidence ?? 0;
|
|
139
|
+
const memories: ExtractedMemory[] = (result.extractions || [])
|
|
140
|
+
.filter((m) => (m.confidence || 1) >= minConfidence)
|
|
141
|
+
.map((m, index) => ({
|
|
142
|
+
content: m.reasoning || JSON.stringify(m.data) || '',
|
|
143
|
+
context: m.type || 'extracted',
|
|
144
|
+
tags: Object.keys(m.data || {}),
|
|
145
|
+
confidence: m.confidence || 1,
|
|
146
|
+
memoryId: result.stored?.[index],
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
memories,
|
|
151
|
+
count: memories.length,
|
|
152
|
+
cost: 0, // Cost is handled by x402
|
|
153
|
+
receiptId: result.metadata?.paymentReceiptId,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Extract from LangChain messages
|
|
159
|
+
*/
|
|
160
|
+
async extractFromMessages(
|
|
161
|
+
messages: Array<{ role: string; content: string }>,
|
|
162
|
+
options?: {
|
|
163
|
+
autoStore?: boolean;
|
|
164
|
+
instructions?: string;
|
|
165
|
+
minConfidence?: number;
|
|
166
|
+
}
|
|
167
|
+
): Promise<ExtractionResult> {
|
|
168
|
+
const trace = messages.map((m) => `${m.role}: ${m.content}`).join('\n');
|
|
169
|
+
|
|
170
|
+
return this.extract(trace, options);
|
|
171
|
+
}
|
|
172
|
+
}
|