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,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffer Memory - Simple conversation history
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Message } from '../core/types.js';
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Buffer Memory
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export interface BufferMemoryOptions {
|
|
12
|
+
memoryKey?: string;
|
|
13
|
+
returnMessages?: boolean;
|
|
14
|
+
maxTokenLimit?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class BufferMemory {
|
|
18
|
+
private messages: Message[] = [];
|
|
19
|
+
private memoryKey: string;
|
|
20
|
+
private returnMessages: boolean;
|
|
21
|
+
private maxTokenLimit?: number;
|
|
22
|
+
|
|
23
|
+
constructor(options: BufferMemoryOptions = {}) {
|
|
24
|
+
this.memoryKey = options.memoryKey ?? 'history';
|
|
25
|
+
this.returnMessages = options.returnMessages ?? false;
|
|
26
|
+
this.maxTokenLimit = options.maxTokenLimit;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Estimate tokens (rough approximation)
|
|
31
|
+
*/
|
|
32
|
+
private estimateTokens(text: string): number {
|
|
33
|
+
return Math.ceil(text.length / 4);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load memory variables for the chain
|
|
38
|
+
*/
|
|
39
|
+
async loadMemoryVariables(_inputs: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
|
|
40
|
+
// Trim if over token limit
|
|
41
|
+
if (this.maxTokenLimit) {
|
|
42
|
+
let tokenCount = 0;
|
|
43
|
+
const trimmedMessages: Message[] = [];
|
|
44
|
+
|
|
45
|
+
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
46
|
+
const msg = this.messages[i];
|
|
47
|
+
const msgTokens = this.estimateTokens(msg.content);
|
|
48
|
+
|
|
49
|
+
if (tokenCount + msgTokens <= this.maxTokenLimit) {
|
|
50
|
+
trimmedMessages.unshift(msg);
|
|
51
|
+
tokenCount += msgTokens;
|
|
52
|
+
} else {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.messages = trimmedMessages;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (this.returnMessages) {
|
|
61
|
+
return { [this.memoryKey]: [...this.messages] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Return as formatted string
|
|
65
|
+
const history = this.messages
|
|
66
|
+
.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
|
|
67
|
+
.join('\n');
|
|
68
|
+
|
|
69
|
+
return { [this.memoryKey]: history };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Save context to memory
|
|
74
|
+
*/
|
|
75
|
+
async saveContext(inputs: Record<string, unknown>, outputs: Record<string, string>): Promise<void> {
|
|
76
|
+
// Extract input and output content
|
|
77
|
+
const inputContent = Object.values(inputs)[0]?.toString() ?? '';
|
|
78
|
+
const outputContent = Object.values(outputs)[0]?.toString() ?? '';
|
|
79
|
+
|
|
80
|
+
if (inputContent) {
|
|
81
|
+
this.messages.push({ role: 'user', content: inputContent });
|
|
82
|
+
}
|
|
83
|
+
if (outputContent) {
|
|
84
|
+
this.messages.push({ role: 'assistant', content: outputContent });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Clear memory
|
|
90
|
+
*/
|
|
91
|
+
async clear(): Promise<void> {
|
|
92
|
+
this.messages = [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get messages
|
|
97
|
+
*/
|
|
98
|
+
getMessages(): Message[] {
|
|
99
|
+
return [...this.messages];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Token Budget Memory
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
export interface TokenBudgetMemoryOptions {
|
|
108
|
+
summarizerLLM?: any;
|
|
109
|
+
maxTokens?: number;
|
|
110
|
+
summarizeThreshold?: number;
|
|
111
|
+
memoryKey?: string;
|
|
112
|
+
returnMessages?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const SUMMARIZE_PROMPT = `Summarize the following conversation history into 2-3 concise sentences. Preserve key facts, decisions, and context. Be extremely terse.
|
|
116
|
+
|
|
117
|
+
Conversation:
|
|
118
|
+
{history}
|
|
119
|
+
|
|
120
|
+
Summary:`;
|
|
121
|
+
|
|
122
|
+
export class TokenBudgetMemory {
|
|
123
|
+
private messages: Message[] = [];
|
|
124
|
+
private summarizerLLM?: any;
|
|
125
|
+
private maxTokens: number;
|
|
126
|
+
private summarizeThreshold: number;
|
|
127
|
+
private memoryKey: string;
|
|
128
|
+
private returnMessages: boolean;
|
|
129
|
+
private summaryPrefix = '';
|
|
130
|
+
|
|
131
|
+
constructor(options: TokenBudgetMemoryOptions = {}) {
|
|
132
|
+
this.summarizerLLM = options.summarizerLLM;
|
|
133
|
+
this.maxTokens = options.maxTokens ?? 4000;
|
|
134
|
+
this.summarizeThreshold = options.summarizeThreshold ?? 0.8;
|
|
135
|
+
this.memoryKey = options.memoryKey ?? 'history';
|
|
136
|
+
this.returnMessages = options.returnMessages ?? false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private estimateTokens(): number {
|
|
140
|
+
let text = this.messages.map(m => m.content).join('\n');
|
|
141
|
+
if (this.summaryPrefix) {
|
|
142
|
+
text += '\n' + this.summaryPrefix;
|
|
143
|
+
}
|
|
144
|
+
return Math.ceil(text.length / 4);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private async compress(): Promise<void> {
|
|
148
|
+
if (!this.messages.length || !this.summarizerLLM) return;
|
|
149
|
+
|
|
150
|
+
// Keep the second half of messages
|
|
151
|
+
const half = Math.floor(this.messages.length / 2);
|
|
152
|
+
const oldMessages = this.messages.slice(0, half);
|
|
153
|
+
this.messages = this.messages.slice(half);
|
|
154
|
+
|
|
155
|
+
const historyStr = oldMessages
|
|
156
|
+
.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
|
|
157
|
+
.join('\n');
|
|
158
|
+
|
|
159
|
+
const prompt = SUMMARIZE_PROMPT.replace('{history}',
|
|
160
|
+
this.summaryPrefix ? `Previous summary: ${this.summaryPrefix}\n\n${historyStr}` : historyStr
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const response = await this.summarizerLLM.invoke(prompt);
|
|
165
|
+
this.summaryPrefix = typeof response === 'string' ? response : response.content ?? '';
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('[TokenBudgetMemory] Compression failed:', error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async loadMemoryVariables(_inputs: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
|
|
172
|
+
const tokensUsed = this.estimateTokens();
|
|
173
|
+
|
|
174
|
+
if (tokensUsed >= this.maxTokens * this.summarizeThreshold) {
|
|
175
|
+
await this.compress();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (this.returnMessages) {
|
|
179
|
+
const content: Message[] = [];
|
|
180
|
+
if (this.summaryPrefix) {
|
|
181
|
+
content.push({ role: 'system', content: `[Earlier context]: ${this.summaryPrefix}` });
|
|
182
|
+
}
|
|
183
|
+
content.push(...this.messages);
|
|
184
|
+
return { [this.memoryKey]: content };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const parts: string[] = [];
|
|
188
|
+
if (this.summaryPrefix) {
|
|
189
|
+
parts.push(`[Earlier context]: ${this.summaryPrefix}`);
|
|
190
|
+
}
|
|
191
|
+
for (const m of this.messages) {
|
|
192
|
+
parts.push(`${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { [this.memoryKey]: parts.join('\n') };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async saveContext(inputs: Record<string, unknown>, outputs: Record<string, string>): Promise<void> {
|
|
199
|
+
const inputContent = Object.values(inputs)[0]?.toString() ?? '';
|
|
200
|
+
const outputContent = Object.values(outputs)[0]?.toString() ?? '';
|
|
201
|
+
|
|
202
|
+
if (inputContent) {
|
|
203
|
+
this.messages.push({ role: 'user', content: inputContent });
|
|
204
|
+
}
|
|
205
|
+
if (outputContent) {
|
|
206
|
+
this.messages.push({ role: 'assistant', content: outputContent });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async clear(): Promise<void> {
|
|
211
|
+
this.messages = [];
|
|
212
|
+
this.summaryPrefix = '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get tokenUsage(): { estimatedTokens: number; budget: number; messageCount: number } {
|
|
216
|
+
return {
|
|
217
|
+
estimatedTokens: this.estimateTokens(),
|
|
218
|
+
budget: this.maxTokens,
|
|
219
|
+
messageCount: this.messages.length,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Parsers
|
|
3
|
+
*
|
|
4
|
+
* Parse LLM outputs into structured formats
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base Output Parser Interface
|
|
11
|
+
*/
|
|
12
|
+
export interface BaseOutputParser<T> {
|
|
13
|
+
parse(text: string): Promise<T>;
|
|
14
|
+
format?(text: string): string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* JSON Output Parser
|
|
19
|
+
*/
|
|
20
|
+
export class JSONOutputParser<T extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
21
|
+
private schema?: T;
|
|
22
|
+
private schemaName?: string;
|
|
23
|
+
|
|
24
|
+
constructor(schema?: T, schemaName?: string) {
|
|
25
|
+
this.schema = schema;
|
|
26
|
+
this.schemaName = schemaName;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async parse(text: string): Promise<any> {
|
|
30
|
+
// Try to extract JSON from text
|
|
31
|
+
const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/) ||
|
|
32
|
+
text.match(/\{[\s\S]*\}/);
|
|
33
|
+
|
|
34
|
+
if (!jsonMatch) {
|
|
35
|
+
throw new Error('No JSON found in output');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const jsonStr = jsonMatch[1] || jsonMatch[0];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(jsonStr);
|
|
42
|
+
|
|
43
|
+
if (this.schema) {
|
|
44
|
+
return this.schema.parse(parsed);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return parsed;
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
throw new Error(`Failed to parse JSON: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
format(text: string): string {
|
|
54
|
+
return `Output the following as valid JSON:\n${text}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* XML Output Parser
|
|
60
|
+
*/
|
|
61
|
+
export class XMLOutputParser {
|
|
62
|
+
private tags: string[];
|
|
63
|
+
|
|
64
|
+
constructor(tags: string[] = ['response']) {
|
|
65
|
+
this.tags = tags;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async parse(text: string): Promise<Record<string, string>> {
|
|
69
|
+
const result: Record<string, string> = {};
|
|
70
|
+
|
|
71
|
+
for (const tag of this.tags) {
|
|
72
|
+
const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i');
|
|
73
|
+
const match = text.match(regex);
|
|
74
|
+
|
|
75
|
+
if (match) {
|
|
76
|
+
result[tag] = match[1].trim();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
format(text: string): string {
|
|
84
|
+
const tagsStr = this.tags.map(t => `<${t}>...</${t}>`).join('\n');
|
|
85
|
+
return `Output the following as XML:\n${tagsStr}\n\nContent:\n${text}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Comma-Separated List Parser
|
|
91
|
+
*/
|
|
92
|
+
export class CommaSeparatedListParser {
|
|
93
|
+
async parse(text: string): Promise<string[]> {
|
|
94
|
+
return text
|
|
95
|
+
.split(',')
|
|
96
|
+
.map(item => item.trim())
|
|
97
|
+
.filter(item => item.length > 0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Markdown Code Block Parser
|
|
103
|
+
*/
|
|
104
|
+
export class MarkdownCodeBlockParser {
|
|
105
|
+
private language?: string;
|
|
106
|
+
|
|
107
|
+
constructor(language?: string) {
|
|
108
|
+
this.language = language;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async parse(text: string): Promise<string> {
|
|
112
|
+
const regex = this.language
|
|
113
|
+
? new RegExp(`\`\`\`${this.language}\\s*([\\s\\S]*?)\`\`\``)
|
|
114
|
+
: /```(?:\w+)?\s*([\s\S]*?)```/;
|
|
115
|
+
|
|
116
|
+
const match = text.match(regex);
|
|
117
|
+
|
|
118
|
+
if (match) {
|
|
119
|
+
return match[1].trim();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Return original if no code block found
|
|
123
|
+
return text.trim();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parser for tool calls
|
|
129
|
+
*/
|
|
130
|
+
export interface ToolCall {
|
|
131
|
+
name: string;
|
|
132
|
+
arguments: Record<string, any>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class ToolCallParser {
|
|
136
|
+
async parse(text: string): Promise<ToolCall[]> {
|
|
137
|
+
const toolCalls: ToolCall[] = [];
|
|
138
|
+
|
|
139
|
+
// Try JSON format
|
|
140
|
+
try {
|
|
141
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
142
|
+
if (jsonMatch) {
|
|
143
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
144
|
+
if (parsed.name && parsed.arguments) {
|
|
145
|
+
return [parsed as ToolCall];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// Not JSON, try other formats
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Try function call format
|
|
153
|
+
const functionMatch = text.match(/(\w+)\(([\s\S]*?)\)/);
|
|
154
|
+
if (functionMatch) {
|
|
155
|
+
const name = functionMatch[1];
|
|
156
|
+
let args: Record<string, any> = {};
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
args = JSON.parse(`{${functionMatch[2]}}`);
|
|
160
|
+
} catch {
|
|
161
|
+
// Simple key=value parsing
|
|
162
|
+
const argPairs = functionMatch[2].split(',');
|
|
163
|
+
for (const pair of argPairs) {
|
|
164
|
+
const [key, value] = pair.split('=').map(s => s.trim());
|
|
165
|
+
if (key) {
|
|
166
|
+
args[key] = value?.replace(/["']/g, '') || '';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
toolCalls.push({ name, arguments: args });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return toolCalls;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Factory functions
|
|
180
|
+
*/
|
|
181
|
+
export function createJSONParser<T extends z.ZodTypeAny>(schema?: T, name?: string): JSONOutputParser<T> {
|
|
182
|
+
return new JSONOutputParser(schema, name);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function createXMLParser(tags?: string[]): XMLOutputParser {
|
|
186
|
+
return new XMLOutputParser(tags);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function createListParser(): CommaSeparatedListParser {
|
|
190
|
+
return new CommaSeparatedListParser();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function createMarkdownParser(language?: string): MarkdownCodeBlockParser {
|
|
194
|
+
return new MarkdownCodeBlockParser(language);
|
|
195
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Registry
|
|
3
|
+
*
|
|
4
|
+
* Store, version, and load prompts from YAML files
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
8
|
+
import { join, basename } from 'path';
|
|
9
|
+
import { logger } from '../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
export interface PromptConfig {
|
|
12
|
+
template: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
variables?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prompt Registry
|
|
20
|
+
*
|
|
21
|
+
* Load and manage prompts from YAML files
|
|
22
|
+
*/
|
|
23
|
+
export class PromptRegistry {
|
|
24
|
+
private prompts: Map<string, PromptConfig> = new Map();
|
|
25
|
+
private directory?: string;
|
|
26
|
+
|
|
27
|
+
constructor(directory?: string) {
|
|
28
|
+
if (directory) {
|
|
29
|
+
this.directory = directory;
|
|
30
|
+
this.loadPrompts(directory);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load prompts from a directory
|
|
36
|
+
*/
|
|
37
|
+
private loadPrompts(directory: string): void {
|
|
38
|
+
if (!existsSync(directory)) {
|
|
39
|
+
logger.warn(`[PromptRegistry] Directory not found: ${directory}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const files = readdirSync(directory).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
45
|
+
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const content = readFileSync(join(directory, file), 'utf-8');
|
|
48
|
+
const config = this.parseYAML(content);
|
|
49
|
+
const name = file.replace(/\.(yaml|yml)$/, '');
|
|
50
|
+
|
|
51
|
+
this.prompts.set(name, config as PromptConfig);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
logger.info(`[PromptRegistry] Loaded ${this.prompts.size} prompts from ${directory}`);
|
|
55
|
+
} catch (error: any) {
|
|
56
|
+
logger.error(`[PromptRegistry] Error loading prompts: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Simple YAML parser
|
|
62
|
+
*/
|
|
63
|
+
private parseYAML(content: string): any {
|
|
64
|
+
const result: any = {};
|
|
65
|
+
const lines = content.split('\n');
|
|
66
|
+
let currentKey = '';
|
|
67
|
+
let currentValue: string[] = [];
|
|
68
|
+
let inArray = false;
|
|
69
|
+
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
const trimmed = line.trim();
|
|
72
|
+
|
|
73
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
74
|
+
|
|
75
|
+
// Check for key: value
|
|
76
|
+
const keyMatch = trimmed.match(/^(\w+):\s*(.*)$/);
|
|
77
|
+
|
|
78
|
+
if (keyMatch) {
|
|
79
|
+
if (currentKey) {
|
|
80
|
+
result[currentKey] = currentValue.join('\n').trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
currentKey = keyMatch[1];
|
|
84
|
+
const value = keyMatch[2];
|
|
85
|
+
|
|
86
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
87
|
+
// Array
|
|
88
|
+
result[currentKey] = value.slice(1, -1).split(',').map((s: string) => s.trim());
|
|
89
|
+
currentKey = '';
|
|
90
|
+
} else if (value) {
|
|
91
|
+
currentValue = [value];
|
|
92
|
+
} else {
|
|
93
|
+
currentValue = [];
|
|
94
|
+
}
|
|
95
|
+
} else if (trimmed.startsWith('- ')) {
|
|
96
|
+
// Array item
|
|
97
|
+
if (!Array.isArray(result[currentKey])) {
|
|
98
|
+
result[currentKey] = [];
|
|
99
|
+
}
|
|
100
|
+
(result[currentKey] as any[]).push(trimmed.slice(2).trim());
|
|
101
|
+
} else {
|
|
102
|
+
// Continuation
|
|
103
|
+
currentValue.push(trimmed);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (currentKey) {
|
|
108
|
+
result[currentKey] = currentValue.join('\n').trim();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register a prompt
|
|
116
|
+
*/
|
|
117
|
+
register(name: string, config: PromptConfig): void {
|
|
118
|
+
this.prompts.set(name, config);
|
|
119
|
+
logger.debug(`[PromptRegistry] Registered prompt: ${name}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Load a prompt by name (supports @version syntax)
|
|
124
|
+
*/
|
|
125
|
+
load(name: string): string {
|
|
126
|
+
const [baseName, version] = name.split('@');
|
|
127
|
+
const prompt = this.prompts.get(baseName);
|
|
128
|
+
|
|
129
|
+
if (!prompt) {
|
|
130
|
+
throw new Error(`Prompt not found: ${baseName}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (version && prompt.version && prompt.version !== version) {
|
|
134
|
+
throw new Error(`Version ${version} not found for prompt: ${baseName}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return prompt.template;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get prompt config
|
|
142
|
+
*/
|
|
143
|
+
get(name: string): PromptConfig | undefined {
|
|
144
|
+
return this.prompts.get(name);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* List all prompts
|
|
149
|
+
*/
|
|
150
|
+
list(): string[] {
|
|
151
|
+
return Array.from(this.prompts.keys());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Format a prompt with variables
|
|
156
|
+
*/
|
|
157
|
+
format(template: string, variables: Record<string, string>): string {
|
|
158
|
+
let result = template;
|
|
159
|
+
|
|
160
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
161
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Factory function
|
|
170
|
+
*/
|
|
171
|
+
export function createPromptRegistry(directory?: string): PromptRegistry {
|
|
172
|
+
return new PromptRegistry(directory);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Simple Prompt Template (standalone)
|
|
177
|
+
*/
|
|
178
|
+
export class SimplePromptTemplate {
|
|
179
|
+
private template: string;
|
|
180
|
+
private inputVariables: string[];
|
|
181
|
+
|
|
182
|
+
constructor(template: string) {
|
|
183
|
+
this.template = template;
|
|
184
|
+
this.inputVariables = this.extractVariables(template);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private extractVariables(template: string): string[] {
|
|
188
|
+
const matches = template.match(/\{(\w+)\}/g) || [];
|
|
189
|
+
return [...new Set(matches.map(m => m.slice(1, -1)))];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
format(variables: Record<string, string>): string {
|
|
193
|
+
let result = this.template;
|
|
194
|
+
|
|
195
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
196
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
get inputVars(): string[] {
|
|
203
|
+
return this.inputVariables;
|
|
204
|
+
}
|
|
205
|
+
}
|