ai-sdk-memory 0.0.1
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 +1 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +334 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +312 -0
- package/dist/index.mjs.map +1 -0
- package/dist/schema.d.mts +31 -0
- package/dist/schema.d.ts +31 -0
- package/dist/schema.js +76 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.mjs +73 -0
- package/dist/schema.mjs.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
package/dist/index.d.mts
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
import { streamText, StreamTextResult, generateText, GenerateTextResult, generateObject, GenerateObjectResult, streamObject, StreamObjectResult } from 'ai';
|
2
|
+
import { SemanticCacheConfig } from './schema.mjs';
|
3
|
+
import 'zod';
|
4
|
+
|
5
|
+
declare function createSemanticCache(config: SemanticCacheConfig): {
|
6
|
+
streamText: <TOOLS extends Record<string, any> = {}>(options: Parameters<typeof streamText<TOOLS>>[0]) => Promise<StreamTextResult<TOOLS, any>>;
|
7
|
+
generateText: <TOOLS extends Record<string, any> = {}, OUTPUT = undefined>(options: Parameters<typeof generateText<TOOLS, OUTPUT>>[0]) => Promise<GenerateTextResult<TOOLS, OUTPUT>>;
|
8
|
+
generateObject: <T = any>(options: Parameters<typeof generateObject>[0]) => Promise<GenerateObjectResult<T>>;
|
9
|
+
streamObject: <T = any>(options: Parameters<typeof streamObject>[0]) => Promise<StreamObjectResult<T, T, any>>;
|
10
|
+
};
|
11
|
+
|
12
|
+
export { createSemanticCache };
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
import { streamText, StreamTextResult, generateText, GenerateTextResult, generateObject, GenerateObjectResult, streamObject, StreamObjectResult } from 'ai';
|
2
|
+
import { SemanticCacheConfig } from './schema.js';
|
3
|
+
import 'zod';
|
4
|
+
|
5
|
+
declare function createSemanticCache(config: SemanticCacheConfig): {
|
6
|
+
streamText: <TOOLS extends Record<string, any> = {}>(options: Parameters<typeof streamText<TOOLS>>[0]) => Promise<StreamTextResult<TOOLS, any>>;
|
7
|
+
generateText: <TOOLS extends Record<string, any> = {}, OUTPUT = undefined>(options: Parameters<typeof generateText<TOOLS, OUTPUT>>[0]) => Promise<GenerateTextResult<TOOLS, OUTPUT>>;
|
8
|
+
generateObject: <T = any>(options: Parameters<typeof generateObject>[0]) => Promise<GenerateObjectResult<T>>;
|
9
|
+
streamObject: <T = any>(options: Parameters<typeof streamObject>[0]) => Promise<StreamObjectResult<T, T, any>>;
|
10
|
+
};
|
11
|
+
|
12
|
+
export { createSemanticCache };
|
package/dist/index.js
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
require('@ai-sdk/provider');
|
4
|
+
var ai = require('ai');
|
5
|
+
var vector = require('@upstash/vector');
|
6
|
+
var redis = require('@upstash/redis');
|
7
|
+
var crypto = require('crypto');
|
8
|
+
var zod = require('zod');
|
9
|
+
require('dotenv/config');
|
10
|
+
|
11
|
+
function _interopNamespace(e) {
|
12
|
+
if (e && e.__esModule) return e;
|
13
|
+
var n = Object.create(null);
|
14
|
+
if (e) {
|
15
|
+
Object.keys(e).forEach(function (k) {
|
16
|
+
if (k !== 'default') {
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
19
|
+
enumerable: true,
|
20
|
+
get: function () { return e[k]; }
|
21
|
+
});
|
22
|
+
}
|
23
|
+
});
|
24
|
+
}
|
25
|
+
n.default = e;
|
26
|
+
return Object.freeze(n);
|
27
|
+
}
|
28
|
+
|
29
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
30
|
+
|
31
|
+
// index.ts
|
32
|
+
var semanticCacheConfigSchema = zod.z.object({
|
33
|
+
model: zod.z.union([
|
34
|
+
zod.z.string(),
|
35
|
+
zod.z.custom((val) => {
|
36
|
+
return val && typeof val === "object";
|
37
|
+
})
|
38
|
+
]),
|
39
|
+
vector: zod.z.object({
|
40
|
+
url: zod.z.url(),
|
41
|
+
token: zod.z.string().min(1)
|
42
|
+
}).optional().default({
|
43
|
+
url: process.env.VECTOR_REST_URL ?? "",
|
44
|
+
token: process.env.VECTOR_REST_TOKEN ?? ""
|
45
|
+
}),
|
46
|
+
redis: zod.z.object({
|
47
|
+
url: zod.z.url(),
|
48
|
+
token: zod.z.string().min(1)
|
49
|
+
}).optional().default({
|
50
|
+
url: process.env.REDIS_REST_URL ?? "",
|
51
|
+
token: process.env.REDIS_REST_TOKEN ?? ""
|
52
|
+
}),
|
53
|
+
threshold: zod.z.number().min(0).max(1).optional().default(0.92),
|
54
|
+
ttl: zod.z.number().positive().optional().default(60 * 60 * 24 * 14),
|
55
|
+
debug: zod.z.boolean().optional().default(false),
|
56
|
+
cacheMode: zod.z.enum(["default", "refresh"]).optional().default("default"),
|
57
|
+
simulateStream: zod.z.object({
|
58
|
+
enabled: zod.z.boolean().optional().default(true),
|
59
|
+
initialDelayInMs: zod.z.number().min(0).optional().default(0),
|
60
|
+
chunkDelayInMs: zod.z.number().min(0).optional().default(10)
|
61
|
+
}).optional().default({
|
62
|
+
enabled: true,
|
63
|
+
initialDelayInMs: 0,
|
64
|
+
chunkDelayInMs: 10
|
65
|
+
}),
|
66
|
+
useFullMessages: zod.z.boolean().optional().default(false)
|
67
|
+
});
|
68
|
+
function validateEnvConfig(config) {
|
69
|
+
const errors = [];
|
70
|
+
if (!config.vector.url) {
|
71
|
+
errors.push(
|
72
|
+
"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable."
|
73
|
+
);
|
74
|
+
}
|
75
|
+
if (!config.vector.token) {
|
76
|
+
errors.push(
|
77
|
+
"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable."
|
78
|
+
);
|
79
|
+
}
|
80
|
+
if (!config.redis.url) {
|
81
|
+
errors.push(
|
82
|
+
"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable."
|
83
|
+
);
|
84
|
+
}
|
85
|
+
if (!config.redis.token) {
|
86
|
+
errors.push(
|
87
|
+
"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable."
|
88
|
+
);
|
89
|
+
}
|
90
|
+
if (errors.length > 0) {
|
91
|
+
throw new Error(
|
92
|
+
`Semantic Cache Configuration Error:
|
93
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`
|
94
|
+
);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
// index.ts
|
99
|
+
var norm = (s) => s.trim().toLowerCase().replace(/\s+/g, " ");
|
100
|
+
var sha = (s) => crypto__namespace.createHash("sha256").update(s).digest("hex");
|
101
|
+
function canonicalizeMessages(msgs) {
|
102
|
+
return msgs.map((m) => ({
|
103
|
+
role: m.role,
|
104
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
105
|
+
}));
|
106
|
+
}
|
107
|
+
function buildScope(options) {
|
108
|
+
return {
|
109
|
+
llmModel: options.model?.modelId ?? String(options.model ?? ""),
|
110
|
+
systemHash: sha(options.system ?? ""),
|
111
|
+
params: sha(
|
112
|
+
JSON.stringify({
|
113
|
+
temperature: options.temperature,
|
114
|
+
topP: options.topP
|
115
|
+
})
|
116
|
+
),
|
117
|
+
toolsHash: sha(JSON.stringify(options.tools ?? {}))
|
118
|
+
};
|
119
|
+
}
|
120
|
+
function createSemanticCache(config) {
|
121
|
+
const parsed = semanticCacheConfigSchema.parse(config);
|
122
|
+
validateEnvConfig(parsed);
|
123
|
+
const {
|
124
|
+
vector: vector$1,
|
125
|
+
redis: redisConfig,
|
126
|
+
threshold,
|
127
|
+
ttl,
|
128
|
+
simulateStream,
|
129
|
+
debug,
|
130
|
+
cacheMode,
|
131
|
+
useFullMessages
|
132
|
+
} = parsed;
|
133
|
+
const model = parsed.model;
|
134
|
+
const index = new vector.Index({
|
135
|
+
url: vector$1.url,
|
136
|
+
token: vector$1.token
|
137
|
+
});
|
138
|
+
const redis$1 = new redis.Redis({
|
139
|
+
url: redisConfig.url,
|
140
|
+
token: redisConfig.token
|
141
|
+
});
|
142
|
+
function getCacheKey(options) {
|
143
|
+
if (options.messages) {
|
144
|
+
const messages = Array.isArray(options.messages) ? options.messages : [];
|
145
|
+
if (!useFullMessages && messages.length > 0) {
|
146
|
+
const lastMessage = messages[messages.length - 1];
|
147
|
+
return JSON.stringify({
|
148
|
+
role: lastMessage.role,
|
149
|
+
content: typeof lastMessage.content === "string" ? lastMessage.content : JSON.stringify(lastMessage.content)
|
150
|
+
});
|
151
|
+
}
|
152
|
+
return JSON.stringify(canonicalizeMessages(options.messages));
|
153
|
+
}
|
154
|
+
if (options.prompt) {
|
155
|
+
return String(options.prompt);
|
156
|
+
}
|
157
|
+
return "";
|
158
|
+
}
|
159
|
+
async function checkSemanticCache(cacheInput, scope) {
|
160
|
+
const promptNorm = norm(cacheInput);
|
161
|
+
const { embedding } = await ai.embed({
|
162
|
+
model,
|
163
|
+
value: promptNorm
|
164
|
+
});
|
165
|
+
const result = await index.query({
|
166
|
+
vector: embedding,
|
167
|
+
topK: 3,
|
168
|
+
includeMetadata: true
|
169
|
+
});
|
170
|
+
const hit = result.find((m) => {
|
171
|
+
if (m.score < threshold) return false;
|
172
|
+
const metadata = m.metadata;
|
173
|
+
if (!metadata) return false;
|
174
|
+
return metadata.llmModel === scope.llmModel && metadata.systemHash === scope.systemHash && metadata.params === scope.params && metadata.toolsHash === scope.toolsHash;
|
175
|
+
});
|
176
|
+
if (hit) {
|
177
|
+
const cached = await redis$1.get(hit.id.toString());
|
178
|
+
if (cached) {
|
179
|
+
if (debug) console.log("\u2705 cache hit", hit.score.toFixed(3));
|
180
|
+
return { cached, embedding, promptNorm };
|
181
|
+
}
|
182
|
+
}
|
183
|
+
if (debug) console.log("\u274C miss -> generating\u2026");
|
184
|
+
return { cached: null, embedding, promptNorm };
|
185
|
+
}
|
186
|
+
async function storeInCache(id, data, embedding, promptNorm, scope) {
|
187
|
+
const lockKey = "lock:" + id;
|
188
|
+
const ok = await redis$1.set(lockKey, "1", { nx: true, ex: 15 });
|
189
|
+
if (!ok) {
|
190
|
+
if (debug)
|
191
|
+
console.log("\u26A0\uFE0F Another process is writing to cache, skipping");
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
try {
|
195
|
+
await redis$1.set(id, data, { ex: ttl });
|
196
|
+
await index.upsert([
|
197
|
+
{
|
198
|
+
id,
|
199
|
+
vector: embedding,
|
200
|
+
metadata: {
|
201
|
+
prompt: promptNorm,
|
202
|
+
...scope
|
203
|
+
}
|
204
|
+
}
|
205
|
+
]);
|
206
|
+
} finally {
|
207
|
+
await redis$1.del(lockKey);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
const semanticCacheMiddleware = {
|
211
|
+
wrapStream: async ({ doStream, params }) => {
|
212
|
+
const cacheInput = getCacheKey(params);
|
213
|
+
const scope = buildScope(params);
|
214
|
+
const promptScope = Object.values(scope).join("|");
|
215
|
+
const { cached, embedding, promptNorm } = await checkSemanticCache(
|
216
|
+
cacheInput,
|
217
|
+
scope
|
218
|
+
);
|
219
|
+
if (cached && cacheMode !== "refresh") {
|
220
|
+
if (debug) console.log("\u2705 Returning cached stream");
|
221
|
+
let chunks = [];
|
222
|
+
if (cached.streamParts) {
|
223
|
+
chunks = cached.streamParts.map((p) => {
|
224
|
+
if (p.type === "response-metadata" && p.timestamp) {
|
225
|
+
return { ...p, timestamp: new Date(p.timestamp) };
|
226
|
+
}
|
227
|
+
return p;
|
228
|
+
});
|
229
|
+
} else if (cached.text) {
|
230
|
+
chunks = [
|
231
|
+
{ type: "text-start", id: cached.id },
|
232
|
+
{ type: "text-delta", delta: cached.text, id: cached.id },
|
233
|
+
{ type: "finish", finishReason: "stop", usage: cached.usage }
|
234
|
+
];
|
235
|
+
}
|
236
|
+
return {
|
237
|
+
stream: ai.simulateReadableStream({
|
238
|
+
initialDelayInMs: simulateStream.enabled ? simulateStream.initialDelayInMs : 0,
|
239
|
+
chunkDelayInMs: simulateStream.enabled ? simulateStream.chunkDelayInMs : 0,
|
240
|
+
chunks
|
241
|
+
})
|
242
|
+
};
|
243
|
+
}
|
244
|
+
const { stream, ...rest } = await doStream();
|
245
|
+
const fullResponse = [];
|
246
|
+
const transformStream = new TransformStream({
|
247
|
+
transform(chunk, controller) {
|
248
|
+
fullResponse.push(chunk);
|
249
|
+
controller.enqueue(chunk);
|
250
|
+
},
|
251
|
+
async flush() {
|
252
|
+
const id = "llm:" + sha(promptScope + "|" + promptNorm);
|
253
|
+
await storeInCache(
|
254
|
+
id,
|
255
|
+
{ streamParts: fullResponse },
|
256
|
+
embedding,
|
257
|
+
promptNorm,
|
258
|
+
scope
|
259
|
+
);
|
260
|
+
}
|
261
|
+
});
|
262
|
+
return {
|
263
|
+
stream: stream.pipeThrough(transformStream),
|
264
|
+
...rest
|
265
|
+
};
|
266
|
+
},
|
267
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
268
|
+
const cacheInput = getCacheKey(params);
|
269
|
+
const scope = buildScope(params);
|
270
|
+
const promptScope = Object.values(scope).join("|");
|
271
|
+
const { cached, embedding, promptNorm } = await checkSemanticCache(
|
272
|
+
cacheInput,
|
273
|
+
scope
|
274
|
+
);
|
275
|
+
if (cached && cacheMode !== "refresh") {
|
276
|
+
if (debug) console.log("\u2705 Returning cached generation");
|
277
|
+
if (cached?.response?.timestamp) {
|
278
|
+
cached.response.timestamp = new Date(cached.response.timestamp);
|
279
|
+
}
|
280
|
+
return cached;
|
281
|
+
}
|
282
|
+
const result = await doGenerate();
|
283
|
+
const id = "llm:" + sha(promptScope + "|" + promptNorm);
|
284
|
+
await storeInCache(id, result, embedding, promptNorm, scope);
|
285
|
+
return result;
|
286
|
+
}
|
287
|
+
};
|
288
|
+
return {
|
289
|
+
streamText: async (options) => {
|
290
|
+
const wrappedModel = ai.wrapLanguageModel({
|
291
|
+
model: typeof options.model === "string" ? ai.gateway(options.model) : options.model,
|
292
|
+
middleware: semanticCacheMiddleware
|
293
|
+
});
|
294
|
+
return ai.streamText({
|
295
|
+
...options,
|
296
|
+
model: wrappedModel
|
297
|
+
});
|
298
|
+
},
|
299
|
+
generateText: async (options) => {
|
300
|
+
const wrappedModel = ai.wrapLanguageModel({
|
301
|
+
model: typeof options.model === "string" ? ai.gateway(options.model) : options.model,
|
302
|
+
middleware: semanticCacheMiddleware
|
303
|
+
});
|
304
|
+
return ai.generateText({
|
305
|
+
...options,
|
306
|
+
model: wrappedModel
|
307
|
+
});
|
308
|
+
},
|
309
|
+
generateObject: async (options) => {
|
310
|
+
const wrappedModel = ai.wrapLanguageModel({
|
311
|
+
model: typeof options.model === "string" ? ai.gateway(options.model) : options.model,
|
312
|
+
middleware: semanticCacheMiddleware
|
313
|
+
});
|
314
|
+
return await ai.generateObject({
|
315
|
+
...options,
|
316
|
+
model: wrappedModel
|
317
|
+
});
|
318
|
+
},
|
319
|
+
streamObject: async (options) => {
|
320
|
+
const wrappedModel = ai.wrapLanguageModel({
|
321
|
+
model: typeof options.model === "string" ? ai.gateway(options.model) : options.model,
|
322
|
+
middleware: semanticCacheMiddleware
|
323
|
+
});
|
324
|
+
return ai.streamObject({
|
325
|
+
...options,
|
326
|
+
model: wrappedModel
|
327
|
+
});
|
328
|
+
}
|
329
|
+
};
|
330
|
+
}
|
331
|
+
|
332
|
+
exports.createSemanticCache = createSemanticCache;
|
333
|
+
//# sourceMappingURL=index.js.map
|
334
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../schema.ts","../index.ts"],"names":["z","crypto","vector","Index","redis","Redis","embed","simulateReadableStream","wrapLanguageModel","gateway","streamText","generateText","generateObject","streamObject"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from \"zod\";\nimport \"dotenv/config\";\n\nexport const semanticCacheConfigSchema = z.object({\n model: z.union([\n z.string(),\n z.custom((val) => {\n return val && typeof val === \"object\";\n }),\n ]),\n vector: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.VECTOR_REST_URL ?? \"\",\n token: process.env.VECTOR_REST_TOKEN ?? \"\",\n }),\n redis: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.REDIS_REST_URL ?? \"\",\n token: process.env.REDIS_REST_TOKEN ?? \"\",\n }),\n threshold: z.number().min(0).max(1).optional().default(0.92),\n ttl: z\n .number()\n .positive()\n .optional()\n .default(60 * 60 * 24 * 14),\n debug: z.boolean().optional().default(false),\n cacheMode: z.enum([\"default\", \"refresh\"]).optional().default(\"default\"),\n simulateStream: z\n .object({\n enabled: z.boolean().optional().default(true),\n initialDelayInMs: z.number().min(0).optional().default(0),\n chunkDelayInMs: z.number().min(0).optional().default(10),\n })\n .optional()\n .default({\n enabled: true,\n initialDelayInMs: 0,\n chunkDelayInMs: 10,\n }),\n useFullMessages: z.boolean().optional().default(false),\n});\n\nexport type SemanticCacheConfig = z.input<typeof semanticCacheConfigSchema>;\nexport type SemanticCacheConfigParsed = z.output<\n typeof semanticCacheConfigSchema\n>;\n\nexport function validateEnvConfig(config: SemanticCacheConfigParsed) {\n const errors: string[] = [];\n\n if (!config.vector.url) {\n errors.push(\n \"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable.\",\n );\n }\n\n if (!config.vector.token) {\n errors.push(\n \"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable.\",\n );\n }\n\n if (!config.redis.url) {\n errors.push(\n \"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable.\",\n );\n }\n\n if (!config.redis.token) {\n errors.push(\n \"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable.\",\n );\n }\n\n if (errors.length > 0) {\n throw new Error(\n `Semantic Cache Configuration Error:\\n${errors.map((e) => ` - ${e}`).join(\"\\n\")}`,\n );\n }\n}\n","import {\n type LanguageModelV2Middleware,\n type LanguageModelV2StreamPart,\n type LanguageModelV2Content,\n type LanguageModelV2CallOptions,\n} from \"@ai-sdk/provider\";\nimport {\n embed,\n streamText,\n generateText,\n generateObject,\n streamObject,\n simulateReadableStream,\n wrapLanguageModel,\n type EmbeddingModel,\n type StreamTextResult,\n type GenerateTextResult,\n type GenerateObjectResult,\n type StreamObjectResult,\n gateway,\n} from \"ai\";\n\nimport { Index } from \"@upstash/vector\";\nimport { Redis } from \"@upstash/redis\";\nimport * as crypto from \"node:crypto\";\nimport {\n semanticCacheConfigSchema,\n type SemanticCacheConfig,\n validateEnvConfig,\n} from \"./schema\";\n\nconst norm = (s: string) => s.trim().toLowerCase().replace(/\\s+/g, \" \");\nconst sha = (s: string) => crypto.createHash(\"sha256\").update(s).digest(\"hex\");\n\nfunction canonicalizeMessages(msgs: any[]) {\n return msgs.map((m) => ({\n role: m.role,\n content:\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n }));\n}\n\nfunction buildScope(options: any) {\n return {\n llmModel: options.model?.modelId ?? String(options.model ?? \"\"),\n systemHash: sha(options.system ?? \"\"),\n params: sha(\n JSON.stringify({\n temperature: options.temperature,\n topP: options.topP,\n }),\n ),\n toolsHash: sha(JSON.stringify(options.tools ?? {})),\n };\n}\n\nexport function createSemanticCache(config: SemanticCacheConfig) {\n const parsed = semanticCacheConfigSchema.parse(config);\n\n validateEnvConfig(parsed);\n\n const {\n vector,\n redis: redisConfig,\n threshold,\n ttl,\n simulateStream,\n debug,\n cacheMode,\n useFullMessages,\n } = parsed;\n const model = parsed.model as EmbeddingModel<string>;\n\n const index = new Index({\n url: vector.url as string,\n token: vector.token as string,\n });\n\n const redis = new Redis({\n url: redisConfig.url as string,\n token: redisConfig.token as string,\n });\n\n function getCacheKey(options: any): string {\n if (options.messages) {\n const messages = Array.isArray(options.messages) ? options.messages : [];\n\n // By default, use only the last message to avoid token limit issues\n if (!useFullMessages && messages.length > 0) {\n const lastMessage = messages[messages.length - 1];\n return JSON.stringify({\n role: lastMessage.role,\n content:\n typeof lastMessage.content === \"string\"\n ? lastMessage.content\n : JSON.stringify(lastMessage.content),\n });\n }\n\n return JSON.stringify(canonicalizeMessages(options.messages));\n }\n if (options.prompt) {\n return String(options.prompt);\n }\n return \"\";\n }\n\n async function checkSemanticCache(cacheInput: string, scope: any) {\n const promptNorm = norm(cacheInput);\n\n const { embedding } = await embed({\n model,\n value: promptNorm,\n });\n\n const result = await index.query({\n vector: embedding,\n topK: 3,\n includeMetadata: true,\n });\n\n const hit = result.find((m) => {\n if (m.score < threshold) return false;\n\n const metadata = m.metadata as any;\n if (!metadata) return false;\n\n return (\n metadata.llmModel === scope.llmModel &&\n metadata.systemHash === scope.systemHash &&\n metadata.params === scope.params &&\n metadata.toolsHash === scope.toolsHash\n );\n });\n\n if (hit) {\n const cached = await redis.get<{\n streamParts?: LanguageModelV2StreamPart[];\n text?: string;\n content?: LanguageModelV2Content;\n [key: string]: any;\n }>(hit.id.toString());\n\n if (cached) {\n if (debug) console.log(\"✅ cache hit\", hit.score.toFixed(3));\n return { cached, embedding, promptNorm };\n }\n }\n\n if (debug) console.log(\"❌ miss -> generating…\");\n return { cached: null, embedding, promptNorm };\n }\n\n async function storeInCache(\n id: string,\n data: any,\n embedding: number[],\n promptNorm: string,\n scope: any,\n ) {\n const lockKey = \"lock:\" + id;\n const ok = await redis.set(lockKey, \"1\", { nx: true, ex: 15 });\n\n if (!ok) {\n if (debug)\n console.log(\"⚠️ Another process is writing to cache, skipping\");\n return;\n }\n\n try {\n await redis.set(id, data, { ex: ttl });\n await index.upsert([\n {\n id,\n vector: embedding,\n metadata: {\n prompt: promptNorm,\n ...scope,\n },\n },\n ]);\n } finally {\n await redis.del(lockKey);\n }\n }\n\n const semanticCacheMiddleware: LanguageModelV2Middleware = {\n wrapStream: async ({ doStream, params }) => {\n const cacheInput = getCacheKey(params);\n const scope = buildScope(params);\n const promptScope = Object.values(scope).join(\"|\");\n\n const { cached, embedding, promptNorm } = await checkSemanticCache(\n cacheInput,\n scope,\n );\n\n if (cached && cacheMode !== \"refresh\") {\n if (debug) console.log(\"✅ Returning cached stream\");\n\n let chunks: LanguageModelV2StreamPart[] = [];\n\n if (cached.streamParts) {\n chunks = cached.streamParts.map((p: any) => {\n if (p.type === \"response-metadata\" && p.timestamp) {\n return { ...p, timestamp: new Date(p.timestamp) };\n }\n return p;\n });\n } else if (cached.text) {\n chunks = [\n { type: \"text-start\", id: cached.id },\n { type: \"text-delta\", delta: cached.text, id: cached.id },\n { type: \"finish\", finishReason: \"stop\", usage: cached.usage },\n ];\n }\n\n return {\n stream: simulateReadableStream({\n initialDelayInMs: simulateStream.enabled\n ? simulateStream.initialDelayInMs\n : 0,\n chunkDelayInMs: simulateStream.enabled\n ? simulateStream.chunkDelayInMs\n : 0,\n chunks,\n }),\n };\n }\n\n const { stream, ...rest } = await doStream();\n\n const fullResponse: LanguageModelV2StreamPart[] = [];\n\n const transformStream = new TransformStream<\n LanguageModelV2StreamPart,\n LanguageModelV2StreamPart\n >({\n transform(chunk, controller) {\n fullResponse.push(chunk);\n controller.enqueue(chunk);\n },\n async flush() {\n const id = \"llm:\" + sha(promptScope + \"|\" + promptNorm);\n await storeInCache(\n id,\n { streamParts: fullResponse },\n embedding,\n promptNorm,\n scope,\n );\n },\n });\n\n return {\n stream: stream.pipeThrough(transformStream),\n ...rest,\n };\n },\n\n wrapGenerate: async ({ doGenerate, params }) => {\n const cacheInput = getCacheKey(params);\n const scope = buildScope(params);\n const promptScope = Object.values(scope).join(\"|\");\n\n const { cached, embedding, promptNorm } = await checkSemanticCache(\n cacheInput,\n scope,\n );\n\n if (cached && cacheMode !== \"refresh\") {\n if (debug) console.log(\"✅ Returning cached generation\");\n\n if (cached?.response?.timestamp) {\n cached.response.timestamp = new Date(cached.response.timestamp);\n }\n\n type GenReturn<T> = T extends () => PromiseLike<infer R> ? R : never;\n return cached as unknown as GenReturn<typeof doGenerate>;\n }\n\n const result = await doGenerate();\n\n const id = \"llm:\" + sha(promptScope + \"|\" + promptNorm);\n await storeInCache(id, result, embedding, promptNorm, scope);\n\n return result;\n },\n };\n\n return {\n streamText: async <TOOLS extends Record<string, any> = {}>(\n options: Parameters<typeof streamText<TOOLS>>[0],\n ): Promise<StreamTextResult<TOOLS, any>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return streamText({\n ...options,\n model: wrappedModel,\n });\n },\n\n generateText: async <\n TOOLS extends Record<string, any> = {},\n OUTPUT = undefined,\n >(\n options: Parameters<typeof generateText<TOOLS, OUTPUT>>[0],\n ): Promise<GenerateTextResult<TOOLS, OUTPUT>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return generateText({\n ...options,\n model: wrappedModel,\n });\n },\n\n generateObject: async <T = any>(\n options: Parameters<typeof generateObject>[0],\n ): Promise<GenerateObjectResult<T>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return (await generateObject({\n ...options,\n model: wrappedModel,\n })) as GenerateObjectResult<T>;\n },\n\n streamObject: async <T = any>(\n options: Parameters<typeof streamObject>[0],\n ): Promise<StreamObjectResult<T, T, any>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return streamObject({\n ...options,\n model: wrappedModel,\n }) as unknown as StreamObjectResult<T, T, any>;\n },\n };\n}\n"]}
|
package/dist/index.mjs
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
import '@ai-sdk/provider';
|
2
|
+
import { wrapLanguageModel, gateway, streamObject, generateObject, generateText, streamText, simulateReadableStream, embed } from 'ai';
|
3
|
+
import { Index } from '@upstash/vector';
|
4
|
+
import { Redis } from '@upstash/redis';
|
5
|
+
import * as crypto from 'crypto';
|
6
|
+
import { z } from 'zod';
|
7
|
+
import 'dotenv/config';
|
8
|
+
|
9
|
+
// index.ts
|
10
|
+
var semanticCacheConfigSchema = z.object({
|
11
|
+
model: z.union([
|
12
|
+
z.string(),
|
13
|
+
z.custom((val) => {
|
14
|
+
return val && typeof val === "object";
|
15
|
+
})
|
16
|
+
]),
|
17
|
+
vector: z.object({
|
18
|
+
url: z.url(),
|
19
|
+
token: z.string().min(1)
|
20
|
+
}).optional().default({
|
21
|
+
url: process.env.VECTOR_REST_URL ?? "",
|
22
|
+
token: process.env.VECTOR_REST_TOKEN ?? ""
|
23
|
+
}),
|
24
|
+
redis: z.object({
|
25
|
+
url: z.url(),
|
26
|
+
token: z.string().min(1)
|
27
|
+
}).optional().default({
|
28
|
+
url: process.env.REDIS_REST_URL ?? "",
|
29
|
+
token: process.env.REDIS_REST_TOKEN ?? ""
|
30
|
+
}),
|
31
|
+
threshold: z.number().min(0).max(1).optional().default(0.92),
|
32
|
+
ttl: z.number().positive().optional().default(60 * 60 * 24 * 14),
|
33
|
+
debug: z.boolean().optional().default(false),
|
34
|
+
cacheMode: z.enum(["default", "refresh"]).optional().default("default"),
|
35
|
+
simulateStream: z.object({
|
36
|
+
enabled: z.boolean().optional().default(true),
|
37
|
+
initialDelayInMs: z.number().min(0).optional().default(0),
|
38
|
+
chunkDelayInMs: z.number().min(0).optional().default(10)
|
39
|
+
}).optional().default({
|
40
|
+
enabled: true,
|
41
|
+
initialDelayInMs: 0,
|
42
|
+
chunkDelayInMs: 10
|
43
|
+
}),
|
44
|
+
useFullMessages: z.boolean().optional().default(false)
|
45
|
+
});
|
46
|
+
function validateEnvConfig(config) {
|
47
|
+
const errors = [];
|
48
|
+
if (!config.vector.url) {
|
49
|
+
errors.push(
|
50
|
+
"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable."
|
51
|
+
);
|
52
|
+
}
|
53
|
+
if (!config.vector.token) {
|
54
|
+
errors.push(
|
55
|
+
"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable."
|
56
|
+
);
|
57
|
+
}
|
58
|
+
if (!config.redis.url) {
|
59
|
+
errors.push(
|
60
|
+
"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable."
|
61
|
+
);
|
62
|
+
}
|
63
|
+
if (!config.redis.token) {
|
64
|
+
errors.push(
|
65
|
+
"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable."
|
66
|
+
);
|
67
|
+
}
|
68
|
+
if (errors.length > 0) {
|
69
|
+
throw new Error(
|
70
|
+
`Semantic Cache Configuration Error:
|
71
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`
|
72
|
+
);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
// index.ts
|
77
|
+
var norm = (s) => s.trim().toLowerCase().replace(/\s+/g, " ");
|
78
|
+
var sha = (s) => crypto.createHash("sha256").update(s).digest("hex");
|
79
|
+
function canonicalizeMessages(msgs) {
|
80
|
+
return msgs.map((m) => ({
|
81
|
+
role: m.role,
|
82
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
83
|
+
}));
|
84
|
+
}
|
85
|
+
function buildScope(options) {
|
86
|
+
return {
|
87
|
+
llmModel: options.model?.modelId ?? String(options.model ?? ""),
|
88
|
+
systemHash: sha(options.system ?? ""),
|
89
|
+
params: sha(
|
90
|
+
JSON.stringify({
|
91
|
+
temperature: options.temperature,
|
92
|
+
topP: options.topP
|
93
|
+
})
|
94
|
+
),
|
95
|
+
toolsHash: sha(JSON.stringify(options.tools ?? {}))
|
96
|
+
};
|
97
|
+
}
|
98
|
+
function createSemanticCache(config) {
|
99
|
+
const parsed = semanticCacheConfigSchema.parse(config);
|
100
|
+
validateEnvConfig(parsed);
|
101
|
+
const {
|
102
|
+
vector,
|
103
|
+
redis: redisConfig,
|
104
|
+
threshold,
|
105
|
+
ttl,
|
106
|
+
simulateStream,
|
107
|
+
debug,
|
108
|
+
cacheMode,
|
109
|
+
useFullMessages
|
110
|
+
} = parsed;
|
111
|
+
const model = parsed.model;
|
112
|
+
const index = new Index({
|
113
|
+
url: vector.url,
|
114
|
+
token: vector.token
|
115
|
+
});
|
116
|
+
const redis = new Redis({
|
117
|
+
url: redisConfig.url,
|
118
|
+
token: redisConfig.token
|
119
|
+
});
|
120
|
+
function getCacheKey(options) {
|
121
|
+
if (options.messages) {
|
122
|
+
const messages = Array.isArray(options.messages) ? options.messages : [];
|
123
|
+
if (!useFullMessages && messages.length > 0) {
|
124
|
+
const lastMessage = messages[messages.length - 1];
|
125
|
+
return JSON.stringify({
|
126
|
+
role: lastMessage.role,
|
127
|
+
content: typeof lastMessage.content === "string" ? lastMessage.content : JSON.stringify(lastMessage.content)
|
128
|
+
});
|
129
|
+
}
|
130
|
+
return JSON.stringify(canonicalizeMessages(options.messages));
|
131
|
+
}
|
132
|
+
if (options.prompt) {
|
133
|
+
return String(options.prompt);
|
134
|
+
}
|
135
|
+
return "";
|
136
|
+
}
|
137
|
+
async function checkSemanticCache(cacheInput, scope) {
|
138
|
+
const promptNorm = norm(cacheInput);
|
139
|
+
const { embedding } = await embed({
|
140
|
+
model,
|
141
|
+
value: promptNorm
|
142
|
+
});
|
143
|
+
const result = await index.query({
|
144
|
+
vector: embedding,
|
145
|
+
topK: 3,
|
146
|
+
includeMetadata: true
|
147
|
+
});
|
148
|
+
const hit = result.find((m) => {
|
149
|
+
if (m.score < threshold) return false;
|
150
|
+
const metadata = m.metadata;
|
151
|
+
if (!metadata) return false;
|
152
|
+
return metadata.llmModel === scope.llmModel && metadata.systemHash === scope.systemHash && metadata.params === scope.params && metadata.toolsHash === scope.toolsHash;
|
153
|
+
});
|
154
|
+
if (hit) {
|
155
|
+
const cached = await redis.get(hit.id.toString());
|
156
|
+
if (cached) {
|
157
|
+
if (debug) console.log("\u2705 cache hit", hit.score.toFixed(3));
|
158
|
+
return { cached, embedding, promptNorm };
|
159
|
+
}
|
160
|
+
}
|
161
|
+
if (debug) console.log("\u274C miss -> generating\u2026");
|
162
|
+
return { cached: null, embedding, promptNorm };
|
163
|
+
}
|
164
|
+
async function storeInCache(id, data, embedding, promptNorm, scope) {
|
165
|
+
const lockKey = "lock:" + id;
|
166
|
+
const ok = await redis.set(lockKey, "1", { nx: true, ex: 15 });
|
167
|
+
if (!ok) {
|
168
|
+
if (debug)
|
169
|
+
console.log("\u26A0\uFE0F Another process is writing to cache, skipping");
|
170
|
+
return;
|
171
|
+
}
|
172
|
+
try {
|
173
|
+
await redis.set(id, data, { ex: ttl });
|
174
|
+
await index.upsert([
|
175
|
+
{
|
176
|
+
id,
|
177
|
+
vector: embedding,
|
178
|
+
metadata: {
|
179
|
+
prompt: promptNorm,
|
180
|
+
...scope
|
181
|
+
}
|
182
|
+
}
|
183
|
+
]);
|
184
|
+
} finally {
|
185
|
+
await redis.del(lockKey);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
const semanticCacheMiddleware = {
|
189
|
+
wrapStream: async ({ doStream, params }) => {
|
190
|
+
const cacheInput = getCacheKey(params);
|
191
|
+
const scope = buildScope(params);
|
192
|
+
const promptScope = Object.values(scope).join("|");
|
193
|
+
const { cached, embedding, promptNorm } = await checkSemanticCache(
|
194
|
+
cacheInput,
|
195
|
+
scope
|
196
|
+
);
|
197
|
+
if (cached && cacheMode !== "refresh") {
|
198
|
+
if (debug) console.log("\u2705 Returning cached stream");
|
199
|
+
let chunks = [];
|
200
|
+
if (cached.streamParts) {
|
201
|
+
chunks = cached.streamParts.map((p) => {
|
202
|
+
if (p.type === "response-metadata" && p.timestamp) {
|
203
|
+
return { ...p, timestamp: new Date(p.timestamp) };
|
204
|
+
}
|
205
|
+
return p;
|
206
|
+
});
|
207
|
+
} else if (cached.text) {
|
208
|
+
chunks = [
|
209
|
+
{ type: "text-start", id: cached.id },
|
210
|
+
{ type: "text-delta", delta: cached.text, id: cached.id },
|
211
|
+
{ type: "finish", finishReason: "stop", usage: cached.usage }
|
212
|
+
];
|
213
|
+
}
|
214
|
+
return {
|
215
|
+
stream: simulateReadableStream({
|
216
|
+
initialDelayInMs: simulateStream.enabled ? simulateStream.initialDelayInMs : 0,
|
217
|
+
chunkDelayInMs: simulateStream.enabled ? simulateStream.chunkDelayInMs : 0,
|
218
|
+
chunks
|
219
|
+
})
|
220
|
+
};
|
221
|
+
}
|
222
|
+
const { stream, ...rest } = await doStream();
|
223
|
+
const fullResponse = [];
|
224
|
+
const transformStream = new TransformStream({
|
225
|
+
transform(chunk, controller) {
|
226
|
+
fullResponse.push(chunk);
|
227
|
+
controller.enqueue(chunk);
|
228
|
+
},
|
229
|
+
async flush() {
|
230
|
+
const id = "llm:" + sha(promptScope + "|" + promptNorm);
|
231
|
+
await storeInCache(
|
232
|
+
id,
|
233
|
+
{ streamParts: fullResponse },
|
234
|
+
embedding,
|
235
|
+
promptNorm,
|
236
|
+
scope
|
237
|
+
);
|
238
|
+
}
|
239
|
+
});
|
240
|
+
return {
|
241
|
+
stream: stream.pipeThrough(transformStream),
|
242
|
+
...rest
|
243
|
+
};
|
244
|
+
},
|
245
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
246
|
+
const cacheInput = getCacheKey(params);
|
247
|
+
const scope = buildScope(params);
|
248
|
+
const promptScope = Object.values(scope).join("|");
|
249
|
+
const { cached, embedding, promptNorm } = await checkSemanticCache(
|
250
|
+
cacheInput,
|
251
|
+
scope
|
252
|
+
);
|
253
|
+
if (cached && cacheMode !== "refresh") {
|
254
|
+
if (debug) console.log("\u2705 Returning cached generation");
|
255
|
+
if (cached?.response?.timestamp) {
|
256
|
+
cached.response.timestamp = new Date(cached.response.timestamp);
|
257
|
+
}
|
258
|
+
return cached;
|
259
|
+
}
|
260
|
+
const result = await doGenerate();
|
261
|
+
const id = "llm:" + sha(promptScope + "|" + promptNorm);
|
262
|
+
await storeInCache(id, result, embedding, promptNorm, scope);
|
263
|
+
return result;
|
264
|
+
}
|
265
|
+
};
|
266
|
+
return {
|
267
|
+
streamText: async (options) => {
|
268
|
+
const wrappedModel = wrapLanguageModel({
|
269
|
+
model: typeof options.model === "string" ? gateway(options.model) : options.model,
|
270
|
+
middleware: semanticCacheMiddleware
|
271
|
+
});
|
272
|
+
return streamText({
|
273
|
+
...options,
|
274
|
+
model: wrappedModel
|
275
|
+
});
|
276
|
+
},
|
277
|
+
generateText: async (options) => {
|
278
|
+
const wrappedModel = wrapLanguageModel({
|
279
|
+
model: typeof options.model === "string" ? gateway(options.model) : options.model,
|
280
|
+
middleware: semanticCacheMiddleware
|
281
|
+
});
|
282
|
+
return generateText({
|
283
|
+
...options,
|
284
|
+
model: wrappedModel
|
285
|
+
});
|
286
|
+
},
|
287
|
+
generateObject: async (options) => {
|
288
|
+
const wrappedModel = wrapLanguageModel({
|
289
|
+
model: typeof options.model === "string" ? gateway(options.model) : options.model,
|
290
|
+
middleware: semanticCacheMiddleware
|
291
|
+
});
|
292
|
+
return await generateObject({
|
293
|
+
...options,
|
294
|
+
model: wrappedModel
|
295
|
+
});
|
296
|
+
},
|
297
|
+
streamObject: async (options) => {
|
298
|
+
const wrappedModel = wrapLanguageModel({
|
299
|
+
model: typeof options.model === "string" ? gateway(options.model) : options.model,
|
300
|
+
middleware: semanticCacheMiddleware
|
301
|
+
});
|
302
|
+
return streamObject({
|
303
|
+
...options,
|
304
|
+
model: wrappedModel
|
305
|
+
});
|
306
|
+
}
|
307
|
+
};
|
308
|
+
}
|
309
|
+
|
310
|
+
export { createSemanticCache };
|
311
|
+
//# sourceMappingURL=index.mjs.map
|
312
|
+
//# sourceMappingURL=index.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../schema.ts","../index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { z } from \"zod\";\nimport \"dotenv/config\";\n\nexport const semanticCacheConfigSchema = z.object({\n model: z.union([\n z.string(),\n z.custom((val) => {\n return val && typeof val === \"object\";\n }),\n ]),\n vector: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.VECTOR_REST_URL ?? \"\",\n token: process.env.VECTOR_REST_TOKEN ?? \"\",\n }),\n redis: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.REDIS_REST_URL ?? \"\",\n token: process.env.REDIS_REST_TOKEN ?? \"\",\n }),\n threshold: z.number().min(0).max(1).optional().default(0.92),\n ttl: z\n .number()\n .positive()\n .optional()\n .default(60 * 60 * 24 * 14),\n debug: z.boolean().optional().default(false),\n cacheMode: z.enum([\"default\", \"refresh\"]).optional().default(\"default\"),\n simulateStream: z\n .object({\n enabled: z.boolean().optional().default(true),\n initialDelayInMs: z.number().min(0).optional().default(0),\n chunkDelayInMs: z.number().min(0).optional().default(10),\n })\n .optional()\n .default({\n enabled: true,\n initialDelayInMs: 0,\n chunkDelayInMs: 10,\n }),\n useFullMessages: z.boolean().optional().default(false),\n});\n\nexport type SemanticCacheConfig = z.input<typeof semanticCacheConfigSchema>;\nexport type SemanticCacheConfigParsed = z.output<\n typeof semanticCacheConfigSchema\n>;\n\nexport function validateEnvConfig(config: SemanticCacheConfigParsed) {\n const errors: string[] = [];\n\n if (!config.vector.url) {\n errors.push(\n \"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable.\",\n );\n }\n\n if (!config.vector.token) {\n errors.push(\n \"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable.\",\n );\n }\n\n if (!config.redis.url) {\n errors.push(\n \"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable.\",\n );\n }\n\n if (!config.redis.token) {\n errors.push(\n \"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable.\",\n );\n }\n\n if (errors.length > 0) {\n throw new Error(\n `Semantic Cache Configuration Error:\\n${errors.map((e) => ` - ${e}`).join(\"\\n\")}`,\n );\n }\n}\n","import {\n type LanguageModelV2Middleware,\n type LanguageModelV2StreamPart,\n type LanguageModelV2Content,\n type LanguageModelV2CallOptions,\n} from \"@ai-sdk/provider\";\nimport {\n embed,\n streamText,\n generateText,\n generateObject,\n streamObject,\n simulateReadableStream,\n wrapLanguageModel,\n type EmbeddingModel,\n type StreamTextResult,\n type GenerateTextResult,\n type GenerateObjectResult,\n type StreamObjectResult,\n gateway,\n} from \"ai\";\n\nimport { Index } from \"@upstash/vector\";\nimport { Redis } from \"@upstash/redis\";\nimport * as crypto from \"node:crypto\";\nimport {\n semanticCacheConfigSchema,\n type SemanticCacheConfig,\n validateEnvConfig,\n} from \"./schema\";\n\nconst norm = (s: string) => s.trim().toLowerCase().replace(/\\s+/g, \" \");\nconst sha = (s: string) => crypto.createHash(\"sha256\").update(s).digest(\"hex\");\n\nfunction canonicalizeMessages(msgs: any[]) {\n return msgs.map((m) => ({\n role: m.role,\n content:\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n }));\n}\n\nfunction buildScope(options: any) {\n return {\n llmModel: options.model?.modelId ?? String(options.model ?? \"\"),\n systemHash: sha(options.system ?? \"\"),\n params: sha(\n JSON.stringify({\n temperature: options.temperature,\n topP: options.topP,\n }),\n ),\n toolsHash: sha(JSON.stringify(options.tools ?? {})),\n };\n}\n\nexport function createSemanticCache(config: SemanticCacheConfig) {\n const parsed = semanticCacheConfigSchema.parse(config);\n\n validateEnvConfig(parsed);\n\n const {\n vector,\n redis: redisConfig,\n threshold,\n ttl,\n simulateStream,\n debug,\n cacheMode,\n useFullMessages,\n } = parsed;\n const model = parsed.model as EmbeddingModel<string>;\n\n const index = new Index({\n url: vector.url as string,\n token: vector.token as string,\n });\n\n const redis = new Redis({\n url: redisConfig.url as string,\n token: redisConfig.token as string,\n });\n\n function getCacheKey(options: any): string {\n if (options.messages) {\n const messages = Array.isArray(options.messages) ? options.messages : [];\n\n // By default, use only the last message to avoid token limit issues\n if (!useFullMessages && messages.length > 0) {\n const lastMessage = messages[messages.length - 1];\n return JSON.stringify({\n role: lastMessage.role,\n content:\n typeof lastMessage.content === \"string\"\n ? lastMessage.content\n : JSON.stringify(lastMessage.content),\n });\n }\n\n return JSON.stringify(canonicalizeMessages(options.messages));\n }\n if (options.prompt) {\n return String(options.prompt);\n }\n return \"\";\n }\n\n async function checkSemanticCache(cacheInput: string, scope: any) {\n const promptNorm = norm(cacheInput);\n\n const { embedding } = await embed({\n model,\n value: promptNorm,\n });\n\n const result = await index.query({\n vector: embedding,\n topK: 3,\n includeMetadata: true,\n });\n\n const hit = result.find((m) => {\n if (m.score < threshold) return false;\n\n const metadata = m.metadata as any;\n if (!metadata) return false;\n\n return (\n metadata.llmModel === scope.llmModel &&\n metadata.systemHash === scope.systemHash &&\n metadata.params === scope.params &&\n metadata.toolsHash === scope.toolsHash\n );\n });\n\n if (hit) {\n const cached = await redis.get<{\n streamParts?: LanguageModelV2StreamPart[];\n text?: string;\n content?: LanguageModelV2Content;\n [key: string]: any;\n }>(hit.id.toString());\n\n if (cached) {\n if (debug) console.log(\"✅ cache hit\", hit.score.toFixed(3));\n return { cached, embedding, promptNorm };\n }\n }\n\n if (debug) console.log(\"❌ miss -> generating…\");\n return { cached: null, embedding, promptNorm };\n }\n\n async function storeInCache(\n id: string,\n data: any,\n embedding: number[],\n promptNorm: string,\n scope: any,\n ) {\n const lockKey = \"lock:\" + id;\n const ok = await redis.set(lockKey, \"1\", { nx: true, ex: 15 });\n\n if (!ok) {\n if (debug)\n console.log(\"⚠️ Another process is writing to cache, skipping\");\n return;\n }\n\n try {\n await redis.set(id, data, { ex: ttl });\n await index.upsert([\n {\n id,\n vector: embedding,\n metadata: {\n prompt: promptNorm,\n ...scope,\n },\n },\n ]);\n } finally {\n await redis.del(lockKey);\n }\n }\n\n const semanticCacheMiddleware: LanguageModelV2Middleware = {\n wrapStream: async ({ doStream, params }) => {\n const cacheInput = getCacheKey(params);\n const scope = buildScope(params);\n const promptScope = Object.values(scope).join(\"|\");\n\n const { cached, embedding, promptNorm } = await checkSemanticCache(\n cacheInput,\n scope,\n );\n\n if (cached && cacheMode !== \"refresh\") {\n if (debug) console.log(\"✅ Returning cached stream\");\n\n let chunks: LanguageModelV2StreamPart[] = [];\n\n if (cached.streamParts) {\n chunks = cached.streamParts.map((p: any) => {\n if (p.type === \"response-metadata\" && p.timestamp) {\n return { ...p, timestamp: new Date(p.timestamp) };\n }\n return p;\n });\n } else if (cached.text) {\n chunks = [\n { type: \"text-start\", id: cached.id },\n { type: \"text-delta\", delta: cached.text, id: cached.id },\n { type: \"finish\", finishReason: \"stop\", usage: cached.usage },\n ];\n }\n\n return {\n stream: simulateReadableStream({\n initialDelayInMs: simulateStream.enabled\n ? simulateStream.initialDelayInMs\n : 0,\n chunkDelayInMs: simulateStream.enabled\n ? simulateStream.chunkDelayInMs\n : 0,\n chunks,\n }),\n };\n }\n\n const { stream, ...rest } = await doStream();\n\n const fullResponse: LanguageModelV2StreamPart[] = [];\n\n const transformStream = new TransformStream<\n LanguageModelV2StreamPart,\n LanguageModelV2StreamPart\n >({\n transform(chunk, controller) {\n fullResponse.push(chunk);\n controller.enqueue(chunk);\n },\n async flush() {\n const id = \"llm:\" + sha(promptScope + \"|\" + promptNorm);\n await storeInCache(\n id,\n { streamParts: fullResponse },\n embedding,\n promptNorm,\n scope,\n );\n },\n });\n\n return {\n stream: stream.pipeThrough(transformStream),\n ...rest,\n };\n },\n\n wrapGenerate: async ({ doGenerate, params }) => {\n const cacheInput = getCacheKey(params);\n const scope = buildScope(params);\n const promptScope = Object.values(scope).join(\"|\");\n\n const { cached, embedding, promptNorm } = await checkSemanticCache(\n cacheInput,\n scope,\n );\n\n if (cached && cacheMode !== \"refresh\") {\n if (debug) console.log(\"✅ Returning cached generation\");\n\n if (cached?.response?.timestamp) {\n cached.response.timestamp = new Date(cached.response.timestamp);\n }\n\n type GenReturn<T> = T extends () => PromiseLike<infer R> ? R : never;\n return cached as unknown as GenReturn<typeof doGenerate>;\n }\n\n const result = await doGenerate();\n\n const id = \"llm:\" + sha(promptScope + \"|\" + promptNorm);\n await storeInCache(id, result, embedding, promptNorm, scope);\n\n return result;\n },\n };\n\n return {\n streamText: async <TOOLS extends Record<string, any> = {}>(\n options: Parameters<typeof streamText<TOOLS>>[0],\n ): Promise<StreamTextResult<TOOLS, any>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return streamText({\n ...options,\n model: wrappedModel,\n });\n },\n\n generateText: async <\n TOOLS extends Record<string, any> = {},\n OUTPUT = undefined,\n >(\n options: Parameters<typeof generateText<TOOLS, OUTPUT>>[0],\n ): Promise<GenerateTextResult<TOOLS, OUTPUT>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return generateText({\n ...options,\n model: wrappedModel,\n });\n },\n\n generateObject: async <T = any>(\n options: Parameters<typeof generateObject>[0],\n ): Promise<GenerateObjectResult<T>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return (await generateObject({\n ...options,\n model: wrappedModel,\n })) as GenerateObjectResult<T>;\n },\n\n streamObject: async <T = any>(\n options: Parameters<typeof streamObject>[0],\n ): Promise<StreamObjectResult<T, T, any>> => {\n const wrappedModel = wrapLanguageModel({\n model:\n typeof options.model === \"string\"\n ? gateway(options.model)\n : options.model,\n middleware: semanticCacheMiddleware,\n });\n\n return streamObject({\n ...options,\n model: wrappedModel,\n }) as unknown as StreamObjectResult<T, T, any>;\n },\n };\n}\n"]}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
|
3
|
+
declare const semanticCacheConfigSchema: z.ZodObject<{
|
4
|
+
model: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<unknown, unknown>]>;
|
5
|
+
vector: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
6
|
+
url: z.ZodURL;
|
7
|
+
token: z.ZodString;
|
8
|
+
}, z.core.$strip>>>;
|
9
|
+
redis: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
10
|
+
url: z.ZodURL;
|
11
|
+
token: z.ZodString;
|
12
|
+
}, z.core.$strip>>>;
|
13
|
+
threshold: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
14
|
+
ttl: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
15
|
+
debug: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
16
|
+
cacheMode: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
17
|
+
default: "default";
|
18
|
+
refresh: "refresh";
|
19
|
+
}>>>;
|
20
|
+
simulateStream: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
21
|
+
enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
22
|
+
initialDelayInMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
23
|
+
chunkDelayInMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
24
|
+
}, z.core.$strip>>>;
|
25
|
+
useFullMessages: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
26
|
+
}, z.core.$strip>;
|
27
|
+
type SemanticCacheConfig = z.input<typeof semanticCacheConfigSchema>;
|
28
|
+
type SemanticCacheConfigParsed = z.output<typeof semanticCacheConfigSchema>;
|
29
|
+
declare function validateEnvConfig(config: SemanticCacheConfigParsed): void;
|
30
|
+
|
31
|
+
export { type SemanticCacheConfig, type SemanticCacheConfigParsed, semanticCacheConfigSchema, validateEnvConfig };
|
package/dist/schema.d.ts
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
|
3
|
+
declare const semanticCacheConfigSchema: z.ZodObject<{
|
4
|
+
model: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<unknown, unknown>]>;
|
5
|
+
vector: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
6
|
+
url: z.ZodURL;
|
7
|
+
token: z.ZodString;
|
8
|
+
}, z.core.$strip>>>;
|
9
|
+
redis: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
10
|
+
url: z.ZodURL;
|
11
|
+
token: z.ZodString;
|
12
|
+
}, z.core.$strip>>>;
|
13
|
+
threshold: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
14
|
+
ttl: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
15
|
+
debug: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
16
|
+
cacheMode: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
17
|
+
default: "default";
|
18
|
+
refresh: "refresh";
|
19
|
+
}>>>;
|
20
|
+
simulateStream: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
21
|
+
enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
22
|
+
initialDelayInMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
23
|
+
chunkDelayInMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
24
|
+
}, z.core.$strip>>>;
|
25
|
+
useFullMessages: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
26
|
+
}, z.core.$strip>;
|
27
|
+
type SemanticCacheConfig = z.input<typeof semanticCacheConfigSchema>;
|
28
|
+
type SemanticCacheConfigParsed = z.output<typeof semanticCacheConfigSchema>;
|
29
|
+
declare function validateEnvConfig(config: SemanticCacheConfigParsed): void;
|
30
|
+
|
31
|
+
export { type SemanticCacheConfig, type SemanticCacheConfigParsed, semanticCacheConfigSchema, validateEnvConfig };
|
package/dist/schema.js
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var zod = require('zod');
|
4
|
+
require('dotenv/config');
|
5
|
+
|
6
|
+
// schema.ts
|
7
|
+
var semanticCacheConfigSchema = zod.z.object({
|
8
|
+
model: zod.z.union([
|
9
|
+
zod.z.string(),
|
10
|
+
zod.z.custom((val) => {
|
11
|
+
return val && typeof val === "object";
|
12
|
+
})
|
13
|
+
]),
|
14
|
+
vector: zod.z.object({
|
15
|
+
url: zod.z.url(),
|
16
|
+
token: zod.z.string().min(1)
|
17
|
+
}).optional().default({
|
18
|
+
url: process.env.VECTOR_REST_URL ?? "",
|
19
|
+
token: process.env.VECTOR_REST_TOKEN ?? ""
|
20
|
+
}),
|
21
|
+
redis: zod.z.object({
|
22
|
+
url: zod.z.url(),
|
23
|
+
token: zod.z.string().min(1)
|
24
|
+
}).optional().default({
|
25
|
+
url: process.env.REDIS_REST_URL ?? "",
|
26
|
+
token: process.env.REDIS_REST_TOKEN ?? ""
|
27
|
+
}),
|
28
|
+
threshold: zod.z.number().min(0).max(1).optional().default(0.92),
|
29
|
+
ttl: zod.z.number().positive().optional().default(60 * 60 * 24 * 14),
|
30
|
+
debug: zod.z.boolean().optional().default(false),
|
31
|
+
cacheMode: zod.z.enum(["default", "refresh"]).optional().default("default"),
|
32
|
+
simulateStream: zod.z.object({
|
33
|
+
enabled: zod.z.boolean().optional().default(true),
|
34
|
+
initialDelayInMs: zod.z.number().min(0).optional().default(0),
|
35
|
+
chunkDelayInMs: zod.z.number().min(0).optional().default(10)
|
36
|
+
}).optional().default({
|
37
|
+
enabled: true,
|
38
|
+
initialDelayInMs: 0,
|
39
|
+
chunkDelayInMs: 10
|
40
|
+
}),
|
41
|
+
useFullMessages: zod.z.boolean().optional().default(false)
|
42
|
+
});
|
43
|
+
function validateEnvConfig(config) {
|
44
|
+
const errors = [];
|
45
|
+
if (!config.vector.url) {
|
46
|
+
errors.push(
|
47
|
+
"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable."
|
48
|
+
);
|
49
|
+
}
|
50
|
+
if (!config.vector.token) {
|
51
|
+
errors.push(
|
52
|
+
"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable."
|
53
|
+
);
|
54
|
+
}
|
55
|
+
if (!config.redis.url) {
|
56
|
+
errors.push(
|
57
|
+
"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable."
|
58
|
+
);
|
59
|
+
}
|
60
|
+
if (!config.redis.token) {
|
61
|
+
errors.push(
|
62
|
+
"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable."
|
63
|
+
);
|
64
|
+
}
|
65
|
+
if (errors.length > 0) {
|
66
|
+
throw new Error(
|
67
|
+
`Semantic Cache Configuration Error:
|
68
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`
|
69
|
+
);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
exports.semanticCacheConfigSchema = semanticCacheConfigSchema;
|
74
|
+
exports.validateEnvConfig = validateEnvConfig;
|
75
|
+
//# sourceMappingURL=schema.js.map
|
76
|
+
//# sourceMappingURL=schema.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../schema.ts"],"names":["z"],"mappings":"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","file":"schema.js","sourcesContent":["import { z } from \"zod\";\nimport \"dotenv/config\";\n\nexport const semanticCacheConfigSchema = z.object({\n model: z.union([\n z.string(),\n z.custom((val) => {\n return val && typeof val === \"object\";\n }),\n ]),\n vector: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.VECTOR_REST_URL ?? \"\",\n token: process.env.VECTOR_REST_TOKEN ?? \"\",\n }),\n redis: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.REDIS_REST_URL ?? \"\",\n token: process.env.REDIS_REST_TOKEN ?? \"\",\n }),\n threshold: z.number().min(0).max(1).optional().default(0.92),\n ttl: z\n .number()\n .positive()\n .optional()\n .default(60 * 60 * 24 * 14),\n debug: z.boolean().optional().default(false),\n cacheMode: z.enum([\"default\", \"refresh\"]).optional().default(\"default\"),\n simulateStream: z\n .object({\n enabled: z.boolean().optional().default(true),\n initialDelayInMs: z.number().min(0).optional().default(0),\n chunkDelayInMs: z.number().min(0).optional().default(10),\n })\n .optional()\n .default({\n enabled: true,\n initialDelayInMs: 0,\n chunkDelayInMs: 10,\n }),\n useFullMessages: z.boolean().optional().default(false),\n});\n\nexport type SemanticCacheConfig = z.input<typeof semanticCacheConfigSchema>;\nexport type SemanticCacheConfigParsed = z.output<\n typeof semanticCacheConfigSchema\n>;\n\nexport function validateEnvConfig(config: SemanticCacheConfigParsed) {\n const errors: string[] = [];\n\n if (!config.vector.url) {\n errors.push(\n \"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable.\",\n );\n }\n\n if (!config.vector.token) {\n errors.push(\n \"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable.\",\n );\n }\n\n if (!config.redis.url) {\n errors.push(\n \"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable.\",\n );\n }\n\n if (!config.redis.token) {\n errors.push(\n \"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable.\",\n );\n }\n\n if (errors.length > 0) {\n throw new Error(\n `Semantic Cache Configuration Error:\\n${errors.map((e) => ` - ${e}`).join(\"\\n\")}`,\n );\n }\n}\n"]}
|
package/dist/schema.mjs
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
import 'dotenv/config';
|
3
|
+
|
4
|
+
// schema.ts
|
5
|
+
var semanticCacheConfigSchema = z.object({
|
6
|
+
model: z.union([
|
7
|
+
z.string(),
|
8
|
+
z.custom((val) => {
|
9
|
+
return val && typeof val === "object";
|
10
|
+
})
|
11
|
+
]),
|
12
|
+
vector: z.object({
|
13
|
+
url: z.url(),
|
14
|
+
token: z.string().min(1)
|
15
|
+
}).optional().default({
|
16
|
+
url: process.env.VECTOR_REST_URL ?? "",
|
17
|
+
token: process.env.VECTOR_REST_TOKEN ?? ""
|
18
|
+
}),
|
19
|
+
redis: z.object({
|
20
|
+
url: z.url(),
|
21
|
+
token: z.string().min(1)
|
22
|
+
}).optional().default({
|
23
|
+
url: process.env.REDIS_REST_URL ?? "",
|
24
|
+
token: process.env.REDIS_REST_TOKEN ?? ""
|
25
|
+
}),
|
26
|
+
threshold: z.number().min(0).max(1).optional().default(0.92),
|
27
|
+
ttl: z.number().positive().optional().default(60 * 60 * 24 * 14),
|
28
|
+
debug: z.boolean().optional().default(false),
|
29
|
+
cacheMode: z.enum(["default", "refresh"]).optional().default("default"),
|
30
|
+
simulateStream: z.object({
|
31
|
+
enabled: z.boolean().optional().default(true),
|
32
|
+
initialDelayInMs: z.number().min(0).optional().default(0),
|
33
|
+
chunkDelayInMs: z.number().min(0).optional().default(10)
|
34
|
+
}).optional().default({
|
35
|
+
enabled: true,
|
36
|
+
initialDelayInMs: 0,
|
37
|
+
chunkDelayInMs: 10
|
38
|
+
}),
|
39
|
+
useFullMessages: z.boolean().optional().default(false)
|
40
|
+
});
|
41
|
+
function validateEnvConfig(config) {
|
42
|
+
const errors = [];
|
43
|
+
if (!config.vector.url) {
|
44
|
+
errors.push(
|
45
|
+
"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable."
|
46
|
+
);
|
47
|
+
}
|
48
|
+
if (!config.vector.token) {
|
49
|
+
errors.push(
|
50
|
+
"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable."
|
51
|
+
);
|
52
|
+
}
|
53
|
+
if (!config.redis.url) {
|
54
|
+
errors.push(
|
55
|
+
"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable."
|
56
|
+
);
|
57
|
+
}
|
58
|
+
if (!config.redis.token) {
|
59
|
+
errors.push(
|
60
|
+
"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable."
|
61
|
+
);
|
62
|
+
}
|
63
|
+
if (errors.length > 0) {
|
64
|
+
throw new Error(
|
65
|
+
`Semantic Cache Configuration Error:
|
66
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`
|
67
|
+
);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
export { semanticCacheConfigSchema, validateEnvConfig };
|
72
|
+
//# sourceMappingURL=schema.mjs.map
|
73
|
+
//# sourceMappingURL=schema.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../schema.ts"],"names":[],"mappings":"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","file":"schema.mjs","sourcesContent":["import { z } from \"zod\";\nimport \"dotenv/config\";\n\nexport const semanticCacheConfigSchema = z.object({\n model: z.union([\n z.string(),\n z.custom((val) => {\n return val && typeof val === \"object\";\n }),\n ]),\n vector: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.VECTOR_REST_URL ?? \"\",\n token: process.env.VECTOR_REST_TOKEN ?? \"\",\n }),\n redis: z\n .object({\n url: z.url(),\n token: z.string().min(1),\n })\n .optional()\n .default({\n url: process.env.REDIS_REST_URL ?? \"\",\n token: process.env.REDIS_REST_TOKEN ?? \"\",\n }),\n threshold: z.number().min(0).max(1).optional().default(0.92),\n ttl: z\n .number()\n .positive()\n .optional()\n .default(60 * 60 * 24 * 14),\n debug: z.boolean().optional().default(false),\n cacheMode: z.enum([\"default\", \"refresh\"]).optional().default(\"default\"),\n simulateStream: z\n .object({\n enabled: z.boolean().optional().default(true),\n initialDelayInMs: z.number().min(0).optional().default(0),\n chunkDelayInMs: z.number().min(0).optional().default(10),\n })\n .optional()\n .default({\n enabled: true,\n initialDelayInMs: 0,\n chunkDelayInMs: 10,\n }),\n useFullMessages: z.boolean().optional().default(false),\n});\n\nexport type SemanticCacheConfig = z.input<typeof semanticCacheConfigSchema>;\nexport type SemanticCacheConfigParsed = z.output<\n typeof semanticCacheConfigSchema\n>;\n\nexport function validateEnvConfig(config: SemanticCacheConfigParsed) {\n const errors: string[] = [];\n\n if (!config.vector.url) {\n errors.push(\n \"Vector URL is required. Provide 'vector.url' or set VECTOR_REST_URL environment variable.\",\n );\n }\n\n if (!config.vector.token) {\n errors.push(\n \"Vector token is required. Provide 'vector.token' or set VECTOR_REST_TOKEN environment variable.\",\n );\n }\n\n if (!config.redis.url) {\n errors.push(\n \"Redis URL is required. Provide 'redis.url' or set REDIS_REST_URL environment variable.\",\n );\n }\n\n if (!config.redis.token) {\n errors.push(\n \"Redis token is required. Provide 'redis.token' or set REDIS_REST_TOKEN environment variable.\",\n );\n }\n\n if (errors.length > 0) {\n throw new Error(\n `Semantic Cache Configuration Error:\\n${errors.map((e) => ` - ${e}`).join(\"\\n\")}`,\n );\n }\n}\n"]}
|
package/package.json
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
{
|
2
|
+
"name": "ai-sdk-memory",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "Semantic caching layer for AI SDK. Automatically generates embeddings for prompts, stores them in Upstash Vector and Redis, and reuses semantically similar responses to reduce token costs.",
|
5
|
+
"main": "./dist/index.js",
|
6
|
+
"module": "./dist/index.js",
|
7
|
+
"types": "./dist/index.d.ts",
|
8
|
+
"exports": {
|
9
|
+
".": {
|
10
|
+
"types": "./dist/index.d.ts",
|
11
|
+
"import": "./dist/index.js",
|
12
|
+
"require": "./dist/index.js"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"files": [
|
16
|
+
"dist",
|
17
|
+
"README.md",
|
18
|
+
"LICENSE"
|
19
|
+
],
|
20
|
+
"scripts": {
|
21
|
+
"build": "tsup",
|
22
|
+
"prepublishOnly": "bun run build",
|
23
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
24
|
+
},
|
25
|
+
"keywords": [
|
26
|
+
"ai",
|
27
|
+
"ai-sdk",
|
28
|
+
"vercel",
|
29
|
+
"cache",
|
30
|
+
"semantic",
|
31
|
+
"semantic-cache",
|
32
|
+
"vector",
|
33
|
+
"upstash",
|
34
|
+
"redis",
|
35
|
+
"embedding",
|
36
|
+
"llm"
|
37
|
+
],
|
38
|
+
"author": "",
|
39
|
+
"license": "MIT",
|
40
|
+
"repository": {
|
41
|
+
"type": "git",
|
42
|
+
"url": "https://github.com/yourusername/ai-sdk-memory.git"
|
43
|
+
},
|
44
|
+
"bugs": {
|
45
|
+
"url": "https://github.com/yourusername/ai-sdk-memory/issues"
|
46
|
+
},
|
47
|
+
"homepage": "https://github.com/yourusername/ai-sdk-memory#readme",
|
48
|
+
"devDependencies": {
|
49
|
+
"@types/bun": "latest",
|
50
|
+
"@types/node": "^24.7.0",
|
51
|
+
"tsup": "^8.5.0",
|
52
|
+
"typescript": "^5.0.0",
|
53
|
+
"vitest": "^3.2.4"
|
54
|
+
},
|
55
|
+
"peerDependencies": {
|
56
|
+
"ai": "^5.0.0"
|
57
|
+
},
|
58
|
+
"dependencies": {
|
59
|
+
"@ai-sdk/provider": "^2.0.0",
|
60
|
+
"@upstash/redis": "^1.35.5",
|
61
|
+
"@upstash/vector": "^1.2.2",
|
62
|
+
"dotenv": "^17.2.3",
|
63
|
+
"zod": "4.1.12"
|
64
|
+
}
|
65
|
+
}
|