lazlo-ai 1.0.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 (126) hide show
  1. package/.env.example +9 -0
  2. package/README.md +278 -0
  3. package/dist/cache/semantic.d.ts +39 -0
  4. package/dist/cache/semantic.d.ts.map +1 -0
  5. package/dist/cache/semantic.js +134 -0
  6. package/dist/cache/semantic.js.map +1 -0
  7. package/dist/chains/llmchain.d.ts +65 -0
  8. package/dist/chains/llmchain.d.ts.map +1 -0
  9. package/dist/chains/llmchain.js +137 -0
  10. package/dist/chains/llmchain.js.map +1 -0
  11. package/dist/chains/rag.d.ts +23 -0
  12. package/dist/chains/rag.d.ts.map +1 -0
  13. package/dist/chains/rag.js +47 -0
  14. package/dist/chains/rag.js.map +1 -0
  15. package/dist/core/types.d.ts +130 -0
  16. package/dist/core/types.d.ts.map +1 -0
  17. package/dist/core/types.js +8 -0
  18. package/dist/core/types.js.map +1 -0
  19. package/dist/document_loaders/index.d.ts +61 -0
  20. package/dist/document_loaders/index.d.ts.map +1 -0
  21. package/dist/document_loaders/index.js +183 -0
  22. package/dist/document_loaders/index.js.map +1 -0
  23. package/dist/embeddings/google.d.ts +43 -0
  24. package/dist/embeddings/google.d.ts.map +1 -0
  25. package/dist/embeddings/google.js +90 -0
  26. package/dist/embeddings/google.js.map +1 -0
  27. package/dist/embeddings/local.d.ts +64 -0
  28. package/dist/embeddings/local.d.ts.map +1 -0
  29. package/dist/embeddings/local.js +95 -0
  30. package/dist/embeddings/local.js.map +1 -0
  31. package/dist/evals/judge.d.ts +22 -0
  32. package/dist/evals/judge.d.ts.map +1 -0
  33. package/dist/evals/judge.js +77 -0
  34. package/dist/evals/judge.js.map +1 -0
  35. package/dist/index.d.ts +28 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +84 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/memory/buffer.d.ts +64 -0
  40. package/dist/memory/buffer.d.ts.map +1 -0
  41. package/dist/memory/buffer.js +168 -0
  42. package/dist/memory/buffer.js.map +1 -0
  43. package/dist/parsers/output.d.ts +64 -0
  44. package/dist/parsers/output.d.ts.map +1 -0
  45. package/dist/parsers/output.js +148 -0
  46. package/dist/parsers/output.js.map +1 -0
  47. package/dist/prompts/registry.d.ts +65 -0
  48. package/dist/prompts/registry.d.ts.map +1 -0
  49. package/dist/prompts/registry.js +170 -0
  50. package/dist/prompts/registry.js.map +1 -0
  51. package/dist/providers/ollama.d.ts +30 -0
  52. package/dist/providers/ollama.d.ts.map +1 -0
  53. package/dist/providers/ollama.js +104 -0
  54. package/dist/providers/ollama.js.map +1 -0
  55. package/dist/providers/openai.d.ts +46 -0
  56. package/dist/providers/openai.d.ts.map +1 -0
  57. package/dist/providers/openai.js +228 -0
  58. package/dist/providers/openai.js.map +1 -0
  59. package/dist/retrievers/index.d.ts +71 -0
  60. package/dist/retrievers/index.d.ts.map +1 -0
  61. package/dist/retrievers/index.js +130 -0
  62. package/dist/retrievers/index.js.map +1 -0
  63. package/dist/router/smartrouter.d.ts +36 -0
  64. package/dist/router/smartrouter.d.ts.map +1 -0
  65. package/dist/router/smartrouter.js +132 -0
  66. package/dist/router/smartrouter.js.map +1 -0
  67. package/dist/text_splitters/index.d.ts +28 -0
  68. package/dist/text_splitters/index.d.ts.map +1 -0
  69. package/dist/text_splitters/index.js +109 -0
  70. package/dist/text_splitters/index.js.map +1 -0
  71. package/dist/tools/decorator.d.ts +26 -0
  72. package/dist/tools/decorator.d.ts.map +1 -0
  73. package/dist/tools/decorator.js +102 -0
  74. package/dist/tools/decorator.js.map +1 -0
  75. package/dist/tools/index.d.ts +7 -0
  76. package/dist/tools/index.d.ts.map +1 -0
  77. package/dist/tools/index.js +6 -0
  78. package/dist/tools/index.js.map +1 -0
  79. package/dist/tools/keiro.d.ts +20 -0
  80. package/dist/tools/keiro.d.ts.map +1 -0
  81. package/dist/tools/keiro.js +67 -0
  82. package/dist/tools/keiro.js.map +1 -0
  83. package/dist/tracing/tracer.d.ts +56 -0
  84. package/dist/tracing/tracer.d.ts.map +1 -0
  85. package/dist/tracing/tracer.js +125 -0
  86. package/dist/tracing/tracer.js.map +1 -0
  87. package/dist/utils/logger.d.ts +25 -0
  88. package/dist/utils/logger.d.ts.map +1 -0
  89. package/dist/utils/logger.js +50 -0
  90. package/dist/utils/logger.js.map +1 -0
  91. package/dist/utils/pricing.d.ts +31 -0
  92. package/dist/utils/pricing.d.ts.map +1 -0
  93. package/dist/utils/pricing.js +108 -0
  94. package/dist/utils/pricing.js.map +1 -0
  95. package/dist/vectorstores/index.d.ts +62 -0
  96. package/dist/vectorstores/index.d.ts.map +1 -0
  97. package/dist/vectorstores/index.js +244 -0
  98. package/dist/vectorstores/index.js.map +1 -0
  99. package/package.json +48 -0
  100. package/src/cache/semantic.ts +175 -0
  101. package/src/chains/llmchain.ts +194 -0
  102. package/src/chains/rag.ts +65 -0
  103. package/src/core/types.ts +178 -0
  104. package/src/document_loaders/index.ts +223 -0
  105. package/src/embeddings/google.ts +119 -0
  106. package/src/embeddings/local.ts +118 -0
  107. package/src/evals/judge.ts +99 -0
  108. package/src/index.ts +121 -0
  109. package/src/memory/buffer.ts +222 -0
  110. package/src/parsers/output.ts +195 -0
  111. package/src/prompts/registry.ts +205 -0
  112. package/src/providers/ollama.ts +151 -0
  113. package/src/providers/openai.ts +320 -0
  114. package/src/retrievers/index.ts +182 -0
  115. package/src/router/smartrouter.ts +172 -0
  116. package/src/text_splitters/index.ts +145 -0
  117. package/src/tools/decorator.ts +145 -0
  118. package/src/tools/index.ts +7 -0
  119. package/src/tools/keiro.ts +92 -0
  120. package/src/tracing/tracer.ts +178 -0
  121. package/src/utils/logger.ts +62 -0
  122. package/src/utils/pricing.ts +133 -0
  123. package/src/vectorstores/index.ts +338 -0
  124. package/test-full.mjs +552 -0
  125. package/test.mjs +74 -0
  126. package/tsconfig.json +30 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Tracing - Built-in observability without SaaS
