@betterdb/semantic-cache 0.1.0 → 0.2.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 +211 -128
- package/dist/SemanticCache.d.ts +85 -5
- package/dist/SemanticCache.js +689 -47
- package/dist/adapters/ai.js +6 -1
- package/dist/adapters/anthropic.d.ts +32 -0
- package/dist/adapters/anthropic.js +94 -0
- package/dist/adapters/langchain.js +6 -1
- package/dist/adapters/langgraph.d.ts +104 -0
- package/dist/adapters/langgraph.js +271 -0
- package/dist/adapters/llamaindex.d.ts +32 -0
- package/dist/adapters/llamaindex.js +76 -0
- package/dist/adapters/openai-responses.d.ts +31 -0
- package/dist/adapters/openai-responses.js +112 -0
- package/dist/adapters/openai.d.ts +42 -0
- package/dist/adapters/openai.js +97 -0
- package/dist/analytics.d.ts +24 -0
- package/dist/analytics.js +116 -0
- package/dist/cluster.d.ts +10 -0
- package/dist/cluster.js +43 -0
- package/dist/defaultCostTable.d.ts +11 -0
- package/dist/defaultCostTable.js +1976 -0
- package/dist/embed/bedrock.d.ts +32 -0
- package/dist/embed/bedrock.js +109 -0
- package/dist/embed/cohere.d.ts +34 -0
- package/dist/embed/cohere.js +37 -0
- package/dist/embed/ollama.d.ts +30 -0
- package/dist/embed/ollama.js +24 -0
- package/dist/embed/openai.d.ts +31 -0
- package/dist/embed/openai.js +66 -0
- package/dist/embed/voyage.d.ts +31 -0
- package/dist/embed/voyage.js +32 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +11 -1
- package/dist/normalizer.d.ts +68 -0
- package/dist/normalizer.js +102 -0
- package/dist/telemetry.d.ts +3 -0
- package/dist/telemetry.js +18 -0
- package/dist/types.d.ts +107 -7
- package/dist/utils.d.ts +58 -0
- package/dist/utils.js +30 -0
- package/package.json +81 -6
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Bedrock embedding helper for @betterdb/semantic-cache.
|
|
3
|
+
*
|
|
4
|
+
* Supports Titan Text Embeddings v2 (1024-dim) and Cohere Embed v3 (1024-dim).
|
|
5
|
+
* Requires @aws-sdk/client-bedrock-runtime as a peer dependency.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createBedrockEmbed } from '@betterdb/semantic-cache/embed/bedrock';
|
|
9
|
+
* const embed = createBedrockEmbed({ modelId: 'amazon.titan-embed-text-v2:0' });
|
|
10
|
+
* const cache = new SemanticCache({ client, embedFn: embed });
|
|
11
|
+
*/
|
|
12
|
+
import type { EmbedFn } from '../types';
|
|
13
|
+
export type BedrockEmbedModelId = 'amazon.titan-embed-text-v2:0' | 'amazon.titan-embed-text-v1' | 'cohere.embed-english-v3' | 'cohere.embed-multilingual-v3' | (string & {});
|
|
14
|
+
export interface BedrockEmbedOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Pre-configured BedrockRuntimeClient instance.
|
|
17
|
+
* If not provided, a new client is created from environment credentials.
|
|
18
|
+
*/
|
|
19
|
+
client?: any;
|
|
20
|
+
/**
|
|
21
|
+
* Model ID to use for embeddings.
|
|
22
|
+
* Default: 'amazon.titan-embed-text-v2:0'
|
|
23
|
+
*/
|
|
24
|
+
modelId?: BedrockEmbedModelId;
|
|
25
|
+
/** AWS region. Used only when client is not provided. Default: AWS_DEFAULT_REGION env var. */
|
|
26
|
+
region?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create an EmbedFn backed by the AWS Bedrock embedding models.
|
|
30
|
+
* Requires @aws-sdk/client-bedrock-runtime to be installed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createBedrockEmbed(opts?: BedrockEmbedOptions): EmbedFn;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createBedrockEmbed = createBedrockEmbed;
|
|
37
|
+
/**
|
|
38
|
+
* Create an EmbedFn backed by the AWS Bedrock embedding models.
|
|
39
|
+
* Requires @aws-sdk/client-bedrock-runtime to be installed.
|
|
40
|
+
*/
|
|
41
|
+
function createBedrockEmbed(opts) {
|
|
42
|
+
const modelId = opts?.modelId ?? 'amazon.titan-embed-text-v2:0';
|
|
43
|
+
let clientPromise = null;
|
|
44
|
+
let CommandClass = null;
|
|
45
|
+
function getClient() {
|
|
46
|
+
if (!clientPromise) {
|
|
47
|
+
clientPromise = (async () => {
|
|
48
|
+
if (opts?.client) {
|
|
49
|
+
// Load InvokeModelCommand separately
|
|
50
|
+
try {
|
|
51
|
+
// @ts-ignore - optional peer dep
|
|
52
|
+
const mod = await Promise.resolve(`${'@aws-sdk/client-bedrock-runtime'}`).then(s => __importStar(require(s)));
|
|
53
|
+
CommandClass = mod.InvokeModelCommand;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new Error('@betterdb/semantic-cache embed/bedrock requires "@aws-sdk/client-bedrock-runtime". Install it.');
|
|
57
|
+
}
|
|
58
|
+
return opts.client;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
// @ts-ignore - optional peer dep
|
|
62
|
+
const mod = await Promise.resolve(`${'@aws-sdk/client-bedrock-runtime'}`).then(s => __importStar(require(s)));
|
|
63
|
+
const { BedrockRuntimeClient, InvokeModelCommand } = mod;
|
|
64
|
+
CommandClass = InvokeModelCommand;
|
|
65
|
+
return new BedrockRuntimeClient({
|
|
66
|
+
region: opts?.region ?? process.env.AWS_DEFAULT_REGION ?? 'us-east-1',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new Error('@betterdb/semantic-cache embed/bedrock requires "@aws-sdk/client-bedrock-runtime". Install it.');
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
}
|
|
74
|
+
return clientPromise;
|
|
75
|
+
}
|
|
76
|
+
return async (text) => {
|
|
77
|
+
const client = await getClient();
|
|
78
|
+
const isTitan = modelId.startsWith('amazon.titan');
|
|
79
|
+
const isCohere = modelId.startsWith('cohere.embed');
|
|
80
|
+
let body;
|
|
81
|
+
if (isTitan) {
|
|
82
|
+
body = { inputText: text };
|
|
83
|
+
}
|
|
84
|
+
else if (isCohere) {
|
|
85
|
+
body = { texts: [text], input_type: 'search_document', truncate: 'END' };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
body = { inputText: text };
|
|
89
|
+
}
|
|
90
|
+
const command = new CommandClass({
|
|
91
|
+
modelId,
|
|
92
|
+
body: JSON.stringify(body),
|
|
93
|
+
contentType: 'application/json',
|
|
94
|
+
accept: 'application/json',
|
|
95
|
+
});
|
|
96
|
+
const response = await client.send(command);
|
|
97
|
+
const decoded = new TextDecoder().decode(response.body);
|
|
98
|
+
const parsed = JSON.parse(decoded);
|
|
99
|
+
if (isTitan) {
|
|
100
|
+
return parsed.embedding ?? [];
|
|
101
|
+
}
|
|
102
|
+
else if (isCohere) {
|
|
103
|
+
const embeddings = parsed.embeddings;
|
|
104
|
+
return embeddings[0] ?? [];
|
|
105
|
+
}
|
|
106
|
+
// Generic fallback
|
|
107
|
+
return parsed.embedding ?? parsed.embeddings?.[0] ?? [];
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohere embedding helper for @betterdb/semantic-cache.
|
|
3
|
+
*
|
|
4
|
+
* Supports Cohere Embed v3 models via the Cohere REST API.
|
|
5
|
+
* Uses native fetch - no SDK required.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createCohereEmbed } from '@betterdb/semantic-cache/embed/cohere';
|
|
9
|
+
* const embed = createCohereEmbed({ model: 'embed-english-v3.0' });
|
|
10
|
+
* const cache = new SemanticCache({ client, embedFn: embed });
|
|
11
|
+
*/
|
|
12
|
+
import type { EmbedFn } from '../types';
|
|
13
|
+
export interface CohereEmbedOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Cohere embedding model.
|
|
16
|
+
* Default: 'embed-english-v3.0' (1024 dimensions).
|
|
17
|
+
* Other options: 'embed-multilingual-v3.0', 'embed-english-light-v3.0'.
|
|
18
|
+
*/
|
|
19
|
+
model?: string;
|
|
20
|
+
/** Cohere API key. Default: COHERE_API_KEY env var. */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** API base URL. Default: 'https://api.cohere.com/v2'. */
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Input type for embedding.
|
|
26
|
+
* Default: 'search_query'. Use 'search_document' when storing.
|
|
27
|
+
*/
|
|
28
|
+
inputType?: 'search_query' | 'search_document' | 'classification' | 'clustering';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create an EmbedFn backed by the Cohere Embed API.
|
|
32
|
+
* Uses native fetch - no SDK required.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createCohereEmbed(opts?: CohereEmbedOptions): EmbedFn;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCohereEmbed = createCohereEmbed;
|
|
4
|
+
/**
|
|
5
|
+
* Create an EmbedFn backed by the Cohere Embed API.
|
|
6
|
+
* Uses native fetch - no SDK required.
|
|
7
|
+
*/
|
|
8
|
+
function createCohereEmbed(opts) {
|
|
9
|
+
const model = opts?.model ?? 'embed-english-v3.0';
|
|
10
|
+
const baseUrl = opts?.baseUrl ?? 'https://api.cohere.com/v2';
|
|
11
|
+
const inputType = opts?.inputType ?? 'search_query';
|
|
12
|
+
return async (text) => {
|
|
13
|
+
const apiKey = opts?.apiKey ?? process.env.COHERE_API_KEY;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
throw new Error('Cohere API key is required. Set COHERE_API_KEY env var or pass apiKey in options.');
|
|
16
|
+
}
|
|
17
|
+
const res = await fetch(`${baseUrl}/embed`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
Authorization: `Bearer ${apiKey}`,
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
model,
|
|
25
|
+
texts: [text],
|
|
26
|
+
input_type: inputType,
|
|
27
|
+
embedding_types: ['float'],
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
const body = await res.text().catch(() => '');
|
|
32
|
+
throw new Error(`Cohere API error: ${res.status} ${body}`);
|
|
33
|
+
}
|
|
34
|
+
const json = (await res.json());
|
|
35
|
+
return json.embeddings.float[0] ?? [];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama embedding helper for @betterdb/semantic-cache.
|
|
3
|
+
*
|
|
4
|
+
* Supports local Ollama embedding models (nomic-embed-text, mxbai-embed-large, etc.).
|
|
5
|
+
* Uses the Ollama REST API directly - no SDK required.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createOllamaEmbed } from '@betterdb/semantic-cache/embed/ollama';
|
|
9
|
+
* const embed = createOllamaEmbed({ model: 'nomic-embed-text' });
|
|
10
|
+
* const cache = new SemanticCache({ client, embedFn: embed });
|
|
11
|
+
*/
|
|
12
|
+
import type { EmbedFn } from '../types';
|
|
13
|
+
export interface OllamaEmbedOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Ollama embedding model name.
|
|
16
|
+
* Default: 'nomic-embed-text' (768 dimensions).
|
|
17
|
+
* Other options: 'mxbai-embed-large' (1024-dim), 'all-minilm' (384-dim).
|
|
18
|
+
*/
|
|
19
|
+
model?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Ollama API base URL.
|
|
22
|
+
* Default: 'http://localhost:11434'
|
|
23
|
+
*/
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create an EmbedFn backed by a local Ollama instance.
|
|
28
|
+
* Uses native fetch - no SDK required.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createOllamaEmbed(opts?: OllamaEmbedOptions): EmbedFn;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOllamaEmbed = createOllamaEmbed;
|
|
4
|
+
/**
|
|
5
|
+
* Create an EmbedFn backed by a local Ollama instance.
|
|
6
|
+
* Uses native fetch - no SDK required.
|
|
7
|
+
*/
|
|
8
|
+
function createOllamaEmbed(opts) {
|
|
9
|
+
const model = opts?.model ?? 'nomic-embed-text';
|
|
10
|
+
const baseUrl = opts?.baseUrl ?? (process.env.OLLAMA_HOST ?? 'http://localhost:11434');
|
|
11
|
+
return async (text) => {
|
|
12
|
+
const res = await fetch(`${baseUrl}/api/embed`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
body: JSON.stringify({ model, input: text }),
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const body = await res.text().catch(() => '');
|
|
19
|
+
throw new Error(`Ollama API error: ${res.status} ${body}`);
|
|
20
|
+
}
|
|
21
|
+
const json = (await res.json());
|
|
22
|
+
return json.embeddings[0] ?? [];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI embedding helper for @betterdb/semantic-cache.
|
|
3
|
+
*
|
|
4
|
+
* Creates an EmbedFn backed by the OpenAI Embeddings API.
|
|
5
|
+
* Requires the 'openai' peer dependency to be installed.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createOpenAIEmbed } from '@betterdb/semantic-cache/embed/openai';
|
|
9
|
+
* const embed = createOpenAIEmbed({ model: 'text-embedding-3-small' });
|
|
10
|
+
* const cache = new SemanticCache({ client, embedFn: embed });
|
|
11
|
+
*/
|
|
12
|
+
import type { EmbedFn } from '../types';
|
|
13
|
+
export interface OpenAIEmbedOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Pre-configured OpenAI client instance.
|
|
16
|
+
* If not provided, a new client is created using the OPENAI_API_KEY env var.
|
|
17
|
+
*/
|
|
18
|
+
client?: any;
|
|
19
|
+
/**
|
|
20
|
+
* Embedding model ID.
|
|
21
|
+
* Default: 'text-embedding-3-small' (1536 dimensions, best cost/quality tradeoff).
|
|
22
|
+
*/
|
|
23
|
+
model?: string;
|
|
24
|
+
/** OpenAI API key. Used only when client is not provided. Default: OPENAI_API_KEY env var. */
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an EmbedFn backed by the OpenAI Embeddings API.
|
|
29
|
+
* Requires the 'openai' package to be installed as a peer dependency.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createOpenAIEmbed(opts?: OpenAIEmbedOptions): EmbedFn;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createOpenAIEmbed = createOpenAIEmbed;
|
|
37
|
+
/**
|
|
38
|
+
* Create an EmbedFn backed by the OpenAI Embeddings API.
|
|
39
|
+
* Requires the 'openai' package to be installed as a peer dependency.
|
|
40
|
+
*/
|
|
41
|
+
function createOpenAIEmbed(opts) {
|
|
42
|
+
const model = opts?.model ?? 'text-embedding-3-small';
|
|
43
|
+
let clientPromise = null;
|
|
44
|
+
function getClient() {
|
|
45
|
+
if (!clientPromise) {
|
|
46
|
+
clientPromise = (async () => {
|
|
47
|
+
if (opts?.client)
|
|
48
|
+
return opts.client;
|
|
49
|
+
try {
|
|
50
|
+
// @ts-ignore - openai is an optional peer dep
|
|
51
|
+
const { OpenAI } = await Promise.resolve().then(() => __importStar(require('openai')));
|
|
52
|
+
return new OpenAI({ apiKey: opts?.apiKey ?? process.env.OPENAI_API_KEY });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new Error('@betterdb/semantic-cache embed/openai requires the "openai" package. Install it: npm install openai');
|
|
56
|
+
}
|
|
57
|
+
})();
|
|
58
|
+
}
|
|
59
|
+
return clientPromise;
|
|
60
|
+
}
|
|
61
|
+
return async (text) => {
|
|
62
|
+
const client = (await getClient());
|
|
63
|
+
const response = await client.embeddings.create({ input: text, model });
|
|
64
|
+
return response.data[0].embedding;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voyage AI embedding helper for @betterdb/semantic-cache.
|
|
3
|
+
*
|
|
4
|
+
* Supports all Voyage AI embedding models (voyage-3, voyage-3-lite, etc.).
|
|
5
|
+
* Uses the Voyage AI REST API directly - no SDK required.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createVoyageEmbed } from '@betterdb/semantic-cache/embed/voyage';
|
|
9
|
+
* const embed = createVoyageEmbed({ model: 'voyage-3-lite' });
|
|
10
|
+
* const cache = new SemanticCache({ client, embedFn: embed });
|
|
11
|
+
*/
|
|
12
|
+
import type { EmbedFn } from '../types';
|
|
13
|
+
export interface VoyageEmbedOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Voyage AI embedding model.
|
|
16
|
+
* Default: 'voyage-3-lite' (512 dimensions, fastest and cheapest).
|
|
17
|
+
* Other options: 'voyage-3' (1024-dim), 'voyage-3-large' (1024-dim).
|
|
18
|
+
*/
|
|
19
|
+
model?: string;
|
|
20
|
+
/** Voyage AI API key. Default: VOYAGE_API_KEY env var. */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** API base URL. Default: 'https://api.voyageai.com/v1'. */
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/** Input type hint for retrieval tasks ('query' or 'document'). Default: 'query'. */
|
|
25
|
+
inputType?: 'query' | 'document';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an EmbedFn backed by the Voyage AI Embeddings API.
|
|
29
|
+
* Uses native fetch - no SDK required.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createVoyageEmbed(opts?: VoyageEmbedOptions): EmbedFn;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createVoyageEmbed = createVoyageEmbed;
|
|
4
|
+
/**
|
|
5
|
+
* Create an EmbedFn backed by the Voyage AI Embeddings API.
|
|
6
|
+
* Uses native fetch - no SDK required.
|
|
7
|
+
*/
|
|
8
|
+
function createVoyageEmbed(opts) {
|
|
9
|
+
const model = opts?.model ?? 'voyage-3-lite';
|
|
10
|
+
const baseUrl = opts?.baseUrl ?? 'https://api.voyageai.com/v1';
|
|
11
|
+
const inputType = opts?.inputType ?? 'query';
|
|
12
|
+
return async (text) => {
|
|
13
|
+
const apiKey = opts?.apiKey ?? process.env.VOYAGE_API_KEY;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
throw new Error('Voyage AI API key is required. Set VOYAGE_API_KEY env var or pass apiKey in options.');
|
|
16
|
+
}
|
|
17
|
+
const res = await fetch(`${baseUrl}/embeddings`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
Authorization: `Bearer ${apiKey}`,
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({ model, input: [text], input_type: inputType }),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const body = await res.text().catch(() => '');
|
|
27
|
+
throw new Error(`Voyage AI API error: ${res.status} ${body}`);
|
|
28
|
+
}
|
|
29
|
+
const json = (await res.json());
|
|
30
|
+
return json.data[0].embedding;
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { SemanticCache } from './SemanticCache';
|
|
2
|
-
export type {
|
|
2
|
+
export type { ThresholdEffectivenessResult } from './SemanticCache';
|
|
3
|
+
export { DEFAULT_COST_TABLE } from './defaultCostTable';
|
|
4
|
+
export type { SemanticCacheOptions, CacheCheckOptions, CacheStoreOptions, CacheCheckResult, CacheStats, IndexInfo, InvalidateResult, CacheConfidence, EmbedFn, ModelCost, RerankOptions, } from './types';
|
|
3
5
|
export { SemanticCacheUsageError, EmbeddingError, ValkeyCommandError, } from './errors';
|
|
6
|
+
export type { ContentBlock, TextBlock, BinaryBlock, ToolCallBlock, ToolResultBlock, ReasoningBlock, BlockHints, } from './utils';
|
|
7
|
+
export type { BinaryRef, BinaryNormalizer, NormalizerConfig } from './normalizer';
|
|
8
|
+
export { hashBase64, hashBytes, hashUrl, fetchAndHash, passthrough, composeNormalizer, defaultNormalizer, } from './normalizer';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ValkeyCommandError = exports.EmbeddingError = exports.SemanticCacheUsageError = exports.SemanticCache = void 0;
|
|
3
|
+
exports.defaultNormalizer = exports.composeNormalizer = exports.passthrough = exports.fetchAndHash = exports.hashUrl = exports.hashBytes = exports.hashBase64 = exports.ValkeyCommandError = exports.EmbeddingError = exports.SemanticCacheUsageError = exports.DEFAULT_COST_TABLE = exports.SemanticCache = void 0;
|
|
4
4
|
var SemanticCache_1 = require("./SemanticCache");
|
|
5
5
|
Object.defineProperty(exports, "SemanticCache", { enumerable: true, get: function () { return SemanticCache_1.SemanticCache; } });
|
|
6
|
+
var defaultCostTable_1 = require("./defaultCostTable");
|
|
7
|
+
Object.defineProperty(exports, "DEFAULT_COST_TABLE", { enumerable: true, get: function () { return defaultCostTable_1.DEFAULT_COST_TABLE; } });
|
|
6
8
|
var errors_1 = require("./errors");
|
|
7
9
|
Object.defineProperty(exports, "SemanticCacheUsageError", { enumerable: true, get: function () { return errors_1.SemanticCacheUsageError; } });
|
|
8
10
|
Object.defineProperty(exports, "EmbeddingError", { enumerable: true, get: function () { return errors_1.EmbeddingError; } });
|
|
9
11
|
Object.defineProperty(exports, "ValkeyCommandError", { enumerable: true, get: function () { return errors_1.ValkeyCommandError; } });
|
|
12
|
+
var normalizer_1 = require("./normalizer");
|
|
13
|
+
Object.defineProperty(exports, "hashBase64", { enumerable: true, get: function () { return normalizer_1.hashBase64; } });
|
|
14
|
+
Object.defineProperty(exports, "hashBytes", { enumerable: true, get: function () { return normalizer_1.hashBytes; } });
|
|
15
|
+
Object.defineProperty(exports, "hashUrl", { enumerable: true, get: function () { return normalizer_1.hashUrl; } });
|
|
16
|
+
Object.defineProperty(exports, "fetchAndHash", { enumerable: true, get: function () { return normalizer_1.fetchAndHash; } });
|
|
17
|
+
Object.defineProperty(exports, "passthrough", { enumerable: true, get: function () { return normalizer_1.passthrough; } });
|
|
18
|
+
Object.defineProperty(exports, "composeNormalizer", { enumerable: true, get: function () { return normalizer_1.composeNormalizer; } });
|
|
19
|
+
Object.defineProperty(exports, "defaultNormalizer", { enumerable: true, get: function () { return normalizer_1.defaultNormalizer; } });
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface BinaryRef {
|
|
2
|
+
kind: "image" | "audio" | "document";
|
|
3
|
+
source: {
|
|
4
|
+
type: "base64";
|
|
5
|
+
data: string;
|
|
6
|
+
mediaType?: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "url";
|
|
9
|
+
url: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "fileId";
|
|
12
|
+
fileId: string;
|
|
13
|
+
provider: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "bytes";
|
|
16
|
+
data: Uint8Array | Buffer;
|
|
17
|
+
};
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export type BinaryNormalizer = (ref: BinaryRef) => Promise<string>;
|
|
21
|
+
export interface NormalizerConfig {
|
|
22
|
+
base64?: (data: string) => string | Promise<string>;
|
|
23
|
+
url?: (urlStr: string) => string | Promise<string>;
|
|
24
|
+
fileId?: (fileId: string, provider: string) => string | Promise<string>;
|
|
25
|
+
bytes?: (data: Uint8Array | Buffer) => string | Promise<string>;
|
|
26
|
+
byKind?: {
|
|
27
|
+
image?: BinaryNormalizer;
|
|
28
|
+
audio?: BinaryNormalizer;
|
|
29
|
+
document?: BinaryNormalizer;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Strip any "data:<mime>;base64," prefix, decode the base64 bytes,
|
|
34
|
+
* and return "sha256:<hex>" of the decoded bytes.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hashBase64(data: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Return "sha256:<hex>" of the raw bytes.
|
|
39
|
+
*/
|
|
40
|
+
export declare function hashBytes(data: Uint8Array | Buffer): string;
|
|
41
|
+
/**
|
|
42
|
+
* Normalize a URL: lowercase scheme+host, drop default ports (80/443),
|
|
43
|
+
* sort query params, return "url:<normalized>".
|
|
44
|
+
*/
|
|
45
|
+
export declare function hashUrl(urlStr: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Fetch a URL, throw if response is not ok, and return "sha256:<hex>"
|
|
48
|
+
* of the response body bytes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function fetchAndHash(url: string): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Return a scheme-prefixed reference without any normalization:
|
|
53
|
+
* - base64 source -> "base64:<data>"
|
|
54
|
+
* - url source -> "url:<url>"
|
|
55
|
+
* - fileId source -> "fileid:<provider>:<fileId>"
|
|
56
|
+
* - bytes source -> "sha256:<hex>" (hashes the bytes)
|
|
57
|
+
*/
|
|
58
|
+
export declare function passthrough(ref: BinaryRef): string;
|
|
59
|
+
/**
|
|
60
|
+
* Build a BinaryNormalizer from a config.
|
|
61
|
+
*
|
|
62
|
+
* Dispatch order:
|
|
63
|
+
* 1. If cfg.byKind[ref.kind] is defined, call it with the full BinaryRef.
|
|
64
|
+
* 2. Otherwise dispatch on ref.source.type using the per-source handlers.
|
|
65
|
+
* 3. Fall back to passthrough for any unhandled source types.
|
|
66
|
+
*/
|
|
67
|
+
export declare function composeNormalizer(cfg?: NormalizerConfig): BinaryNormalizer;
|
|
68
|
+
export declare const defaultNormalizer: BinaryNormalizer;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultNormalizer = void 0;
|
|
4
|
+
exports.hashBase64 = hashBase64;
|
|
5
|
+
exports.hashBytes = hashBytes;
|
|
6
|
+
exports.hashUrl = hashUrl;
|
|
7
|
+
exports.fetchAndHash = fetchAndHash;
|
|
8
|
+
exports.passthrough = passthrough;
|
|
9
|
+
exports.composeNormalizer = composeNormalizer;
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
// --- Normalizer functions ---
|
|
12
|
+
/**
|
|
13
|
+
* Strip any "data:<mime>;base64," prefix, decode the base64 bytes,
|
|
14
|
+
* and return "sha256:<hex>" of the decoded bytes.
|
|
15
|
+
*/
|
|
16
|
+
function hashBase64(data) {
|
|
17
|
+
const raw = data.includes(";base64,") ? data.split(";base64,")[1] : data;
|
|
18
|
+
const bytes = Buffer.from(raw, "base64");
|
|
19
|
+
return "sha256:" + (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Return "sha256:<hex>" of the raw bytes.
|
|
23
|
+
*/
|
|
24
|
+
function hashBytes(data) {
|
|
25
|
+
return "sha256:" + (0, node_crypto_1.createHash)("sha256").update(data).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalize a URL: lowercase scheme+host, drop default ports (80/443),
|
|
29
|
+
* sort query params, return "url:<normalized>".
|
|
30
|
+
*/
|
|
31
|
+
function hashUrl(urlStr) {
|
|
32
|
+
const url = new URL(urlStr);
|
|
33
|
+
// URL constructor lowercases scheme and hostname; also drops default ports
|
|
34
|
+
url.searchParams.sort();
|
|
35
|
+
return "url:" + url.toString();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fetch a URL, throw if response is not ok, and return "sha256:<hex>"
|
|
39
|
+
* of the response body bytes.
|
|
40
|
+
*/
|
|
41
|
+
async function fetchAndHash(url) {
|
|
42
|
+
const res = await fetch(url);
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(`fetchAndHash: HTTP ${res.status} for ${url}`);
|
|
45
|
+
}
|
|
46
|
+
const buf = await res.arrayBuffer();
|
|
47
|
+
return "sha256:" + (0, node_crypto_1.createHash)("sha256").update(Buffer.from(buf)).digest("hex");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Return a scheme-prefixed reference without any normalization:
|
|
51
|
+
* - base64 source -> "base64:<data>"
|
|
52
|
+
* - url source -> "url:<url>"
|
|
53
|
+
* - fileId source -> "fileid:<provider>:<fileId>"
|
|
54
|
+
* - bytes source -> "sha256:<hex>" (hashes the bytes)
|
|
55
|
+
*/
|
|
56
|
+
function passthrough(ref) {
|
|
57
|
+
const { source } = ref;
|
|
58
|
+
switch (source.type) {
|
|
59
|
+
case "base64":
|
|
60
|
+
return "base64:" + source.data;
|
|
61
|
+
case "url":
|
|
62
|
+
return "url:" + source.url;
|
|
63
|
+
case "fileId":
|
|
64
|
+
return "fileid:" + source.provider + ":" + source.fileId;
|
|
65
|
+
case "bytes":
|
|
66
|
+
return hashBytes(source.data);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// --- Factory ---
|
|
70
|
+
/**
|
|
71
|
+
* Build a BinaryNormalizer from a config.
|
|
72
|
+
*
|
|
73
|
+
* Dispatch order:
|
|
74
|
+
* 1. If cfg.byKind[ref.kind] is defined, call it with the full BinaryRef.
|
|
75
|
+
* 2. Otherwise dispatch on ref.source.type using the per-source handlers.
|
|
76
|
+
* 3. Fall back to passthrough for any unhandled source types.
|
|
77
|
+
*/
|
|
78
|
+
function composeNormalizer(cfg = {}) {
|
|
79
|
+
return async (ref) => {
|
|
80
|
+
// byKind takes priority
|
|
81
|
+
const kindFn = cfg.byKind?.[ref.kind];
|
|
82
|
+
if (kindFn)
|
|
83
|
+
return kindFn(ref);
|
|
84
|
+
const { source } = ref;
|
|
85
|
+
switch (source.type) {
|
|
86
|
+
case "base64":
|
|
87
|
+
return cfg.base64 ? cfg.base64(source.data) : passthrough(ref);
|
|
88
|
+
case "url":
|
|
89
|
+
return cfg.url ? cfg.url(source.url) : passthrough(ref);
|
|
90
|
+
case "fileId":
|
|
91
|
+
return cfg.fileId
|
|
92
|
+
? cfg.fileId(source.fileId, source.provider)
|
|
93
|
+
: passthrough(ref);
|
|
94
|
+
case "bytes":
|
|
95
|
+
return cfg.bytes ? cfg.bytes(source.data) : passthrough(ref);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
exports.defaultNormalizer = composeNormalizer({
|
|
100
|
+
base64: hashBase64,
|
|
101
|
+
bytes: hashBytes,
|
|
102
|
+
});
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ interface CacheMetrics {
|
|
|
10
10
|
similarityScore: Histogram;
|
|
11
11
|
operationDuration: Histogram;
|
|
12
12
|
embeddingDuration: Histogram;
|
|
13
|
+
costSavedTotal: Counter;
|
|
14
|
+
embeddingCacheTotal: Counter;
|
|
15
|
+
staleModelEvictions: Counter;
|
|
13
16
|
}
|
|
14
17
|
export interface Telemetry {
|
|
15
18
|
tracer: Tracer;
|