@betterdb/semantic-cache 0.1.0 → 0.4.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.
Files changed (43) hide show
  1. package/README.md +233 -124
  2. package/dist/SemanticCache.d.ts +127 -7
  3. package/dist/SemanticCache.js +867 -48
  4. package/dist/adapters/ai.js +6 -1
  5. package/dist/adapters/anthropic.d.ts +32 -0
  6. package/dist/adapters/anthropic.js +94 -0
  7. package/dist/adapters/langchain.js +6 -1
  8. package/dist/adapters/langgraph.d.ts +104 -0
  9. package/dist/adapters/langgraph.js +271 -0
  10. package/dist/adapters/llamaindex.d.ts +32 -0
  11. package/dist/adapters/llamaindex.js +76 -0
  12. package/dist/adapters/openai-responses.d.ts +31 -0
  13. package/dist/adapters/openai-responses.js +112 -0
  14. package/dist/adapters/openai.d.ts +42 -0
  15. package/dist/adapters/openai.js +97 -0
  16. package/dist/analytics.d.ts +24 -0
  17. package/dist/analytics.js +116 -0
  18. package/dist/cluster.d.ts +10 -0
  19. package/dist/cluster.js +43 -0
  20. package/dist/defaultCostTable.d.ts +11 -0
  21. package/dist/defaultCostTable.js +1976 -0
  22. package/dist/discovery.d.ts +67 -0
  23. package/dist/discovery.js +140 -0
  24. package/dist/embed/bedrock.d.ts +32 -0
  25. package/dist/embed/bedrock.js +109 -0
  26. package/dist/embed/cohere.d.ts +34 -0
  27. package/dist/embed/cohere.js +37 -0
  28. package/dist/embed/ollama.d.ts +30 -0
  29. package/dist/embed/ollama.js +24 -0
  30. package/dist/embed/openai.d.ts +31 -0
  31. package/dist/embed/openai.js +66 -0
  32. package/dist/embed/voyage.d.ts +31 -0
  33. package/dist/embed/voyage.js +32 -0
  34. package/dist/index.d.ts +8 -1
  35. package/dist/index.js +13 -1
  36. package/dist/normalizer.d.ts +68 -0
  37. package/dist/normalizer.js +102 -0
  38. package/dist/telemetry.d.ts +5 -0
  39. package/dist/telemetry.js +30 -0
  40. package/dist/types.d.ts +128 -7
  41. package/dist/utils.d.ts +58 -0
  42. package/dist/utils.js +30 -0
  43. package/package.json +81 -6
