genai-lite 0.2.0 → 0.3.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 (59) hide show
  1. package/README.md +508 -30
  2. package/dist/config/presets.json +121 -17
  3. package/dist/index.d.ts +3 -3
  4. package/dist/index.js +4 -3
  5. package/dist/llm/LLMService.createMessages.test.d.ts +4 -0
  6. package/dist/llm/LLMService.createMessages.test.js +364 -0
  7. package/dist/llm/LLMService.d.ts +49 -47
  8. package/dist/llm/LLMService.js +208 -303
  9. package/dist/llm/LLMService.original.d.ts +147 -0
  10. package/dist/llm/LLMService.original.js +656 -0
  11. package/dist/llm/LLMService.prepareMessage.test.d.ts +1 -0
  12. package/dist/llm/LLMService.prepareMessage.test.js +303 -0
  13. package/dist/llm/LLMService.sendMessage.preset.test.d.ts +1 -0
  14. package/dist/llm/LLMService.sendMessage.preset.test.js +153 -0
  15. package/dist/llm/LLMService.test.js +275 -0
  16. package/dist/llm/clients/AnthropicClientAdapter.js +64 -10
  17. package/dist/llm/clients/AnthropicClientAdapter.test.js +11 -1
  18. package/dist/llm/clients/GeminiClientAdapter.js +70 -11
  19. package/dist/llm/clients/GeminiClientAdapter.test.js +125 -1
  20. package/dist/llm/clients/MockClientAdapter.js +9 -3
  21. package/dist/llm/clients/MockClientAdapter.test.js +11 -1
  22. package/dist/llm/clients/OpenAIClientAdapter.js +26 -10
  23. package/dist/llm/clients/OpenAIClientAdapter.test.js +11 -1
  24. package/dist/llm/config.js +117 -2
  25. package/dist/llm/config.test.js +17 -0
  26. package/dist/llm/services/AdapterRegistry.d.ts +59 -0
  27. package/dist/llm/services/AdapterRegistry.js +113 -0
  28. package/dist/llm/services/AdapterRegistry.test.d.ts +1 -0
  29. package/dist/llm/services/AdapterRegistry.test.js +239 -0
  30. package/dist/llm/services/ModelResolver.d.ts +35 -0
  31. package/dist/llm/services/ModelResolver.js +116 -0
  32. package/dist/llm/services/ModelResolver.test.d.ts +1 -0
  33. package/dist/llm/services/ModelResolver.test.js +158 -0
  34. package/dist/llm/services/PresetManager.d.ts +27 -0
  35. package/dist/llm/services/PresetManager.js +50 -0
  36. package/dist/llm/services/PresetManager.test.d.ts +1 -0
  37. package/dist/llm/services/PresetManager.test.js +210 -0
  38. package/dist/llm/services/RequestValidator.d.ts +31 -0
  39. package/dist/llm/services/RequestValidator.js +122 -0
  40. package/dist/llm/services/RequestValidator.test.d.ts +1 -0
  41. package/dist/llm/services/RequestValidator.test.js +159 -0
  42. package/dist/llm/services/SettingsManager.d.ts +32 -0
  43. package/dist/llm/services/SettingsManager.js +223 -0
  44. package/dist/llm/services/SettingsManager.test.d.ts +1 -0
  45. package/dist/llm/services/SettingsManager.test.js +266 -0
  46. package/dist/llm/types.d.ts +107 -0
  47. package/dist/prompting/builder.d.ts +4 -0
  48. package/dist/prompting/builder.js +12 -61
  49. package/dist/prompting/content.js +3 -9
  50. package/dist/prompting/index.d.ts +2 -3
  51. package/dist/prompting/index.js +4 -5
  52. package/dist/prompting/parser.d.ts +80 -0
  53. package/dist/prompting/parser.js +133 -0
  54. package/dist/prompting/parser.test.js +348 -0
  55. package/dist/prompting/template.d.ts +8 -0
  56. package/dist/prompting/template.js +89 -6
  57. package/dist/prompting/template.test.js +116 -0
  58. package/package.json +3 -2
  59. package/src/config/presets.json +122 -17
