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.
- package/.env.example +9 -0
- package/README.md +278 -0
- package/dist/cache/semantic.d.ts +39 -0
- package/dist/cache/semantic.d.ts.map +1 -0
- package/dist/cache/semantic.js +134 -0
- package/dist/cache/semantic.js.map +1 -0
- package/dist/chains/llmchain.d.ts +65 -0
- package/dist/chains/llmchain.d.ts.map +1 -0
- package/dist/chains/llmchain.js +137 -0
- package/dist/chains/llmchain.js.map +1 -0
- package/dist/chains/rag.d.ts +23 -0
- package/dist/chains/rag.d.ts.map +1 -0
- package/dist/chains/rag.js +47 -0
- package/dist/chains/rag.js.map +1 -0
- package/dist/core/types.d.ts +130 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -0
- package/dist/document_loaders/index.d.ts +61 -0
- package/dist/document_loaders/index.d.ts.map +1 -0
- package/dist/document_loaders/index.js +183 -0
- package/dist/document_loaders/index.js.map +1 -0
- package/dist/embeddings/google.d.ts +43 -0
- package/dist/embeddings/google.d.ts.map +1 -0
- package/dist/embeddings/google.js +90 -0
- package/dist/embeddings/google.js.map +1 -0
- package/dist/embeddings/local.d.ts +64 -0
- package/dist/embeddings/local.d.ts.map +1 -0
- package/dist/embeddings/local.js +95 -0
- package/dist/embeddings/local.js.map +1 -0
- package/dist/evals/judge.d.ts +22 -0
- package/dist/evals/judge.d.ts.map +1 -0
- package/dist/evals/judge.js +77 -0
- package/dist/evals/judge.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/buffer.d.ts +64 -0
- package/dist/memory/buffer.d.ts.map +1 -0
- package/dist/memory/buffer.js +168 -0
- package/dist/memory/buffer.js.map +1 -0
- package/dist/parsers/output.d.ts +64 -0
- package/dist/parsers/output.d.ts.map +1 -0
- package/dist/parsers/output.js +148 -0
- package/dist/parsers/output.js.map +1 -0
- package/dist/prompts/registry.d.ts +65 -0
- package/dist/prompts/registry.d.ts.map +1 -0
- package/dist/prompts/registry.js +170 -0
- package/dist/prompts/registry.js.map +1 -0
- package/dist/providers/ollama.d.ts +30 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +104 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +46 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +228 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/retrievers/index.d.ts +71 -0
- package/dist/retrievers/index.d.ts.map +1 -0
- package/dist/retrievers/index.js +130 -0
- package/dist/retrievers/index.js.map +1 -0
- package/dist/router/smartrouter.d.ts +36 -0
- package/dist/router/smartrouter.d.ts.map +1 -0
- package/dist/router/smartrouter.js +132 -0
- package/dist/router/smartrouter.js.map +1 -0
- package/dist/text_splitters/index.d.ts +28 -0
- package/dist/text_splitters/index.d.ts.map +1 -0
- package/dist/text_splitters/index.js +109 -0
- package/dist/text_splitters/index.js.map +1 -0
- package/dist/tools/decorator.d.ts +26 -0
- package/dist/tools/decorator.d.ts.map +1 -0
- package/dist/tools/decorator.js +102 -0
- package/dist/tools/decorator.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/keiro.d.ts +20 -0
- package/dist/tools/keiro.d.ts.map +1 -0
- package/dist/tools/keiro.js +67 -0
- package/dist/tools/keiro.js.map +1 -0
- package/dist/tracing/tracer.d.ts +56 -0
- package/dist/tracing/tracer.d.ts.map +1 -0
- package/dist/tracing/tracer.js +125 -0
- package/dist/tracing/tracer.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +50 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pricing.d.ts +31 -0
- package/dist/utils/pricing.d.ts.map +1 -0
- package/dist/utils/pricing.js +108 -0
- package/dist/utils/pricing.js.map +1 -0
- package/dist/vectorstores/index.d.ts +62 -0
- package/dist/vectorstores/index.d.ts.map +1 -0
- package/dist/vectorstores/index.js +244 -0
- package/dist/vectorstores/index.js.map +1 -0
- package/package.json +48 -0
- package/src/cache/semantic.ts +175 -0
- package/src/chains/llmchain.ts +194 -0
- package/src/chains/rag.ts +65 -0
- package/src/core/types.ts +178 -0
- package/src/document_loaders/index.ts +223 -0
- package/src/embeddings/google.ts +119 -0
- package/src/embeddings/local.ts +118 -0
- package/src/evals/judge.ts +99 -0
- package/src/index.ts +121 -0
- package/src/memory/buffer.ts +222 -0
- package/src/parsers/output.ts +195 -0
- package/src/prompts/registry.ts +205 -0
- package/src/providers/ollama.ts +151 -0
- package/src/providers/openai.ts +320 -0
- package/src/retrievers/index.ts +182 -0
- package/src/router/smartrouter.ts +172 -0
- package/src/text_splitters/index.ts +145 -0
- package/src/tools/decorator.ts +145 -0
- package/src/tools/index.ts +7 -0
- package/src/tools/keiro.ts +92 -0
- package/src/tracing/tracer.ts +178 -0
- package/src/utils/logger.ts +62 -0
- package/src/utils/pricing.ts +133 -0
- package/src/vectorstores/index.ts +338 -0
- package/test-full.mjs +552 -0
- package/test.mjs +74 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Cache - Cache LLM responses with semantic similarity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
// Simple in-memory embeddings for cosine similarity
|
|
8
|
+
// In production, you'd use sentence-transformers or an embedding API
|
|
9
|
+
type Embedding = number[];
|
|
10
|
+
|
|
11
|
+
interface CacheEntry {
|
|
12
|
+
prompt: string;
|
|
13
|
+
response: string;
|
|
14
|
+
embedding: Embedding;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Cosine Similarity
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
function cosineSimilarity(v1: Embedding, v2: Embedding): number {
|
|
22
|
+
const dot = v1.reduce((sum, a, i) => sum + a * v2[i], 0);
|
|
23
|
+
const norm1 = Math.sqrt(v1.reduce((sum, a) => sum + a * a, 0));
|
|
24
|
+
const norm2 = Math.sqrt(v2.reduce((sum, b) => sum + b * b, 0));
|
|
25
|
+
|
|
26
|
+
if (norm1 === 0 || norm2 === 0) return 0;
|
|
27
|
+
return dot / (norm1 * norm2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Simple hash-based embedding (for demo - in production use real embeddings)
|
|
31
|
+
// This creates a deterministic "embedding" from the text
|
|
32
|
+
function simpleEmbedding(text: string): Embedding {
|
|
33
|
+
const hash = new Map<string, number>();
|
|
34
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
35
|
+
|
|
36
|
+
// Create a simple bag-of-words embedding
|
|
37
|
+
for (let i = 0; i < 384; i++) {
|
|
38
|
+
hash.set(String(i), 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
words.forEach((word, idx) => {
|
|
42
|
+
const hash1 = (word.charCodeAt(0) * 31 + idx) % 384;
|
|
43
|
+
const hash2 = (word.length * 17 + idx) % 384;
|
|
44
|
+
hash.set(String(hash1), (hash.get(String(hash1)) ?? 0) + 1);
|
|
45
|
+
hash.set(String(hash2), (hash.get(String(hash2)) ?? 0) + 0.5);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Normalize
|
|
49
|
+
const embedding: Embedding = [];
|
|
50
|
+
let norm = 0;
|
|
51
|
+
for (let i = 0; i < 384; i++) {
|
|
52
|
+
const val = hash.get(String(i)) ?? 0;
|
|
53
|
+
embedding.push(val);
|
|
54
|
+
norm += val * val;
|
|
55
|
+
}
|
|
56
|
+
norm = Math.sqrt(norm);
|
|
57
|
+
return embedding.map(v => v / (norm || 1));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Semantic Cache
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
export interface SemanticCacheOptions {
|
|
65
|
+
threshold?: number; // Cosine similarity threshold (default: 0.92)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class SemanticCache {
|
|
69
|
+
private store: CacheEntry[] = [];
|
|
70
|
+
private threshold: number;
|
|
71
|
+
private hits = 0;
|
|
72
|
+
private misses = 0;
|
|
73
|
+
private tokensSaved = 0;
|
|
74
|
+
|
|
75
|
+
constructor(options: SemanticCacheOptions = {}) {
|
|
76
|
+
this.threshold = options.threshold ?? 0.92;
|
|
77
|
+
logger.info(`SemanticCache ready. Threshold: ${this.threshold}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Look up a cached response
|
|
82
|
+
*/
|
|
83
|
+
lookup(prompt: string): string | null {
|
|
84
|
+
if (this.store.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const queryEmbedding = simpleEmbedding(prompt);
|
|
89
|
+
let bestScore = 0;
|
|
90
|
+
let bestResponse: string | null = null;
|
|
91
|
+
|
|
92
|
+
for (const entry of this.store) {
|
|
93
|
+
const score = cosineSimilarity(queryEmbedding, entry.embedding);
|
|
94
|
+
if (score > bestScore) {
|
|
95
|
+
bestScore = score;
|
|
96
|
+
bestResponse = entry.response;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (bestScore >= this.threshold) {
|
|
101
|
+
logger.info(`[SemanticCache] CACHE HIT (similarity=${bestScore.toFixed(3)}). Skipping LLM call.`);
|
|
102
|
+
this.hits++;
|
|
103
|
+
this.tokensSaved += bestResponse!.length / 4;
|
|
104
|
+
return bestResponse;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Store a response in the cache
|
|
112
|
+
*/
|
|
113
|
+
put(prompt: string, response: string): void {
|
|
114
|
+
const embedding = simpleEmbedding(prompt);
|
|
115
|
+
this.store.push({ prompt, response, embedding });
|
|
116
|
+
logger.debug(`[SemanticCache] Stored response for: ${prompt.slice(0, 50)}...`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Wrap an LLM to use caching
|
|
121
|
+
*/
|
|
122
|
+
wrap<T extends { invoke(prompt: string): Promise<any> }>(llm: T): T & { invoke(prompt: string): Promise<any> } {
|
|
123
|
+
const cache = this;
|
|
124
|
+
|
|
125
|
+
const cachedInvoke = async function(prompt: string) {
|
|
126
|
+
const cached = cache.lookup(prompt);
|
|
127
|
+
if (cached !== null) {
|
|
128
|
+
return cached;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
cache.misses++;
|
|
132
|
+
logger.debug('[SemanticCache] CACHE MISS. Calling LLM.');
|
|
133
|
+
|
|
134
|
+
const response = await llm.invoke(prompt);
|
|
135
|
+
const responseStr = typeof response === 'string' ? response : response.content ?? '';
|
|
136
|
+
cache.put(prompt, responseStr);
|
|
137
|
+
|
|
138
|
+
return response;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return Object.assign(cachedInvoke, llm);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Print cache statistics
|
|
146
|
+
*/
|
|
147
|
+
printStats(): void {
|
|
148
|
+
const total = this.hits + this.misses;
|
|
149
|
+
const hitRate = total > 0 ? (this.hits / total * 100).toFixed(1) : '0.0';
|
|
150
|
+
const costSaved = (this.tokensSaved / 1000 * 0.002).toFixed(4);
|
|
151
|
+
|
|
152
|
+
console.log(`
|
|
153
|
+
${'='.repeat(50)}
|
|
154
|
+
Lazlo SemanticCache Stats
|
|
155
|
+
${'='.repeat(50)}
|
|
156
|
+
Total calls : ${total}
|
|
157
|
+
Cache hits : ${this.hits} (${hitRate}%)
|
|
158
|
+
Cache misses: ${this.misses}
|
|
159
|
+
Est. tokens saved : ~${this.tokensSaved.toLocaleString()}
|
|
160
|
+
Est. cost avoided : ~$${costSaved}
|
|
161
|
+
${'='.repeat(50)}
|
|
162
|
+
`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clear the cache
|
|
167
|
+
*/
|
|
168
|
+
clear(): void {
|
|
169
|
+
this.store = [];
|
|
170
|
+
this.hits = 0;
|
|
171
|
+
this.misses = 0;
|
|
172
|
+
this.tokensSaved = 0;
|
|
173
|
+
logger.info('SemanticCache cleared.');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLMChain - Simple prompt chaining
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BaseChatModel, Message, ChainInputs, ChainOutputs, ToolDefinition } from '../core/types.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { getPricing, estimateTokens, calculateCost } from '../utils/pricing.js';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Prompt Template
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export class PromptTemplate {
|
|
14
|
+
private template: string;
|
|
15
|
+
private inputVariables: string[];
|
|
16
|
+
|
|
17
|
+
constructor(template: string) {
|
|
18
|
+
this.template = template;
|
|
19
|
+
// Extract variables from {variable} patterns
|
|
20
|
+
const regex = /\{([^}]+)\}/g;
|
|
21
|
+
const matches = template.match(regex);
|
|
22
|
+
this.inputVariables = matches ? matches.map(m => m.slice(1, -1)) : [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
format(values: Record<string, unknown>): string {
|
|
26
|
+
// Check for missing variables
|
|
27
|
+
const missing = this.inputVariables.filter(v => !(v in values));
|
|
28
|
+
if (missing.length > 0) {
|
|
29
|
+
throw new Error(`Missing required variables: ${missing.join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let result = this.template;
|
|
33
|
+
for (const [key, value] of Object.entries(values)) {
|
|
34
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value));
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get inputVars(): string[] {
|
|
40
|
+
return this.inputVariables;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// LLM Chain
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
export interface LLMChainOptions {
|
|
49
|
+
llm: BaseChatModel;
|
|
50
|
+
prompt: PromptTemplate;
|
|
51
|
+
outputKey?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CostEstimate {
|
|
55
|
+
estimatedTokensIn: number;
|
|
56
|
+
estimatedTokensOut: number;
|
|
57
|
+
estimatedCostUsd: number;
|
|
58
|
+
estimatedLatencyMs: number;
|
|
59
|
+
model: string;
|
|
60
|
+
pricing: { input: number; output: number };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class LLMChain {
|
|
64
|
+
private llm: BaseChatModel;
|
|
65
|
+
private prompt: PromptTemplate;
|
|
66
|
+
private outputKey: string;
|
|
67
|
+
|
|
68
|
+
constructor(options: LLMChainOptions) {
|
|
69
|
+
this.llm = options.llm;
|
|
70
|
+
this.prompt = options.prompt;
|
|
71
|
+
this.outputKey = options.outputKey ?? 'text';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Estimate cost before running
|
|
76
|
+
*/
|
|
77
|
+
estimateCost(inputs: ChainInputs, model?: string): CostEstimate {
|
|
78
|
+
const modelName = model ?? (this.llm as any).defaultModel ?? 'default';
|
|
79
|
+
const formattedPrompt = this.prompt.format(inputs);
|
|
80
|
+
const estimatedTokensIn = estimateTokens(formattedPrompt);
|
|
81
|
+
const estimatedTokensOut = Math.max(1, Math.floor(estimatedTokensIn / 4));
|
|
82
|
+
|
|
83
|
+
const pricing = getPricing(modelName);
|
|
84
|
+
const estimatedCost =
|
|
85
|
+
(estimatedTokensIn / 1_000_000 * pricing.input) +
|
|
86
|
+
(estimatedTokensOut / 1_000_000 * pricing.output);
|
|
87
|
+
|
|
88
|
+
// Rough latency: 100ms per 1K input + 50ms per 1K output
|
|
89
|
+
const estimatedLatency = Math.floor(estimatedTokensIn * 0.1 + estimatedTokensOut * 0.05);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
estimatedTokensIn,
|
|
93
|
+
estimatedTokensOut,
|
|
94
|
+
estimatedCostUsd: Math.round(estimatedCost * 1_000_000) / 1_000_000,
|
|
95
|
+
estimatedLatencyMs: estimatedLatency,
|
|
96
|
+
model: modelName,
|
|
97
|
+
pricing,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Invoke the chain synchronously
|
|
103
|
+
*/
|
|
104
|
+
async invoke(inputs: ChainInputs): Promise<ChainOutputs> {
|
|
105
|
+
const formattedPrompt = this.prompt.format(inputs);
|
|
106
|
+
logger.debug(`[Chain] Prompt: ${formattedPrompt.slice(0, 100)}...`);
|
|
107
|
+
|
|
108
|
+
const messages: Message[] = [{ role: 'user', content: formattedPrompt }];
|
|
109
|
+
const response = await this.llm.invoke(messages);
|
|
110
|
+
|
|
111
|
+
const outputs: ChainOutputs = {
|
|
112
|
+
[this.outputKey]: response.content,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Include usage info if available
|
|
116
|
+
if (response.usage) {
|
|
117
|
+
(outputs as any).usage = response.usage;
|
|
118
|
+
(outputs as any).cost = calculateCost(
|
|
119
|
+
response.model ?? 'default',
|
|
120
|
+
response.usage.prompt_tokens,
|
|
121
|
+
response.usage.completion_tokens
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return outputs;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Stream the response
|
|
130
|
+
*/
|
|
131
|
+
async *stream(inputs: ChainInputs): AsyncGenerator<string> {
|
|
132
|
+
const formattedPrompt = this.prompt.format(inputs);
|
|
133
|
+
logger.debug(`[Chain Stream] Prompt: ${formattedPrompt.slice(0, 100)}...`);
|
|
134
|
+
|
|
135
|
+
const messages: Message[] = [{ role: 'user', content: formattedPrompt }];
|
|
136
|
+
|
|
137
|
+
for await (const chunk of this.llm.stream?.(messages) ?? []) {
|
|
138
|
+
yield chunk.delta;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Async invoke
|
|
144
|
+
*/
|
|
145
|
+
async ainvoke(inputs: ChainInputs): Promise<ChainOutputs> {
|
|
146
|
+
return this.invoke(inputs);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Async stream
|
|
151
|
+
*/
|
|
152
|
+
async *astream(inputs: ChainInputs): AsyncGenerator<string> {
|
|
153
|
+
yield* this.stream(inputs);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Sequential Chain
|
|
159
|
+
// ============================================================================
|
|
160
|
+
|
|
161
|
+
export class SequentialChain {
|
|
162
|
+
private chains: LLMChain[];
|
|
163
|
+
private inputVariables: string[];
|
|
164
|
+
private outputVariables: string[];
|
|
165
|
+
|
|
166
|
+
constructor(options: {
|
|
167
|
+
chains: LLMChain[];
|
|
168
|
+
inputVariables: string[];
|
|
169
|
+
outputVariables: string[];
|
|
170
|
+
}) {
|
|
171
|
+
this.chains = options.chains;
|
|
172
|
+
this.inputVariables = options.inputVariables;
|
|
173
|
+
this.outputVariables = options.outputVariables;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async invoke(inputs: ChainInputs): Promise<ChainOutputs> {
|
|
177
|
+
let currentState = { ...inputs };
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < this.chains.length; i++) {
|
|
180
|
+
logger.debug(`[SequentialChain] Executing Chain ${i + 1}/${this.chains.length}`);
|
|
181
|
+
const chainOutput = await this.chains[i].invoke(currentState);
|
|
182
|
+
currentState = { ...currentState, ...chainOutput };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Return only the requested output variables
|
|
186
|
+
const result: ChainOutputs = {};
|
|
187
|
+
for (const key of this.outputVariables) {
|
|
188
|
+
if (key in currentState) {
|
|
189
|
+
result[key] = currentState[key];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG Chain - Retrieval-Augmented Generation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BaseChatModel, BaseRetriever, Document } from '../core/types.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_PROMPT = `You are a helpful assistant. Use the following context to answer the query.
|
|
9
|
+
|
|
10
|
+
Context:
|
|
11
|
+
{context}
|
|
12
|
+
|
|
13
|
+
Query: {query}
|
|
14
|
+
|
|
15
|
+
Answer:`;
|
|
16
|
+
|
|
17
|
+
export class RetrievalQAChain {
|
|
18
|
+
private llm: BaseChatModel;
|
|
19
|
+
private retriever: BaseRetriever;
|
|
20
|
+
private promptTemplate: string;
|
|
21
|
+
|
|
22
|
+
constructor(options: {
|
|
23
|
+
llm: BaseChatModel;
|
|
24
|
+
retriever: BaseRetriever;
|
|
25
|
+
promptTemplate?: string;
|
|
26
|
+
}) {
|
|
27
|
+
this.llm = options.llm;
|
|
28
|
+
this.retriever = options.retriever;
|
|
29
|
+
this.promptTemplate = options.promptTemplate ?? DEFAULT_PROMPT;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async invoke(query: string): Promise<{ text: string; documents: Document[] }> {
|
|
33
|
+
const docs = await this.retriever.getRelevantDocuments(query);
|
|
34
|
+
|
|
35
|
+
// Combine documents into context
|
|
36
|
+
const contextParts = docs.map(doc => doc.pageContent);
|
|
37
|
+
const context = contextParts.join('\n\n---\n\n');
|
|
38
|
+
|
|
39
|
+
// Format prompt
|
|
40
|
+
const prompt = this.promptTemplate
|
|
41
|
+
.replace('{context}', context)
|
|
42
|
+
.replace('{query}', query);
|
|
43
|
+
|
|
44
|
+
logger.debug(`[RetrievalQAChain] Retrieved ${docs.length} documents. Invoking LLM.`);
|
|
45
|
+
|
|
46
|
+
const response = await this.llm.invoke([
|
|
47
|
+
{ role: 'user', content: prompt }
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
text: response.content,
|
|
52
|
+
documents: docs,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Factory function for RAG chain
|
|
59
|
+
*/
|
|
60
|
+
export function createRetrievalChain(
|
|
61
|
+
llm: BaseChatModel,
|
|
62
|
+
retriever: BaseRetriever
|
|
63
|
+
): RetrievalQAChain {
|
|
64
|
+
return new RetrievalQAChain({ llm, retriever });
|
|
65
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazlo Core Types & Protocols
|
|
3
|
+
*
|
|
4
|
+
* The foundation of Lazlo - protocol definitions that any LLM, tool, or memory
|
|
5
|
+
* implementation must satisfy. No massive base classes, just duck typing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Message Types
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export interface Message {
|
|
13
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
14
|
+
content: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
tool_call_id?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ToolCall {
|
|
20
|
+
id: string;
|
|
21
|
+
type: 'function';
|
|
22
|
+
function: {
|
|
23
|
+
name: string;
|
|
24
|
+
arguments: string | Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ChatResponse {
|
|
29
|
+
content: string;
|
|
30
|
+
tool_calls?: ToolCall[];
|
|
31
|
+
usage?: {
|
|
32
|
+
prompt_tokens: number;
|
|
33
|
+
completion_tokens: number;
|
|
34
|
+
total_tokens: number;
|
|
35
|
+
};
|
|
36
|
+
model?: string;
|
|
37
|
+
finish_reason?: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface StreamChunk {
|
|
41
|
+
delta: string;
|
|
42
|
+
finish_reason?: string;
|
|
43
|
+
index?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Protocol Interfaces (Duck Typing Support)
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
export interface BaseLLM {
|
|
51
|
+
invoke(prompt: string, options?: LLMInvokeOptions): Promise<string>;
|
|
52
|
+
ainvoke(prompt: string, options?: LLMInvokeOptions): Promise<string>;
|
|
53
|
+
stream?(prompt: string, options?: LLMInvokeOptions): AsyncGenerator<string>;
|
|
54
|
+
astream?(prompt: string, options?: LLMInvokeOptions): AsyncGenerator<string>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BaseChatModel {
|
|
58
|
+
readonly supportsTools: boolean;
|
|
59
|
+
readonly supportsStructuredOutput: boolean;
|
|
60
|
+
|
|
61
|
+
invoke(messages: Message[], options?: ChatInvokeOptions): Promise<ChatResponse>;
|
|
62
|
+
ainvoke?(messages: Message[], options?: ChatInvokeOptions): Promise<ChatResponse>;
|
|
63
|
+
|
|
64
|
+
stream?(messages: Message[], options?: ChatInvokeOptions): AsyncGenerator<StreamChunk>;
|
|
65
|
+
astream?(messages: Message[], options?: ChatInvokeOptions): AsyncGenerator<StreamChunk>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BaseTool {
|
|
69
|
+
name: string;
|
|
70
|
+
description: string;
|
|
71
|
+
argsSchema?: Record<string, unknown>;
|
|
72
|
+
|
|
73
|
+
invoke(input: string | Record<string, unknown>): Promise<string>;
|
|
74
|
+
ainvoke?(input: string | Record<string, unknown>): Promise<string>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface BaseMemory {
|
|
78
|
+
loadMemoryVariables(inputs: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
79
|
+
saveContext(inputs: Record<string, unknown>, outputs: Record<string, string>): Promise<void>;
|
|
80
|
+
clear(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BaseRetriever {
|
|
84
|
+
getRelevantDocuments(query: string): Promise<Document[]>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface BaseVectorStore {
|
|
88
|
+
addTexts(texts: string[], metadatas?: Record<string, unknown>[]): Promise<string[]>;
|
|
89
|
+
similaritySearch(query: string, k?: number): Promise<Document[]>;
|
|
90
|
+
addDocuments?(documents: Document[]): Promise<string[]>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface BaseEmbeddings {
|
|
94
|
+
embedQuery(text: string): Promise<number[]>;
|
|
95
|
+
embedDocuments(texts: string[]): Promise<number[][]>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Document Types
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
export interface Document {
|
|
103
|
+
pageContent: string;
|
|
104
|
+
metadata: Record<string, unknown>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Options Types
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
export interface LLMInvokeOptions {
|
|
112
|
+
model?: string;
|
|
113
|
+
temperature?: number;
|
|
114
|
+
maxTokens?: number;
|
|
115
|
+
topP?: number;
|
|
116
|
+
frequencyPenalty?: number;
|
|
117
|
+
presencePenalty?: number;
|
|
118
|
+
stop?: string | string[];
|
|
119
|
+
[key: string]: unknown;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ChatInvokeOptions extends LLMInvokeOptions {
|
|
123
|
+
tools?: ToolDefinition[];
|
|
124
|
+
responseFormat?: ResponseFormat;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ToolDefinition {
|
|
128
|
+
type: 'function';
|
|
129
|
+
function: {
|
|
130
|
+
name: string;
|
|
131
|
+
description: string;
|
|
132
|
+
parameters: Record<string, unknown>;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface ResponseFormat {
|
|
137
|
+
type: 'text' | 'json_object' | 'json_schema';
|
|
138
|
+
json_schema?: {
|
|
139
|
+
name: string;
|
|
140
|
+
schema: Record<string, unknown>;
|
|
141
|
+
strict?: boolean;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Chain Types
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
export interface ChainInputs {
|
|
150
|
+
[key: string]: unknown;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ChainOutputs {
|
|
154
|
+
[key: string]: unknown;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface Chain {
|
|
158
|
+
invoke(inputs: ChainInputs): Promise<ChainOutputs>;
|
|
159
|
+
ainvoke?(inputs: ChainInputs): Promise<ChainOutputs>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Pricing Types
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
export interface ModelPricing {
|
|
167
|
+
input: number; // per 1M tokens
|
|
168
|
+
output: number; // per 1M tokens
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Cached Types
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
176
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
177
|
+
export interface JsonObject { [key: string]: JsonValue; }
|
|
178
|
+
export interface JsonArray extends Array<JsonValue> {}
|