@@ -0,0 +1,67 @@
1
+ import type { Valkey } from './types';
2
+ export declare const PROTOCOL_VERSION = 1;
3
+ export declare const REGISTRY_KEY = "__betterdb:caches";
4
+ export declare const PROTOCOL_KEY = "__betterdb:protocol";
5
+ export declare const HEARTBEAT_KEY_PREFIX = "__betterdb:heartbeat:";
6
+ export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 30000;
7
+ export declare const HEARTBEAT_TTL_SECONDS = 60;
8
+ export declare const CACHE_TYPE: "semantic_cache";
9
+ export type CacheType = typeof CACHE_TYPE;
10
+ export interface DiscoveryOptions {
11
+ enabled?: boolean;
12
+ heartbeatIntervalMs?: number;
13
+ includeCategories?: boolean;
14
+ }
15
+ export interface MarkerMetadata {
16
+ type: CacheType;
17
+ prefix: string;
18
+ version: string;
19
+ protocol_version: number;
20
+ capabilities: string[];
21
+ stats_key: string;
22
+ started_at: string;
23
+ pid?: number;
24
+ hostname?: string;
25
+ [extra: string]: unknown;
26
+ }
27
+ export interface BuildSemanticMetadataInput {
28
+ name: string;
29
+ version: string;
30
+ defaultThreshold: number;
31
+ categoryThresholds: Record<string, number>;
32
+ uncertaintyBand: number;
33
+ includeCategories: boolean;
34
+ }
35
+ export declare function buildSemanticMetadata(input: BuildSemanticMetadataInput): MarkerMetadata;
36
+ export interface DiscoveryLogger {
37
+ warn: (msg: string) => void;
38
+ debug: (msg: string) => void;
39
+ }
40
+ export interface DiscoveryManagerDeps {
41
+ client: Valkey;
42
+ name: string;
43
+ metadata: MarkerMetadata;
44
+ heartbeatIntervalMs?: number;
45
+ logger?: DiscoveryLogger;
46
+ onWriteFailed?: () => void;
47
+ }
48
+ export declare class DiscoveryManager {
49
+ private readonly client;
50
+ private readonly name;
51
+ private readonly metadata;
52
+ private readonly heartbeatIntervalMs;
53
+ private readonly heartbeatKey;
54
+ private readonly logger;
55
+ private readonly onWriteFailed;
56
+ private heartbeatHandle;
57
+ constructor(deps: DiscoveryManagerDeps);
58
+ register(): Promise<void>;
59
+ stop(opts: {
60
+ deleteHeartbeat: boolean;
61
+ }): Promise<void>;
62
+ tickHeartbeat(): Promise<void>;
63
+ private startHeartbeat;
64
+ private safeHget;
65
+ private safeCall;
66
+ private checkCollision;
67
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiscoveryManager = exports.CACHE_TYPE = exports.HEARTBEAT_TTL_SECONDS = exports.DEFAULT_HEARTBEAT_INTERVAL_MS = exports.HEARTBEAT_KEY_PREFIX = exports.PROTOCOL_KEY = exports.REGISTRY_KEY = exports.PROTOCOL_VERSION = void 0;
4
+ exports.buildSemanticMetadata = buildSemanticMetadata;
5
+ const node_os_1 = require("node:os");
6
+ const errors_1 = require("./errors");
7
+ exports.PROTOCOL_VERSION = 1;
8
+ exports.REGISTRY_KEY = '__betterdb:caches';
9
+ exports.PROTOCOL_KEY = '__betterdb:protocol';
10
+ exports.HEARTBEAT_KEY_PREFIX = '__betterdb:heartbeat:';
11
+ exports.DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
12
+ exports.HEARTBEAT_TTL_SECONDS = 60;
13
+ exports.CACHE_TYPE = 'semantic_cache';
14
+ function buildSemanticMetadata(input) {
15
+ const metadata = {
16
+ type: exports.CACHE_TYPE,
17
+ prefix: input.name,
18
+ version: input.version,
19
+ protocol_version: exports.PROTOCOL_VERSION,
20
+ capabilities: ['invalidate', 'similarity_distribution', 'threshold_adjust'],
21
+ index_name: `${input.name}:idx`,
22
+ stats_key: `${input.name}:__stats`,
23
+ config_key: `${input.name}:__config`,
24
+ default_threshold: input.defaultThreshold,
25
+ uncertainty_band: input.uncertaintyBand,
26
+ started_at: new Date().toISOString(),
27
+ pid: process.pid,
28
+ hostname: (0, node_os_1.hostname)(),
29
+ };
30
+ if (input.includeCategories && Object.keys(input.categoryThresholds).length > 0) {
31
+ metadata.category_thresholds = { ...input.categoryThresholds };
32
+ }
33
+ return metadata;
34
+ }
35
+ const noopLogger = {
36
+ warn: () => { },
37
+ debug: () => { },
38
+ };
39
+ function errMsg(err) {
40
+ return err instanceof Error ? err.message : String(err);
41
+ }
42
+ class DiscoveryManager {
43
+ client;
44
+ name;
45
+ metadata;
46
+ heartbeatIntervalMs;
47
+ heartbeatKey;
48
+ logger;
49
+ onWriteFailed;
50
+ heartbeatHandle = null;
51
+ constructor(deps) {
52
+ this.client = deps.client;
53
+ this.name = deps.name;
54
+ this.metadata = deps.metadata;
55
+ this.heartbeatIntervalMs = deps.heartbeatIntervalMs ?? exports.DEFAULT_HEARTBEAT_INTERVAL_MS;
56
+ this.heartbeatKey = `${exports.HEARTBEAT_KEY_PREFIX}${deps.name}`;
57
+ this.logger = deps.logger ?? noopLogger;
58
+ this.onWriteFailed = deps.onWriteFailed ?? (() => { });
59
+ }
60
+ async register() {
61
+ const existingJson = await this.safeHget();
62
+ if (existingJson !== null) {
63
+ this.checkCollision(existingJson);
64
+ }
65
+ await this.safeCall(() => this.client.hset(exports.REGISTRY_KEY, this.name, JSON.stringify(this.metadata)), 'HSET registry');
66
+ await this.safeCall(() => this.client.set(exports.PROTOCOL_KEY, String(exports.PROTOCOL_VERSION), 'NX'), 'SET protocol');
67
+ await this.tickHeartbeat();
68
+ this.startHeartbeat();
69
+ }
70
+ async stop(opts) {
71
+ if (this.heartbeatHandle) {
72
+ clearInterval(this.heartbeatHandle);
73
+ this.heartbeatHandle = null;
74
+ }
75
+ if (!opts.deleteHeartbeat) {
76
+ return;
77
+ }
78
+ try {
79
+ await this.client.del(this.heartbeatKey);
80
+ }
81
+ catch (err) {
82
+ this.logger.debug(`discovery: DEL heartbeat failed: ${errMsg(err)}`);
83
+ }
84
+ }
85
+ async tickHeartbeat() {
86
+ const now = new Date().toISOString();
87
+ try {
88
+ await this.client.set(this.heartbeatKey, now, 'EX', exports.HEARTBEAT_TTL_SECONDS);
89
+ }
90
+ catch (err) {
91
+ this.logger.debug(`discovery: heartbeat SET failed: ${errMsg(err)}`);
92
+ this.onWriteFailed();
93
+ }
94
+ }
95
+ startHeartbeat() {
96
+ if (this.heartbeatHandle) {
97
+ clearInterval(this.heartbeatHandle);
98
+ }
99
+ const handle = setInterval(() => {
100
+ void this.tickHeartbeat();
101
+ }, this.heartbeatIntervalMs);
102
+ handle.unref?.();
103
+ this.heartbeatHandle = handle;
104
+ }
105
+ async safeHget() {
106
+ try {
107
+ return await this.client.hget(exports.REGISTRY_KEY, this.name);
108
+ }
109
+ catch (err) {
110
+ this.logger.warn(`discovery: HGET registry failed: ${errMsg(err)}`);
111
+ this.onWriteFailed();
112
+ return null;
113
+ }
114
+ }
115
+ async safeCall(fn, label) {
116
+ try {
117
+ await fn();
118
+ }
119
+ catch (err) {
120
+ this.logger.warn(`discovery: ${label} failed: ${errMsg(err)}`);
121
+ this.onWriteFailed();
122
+ }
123
+ }
124
+ checkCollision(existingJson) {
125
+ let parsed;
126
+ try {
127
+ parsed = JSON.parse(existingJson);
128
+ }
129
+ catch {
130
+ return;
131
+ }
132
+ if (parsed.type && parsed.type !== exports.CACHE_TYPE) {
133
+ throw new errors_1.SemanticCacheUsageError(`cache name collision: '${this.name}' is already registered as type '${String(parsed.type)}' on this Valkey instance`);
134
+ }
135
+ if (parsed.version && parsed.version !== this.metadata.version) {
136
+ this.logger.warn(`discovery: overwriting marker for '${this.name}' (existing version ${String(parsed.version)}, this version ${this.metadata.version})`);
137
+ }
138
+ }
139
+ }
140
+ exports.DiscoveryManager = DiscoveryManager;
@@ -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,10 @@
1
1
  export { SemanticCache } from './SemanticCache';
2
- export type { SemanticCacheOptions, CacheCheckOptions, CacheStoreOptions, CacheCheckResult, CacheStats, IndexInfo, InvalidateResult, CacheConfidence, EmbedFn, } from './types';
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, ConfigRefreshOptions, } from './types';
3
5
  export { SemanticCacheUsageError, EmbeddingError, ValkeyCommandError, } from './errors';
6
+ export type { ContentBlock, TextBlock, BinaryBlock, ToolCallBlock, ToolResultBlock, ReasoningBlock, BlockHints, } from './utils';
7
+ export { escapeTag } from './utils';
8
+ export type { BinaryRef, BinaryNormalizer, NormalizerConfig } from './normalizer';
9
+ export { hashBase64, hashBytes, hashUrl, fetchAndHash, passthrough, composeNormalizer, defaultNormalizer, } from './normalizer';
10
+ export type { DiscoveryOptions } from './discovery';