@hazeljs/prompts 0.2.0-beta.64 → 0.2.0-beta.66

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 (2) hide show
  1. package/README.md +461 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,461 @@
1
+ # @hazeljs/prompts
2
+
3
+ **Centralized, versioned prompt management for HazelJS AI, RAG, and Agent packages.**
4
+
5
+ Define typed prompt templates with named `{variable}` placeholders, store them in a global registry, and swap any prompt at startup — without touching the code that uses them.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@hazeljs/prompts.svg)](https://www.npmjs.com/package/@hazeljs/prompts)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/prompts)](https://www.npmjs.com/package/@hazeljs/prompts)
9
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
10
+
11
+ ## Features
12
+
13
+ - **Typed templates** — `PromptTemplate<{ var1, var2 }>` enforces the variable shape at compile time
14
+ - **Global registry** — `PromptRegistry.register()` at import time; `get()` anywhere, zero overhead
15
+ - **Override any prompt** — swap built-in `@hazeljs/agent` and `@hazeljs/rag` prompts at startup with `override()`
16
+ - **Versioning** — every template carries a `version`; retrieve a specific version with `get(key, version)`
17
+ - **5 storage backends** — Memory, File, Redis, Database (generic adapter), MultiStore (fan-out)
18
+ - **Hot-swap** — update prompts in Redis/DB and reload without restarting the process
19
+ - **Zero runtime dependencies** — the core package has no production dependencies
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @hazeljs/prompts
27
+ ```
28
+
29
+ Optional peer dependencies for store backends:
30
+
31
+ ```bash
32
+ npm install ioredis # RedisStore
33
+ npm install @prisma/client # DatabaseStore with Prisma
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ### 1. Define and register a template
41
+
42
+ ```typescript
43
+ import { PromptTemplate, PromptRegistry } from '@hazeljs/prompts';
44
+
45
+ const answerPrompt = new PromptTemplate<{ context: string; question: string }>(
46
+ `Answer the question using only the context below. Be concise.
47
+
48
+ Context: {context}
49
+
50
+ Question: {question}
51
+
52
+ Answer:`,
53
+ { name: 'RAG Answer', version: '1.0.0' },
54
+ );
55
+
56
+ // Register under a namespaced key — safe to call at module load time
57
+ PromptRegistry.register('myapp:rag:answer', answerPrompt);
58
+ ```
59
+
60
+ ### 2. Render at runtime
61
+
62
+ ```typescript
63
+ import { PromptRegistry } from '@hazeljs/prompts';
64
+
65
+ const tpl = PromptRegistry.get<{ context: string; question: string }>('myapp:rag:answer');
66
+
67
+ const prompt = tpl.render({
68
+ context: 'HazelJS is a TypeScript-first framework for AI-native applications.',
69
+ question: 'What is HazelJS?',
70
+ });
71
+
72
+ // Pass `prompt` to your LLM of choice
73
+ ```
74
+
75
+ ### 3. Override at startup
76
+
77
+ ```typescript
78
+ import { PromptRegistry, PromptTemplate } from '@hazeljs/prompts';
79
+
80
+ // Runs before any agent or RAG pipeline is created
81
+ PromptRegistry.override(
82
+ 'myapp:rag:answer',
83
+ new PromptTemplate<{ context: string; question: string }>(
84
+ `You are a helpful assistant. Use the context to answer.\nContext: {context}\nQ: {question}\nA:`,
85
+ { name: 'Custom Answer', version: '2.0.0' },
86
+ ),
87
+ );
88
+ ```
89
+
90
+ ---
91
+
92
+ ## PromptTemplate
93
+
94
+ `PromptTemplate<TVariables>` is the core primitive. It holds a template string and metadata, and exposes a single `.render()` method.
95
+
96
+ ```typescript
97
+ import { PromptTemplate } from '@hazeljs/prompts';
98
+
99
+ // Typed — TypeScript enforces the variable shape
100
+ const tpl = new PromptTemplate<{ name: string; tier: string }>(
101
+ 'Hello {name}, you are on the {tier} plan.',
102
+ { name: 'Welcome Message', version: '1.0.0' },
103
+ );
104
+
105
+ const text = tpl.render({ name: 'Alice', tier: 'pro' });
106
+ // "Hello Alice, you are on the pro plan."
107
+ ```
108
+
109
+ **Placeholder rules:**
110
+ - Syntax: `{variableName}` (alphanumeric + underscore)
111
+ - Missing variable → placeholder left as-is (`{missing}` stays `{missing}`)
112
+ - Extra variables in `.render()` are silently ignored
113
+
114
+ ### PromptMetadata
115
+
116
+ ```typescript
117
+ interface PromptMetadata {
118
+ name: string; // Human-readable display name
119
+ version?: string; // Semver string — enables get(key, version)
120
+ description?: string; // Optional description
121
+ }
122
+ ```
123
+
124
+ ---
125
+
126
+ ## PromptRegistry
127
+
128
+ A global static class — no instantiation needed. Prompts registered in one module are immediately available across the entire process.
129
+
130
+ ### Key naming convention
131
+
132
+ Use a colon-separated `package:scope:action` scheme to avoid collisions:
133
+
134
+ ```
135
+ rag:graph:entity-extraction
136
+ agent:supervisor:routing
137
+ myapp:checkout:upsell-prompt
138
+ ```
139
+
140
+ ### Sync API
141
+
142
+ ```typescript
143
+ import { PromptRegistry } from '@hazeljs/prompts';
144
+
145
+ // Register (no-op if key already exists — safe for default prompts)
146
+ PromptRegistry.register('myapp:qa:answer', template);
147
+
148
+ // Override (always replaces — use at application startup)
149
+ PromptRegistry.override('myapp:qa:answer', customTemplate);
150
+
151
+ // Get latest version (throws if not registered)
152
+ const tpl = PromptRegistry.get('myapp:qa:answer');
153
+
154
+ // Get a specific version
155
+ const v1 = PromptRegistry.get('myapp:qa:answer', '1.0.0');
156
+
157
+ // Check existence
158
+ PromptRegistry.has('myapp:qa:answer'); // → boolean
159
+ PromptRegistry.has('myapp:qa:answer', '1.0.0'); // → boolean for version
160
+
161
+ // List all registered keys
162
+ PromptRegistry.list(); // → string[]
163
+
164
+ // List all cached versions for a key
165
+ PromptRegistry.versions('myapp:qa:answer'); // → ['1.0.0', '2.0.0']
166
+
167
+ // Remove a prompt (useful in tests)
168
+ PromptRegistry.unregister('myapp:qa:answer');
169
+ PromptRegistry.unregister('myapp:qa:answer', '1.0.0'); // specific version only
170
+
171
+ // Clear all (tests only)
172
+ PromptRegistry.clear();
173
+ ```
174
+
175
+ ### Async Store API
176
+
177
+ Use these when store backends are configured:
178
+
179
+ ```typescript
180
+ // Load from store, falling back through configured stores in order
181
+ const tpl = await PromptRegistry.getAsync('myapp:qa:answer');
182
+ const tplV2 = await PromptRegistry.getAsync('myapp:qa:answer', '2.0.0');
183
+
184
+ // Persist a single prompt to all configured stores
185
+ await PromptRegistry.save('myapp:qa:answer');
186
+
187
+ // Persist all registered prompts
188
+ await PromptRegistry.saveAll();
189
+
190
+ // Load all prompts from the primary store into the cache
191
+ await PromptRegistry.loadAll(); // does not overwrite existing cache entries
192
+ await PromptRegistry.loadAll(true); // overwrite = true
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Store Backends
198
+
199
+ ### MemoryStore
200
+
201
+ In-memory only — useful for testing and explicit in-process prompt libraries:
202
+
203
+ ```typescript
204
+ import { MemoryStore, PromptRegistry } from '@hazeljs/prompts';
205
+
206
+ PromptRegistry.configure([new MemoryStore()]);
207
+ ```
208
+
209
+ ### FileStore
210
+
211
+ Persists prompts to a JSON file on disk:
212
+
213
+ ```typescript
214
+ import { FileStore, PromptRegistry } from '@hazeljs/prompts';
215
+
216
+ PromptRegistry.configure([new FileStore({ filePath: './prompts/library.json' })]);
217
+
218
+ await PromptRegistry.saveAll(); // write to disk
219
+ await PromptRegistry.loadAll(); // read from disk on startup
220
+ ```
221
+
222
+ ### RedisStore
223
+
224
+ Stores prompts in Redis — ideal for multi-instance deployments and hot-swap:
225
+
226
+ ```typescript
227
+ import Redis from 'ioredis';
228
+ import { RedisStore, PromptRegistry } from '@hazeljs/prompts';
229
+
230
+ const redis = new Redis({ host: 'localhost', port: 6379 });
231
+
232
+ PromptRegistry.configure([
233
+ new RedisStore({ client: redis, keyPrefix: 'hazel:prompts:' }),
234
+ ]);
235
+
236
+ // Load on startup
237
+ await PromptRegistry.loadAll();
238
+
239
+ // Hot-swap: update a prompt and push it to Redis without restarting
240
+ PromptRegistry.override('myapp:qa:answer', updatedTemplate);
241
+ await PromptRegistry.save('myapp:qa:answer');
242
+ ```
243
+
244
+ ### DatabaseStore
245
+
246
+ Stores prompts in any relational database via a generic adapter:
247
+
248
+ ```typescript
249
+ import { DatabaseStore, PromptRegistry } from '@hazeljs/prompts';
250
+ import type { DatabaseAdapter, PromptEntry } from '@hazeljs/prompts';
251
+
252
+ // Implement the adapter for your ORM (Prisma example)
253
+ class PrismaPromptAdapter implements DatabaseAdapter {
254
+ constructor(private readonly prisma: PrismaClient) {}
255
+
256
+ async get(key: string, version?: string): Promise<PromptEntry | undefined> {
257
+ const row = await this.prisma.prompt.findFirst({ where: { key } });
258
+ if (!row) return undefined;
259
+ return { key: row.key, template: row.template, metadata: JSON.parse(row.metadata) };
260
+ }
261
+
262
+ async set(entry: PromptEntry): Promise<void> {
263
+ await this.prisma.prompt.upsert({
264
+ where: { key: entry.key },
265
+ create: { key: entry.key, template: entry.template, metadata: JSON.stringify(entry.metadata) },
266
+ update: { template: entry.template, metadata: JSON.stringify(entry.metadata) },
267
+ });
268
+ }
269
+
270
+ async getAll(): Promise<PromptEntry[]> {
271
+ const rows = await this.prisma.prompt.findMany();
272
+ return rows.map(r => ({ key: r.key, template: r.template, metadata: JSON.parse(r.metadata) }));
273
+ }
274
+ }
275
+
276
+ PromptRegistry.configure([
277
+ new DatabaseStore({ adapter: new PrismaPromptAdapter(new PrismaClient()) }),
278
+ ]);
279
+ ```
280
+
281
+ ### MultiStore
282
+
283
+ Fan-out store that writes to all backends simultaneously and reads from the first that has the key. Use for high-availability (Redis primary + file fallback):
284
+
285
+ ```typescript
286
+ import { MultiStore, FileStore, RedisStore, PromptRegistry } from '@hazeljs/prompts';
287
+ import Redis from 'ioredis';
288
+
289
+ PromptRegistry.configure([
290
+ new MultiStore([
291
+ new RedisStore({ client: new Redis(process.env.REDIS_URL) }),
292
+ new FileStore({ filePath: './prompts/fallback.json' }),
293
+ ]),
294
+ ]);
295
+
296
+ await PromptRegistry.saveAll(); // writes to both stores
297
+ ```
298
+
299
+ ### Configuring stores
300
+
301
+ ```typescript
302
+ // Replace all stores at once
303
+ PromptRegistry.configure([storeA, storeB]);
304
+
305
+ // Append without replacing
306
+ PromptRegistry.addStore(storeC);
307
+
308
+ // Inspect configured stores
309
+ PromptRegistry.storeNames(); // → ['RedisStore', 'FileStore']
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Overriding Built-In Package Prompts
315
+
316
+ `@hazeljs/agent` and `@hazeljs/rag` register their internal prompts under predictable keys. Override them at application startup to tune behaviour without forking:
317
+
318
+ ```typescript
319
+ import { PromptRegistry, PromptTemplate } from '@hazeljs/prompts';
320
+
321
+ // Tune the GraphRAG entity extraction prompt for a legal document corpus
322
+ PromptRegistry.override(
323
+ 'rag:graph:entity-extraction',
324
+ new PromptTemplate<{ text: string }>(
325
+ `Extract legal entities (parties, clauses, obligations, dates) from this text.
326
+ Return JSON: { entities: [...], relationships: [...] }
327
+
328
+ Text: {text}`,
329
+ { name: 'Legal Entity Extraction', version: '1.0.0' },
330
+ ),
331
+ );
332
+
333
+ // Customise the supervisor routing prompt used by SupervisorAgent
334
+ PromptRegistry.override(
335
+ 'agent:supervisor:routing',
336
+ new PromptTemplate<{ task: string; workers: string }>(
337
+ `You are a project manager. Decompose the task and assign each subtask to the best worker.
338
+ Workers: {workers}
339
+ Task: {task}
340
+ Respond with JSON: [{ "worker": "...", "subtask": "..." }]`,
341
+ { name: 'Custom Supervisor', version: '2.0.0' },
342
+ ),
343
+ );
344
+ ```
345
+
346
+ ---
347
+
348
+ ## Use Cases
349
+
350
+ ### Prompt A/B testing
351
+
352
+ Register two versions and switch between them without a deploy:
353
+
354
+ ```typescript
355
+ PromptRegistry.register('myapp:qa:answer', promptV1);
356
+
357
+ // Later — override with new version and save to Redis
358
+ PromptRegistry.override('myapp:qa:answer', promptV2);
359
+ await PromptRegistry.save('myapp:qa:answer');
360
+ ```
361
+
362
+ ### Multi-tenant prompts
363
+
364
+ Load tenant-specific prompts from the database at request time:
365
+
366
+ ```typescript
367
+ async function getPromptForTenant(tenantId: string, key: string) {
368
+ const tenantKey = `tenant:${tenantId}:${key}`;
369
+ if (PromptRegistry.has(tenantKey)) return PromptRegistry.get(tenantKey);
370
+ return await PromptRegistry.getAsync(tenantKey); // falls back to DB store
371
+ }
372
+ ```
373
+
374
+ ### Exposing tools via MCP
375
+
376
+ Use registry-driven prompts inside `@Tool()` methods and expose them as MCP tools:
377
+
378
+ ```typescript
379
+ import { PromptRegistry } from '@hazeljs/prompts';
380
+ import { Tool, ToolRegistry } from '@hazeljs/agent';
381
+ import { createMcpServer } from '@hazeljs/mcp';
382
+
383
+ class SupportAgent {
384
+ @Tool({
385
+ description: 'Triage a support issue and return urgency and category.',
386
+ parameters: [
387
+ { name: 'issue', type: 'string', description: 'Issue description', required: true },
388
+ ],
389
+ })
390
+ async triage(input: { issue: string }) {
391
+ const tpl = PromptRegistry.get<{ issue: string }>('support:ticket:triage');
392
+ const prompt = tpl.render(input);
393
+ return await callLLM(prompt);
394
+ }
395
+ }
396
+
397
+ const registry = new ToolRegistry();
398
+ registry.registerAgentTools('support', new SupportAgent());
399
+ createMcpServer({ registry }).listenStdio();
400
+ ```
401
+
402
+ ---
403
+
404
+ ## API Reference
405
+
406
+ ### `PromptTemplate<TVariables>`
407
+
408
+ | Method / Property | Description |
409
+ |---|---|
410
+ | `new PromptTemplate(template, metadata)` | Create a new template |
411
+ | `.template` | Raw template string (read-only) |
412
+ | `.metadata` | `PromptMetadata` object (read-only) |
413
+ | `.render(variables: TVariables)` | Interpolate placeholders and return the rendered string |
414
+
415
+ ### `PromptRegistry` (static)
416
+
417
+ | Method | Description |
418
+ |---|---|
419
+ | `register(key, template)` | Register if key not already set |
420
+ | `override(key, template)` | Always register (overwrites existing) |
421
+ | `get(key, version?)` | Sync get — throws if not found |
422
+ | `has(key, version?)` | Returns `boolean` |
423
+ | `list()` | Returns all registered keys |
424
+ | `versions(key)` | Returns all cached version strings for a key |
425
+ | `unregister(key, version?)` | Remove from cache |
426
+ | `clear()` | Remove all from cache |
427
+ | `configure(stores)` | Replace store list |
428
+ | `addStore(store)` | Append a store |
429
+ | `storeNames()` | Names of configured stores |
430
+ | `getAsync(key, version?)` | Async get — falls back to stores |
431
+ | `save(key, version?)` | Persist one prompt to all stores |
432
+ | `saveAll()` | Persist all prompts to all stores |
433
+ | `loadAll(overwrite?)` | Load all from primary store into cache |
434
+
435
+ ### `PromptStore` interface
436
+
437
+ ```typescript
438
+ interface PromptStore {
439
+ name: string;
440
+ get(key: string, version?: string): Promise<PromptEntry | undefined>;
441
+ set(entry: PromptEntry): Promise<void>;
442
+ getAll(): Promise<PromptEntry[]>;
443
+ }
444
+ ```
445
+
446
+ ---
447
+
448
+ ## License
449
+
450
+ Apache 2.0
451
+
452
+ ## Contributing
453
+
454
+ Contributions are welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
455
+
456
+ ## Links
457
+
458
+ - [Documentation](https://hazeljs.com/docs/packages/prompts)
459
+ - [GitHub](https://github.com/hazeljs/hazeljs)
460
+ - [Issues](https://github.com/hazeljs/hazeljs/issues)
461
+ - [Discord](https://discord.com/channels/1448263814238965833/1448263814859456575)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/prompts",
3
- "version": "0.2.0-beta.64",
3
+ "version": "0.2.0-beta.66",
4
4
  "description": "Typed, overridable prompt templates for HazelJS AI, RAG, and Agent packages",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -70,5 +70,5 @@
70
70
  }
71
71
  }
72
72
  },
73
- "gitHead": "52562169540a50bb37ee4fdc818424e92c2c645b"
73
+ "gitHead": "f5e6d854e9a8e8ddc7e3dc93ec5eb9744aa485e7"
74
74
  }