package/README.md CHANGED
@@ -27,7 +27,7 @@ import { LLMService, fromEnvironment } from 'genai-lite';
27
27
  // Create service with environment variable API key provider
28
28
  const llmService = new LLMService(fromEnvironment);
29
29
 
30
- // Send a message to OpenAI
30
+ // Option 1: Direct message sending
31
31
  const response = await llmService.sendMessage({
32
32
  providerId: 'openai',
33
33
  modelId: 'gpt-4.1-mini',
@@ -37,6 +37,19 @@ const response = await llmService.sendMessage({
37
37
  ]
38
38
  });
39
39
 
40
+ // Option 2: Create messages from template (recommended for complex prompts)
41
+ const { messages } = await llmService.createMessages({
42
+ template: '<SYSTEM>You are a helpful assistant.</SYSTEM><USER>Hello, how are you?</USER>',
43
+ providerId: 'openai',
44
+ modelId: 'gpt-4.1-mini'
45
+ });
46
+
47
+ const response2 = await llmService.sendMessage({
48
+ providerId: 'openai',
49
+ modelId: 'gpt-4.1-mini',
50
+ messages
51
+ });
52
+
40
53
  if (response.object === 'chat.completion') {
41
54
  console.log(response.choices[0].message.content);
42
55
  } else {
@@ -111,6 +124,16 @@ const llmService = new LLMService(myKeyProvider);
111
124
  - `codestral-2501` - Specialized for code generation
112
125
  - `devstral-small-2505` - Compact development-focused model
113
126
 
127
+ ### Models with Reasoning Support
128
+
129
+ Some models include advanced reasoning/thinking capabilities that enhance their problem-solving abilities:
130
+
131
+ - **Anthropic**: Claude Sonnet 4, Claude Opus 4, Claude 3.7 Sonnet
132
+ - **Google Gemini**: Gemini 2.5 Pro (always on), Gemini 2.5 Flash, Gemini 2.5 Flash-Lite Preview
133
+ - **OpenAI**: o4-mini (always on)
134
+
135
+ See the [Reasoning Mode](#reasoning-mode) section for usage details.
136
+
114
137
  ## Advanced Usage
115
138
 
116
139
  ### Custom Settings
@@ -129,6 +152,163 @@ const response = await llmService.sendMessage({
129
152
  });
130
153
  ```
131
154
 
155
+ ### Reasoning Mode
156
+
157
+ Enable advanced reasoning capabilities for supported models to get step-by-step thinking and improved problem-solving:
158
+
159
+ ```typescript
160
+ // Enable reasoning with automatic token budget
161
+ const response = await llmService.sendMessage({
162
+ providerId: 'gemini',
163
+ modelId: 'gemini-2.5-flash',
164
+ messages: [{ role: 'user', content: 'Solve this step by step: If a train travels 120km in 2 hours, what is its speed in m/s?' }],
165
+ settings: {
166
+ reasoning: {
167
+ enabled: true // Let the model decide how much thinking to do
168
+ }
169
+ }
170
+ });
171
+
172
+ // Use effort levels for quick control
173
+ const response = await llmService.sendMessage({
174
+ providerId: 'anthropic',
175
+ modelId: 'claude-3-7-sonnet-20250219',
176
+ messages: [{ role: 'user', content: 'Analyze this complex problem...' }],
177
+ settings: {
178
+ reasoning: {
179
+ enabled: true,
180
+ effort: 'high' // 'low', 'medium', or 'high'
181
+ }
182
+ }
183
+ });
184
+
185
+ // Set specific token budget for reasoning
186
+ const response = await llmService.sendMessage({
187
+ providerId: 'gemini',
188
+ modelId: 'gemini-2.5-flash-lite-preview-06-17',
189
+ messages: [{ role: 'user', content: 'What is the square root of 144?' }],
190
+ settings: {
191
+ reasoning: {
192
+ enabled: true,
193
+ maxTokens: 5000 // Specific token budget for reasoning
194
+ }
195
+ }
196
+ });
197
+
198
+ // Access reasoning output (if available)
199
+ if (response.object === 'chat.completion' && response.choices[0].reasoning) {
200
+ console.log('Model reasoning:', response.choices[0].reasoning);
201
+ console.log('Final answer:', response.choices[0].message.content);
202
+ }
203
+ ```
204
+
205
+ **Reasoning Options:**
206
+ - `enabled`: Turn reasoning on/off (some models like o4-mini and Gemini 2.5 Pro have it always on)
207
+ - `effort`: Quick presets - 'low' (20% budget), 'medium' (50%), 'high' (80%)
208
+ - `maxTokens`: Specific token budget for reasoning
209
+ - `exclude`: Set to `true` to enable reasoning but exclude it from the response
210
+
211
+ **Important Notes:**
212
+ - Reasoning tokens are billed separately and may cost more
213
+ - Some models (o4-mini, Gemini 2.5 Pro) cannot disable reasoning
214
+ - Not all models support reasoning - check the [supported models](#models-with-reasoning-support) list
215
+ - The `reasoning` field in the response contains the model's thought process (when available)
216
+
217
+ ### Automatic Thinking Extraction
218
+
219
+ genai-lite can capture reasoning from any model by automatically extracting content wrapped in XML tags. When models output their thinking process in tags like `<thinking>`, the library automatically moves this content to the standardized `reasoning` field. This works with all models, providing a consistent interface for accessing model reasoning:
220
+
221
+ ```typescript
222
+ // Prompt the model to think step-by-step in a <thinking> tag
223
+ const response = await llmService.sendMessage({
224
+ providerId: 'openai',
225
+ modelId: 'gpt-4.1',
226
+ messages: [{
227
+ role: 'system',
228
+ content: 'When solving problems, first write your reasoning inside <thinking> tags, then provide the answer.'
229
+ }, {
230
+ role: 'user',
231
+ content: 'Please think through this problem step by step before answering: What is 15% of 240?'
232
+ }],
233
+ settings: {
234
+ thinkingExtraction: { enabled: true } // Must explicitly enable
235
+ }
236
+ });
237
+
238
+ // If the model responds with:
239
+ // "<thinking>15% means 15/100 = 0.15. So 15% of 240 = 0.15 × 240 = 36.</thinking>The answer is 36."
240
+ //
241
+ // The response will have:
242
+ // - response.choices[0].message.content = "The answer is 36."
243
+ // - response.choices[0].reasoning = "<!-- Extracted by genai-lite from <thinking> tag -->\n15% means 15/100 = 0.15. So 15% of 240 = 0.15 × 240 = 36."
244
+
245
+ // If the model doesn't include the <thinking> tag, you'll get an error (with default 'auto' mode)
246
+ ```
247
+
248
+ **Configuration Options:**
249
+
250
+ ```typescript
251
+ const response = await llmService.sendMessage({
252
+ providerId: 'anthropic',
253
+ modelId: 'claude-3-5-haiku-20241022',
254
+ messages: [{ role: 'user', content: 'Solve this step by step...' }],
255
+ settings: {
256
+ thinkingExtraction: {
257
+ enabled: true, // Must explicitly enable (default: false)
258
+ tag: 'scratchpad', // Custom tag name (default: 'thinking')
259
+ onMissing: 'auto' // Smart enforcement (see below)
260
+ }
261
+ }
262
+ });
263
+ ```
264
+
265
+ **The `onMissing` Property:**
266
+
267
+ The `onMissing` property controls what happens when the expected thinking tag is not found:
268
+
269
+ - `'ignore'`: Silently continue without the tag
270
+ - `'warn'`: Log a warning but continue processing
271
+ - `'error'`: Return an error response
272
+ - `'auto'` (default): Intelligently decide based on the model's native reasoning capabilities
273
+
274
+ **How `'auto'` Mode Works:**
275
+
276
+ ```typescript
277
+ // With non-native reasoning models (e.g., GPT-4)
278
+ const response = await llmService.sendMessage({
279
+ providerId: 'openai',
280
+ modelId: 'gpt-4.1',
281
+ messages: [{
282
+ role: 'system',
283
+ content: 'Always think in <thinking> tags before answering.'
284
+ }, {
285
+ role: 'user',
286
+ content: 'What is 15% of 240?'
287
+ }],
288
+ settings: {
289
+ thinkingExtraction: { enabled: true } // onMissing: 'auto' is default
290
+ }
291
+ });
292
+ // Result: ERROR if <thinking> tag is missing (strict enforcement)
293
+
294
+ // With native reasoning models (e.g., Claude with reasoning enabled)
295
+ const response = await llmService.sendMessage({
296
+ providerId: 'anthropic',
297
+ modelId: 'claude-3-7-sonnet-20250219',
298
+ messages: [/* same prompt */],
299
+ settings: {
300
+ reasoning: { enabled: true },
301
+ thinkingExtraction: { enabled: true }
302
+ }
303
+ });
304
+ // Result: SUCCESS even if <thinking> tag is missing (lenient for native reasoning)
305
+ ```
306
+
307
+ This intelligent enforcement ensures that:
308
+ - Non-native models are held to strict requirements when instructed to use thinking tags
309
+ - Native reasoning models aren't penalized for using their built-in reasoning instead of tags
310
+ - The same prompt can work across different model types
311
+
132
312
  ### Provider Information
133
313
 
134
314
  ```typescript
@@ -144,22 +324,27 @@ const presets = llmService.getPresets();
144
324
 
145
325
  ### Model Presets
146
326
 
147
- genai-lite includes a built-in set of model presets for common use cases. You can use these defaults, extend them with your own, or replace them entirely.
327
+ genai-lite includes a comprehensive set of model presets for common use cases. You can use these defaults, extend them with your own, or replace them entirely.
148
328
 
149
329
  #### Using Default Presets
150
330
 
331
+ The library ships with over 20 pre-configured presets (defined in `src/config/presets.json`), including specialized "thinking" presets for models with reasoning capabilities:
332
+
151
333
  ```typescript
152
334
  const llmService = new LLMService(fromEnvironment);
153
335
 
154
336
  // Get all default presets
155
337
  const presets = llmService.getPresets();
156
338
  // Returns presets like:
157
- // - anthropic-claude-3-5-sonnet-20241022-default
339
+ // - anthropic-claude-sonnet-4-20250514-default
340
+ // - anthropic-claude-sonnet-4-20250514-thinking (reasoning enabled)
158
341
  // - openai-gpt-4.1-default
159
- // - google-gemini-2.5-pro
160
- // ... and more
342
+ // - google-gemini-2.5-flash-thinking (reasoning enabled)
343
+ // ... and many more
161
344
  ```
162
345
 
346
+ The thinking presets automatically enable reasoning mode for supported models, making it easy to leverage advanced problem-solving capabilities without manual configuration.
347
+
163
348
  #### Extending Default Presets
164
349
 
165
350
  ```typescript
@@ -213,6 +398,243 @@ const llmService = new LLMService(fromEnvironment, {
213
398
  });
214
399
  ```
215
400
 
401
+ ### Using Presets with Messages
402
+
403
+ You can use presets directly in `sendMessage` calls:
404
+
405
+ ```typescript
406
+ // Send a message using a preset
407
+ const response = await llmService.sendMessage({
408
+ presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking',
409
+ messages: [{ role: 'user', content: 'Solve this complex problem...' }]
410
+ });
411
+
412
+ // Override preset settings
413
+ const response = await llmService.sendMessage({
414
+ presetId: 'openai-gpt-4.1-default',
415
+ messages: [{ role: 'user', content: 'Write a story' }],
416
+ settings: {
417
+ temperature: 0.9, // Override preset's temperature
418
+ maxTokens: 3000
419
+ }
420
+ });
421
+ ```
422
+
423
+ ### Creating Messages from Templates
424
+
425
+ The library provides a powerful `createMessages` method that combines template rendering, model context injection, and role tag parsing into a single, intuitive API:
426
+
427
+ ```typescript
428
+ // Basic example: Create model-aware messages
429
+ const { messages, modelContext } = await llmService.createMessages({
430
+ template: `
431
+ <SYSTEM>
432
+ You are a {{ thinking_enabled ? "thoughtful" : "helpful" }} assistant.
433
+ {{ thinking_available && !thinking_enabled ? "Note: Reasoning mode is available for complex problems." : "" }}
434
+ </SYSTEM>
435
+ <USER>{{ question }}</USER>
436
+ `,
437
+ variables: {
438
+ question: 'What is the optimal algorithm for finding the shortest path in a weighted graph?'
439
+ },
440
+ presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
441
+ });
442
+
443
+ // The messages are ready to send
444
+ const response = await llmService.sendMessage({
445
+ presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking',
446
+ messages: messages
447
+ });
448
+
449
+ // Advanced example: Conditional context and multi-turn conversation
450
+ const { messages } = await llmService.createMessages({
451
+ template: `
452
+ <SYSTEM>You are an expert code reviewer.</SYSTEM>
453
+ {{ hasContext ? '<USER>Context: {{context}}</USER>' : '' }}
454
+ <USER>Review this code:
455
+ ```{{language}}
456
+ {{code}}
457
+ ```</USER>
458
+ {{ hasExamples ? examples : '' }}
459
+ <USER>Focus on {{ focusAreas.join(', ') }}.</USER>
460
+ `,
461
+ variables: {
462
+ hasContext: true,
463
+ context: 'This is part of a high-performance web server',
464
+ language: 'typescript',
465
+ code: 'async function handleRequest(req: Request) { ... }',
466
+ hasExamples: true,
467
+ examples: '<ASSISTANT>I\'ll review your code focusing on the areas you mentioned.</ASSISTANT>',
468
+ focusAreas: ['error handling', 'performance', 'type safety']
469
+ },
470
+ providerId: 'openai',
471
+ modelId: 'gpt-4.1'
472
+ });
473
+ ```
474
+
475
+ The method provides:
476
+ - **Unified API**: Single method for all prompt creation needs
477
+ - **Model Context Injection**: Automatically injects model-specific variables
478
+ - **Template Rendering**: Full support for conditionals and variable substitution
479
+ - **Role Tag Parsing**: Converts `<SYSTEM>`, `<USER>`, and `<ASSISTANT>` tags to messages
480
+
481
+ Available model context variables:
482
+ - `thinking_enabled`: Whether reasoning/thinking is enabled for this request
483
+ - `thinking_available`: Whether the model supports reasoning/thinking
484
+ - `model_id`: The resolved model ID
485
+ - `provider_id`: The resolved provider ID
486
+ - `reasoning_effort`: The reasoning effort level if specified
487
+ - `reasoning_max_tokens`: The reasoning token budget if specified
488
+
489
+ #### Advanced Features
490
+
491
+ **Dynamic Role Injection:**
492
+ Variables can dynamically inject entire role blocks, enabling flexible conversation flows:
493
+
494
+ ```typescript
495
+ const { messages } = await llmService.createMessages({
496
+ template: `
497
+ {{ includeSystemPrompt ? '<SYSTEM>{{systemPrompt}}</SYSTEM>' : '' }}
498
+ {{ examples ? examples : '' }}
499
+ <USER>{{userQuery}}</USER>
500
+ `,
501
+ variables: {
502
+ includeSystemPrompt: true,
503
+ systemPrompt: 'You are an expert code reviewer.',
504
+ examples: `
505
+ <USER>Review this code: const x = 1</USER>
506
+ <ASSISTANT>The variable name 'x' is not descriptive...</ASSISTANT>
507
+ `,
508
+ userQuery: 'Review this: const data = fetchData()'
509
+ },
510
+ presetId: 'anthropic-claude-3-5-sonnet-20241022'
511
+ });
512
+ ```
513
+
514
+ **Combining with Thinking Extraction:**
515
+ When using models without native reasoning support, combine createMessages with thinking extraction:
516
+
517
+ ```typescript
518
+ // Prompt any model to think before answering
519
+ const { messages } = await llmService.createMessages({
520
+ template: `
521
+ <SYSTEM>
522
+ When solving problems, first write your step-by-step reasoning inside <thinking> tags,
523
+ then provide your final answer.
524
+ </SYSTEM>
525
+ <USER>{{ question }}</USER>
526
+ `,
527
+ variables: { question: 'If a train travels 120km in 2 hours, what is its speed in m/s?' },
528
+ providerId: 'openai',
529
+ modelId: 'gpt-4.1'
530
+ });
531
+
532
+ // Send with automatic thinking extraction
533
+ const response = await llmService.sendMessage({
534
+ providerId: 'openai',
535
+ modelId: 'gpt-4.1',
536
+ messages,
537
+ settings: {
538
+ thinkingExtraction: { enabled: true } // Default, but shown for clarity
539
+ }
540
+ });
541
+
542
+ // Access both reasoning and answer
543
+ if (response.object === 'chat.completion') {
544
+ console.log('Reasoning:', response.choices[0].reasoning);
545
+ console.log('Answer:', response.choices[0].message.content);
546
+ }
547
+ ```
548
+
549
+ ### Self-Contained Templates with Metadata
550
+
551
+ Templates can now include their own settings using a `<META>` block, making them truly self-contained and reusable:
552
+
553
+ ```typescript
554
+ // Define a template with embedded settings
555
+ const creativeWritingTemplate = `
556
+ <META>
557
+ {
558
+ "settings": {
559
+ "temperature": 0.9,
560
+ "maxTokens": 3000,
561
+ "thinkingExtraction": { "enabled": true, "tag": "reasoning" }
562
+ }
563
+ }
564
+ </META>
565
+ <SYSTEM>
566
+ You are a creative writer. Use <reasoning> tags to outline your story structure
567
+ before writing the actual story.
568
+ </SYSTEM>
569
+ <USER>Write a short story about {{ topic }}</USER>
570
+ `;
571
+
572
+ // Use the template - settings are automatically extracted
573
+ const { messages, settings } = await llmService.createMessages({
574
+ template: creativeWritingTemplate,
575
+ variables: { topic: 'a robot discovering music' },
576
+ providerId: 'openai',
577
+ modelId: 'gpt-4.1'
578
+ });
579
+
580
+ // Send the message with the template's settings
581
+ const response = await llmService.sendMessage({
582
+ providerId: 'openai',
583
+ modelId: 'gpt-4.1',
584
+ messages,
585
+ settings // Uses temperature: 0.9, maxTokens: 3000, etc.
586
+ });
587
+ ```
588
+
589
+ **Benefits of Self-Contained Templates:**
590
+ - **Portability**: Templates carry their optimal settings with them
591
+ - **Consistency**: Same template always uses the same settings
592
+ - **Less Error-Prone**: No need to remember settings for each template
593
+ - **Shareable**: Easy to share templates with all necessary configuration
594
+
595
+ **Settings Hierarchy:**
596
+ When multiple settings sources exist, they are merged in this order (later overrides earlier):
597
+ 1. Model defaults (lowest priority)
598
+ 2. Preset settings
599
+ 3. Template `<META>` settings
600
+ 4. Runtime settings in `sendMessage()` (highest priority)
601
+
602
+ ```typescript
603
+ // Example of settings hierarchy
604
+ const { messages, settings: templateSettings } = await llmService.createMessages({
605
+ template: `<META>{"settings": {"temperature": 0.8}}</META><USER>Hello</USER>`,
606
+ presetId: 'some-preset' // Preset might have temperature: 0.7
607
+ });
608
+
609
+ // Final temperature will be 0.9 (runtime overrides all)
610
+ const response = await llmService.sendMessage({
611
+ presetId: 'some-preset',
612
+ messages,
613
+ settings: {
614
+ ...templateSettings,
615
+ temperature: 0.9 // Runtime override
616
+ }
617
+ });
618
+ ```
619
+
620
+ **Validation:**
621
+ Invalid settings in the `<META>` block are logged as warnings and ignored:
622
+
623
+ ```typescript
624
+ const template = `
625
+ <META>
626
+ {
627
+ "settings": {
628
+ "temperature": 3.0, // Invalid: will be ignored with warning
629
+ "maxTokens": 2000, // Valid: will be used
630
+ "unknownSetting": "foo" // Unknown: will be ignored with warning
631
+ }
632
+ }
633
+ </META>
634
+ <USER>Test</USER>
635
+ `;
636
+ ```
637
+
216
638
  ### Error Handling
217
639
 
218
640
  ```typescript
@@ -284,13 +706,19 @@ genai-lite is written in TypeScript and provides comprehensive type definitions:
284
706
  ```typescript
285
707
  import type {
286
708
  LLMChatRequest,
709
+ LLMChatRequestWithPreset,
287
710
  LLMResponse,
288
711
  LLMFailureResponse,
289
712
  LLMSettings,
713
+ LLMReasoningSettings,
714
+ LLMThinkingExtractionSettings,
290
715
  ApiKeyProvider,
291
716
  ModelPreset,
292
717
  LLMServiceOptions,
293
- PresetMode
718
+ PresetMode,
719
+ ModelContext,
720
+ CreateMessagesResult,
721
+ TemplateMetadata
294
722
  } from 'genai-lite';
295
723
  ```
296
724
 
@@ -412,6 +840,19 @@ const prompt = renderTemplate(
412
840
  );
413
841
  // Result includes the context line when hasContext is true
414
842
 
843
+ // Using logical operators in conditions
844
+ const accessControl = renderTemplate(
845
+ '{{ isAuthenticated && !isBanned ? `Welcome {{username}}!` : `Access denied` }}',
846
+ { isAuthenticated: true, isBanned: false, username: 'Alice' }
847
+ );
848
+ // Result: "Welcome Alice!"
849
+
850
+ const notification = renderTemplate(
851
+ '{{ hasEmail || hasPhone ? `Contact info available` : `No contact info` }}',
852
+ { hasEmail: false, hasPhone: true }
853
+ );
854
+ // Result: "Contact info available"
855
+
415
856
  // Complex template with multiple conditionals
416
857
  const complexTemplate = `
417
858
  System: You are a {{ role }} assistant.
@@ -440,10 +881,17 @@ const result = renderTemplate(complexTemplate, {
440
881
  Template syntax supports:
441
882
  - **Simple substitution**: `{{ variableName }}`
442
883
  - **Ternary conditionals**: `{{ condition ? `true result` : `false result` }}`
884
+ - **Logical operators in conditions**:
885
+ - NOT: `{{ !isDisabled ? `enabled` : `disabled` }}`
886
+ - AND: `{{ hasPermission && isActive ? `show` : `hide` }}`
887
+ - OR: `{{ isAdmin || isOwner ? `allow` : `deny` }}`
888
+ - Combined: `{{ !isDraft && isPublished ? `visible` : `hidden` }}`
443
889
  - **Nested variables**: `{{ show ? `Name: {{name}}` : `Anonymous` }}`
444
890
  - **Multi-line strings**: Use backticks to preserve formatting
445
891
  - **Intelligent newline handling**: Empty results remove trailing newlines
446
892
 
893
+ Note: Logical operators support up to 2 operands and don't support parentheses or mixing && and ||.
894
+
447
895
  ### Example: Building Dynamic LLM Prompts
448
896
 
449
897
  Combine the template engine with other utilities for powerful prompt generation:
@@ -491,36 +939,65 @@ const response = await llm.sendMessage({
491
939
  });
492
940
  ```
493
941
 
494
- ### Prompt Builder Utilities
942
+ ### Prompt Engineering Utilities
495
943
 
496
- genai-lite provides powerful utilities for building and parsing structured prompts:
944
+ genai-lite provides powerful utilities for working with prompts and responses:
497
945
 
498
- #### Parsing Messages from Templates
946
+ #### Creating Messages from Templates
499
947
 
500
- Convert template strings with role tags into LLM message arrays:
948
+ The recommended way to create messages is using `LLMService.createMessages`, which provides a unified API for template rendering, model context injection, and role tag parsing:
501
949
 
502
950
  ```typescript
503
- import { buildMessagesFromTemplate } from 'genai-lite/prompting';
504
-
505
- const template = `
506
- <SYSTEM>You are a helpful assistant specialized in {{expertise}}.</SYSTEM>
507
- <USER>Help me with {{task}}</USER>
508
- <ASSISTANT>I'll help you with {{task}}. Let me analyze the requirements...</ASSISTANT>
509
- <USER>Can you provide more details?</USER>
510
- `;
951
+ // Basic multi-turn conversation
952
+ const { messages } = await llmService.createMessages({
953
+ template: `
954
+ <SYSTEM>You are a helpful assistant specialized in {{expertise}}.</SYSTEM>
955
+ <USER>Help me with {{task}}</USER>
956
+ <ASSISTANT>I'll help you with {{task}}. Let me analyze the requirements...</ASSISTANT>
957
+ <USER>Can you provide more details?</USER>
958
+ `,
959
+ variables: {
960
+ expertise: 'TypeScript and React',
961
+ task: 'building a custom hook'
962
+ },
963
+ presetId: 'openai-gpt-4.1-default' // Optional: adds model context
964
+ });
511
965
 
512
- const messages = buildMessagesFromTemplate(template, {
513
- expertise: 'TypeScript and React',
514
- task: 'building a custom hook'
966
+ // Advanced: Leverage model context for adaptive prompts
967
+ const { messages, modelContext } = await llmService.createMessages({
968
+ template: `
969
+ <SYSTEM>
970
+ You are a {{ thinking_enabled ? 'analytical problem solver' : 'quick helper' }}.
971
+ {{ model_id.includes('claude') ? 'Use your advanced reasoning capabilities.' : '' }}
972
+ </SYSTEM>
973
+ <USER>
974
+ {{ thinking_enabled ? 'Please solve this step-by-step:' : 'Please answer:' }}
975
+ {{ question }}
976
+ </USER>
977
+ `,
978
+ variables: { question: 'What causes the seasons on Earth?' },
979
+ presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
515
980
  });
516
981
 
517
- // Result: Array of LLMMessage objects ready for the API
518
- // [
519
- // { role: 'system', content: 'You are a helpful assistant specialized in TypeScript and React.' },
520
- // { role: 'user', content: 'Help me with building a custom hook' },
521
- // { role: 'assistant', content: "I'll help you with building a custom hook. Let me analyze..." },
522
- // { role: 'user', content: 'Can you provide more details?' }
523
- // ]
982
+ console.log('Model context:', modelContext);
983
+ // Output: { thinking_enabled: true, thinking_available: true, model_id: 'claude-3-7-sonnet-20250219', ... }
984
+ ```
985
+
986
+ **Low-Level Utilities:**
987
+ For cases where you need template parsing without model context:
988
+
989
+ ```typescript
990
+ import { parseRoleTags, renderTemplate } from 'genai-lite/prompting';
991
+
992
+ // Render variables first
993
+ const rendered = renderTemplate(
994
+ '<SYSTEM>You are a {{role}} assistant.</SYSTEM><USER>{{query}}</USER>',
995
+ { role: 'helpful', query: 'What is TypeScript?' }
996
+ );
997
+
998
+ // Then parse role tags
999
+ const messages = parseRoleTags(rendered);
1000
+ // Result: [{ role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'What is TypeScript?' }]
524
1001
  ```
525
1002
 
526
1003
  #### Extracting Random Variables for Few-Shot Learning
@@ -616,10 +1093,11 @@ console.log(parsed.SUGGESTIONS); // The suggestions text
616
1093
  console.log(parsed.REFACTORED_CODE); // The refactored code
617
1094
  ```
618
1095
 
619
- These prompt builder utilities enable:
620
- - **Structured Conversations**: Build multi-turn conversations from templates
1096
+ These utilities enable:
1097
+ - **Structured Conversations**: Build multi-turn conversations from templates with model context awareness
621
1098
  - **Few-Shot Learning**: Randomly sample examples to improve AI responses
622
1099
  - **Reliable Output Parsing**: Extract specific sections from AI responses
1100
+ - **Automatic Thinking Extraction**: Capture reasoning from any model using XML tags
623
1101
  - **Template Reusability**: Define templates once, use with different variables
624
1102
  - **Type Safety**: Full TypeScript support with LLMMessage types
625
1103