3
+ */
4
+
5
+ import { logger } from '../utils/logger.js';
6
+
7
+ // ============================================================================
8
+ // Span
9
+ // ============================================================================
10
+
11
+ export interface SpanData {
12
+ name: string;
13
+ type: string;
14
+ durationMs: number;
15
+ tokensIn: number;
16
+ tokensOut: number;
17
+ costUsd: number;
18
+ error?: string;
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+
22
+ class Span {
23
+ name: string;
24
+ type: string;
25
+ startTime: number;
26
+ endTime?: number;
27
+ tokensIn = 0;
28
+ tokensOut = 0;
29
+ metadata: Record<string, unknown> = {};
30
+ error?: string;
31
+
32
+ constructor(name: string, type: string = 'llm') {
33
+ this.name = name;
34
+ this.type = type;
35
+ this.startTime = Date.now();
36
+ }
37
+
38
+ finish(): void {
39
+ this.endTime = Date.now();
40
+ }
41
+
42
+ get durationMs(): number {
43
+ if (!this.endTime) return 0;
44
+ return this.endTime - this.startTime;
45
+ }
46
+
47
+ get costUsd(): number {
48
+ const rate = this.type === 'llm' ? 0.002 : 0;
49
+ return ((this.tokensIn + this.tokensOut) / 1000) * rate;
50
+ }
51
+
52
+ toDict(): SpanData {
53
+ return {
54
+ name: this.name,
55
+ type: this.type,
56
+ durationMs: this.durationMs,
57
+ tokensIn: this.tokensIn,
58
+ tokensOut: this.tokensOut,
59
+ costUsd: this.costUsd,
60
+ error: this.error,
61
+ metadata: this.metadata,
62
+ };
63
+ }
64
+ }
65
+
66
+ // ============================================================================
67
+ // Tracer
68
+ // ============================================================================
69
+
70
+ interface Run {
71
+ name: string;
72
+ correlationId: string;
73
+ totalDurationMs: number;
74
+ totalTokens: number;
75
+ totalCostUsd: number;
76
+ spans: SpanData[];
77
+ }
78
+
79
+ export class Tracer {
80
+ private runs: Run[] = [];
81
+ private currentRunName?: string;
82
+ private currentSpans: Span[] = [];
83
+
84
+ /**
85
+ * Start a trace context
86
+ */
87
+ trace(runName: string): { tracer: Tracer; span: (name: string, type?: string) => Span; end: () => void } {
88
+ this.currentRunName = runName;
89
+ this.currentSpans = [];
90
+ const runStart = Date.now();
91
+
92
+ logger.info(`[Tracer] Run '${runName}' started.`);
93
+
94
+ return {
95
+ tracer: this,
96
+ span: (name: string, type: string = 'llm') => {
97
+ const s = new Span(name, type);
98
+ this.currentSpans.push(s);
99
+ return s;
100
+ },
101
+ end: () => {
102
+ const runEnd = Date.now();
103
+ const runDuration = runEnd - runStart;
104
+ const totalCost = this.currentSpans.reduce((sum, s) => sum + s.costUsd, 0);
105
+ const totalTokens = this.currentSpans.reduce((sum, s) => sum + s.tokensIn + s.tokensOut, 0);
106
+
107
+ this.runs.push({
108
+ name: runName,
109
+ correlationId: Math.random().toString(36).slice(2, 10),
110
+ totalDurationMs: runDuration,
111
+ totalTokens,
112
+ totalCostUsd: totalCost,
113
+ spans: this.currentSpans.map(s => s.toDict()),
114
+ });
115
+
116
+ logger.info(`[Tracer] Run '${runName}' finished in ${runDuration}ms. Est. cost: $${totalCost.toFixed(5)}`);
117
+ },
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Record an event/span
123
+ */
124
+ event(
125
+ name: string,
126
+ type: string = 'llm',
127
+ tokensIn: number = 0,
128
+ tokensOut: number = 0,
129
+ metadata?: Record<string, unknown>
130
+ ): void {
131
+ const span = new Span(name, type);
132
+ span.tokensIn = tokensIn;
133
+ span.tokensOut = tokensOut;
134
+ span.metadata = metadata ?? {};
135
+ span.finish();
136
+ this.currentSpans.push(span);
137
+ }
138
+
139
+ /**
140
+ * Print trace summary
141
+ */
142
+ printSummary(): void {
143
+ if (this.runs.length === 0) {
144
+ console.log('No traces recorded yet.');
145
+ return;
146
+ }
147
+
148
+ for (const run of this.runs) {
149
+ console.log(`\n${'='.repeat(70)}`);
150
+ console.log(` Lazlo Trace: ${run.name}`);
151
+ console.log(` Total: ${run.totalDurationMs}ms | ~${run.totalTokens.toLocaleString()} tokens | Est. $${run.totalCostUsd.toFixed(5)}`);
152
+ console.log(`${'='.repeat(70)}`);
153
+ console.log(` ${'Step'.padEnd(28)} ${'Type'.padEnd(12)} ${'ms'.padStart(8)} ${'Tok-In'.padStart(8)} ${'Tok-Out'.padStart(8)} ${'Cost'.padStart(10)}`);
154
+ console.log(` ${'-'.repeat(68)}`);
155
+
156
+ for (const s of run.spans) {
157
+ const status = s.error ? '❌' : '✓';
158
+ console.log(
159
+ ` ${status} ${s.name.slice(0, 26).padEnd(28)} ${s.type.padEnd(12)} ` +
160
+ `${s.durationMs.toString().padStart(7)}ms ${s.tokensIn.toString().padStart(8)} ` +
161
+ `${s.tokensOut.toString().padStart(8)} $${s.costUsd.toFixed(5).padStart(9)}`
162
+ );
163
+ }
164
+ console.log(`${'='.repeat(70)}\n`);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Clear all traces
170
+ */
171
+ clear(): void {
172
+ this.runs = [];
173
+ this.currentSpans = [];
174
+ }
175
+ }
176
+
177
+ // Singleton instance
178
+ export const tracer = new Tracer();
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Lazlo Logger - Simple, structured logging
3
+ */
4
+
5
+ export enum LogLevel {
6
+ DEBUG = 0,
7
+ INFO = 1,
8
+ WARN = 2,
9
+ ERROR = 3,
10
+ }
11
+
12
+ const LEVEL_NAMES = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
13
+
14
+ class Logger {
15
+ private level: LogLevel = LogLevel.INFO;
16
+ private prefix: string = '';
17
+
18
+ constructor(prefix: string = '') {
19
+ this.prefix = prefix;
20
+ }
21
+
22
+ setLevel(level: LogLevel): void {
23
+ this.level = level;
24
+ }
25
+
26
+ setPrefix(prefix: string): void {
27
+ this.prefix = prefix;
28
+ }
29
+
30
+ private log(level: LogLevel, ...args: unknown[]): void {
31
+ if (level < this.level) return;
32
+
33
+ const timestamp = new Date().toISOString();
34
+ const levelName = LEVEL_NAMES[level];
35
+ const prefix = this.prefix ? `[${this.prefix}] ` : '';
36
+
37
+ console.log(`\x1b[90m${timestamp}\x1b[0m ${levelName} ${prefix}`, ...args);
38
+ }
39
+
40
+ debug(...args: unknown[]): void {
41
+ this.log(LogLevel.DEBUG, ...args);
42
+ }
43
+
44
+ info(...args: unknown[]): void {
45
+ this.log(LogLevel.INFO, ...args);
46
+ }
47
+
48
+ warn(...args: unknown[]): void {
49
+ this.log(LogLevel.WARN, ...args);
50
+ }
51
+
52
+ error(...args: unknown[]): void {
53
+ this.log(LogLevel.ERROR, ...args);
54
+ }
55
+ }
56
+
57
+ export const logger = new Logger('Lazlo');
58
+
59
+ // Factory for child loggers
60
+ export function createLogger(prefix: string): Logger {
61
+ return new Logger(prefix);
62
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Lazlo Pricing - Model pricing lookup and cost calculation
3
+ */
4
+
5
+ import { ModelPricing } from '../core/types.js';
6
+
7
+ // Default pricing for common models (per 1M tokens)
8
+ const DEFAULT_PRICING: Record<string, ModelPricing> = {
9
+ // OpenAI
10
+ 'gpt-4o': { input: 2.50, output: 10.00 },
11
+ 'gpt-4o-mini': { input: 0.15, output: 0.60 },
12
+ 'gpt-4-turbo': { input: 10.00, output: 30.00 },
13
+ 'gpt-3.5-turbo': { input: 0.50, output: 1.50 },
14
+ 'gpt-4': { input: 30.00, output: 60.00 },
15
+
16
+ // Anthropic
17
+ 'claude-3-5-sonnet': { input: 3.00, output: 15.00 },
18
+ 'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00 },
19
+ 'claude-3-opus': { input: 15.00, output: 75.00 },
20
+ 'claude-3-sonnet': { input: 3.00, output: 15.00 },
21
+ 'claude-3-haiku': { input: 0.25, output: 1.25 },
22
+
23
+ // Google
24
+ 'gemini-1.5-flash': { input: 0.075, output: 0.30 },
25
+ 'gemini-1.5-pro': { input: 1.25, output: 5.00 },
26
+ 'gemini-2.0-flash': { input: 0.00, output: 0.00 },
27
+ 'gemini-2.0-flash-001': { input: 0.00, output: 0.00 },
28
+ 'gemini-2.0-pro': { input: 1.25, output: 5.00 },
29
+
30
+ // DeepSeek
31
+ 'deepseek-chat': { input: 0.14, output: 0.28 },
32
+ 'deepseek-coder': { input: 0.14, output: 0.28 },
33
+
34
+ // Meta Llama
35
+ 'llama-3.1-70b': { input: 0.00, output: 0.00 },
36
+ 'llama-3.1-8b': { input: 0.00, output: 0.00 },
37
+ 'llama-3-70b': { input: 0.00, output: 0.00 },
38
+ 'llama-3-8b': { input: 0.00, output: 0.00 },
39
+ };
40
+
41
+ // Custom pricing set by user
42
+ const customPricing: Map<string, ModelPricing> = new Map();
43
+
44
+ /**
45
+ * Get pricing for a model
46
+ */
47
+ export function getPricing(model: string): ModelPricing {
48
+ // Check custom pricing first
49
+ if (customPricing.has(model)) {
50
+ return customPricing.get(model)!;
51
+ }
52
+
53
+ // Try exact match
54
+ if (DEFAULT_PRICING[model]) {
55
+ return DEFAULT_PRICING[model];
56
+ }
57
+
58
+ // Try partial match
59
+ const modelLower = model.toLowerCase();
60
+ for (const [key, pricing] of Object.entries(DEFAULT_PRICING)) {
61
+ if (modelLower.includes(key.toLowerCase()) || key.toLowerCase().includes(modelLower)) {
62
+ return pricing;
63
+ }
64
+ }
65
+
66
+ // Return placeholder - user needs to set pricing
67
+ return { input: 0, output: 0 };
68
+ }
69
+
70
+ /**
71
+ * Set custom pricing for a model
72
+ */
73
+ export function setPricing(model: string, input: number, output: number): void {
74
+ customPricing.set(model, { input, output });
75
+ }
76
+
77
+ /**
78
+ * Calculate cost in USD
79
+ */
80
+ export function calculateCost(
81
+ model: string,
82
+ tokensIn: number,
83
+ tokensOut: number
84
+ ): number {
85
+ const pricing = getPricing(model);
86
+ return (tokensIn / 1_000_000 * pricing.input) + (tokensOut / 1_000_000 * pricing.output);
87
+ }
88
+
89
+ /**
90
+ * Estimate tokens from text (rough approximation)
91
+ */
92
+ export function estimateTokens(text: string): number {
93
+ // Rough estimate: ~4 characters per token on average
94
+ return Math.max(1, Math.ceil(text.length / 4));
95
+ }
96
+
97
+ /**
98
+ * Estimate cost from text
99
+ */
100
+ export function estimateCost(
101
+ model: string,
102
+ textIn: string,
103
+ textOut: string
104
+ ): number {
105
+ const tokensIn = estimateTokens(textIn);
106
+ const tokensOut = estimateTokens(textOut);
107
+ return calculateCost(model, tokensIn, tokensOut);
108
+ }
109
+
110
+ /**
111
+ * Load pricing from environment variables
112
+ * Call this after setting process.env if needed
113
+ * Format: LAZLO_PRICING_model_name=input,output
114
+ */
115
+ export function loadEnvPricing(env?: Record<string, string | undefined>): void {
116
+ const environment = env ?? (typeof globalThis !== 'undefined' ? (globalThis as any).process?.env : {});
117
+
118
+ for (const [key, value] of Object.entries(environment)) {
119
+ if (key.startsWith('LAZLO_PRICING_')) {
120
+ const modelName = key.replace('LAZLO_PRICING_', '').toLowerCase();
121
+ if (typeof value === 'string') {
122
+ const parts = value.split(',');
123
+ if (parts.length === 2) {
124
+ const input = parseFloat(parts[0]);
125
+ const output = parseFloat(parts[1]);
126
+ if (!isNaN(input) && !isNaN(output)) {
127
+ setPricing(modelName, input, output);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Vector Stores
3
+ *
4
+ * In-Memory, Chroma, and SQLite-Vector stores for document retrieval
5
+ */
6
+
7
+ import { logger } from '../utils/logger.js';
8
+
9
+ export interface Document {
10
+ pageContent: string;
11
+ metadata: Record<string, unknown>;
12
+ }
13
+
14
+ export interface BaseEmbeddings {
15
+ embedQuery(text: string): Promise<number[]>;
16
+ embedDocuments(texts: string[]): Promise<number[][]>;
17
+ }
18
+
19
+ // ============================================================================
20
+ // Cosine Similarity
21
+ // ============================================================================
22
+
23
+ function cosineSimilarity(a: number[], b: number[]): number {
24
+ const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
25
+ const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
26
+ const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
27
+
28
+ if (normA === 0 || normB === 0) return 0;
29
+ return dot / (normA * normB);
30
+ }
31
+
32
+ // ============================================================================
33
+ // In-Memory Vector Store
34
+ // ============================================================================
35
+
36
+ interface StoredDoc {
37
+ pageContent: string;
38
+ metadata: Record<string, unknown>;
39
+ embedding: number[];
40
+ }
41
+
42
+ export class InMemoryVectorStore {
43
+ private embeddingModel?: BaseEmbeddings;
44
+ private store: StoredDoc[] = [];
45
+
46
+ constructor(embeddingModel?: BaseEmbeddings) {
47
+ this.embeddingModel = embeddingModel;
48
+ logger.info('[Lazlo] InMemoryVectorStore ready.');
49
+ }
50
+
51
+ async addDocuments(documents: Document[]): Promise<void> {
52
+ if (!this.embeddingModel) {
53
+ throw new Error('No embedding model provided');
54
+ }
55
+
56
+ const texts = documents.map(d => d.pageContent);
57
+ const metadatas = documents.map(d => d.metadata);
58
+ const embeddings = await this.embeddingModel.embedDocuments(texts);
59
+
60
+ documents.forEach((doc, i) => {
61
+ this.store.push({
62
+ pageContent: doc.pageContent,
63
+ metadata: doc.metadata,
64
+ embedding: embeddings[i],
65
+ });
66
+ });
67
+ }
68
+
69
+ async similaritySearch(query: string, k: number = 4): Promise<Document[]> {
70
+ if (!this.embeddingModel) {
71
+ throw new Error('No embedding model provided');
72
+ }
73
+
74
+ const queryEmbedding = await this.embeddingModel.embedQuery(query);
75
+
76
+ // Score all documents
77
+ const scored = this.store.map(doc => ({
78
+ doc,
79
+ score: cosineSimilarity(queryEmbedding, doc.embedding),
80
+ }));
81
+
82
+ // Sort by score descending
83
+ scored.sort((a, b) => b.score - a.score);
84
+
85
+ // Return top k
86
+ return scored.slice(0, k).map(s => ({
87
+ pageContent: s.doc.pageContent,
88
+ metadata: s.doc.metadata,
89
+ }));
90
+ }
91
+
92
+ async similaritySearchWithScore(query: string, k: number = 4): Promise<[Document, number][]> {
93
+ if (!this.embeddingModel) {
94
+ throw new Error('No embedding model provided');
95
+ }
96
+
97
+ const queryEmbedding = await this.embeddingModel.embedQuery(query);
98
+
99
+ const scored = this.store.map(doc => ({
100
+ doc,
101
+ score: cosineSimilarity(queryEmbedding, doc.embedding),
102
+ }));
103
+
104
+ scored.sort((a, b) => b.score - a.score);
105
+
106
+ return scored.slice(0, k).map(s => [
107
+ { pageContent: s.doc.pageContent, metadata: s.doc.metadata },
108
+ s.score,
109
+ ]);
110
+ }
111
+
112
+ delete(): void {
113
+ this.store = [];
114
+ }
115
+ }
116
+
117
+ // ============================================================================
118
+ // Chroma Vector Store (Client-side implementation)
119
+ // ============================================================================
120
+
121
+ export class ChromaVectorStore {
122
+ private embeddingModel?: BaseEmbeddings;
123
+ private collectionName: string;
124
+ private documents: Map<string, StoredDoc> = new Map();
125
+
126
+ constructor(options: {
127
+ embeddingModel?: BaseEmbeddings;
128
+ collectionName?: string;
129
+ } = {}) {
130
+ this.embeddingModel = options.embeddingModel;
131
+ this.collectionName = options.collectionName || 'lazlo-collection';
132
+ logger.info(`[Lazlo] ChromaVectorStore ready. Collection: ${this.collectionName}`);
133
+ }
134
+
135
+ async addDocuments(documents: Document[]): Promise<string[]> {
136
+ if (!this.embeddingModel) {
137
+ throw new Error('No embedding model provided');
138
+ }
139
+
140
+ const texts = documents.map(d => d.pageContent);
141
+ const embeddings = await this.embeddingModel.embedDocuments(texts);
142
+ const ids: string[] = [];
143
+
144
+ documents.forEach((doc, i) => {
145
+ const id = crypto.randomUUID();
146
+ this.documents.set(id, {
147
+ pageContent: doc.pageContent,
148
+ metadata: doc.metadata,
149
+ embedding: embeddings[i],
150
+ });
151
+ ids.push(id);
152
+ });
153
+
154
+ return ids;
155
+ }
156
+
157
+ async similaritySearch(query: string, k: number = 4): Promise<Document[]> {
158
+ if (!this.embeddingModel) {
159
+ throw new Error('No embedding model provided');
160
+ }
161
+
162
+ const queryEmbedding = await this.embeddingModel.embedQuery(query);
163
+
164
+ const scored = Array.from(this.documents.entries()).map(([id, doc]) => ({
165
+ id,
166
+ doc,
167
+ score: cosineSimilarity(queryEmbedding, doc.embedding),
168
+ }));
169
+
170
+ scored.sort((a, b) => b.score - a.score);
171
+
172
+ return scored.slice(0, k).map(s => ({
173
+ pageContent: s.doc.pageContent,
174
+ metadata: s.doc.metadata,
175
+ }));
176
+ }
177
+
178
+ async similaritySearchWithScore(query: string, k: number = 4): Promise<[Document, number][]> {
179
+ if (!this.embeddingModel) {
180
+ throw new Error('No embedding model provided');
181
+ }
182
+
183
+ const queryEmbedding = await this.embeddingModel.embedQuery(query);
184
+
185
+ const scored = Array.from(this.documents.entries()).map(([id, doc]) => ({
186
+ id,
187
+ doc,
188
+ score: cosineSimilarity(queryEmbedding, doc.embedding),
189
+ }));
190
+
191
+ scored.sort((a, b) => b.score - a.score);
192
+
193
+ return scored.slice(0, k).map(s => [
194
+ { pageContent: s.doc.pageContent, metadata: s.doc.metadata },
195
+ s.score,
196
+ ]);
197
+ }
198
+
199
+ async delete(ids: string[]): Promise<void> {
200
+ for (const id of ids) {
201
+ this.documents.delete(id);
202
+ }
203
+ }
204
+
205
+ async get(ids: string[]): Promise<Document[]> {
206
+ return ids
207
+ .map(id => this.documents.get(id))
208
+ .filter((d): d is StoredDoc => d !== undefined)
209
+ .map(d => ({ pageContent: d.pageContent, metadata: d.metadata }));
210
+ }
211
+ }
212
+
213
+ // ============================================================================
214
+ // SQLite-Vector Store (using better-sqlite3 if available)
215
+ // ============================================================================
216
+
217
+ export class SQLiteVectorStore {
218
+ private embeddingModel?: BaseEmbeddings;
219
+ private dbPath: string;
220
+ private documents: Map<string, StoredDoc> = new Map();
221
+ private db: any = null;
222
+
223
+ constructor(options: {
224
+ embeddingModel?: BaseEmbeddings;
225
+ dbPath?: string;
226
+ } = {}) {
227
+ this.embeddingModel = options.embeddingModel;
228
+ this.dbPath = options.dbPath || ':memory:';
229
+ logger.info(`[Lazlo] SQLiteVectorStore ready. Path: ${this.dbPath}`);
230
+
231
+ // Initialize in-memory storage (full SQLite-Vector requires native module)
232
+ this.initialize();
233
+ }
234
+
235
+ private initialize(): void {
236
+ // In-memory storage as fallback (actual sqlite-vec requires native module)
237
+ logger.debug('[SQLiteVectorStore] Using in-memory storage (install better-sqlite3 for full SQLite-Vector support)');
238
+ }
239
+
240
+ async addDocuments(documents: Document[]): Promise<string[]> {
241
+ if (!this.embeddingModel) {
242
+ throw new Error('No embedding model provided');
243
+ }
244
+
245
+ const texts = documents.map(d => d.pageContent);
246
+ const embeddings = await this.embeddingModel.embedDocuments(texts);
247
+ const ids: string[] = [];
248
+
249
+ documents.forEach((doc, i) => {
250
+ const id = crypto.randomUUID();
251
+ this.documents.set(id, {
252
+ pageContent: doc.pageContent,
253
+ metadata: doc.metadata,
254
+ embedding: embeddings[i],
255
+ });
256
+ ids.push(id);
257
+ });
258
+
259
+ return ids;
260
+ }
261
+
262
+ async similaritySearch(query: string, k: number = 4): Promise<Document[]> {
263
+ if (!this.embeddingModel) {
264
+ throw new Error('No embedding model provided');
265
+ }
266
+
267
+ const queryEmbedding = await this.embeddingModel.embedQuery(query);
268
+
269
+ const scored = Array.from(this.documents.entries()).map(([id, doc]) => ({
270
+ id,
271
+ doc,
272
+ score: cosineSimilarity(queryEmbedding, doc.embedding),
273
+ }));
274
+
275
+ scored.sort((a, b) => b.score - a.score);
276
+
277
+ return scored.slice(0, k).map(s => ({
278
+ pageContent: s.doc.pageContent,
279
+ metadata: s.doc.metadata,
280
+ }));
281
+ }
282
+
283
+ async similaritySearchWithScore(query: string, k: number = 4): Promise<[Document, number][]> {
284
+ if (!this.embeddingModel) {
285
+ throw new Error('No embedding model provided');
286
+ }
287
+
288
+ const queryEmbedding = await this.embeddingModel.embedQuery(query);
289
+
290
+ const scored = Array.from(this.documents.entries()).map(([id, doc]) => ({
291
+ id,
292
+ doc,
293
+ score: cosineSimilarity(queryEmbedding, doc.embedding),
294
+ }));
295
+
296
+ scored.sort((a, b) => b.score - a.score);
297
+
298
+ return scored.slice(0, k).map(s => [
299
+ { pageContent: s.doc.pageContent, metadata: s.doc.metadata },
300
+ s.score,
301
+ ]);
302
+ }
303
+
304
+ async delete(ids: string[]): Promise<void> {
305
+ for (const id of ids) {
306
+ this.documents.delete(id);
307
+ }
308
+ }
309
+
310
+ async get(ids: string[]): Promise<Document[]> {
311
+ return ids
312
+ .map(id => this.documents.get(id))
313
+ .filter((d): d is StoredDoc => d !== undefined)
314
+ .map(d => ({ pageContent: d.pageContent, metadata: d.metadata }));
315
+ }
316
+ }
317
+
318
+ // ============================================================================
319
+ // Factory
320
+ // ============================================================================
321
+
322
+ export function createVectorStore(embeddingModel?: BaseEmbeddings): InMemoryVectorStore {
323
+ return new InMemoryVectorStore(embeddingModel);
324
+ }
325
+
326
+ export function createChromaVectorStore(options?: {
327
+ embeddingModel?: BaseEmbeddings;
328
+ collectionName?: string;
329
+ }): ChromaVectorStore {
330
+ return new ChromaVectorStore(options);
331
+ }
332
+
333
+ export function createSQLiteVectorStore(options?: {
334
+ embeddingModel?: BaseEmbeddings;
335
+ dbPath?: string;
336
+ }): SQLiteVectorStore {
337
+ return new SQLiteVectorStore(options);
338
+ }