genai-lite 0.4.0 → 0.4.2
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/README.md +47 -37
- package/dist/llm/LLMService.d.ts +29 -2
- package/dist/llm/LLMService.js +80 -36
- package/dist/llm/config.js +4 -4
- package/dist/llm/services/SettingsManager.js +17 -11
- package/dist/llm/types.d.ts +81 -22
- package/dist/prompting/parser.d.ts +2 -2
- package/dist/prompting/parser.js +2 -2
- package/package.json +1 -1
- package/dist/llm/LLMService.createMessages.test.d.ts +0 -4
- package/dist/llm/LLMService.createMessages.test.js +0 -364
- package/dist/llm/LLMService.original.d.ts +0 -147
- package/dist/llm/LLMService.original.js +0 -656
- package/dist/llm/LLMService.prepareMessage.test.d.ts +0 -1
- package/dist/llm/LLMService.prepareMessage.test.js +0 -303
- package/dist/llm/LLMService.presets.test.d.ts +0 -1
- package/dist/llm/LLMService.presets.test.js +0 -210
- package/dist/llm/LLMService.sendMessage.preset.test.d.ts +0 -1
- package/dist/llm/LLMService.sendMessage.preset.test.js +0 -153
- package/dist/llm/LLMService.test.d.ts +0 -1
- package/dist/llm/LLMService.test.js +0 -639
- package/dist/llm/clients/AnthropicClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/AnthropicClientAdapter.test.js +0 -273
- package/dist/llm/clients/GeminiClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/GeminiClientAdapter.test.js +0 -405
- package/dist/llm/clients/LlamaCppClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/LlamaCppClientAdapter.test.js +0 -447
- package/dist/llm/clients/LlamaCppServerClient.test.d.ts +0 -1
- package/dist/llm/clients/LlamaCppServerClient.test.js +0 -294
- package/dist/llm/clients/MockClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/MockClientAdapter.test.js +0 -250
- package/dist/llm/clients/OpenAIClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/OpenAIClientAdapter.test.js +0 -258
- package/dist/llm/clients/adapterErrorUtils.test.d.ts +0 -1
- package/dist/llm/clients/adapterErrorUtils.test.js +0 -123
- package/dist/llm/config.test.d.ts +0 -1
- package/dist/llm/config.test.js +0 -176
- package/dist/llm/services/AdapterRegistry.test.d.ts +0 -1
- package/dist/llm/services/AdapterRegistry.test.js +0 -239
- package/dist/llm/services/ModelResolver.test.d.ts +0 -1
- package/dist/llm/services/ModelResolver.test.js +0 -179
- package/dist/llm/services/PresetManager.test.d.ts +0 -1
- package/dist/llm/services/PresetManager.test.js +0 -210
- package/dist/llm/services/RequestValidator.test.d.ts +0 -1
- package/dist/llm/services/RequestValidator.test.js +0 -159
- package/dist/llm/services/SettingsManager.test.d.ts +0 -1
- package/dist/llm/services/SettingsManager.test.js +0 -266
- package/dist/prompting/builder.d.ts +0 -38
- package/dist/prompting/builder.js +0 -63
- package/dist/prompting/builder.test.d.ts +0 -4
- package/dist/prompting/builder.test.js +0 -109
- package/dist/prompting/content.test.d.ts +0 -4
- package/dist/prompting/content.test.js +0 -212
- package/dist/prompting/parser.test.d.ts +0 -4
- package/dist/prompting/parser.test.js +0 -464
- package/dist/prompting/template.test.d.ts +0 -1
- package/dist/prompting/template.test.js +0 -250
- package/dist/providers/fromEnvironment.test.d.ts +0 -1
- package/dist/providers/fromEnvironment.test.js +0 -59
package/README.md
CHANGED
|
@@ -295,9 +295,11 @@ if (response.object === 'chat.completion' && response.choices[0].reasoning) {
|
|
|
295
295
|
- Not all models support reasoning - check the [supported models](#models-with-reasoning-support) list
|
|
296
296
|
- The `reasoning` field in the response contains the model's thought process (when available)
|
|
297
297
|
|
|
298
|
-
###
|
|
298
|
+
### Thinking Extraction and Enforcement
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
For models without native reasoning, you can prompt them to output reasoning in XML tags like `<thinking>`. The library then extracts these tags and moves the content to the standardized `reasoning` field, providing a consistent interface across all models.
|
|
301
|
+
|
|
302
|
+
**Key point:** The library doesn't make models think automatically—you must explicitly instruct non-reasoning models to use thinking tags in your prompt. The library then enforces that these tags are present (for non-reasoning models) or accepts native reasoning (for reasoning models).
|
|
301
303
|
|
|
302
304
|
```typescript
|
|
303
305
|
// Prompt the model to think step-by-step in a <thinking> tag
|
|
@@ -312,7 +314,7 @@ const response = await llmService.sendMessage({
|
|
|
312
314
|
content: 'Please think through this problem step by step before answering: What is 15% of 240?'
|
|
313
315
|
}],
|
|
314
316
|
settings: {
|
|
315
|
-
|
|
317
|
+
thinkingTagFallback: { enabled: true } // Must explicitly enable
|
|
316
318
|
}
|
|
317
319
|
});
|
|
318
320
|
|
|
@@ -334,25 +336,25 @@ const response = await llmService.sendMessage({
|
|
|
334
336
|
modelId: 'claude-3-5-haiku-20241022',
|
|
335
337
|
messages: [{ role: 'user', content: 'Solve this step by step...' }],
|
|
336
338
|
settings: {
|
|
337
|
-
|
|
339
|
+
thinkingTagFallback: {
|
|
338
340
|
enabled: true, // Must explicitly enable (default: false)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
tagName: 'scratchpad', // Custom tag name (default: 'thinking')
|
|
342
|
+
enforce: true // Smart enforcement (see below)
|
|
341
343
|
}
|
|
342
344
|
}
|
|
343
345
|
});
|
|
344
346
|
```
|
|
345
347
|
|
|
346
|
-
**The `
|
|
348
|
+
**The `enforce` Property:**
|
|
349
|
+
|
|
350
|
+
The `enforce` boolean controls whether thinking tags are required when native reasoning is not active:
|
|
347
351
|
|
|
348
|
-
|
|
352
|
+
- `enforce: true` - Error if tags missing AND native reasoning not active (smart enforcement)
|
|
353
|
+
- `enforce: false` (default) - Extract tags if present, never error
|
|
349
354
|
|
|
350
|
-
-
|
|
351
|
-
- `'warn'`: Log a warning but continue processing
|
|
352
|
-
- `'error'`: Return an error response with the original response preserved in `partialResponse`
|
|
353
|
-
- `'auto'` (default): Intelligently decide based on the model's native reasoning capabilities
|
|
355
|
+
The enforcement is **always smart** - it automatically checks if native reasoning is active and only enforces when the model needs tags as a fallback.
|
|
354
356
|
|
|
355
|
-
**How
|
|
357
|
+
**How Smart Enforcement Works:**
|
|
356
358
|
|
|
357
359
|
```typescript
|
|
358
360
|
// With non-native reasoning models (e.g., GPT-4)
|
|
@@ -367,10 +369,10 @@ const response = await llmService.sendMessage({
|
|
|
367
369
|
content: 'What is 15% of 240?'
|
|
368
370
|
}],
|
|
369
371
|
settings: {
|
|
370
|
-
|
|
372
|
+
thinkingTagFallback: { enabled: true, enforce: true }
|
|
371
373
|
}
|
|
372
374
|
});
|
|
373
|
-
// Result: ERROR if <thinking> tag is missing (
|
|
375
|
+
// Result: ERROR if <thinking> tag is missing (native reasoning not active)
|
|
374
376
|
// The response is still accessible via errorResponse.partialResponse
|
|
375
377
|
|
|
376
378
|
// With native reasoning models (e.g., Claude with reasoning enabled)
|
|
@@ -380,10 +382,10 @@ const response = await llmService.sendMessage({
|
|
|
380
382
|
messages: [/* same prompt */],
|
|
381
383
|
settings: {
|
|
382
384
|
reasoning: { enabled: true },
|
|
383
|
-
|
|
385
|
+
thinkingTagFallback: { enabled: true, enforce: true }
|
|
384
386
|
}
|
|
385
387
|
});
|
|
386
|
-
// Result: SUCCESS even if <thinking> tag is missing (
|
|
388
|
+
// Result: SUCCESS even if <thinking> tag is missing (native reasoning is active)
|
|
387
389
|
```
|
|
388
390
|
|
|
389
391
|
This intelligent enforcement ensures that:
|
|
@@ -510,13 +512,10 @@ The library provides a powerful `createMessages` method that combines template r
|
|
|
510
512
|
// Basic example: Create model-aware messages
|
|
511
513
|
const { messages, modelContext } = await llmService.createMessages({
|
|
512
514
|
template: `
|
|
513
|
-
<SYSTEM>
|
|
514
|
-
You are a {{ thinking_enabled ? "thoughtful" : "helpful" }} assistant.
|
|
515
|
-
{{ thinking_available && !thinking_enabled ? "Note: Reasoning mode is available for complex problems." : "" }}
|
|
516
|
-
</SYSTEM>
|
|
515
|
+
<SYSTEM>You are a helpful assistant.</SYSTEM>
|
|
517
516
|
<USER>{{ question }}</USER>
|
|
518
517
|
`,
|
|
519
|
-
variables: {
|
|
518
|
+
variables: {
|
|
520
519
|
question: 'What is the optimal algorithm for finding the shortest path in a weighted graph?'
|
|
521
520
|
},
|
|
522
521
|
presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
|
|
@@ -560,14 +559,26 @@ The method provides:
|
|
|
560
559
|
- **Template Rendering**: Full support for conditionals and variable substitution
|
|
561
560
|
- **Role Tag Parsing**: Converts `<SYSTEM>`, `<USER>`, and `<ASSISTANT>` tags to messages
|
|
562
561
|
|
|
563
|
-
Available model context variables
|
|
564
|
-
|
|
565
|
-
- `
|
|
562
|
+
**Available model context variables:**
|
|
563
|
+
|
|
564
|
+
- `native_reasoning_active`: Whether native reasoning is **currently active** for this request
|
|
565
|
+
- `true`: The model is using built-in reasoning (e.g., Claude 4, o4-mini, Gemini 2.5 Pro with reasoning enabled)
|
|
566
|
+
- `false`: No native reasoning is active (either because the model doesn't support it, or it's been disabled)
|
|
567
|
+
- `native_reasoning_capable`: Whether the model **has the capability** to use native reasoning
|
|
568
|
+
- `true`: Model supports native reasoning (may or may not be enabled)
|
|
569
|
+
- `false`: Model does not support native reasoning
|
|
566
570
|
- `model_id`: The resolved model ID
|
|
567
571
|
- `provider_id`: The resolved provider ID
|
|
568
572
|
- `reasoning_effort`: The reasoning effort level if specified
|
|
569
573
|
- `reasoning_max_tokens`: The reasoning token budget if specified
|
|
570
574
|
|
|
575
|
+
**Best Practice for Templates:**
|
|
576
|
+
When adding thinking tag instructions to your templates, **always use `requires_tags_for_thinking`** (the NOT operator). This ensures:
|
|
577
|
+
- Models with active native reasoning get clean, direct prompts
|
|
578
|
+
- Models without native reasoning get explicit instructions to use `<thinking>` tags
|
|
579
|
+
|
|
580
|
+
Example: `{{ requires_tags_for_thinking ? ' Write your reasoning in <thinking> tags first.' : '' }}`
|
|
581
|
+
|
|
571
582
|
#### Advanced Features
|
|
572
583
|
|
|
573
584
|
**Dynamic Role Injection:**
|
|
@@ -617,7 +628,7 @@ const response = await llmService.sendMessage({
|
|
|
617
628
|
modelId: 'gpt-4.1',
|
|
618
629
|
messages,
|
|
619
630
|
settings: {
|
|
620
|
-
|
|
631
|
+
thinkingTagFallback: { enabled: true } // Default, but shown for clarity
|
|
621
632
|
}
|
|
622
633
|
});
|
|
623
634
|
|
|
@@ -640,7 +651,7 @@ const creativeWritingTemplate = `
|
|
|
640
651
|
"settings": {
|
|
641
652
|
"temperature": 0.9,
|
|
642
653
|
"maxTokens": 3000,
|
|
643
|
-
"
|
|
654
|
+
"thinkingTagFallback": { "enabled": true, "tagName": "reasoning" }
|
|
644
655
|
}
|
|
645
656
|
}
|
|
646
657
|
</META>
|
|
@@ -1045,14 +1056,14 @@ const llmService = new LLMService(electronKeyProvider);
|
|
|
1045
1056
|
genai-lite is written in TypeScript and provides comprehensive type definitions:
|
|
1046
1057
|
|
|
1047
1058
|
```typescript
|
|
1048
|
-
import type {
|
|
1059
|
+
import type {
|
|
1049
1060
|
LLMChatRequest,
|
|
1050
1061
|
LLMChatRequestWithPreset,
|
|
1051
1062
|
LLMResponse,
|
|
1052
1063
|
LLMFailureResponse,
|
|
1053
1064
|
LLMSettings,
|
|
1054
1065
|
LLMReasoningSettings,
|
|
1055
|
-
|
|
1066
|
+
LLMThinkingTagFallbackSettings,
|
|
1056
1067
|
ApiKeyProvider,
|
|
1057
1068
|
ModelPreset,
|
|
1058
1069
|
LLMServiceOptions,
|
|
@@ -1324,24 +1335,23 @@ const { messages } = await llmService.createMessages({
|
|
|
1324
1335
|
presetId: 'openai-gpt-4.1-default' // Optional: adds model context
|
|
1325
1336
|
});
|
|
1326
1337
|
|
|
1327
|
-
// Advanced:
|
|
1338
|
+
// Advanced: Adaptive prompts based on model capabilities
|
|
1328
1339
|
const { messages, modelContext } = await llmService.createMessages({
|
|
1329
1340
|
template: `
|
|
1330
1341
|
<SYSTEM>
|
|
1331
|
-
You are a
|
|
1332
|
-
{{
|
|
1342
|
+
You are a problem-solving assistant.
|
|
1343
|
+
{{ requires_tags_for_thinking ? ' For complex problems, write your reasoning in <thinking> tags before answering.' : '' }}
|
|
1333
1344
|
</SYSTEM>
|
|
1334
|
-
<USER>
|
|
1335
|
-
{{ thinking_enabled ? 'Please solve this step-by-step:' : 'Please answer:' }}
|
|
1336
|
-
{{ question }}
|
|
1337
|
-
</USER>
|
|
1345
|
+
<USER>{{ question }}</USER>
|
|
1338
1346
|
`,
|
|
1347
|
+
// Note: Use requires_tags_for_thinking (NOT operator) - only instruct models that don't have active native reasoning
|
|
1339
1348
|
variables: { question: 'What causes the seasons on Earth?' },
|
|
1340
1349
|
presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
|
|
1341
1350
|
});
|
|
1342
1351
|
|
|
1343
1352
|
console.log('Model context:', modelContext);
|
|
1344
|
-
// Output: {
|
|
1353
|
+
// Output: { native_reasoning_active: true, native_reasoning_capable: true, model_id: 'claude-3-7-sonnet-20250219', ... }
|
|
1354
|
+
// Note: With a reasoning model, the system prompt won't include thinking tag instructions
|
|
1345
1355
|
```
|
|
1346
1356
|
|
|
1347
1357
|
**Low-Level Utilities:**
|
package/dist/llm/LLMService.d.ts
CHANGED
|
@@ -75,19 +75,45 @@ export declare class LLMService {
|
|
|
75
75
|
* injection, and role tag parsing into a single, intuitive API. It replaces the need
|
|
76
76
|
* to chain prepareMessage and buildMessagesFromTemplate for model-aware multi-turn prompts.
|
|
77
77
|
*
|
|
78
|
+
* **Model Context Injection:**
|
|
79
|
+
* When a presetId or providerId/modelId is provided, this method automatically injects
|
|
80
|
+
* model context variables into your templates:
|
|
81
|
+
* - `native_reasoning_active`: Whether native reasoning is currently active
|
|
82
|
+
* - `native_reasoning_capable`: Whether the model supports native reasoning
|
|
83
|
+
* - `requires_tags_for_thinking`: Whether thinking tags are needed (true when native reasoning not active)
|
|
84
|
+
* - `model_id`, `provider_id`, `reasoning_effort`, `reasoning_max_tokens`
|
|
85
|
+
*
|
|
86
|
+
* **Best Practice for Thinking Tags:**
|
|
87
|
+
* When adding thinking tag instructions, use requires_tags_for_thinking:
|
|
88
|
+
* `{{ requires_tags_for_thinking ? 'Write your reasoning in <thinking> tags first.' : '' }}`
|
|
89
|
+
*
|
|
78
90
|
* @param options Options for creating messages
|
|
79
|
-
* @returns Promise resolving to parsed messages and
|
|
91
|
+
* @returns Promise resolving to parsed messages, model context, and template settings
|
|
80
92
|
*
|
|
81
93
|
* @example
|
|
82
94
|
* ```typescript
|
|
95
|
+
* // Basic usage
|
|
83
96
|
* const { messages } = await llm.createMessages({
|
|
84
97
|
* template: `
|
|
85
|
-
* <SYSTEM>You are a
|
|
98
|
+
* <SYSTEM>You are a helpful assistant.</SYSTEM>
|
|
86
99
|
* <USER>Help me with {{ task }}</USER>
|
|
87
100
|
* `,
|
|
88
101
|
* variables: { task: 'understanding async/await' },
|
|
89
102
|
* presetId: 'openai-gpt-4.1-default'
|
|
90
103
|
* });
|
|
104
|
+
*
|
|
105
|
+
* // Model-aware template with thinking tags
|
|
106
|
+
* const { messages, modelContext } = await llm.createMessages({
|
|
107
|
+
* template: `
|
|
108
|
+
* <SYSTEM>
|
|
109
|
+
* You are a problem-solving assistant.
|
|
110
|
+
* {{ requires_tags_for_thinking ? 'For complex problems, write your reasoning in <thinking> tags first.' : '' }}
|
|
111
|
+
* </SYSTEM>
|
|
112
|
+
* <USER>{{ question }}</USER>
|
|
113
|
+
* `,
|
|
114
|
+
* variables: { question: 'Explain recursion' },
|
|
115
|
+
* presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
|
|
116
|
+
* });
|
|
91
117
|
* ```
|
|
92
118
|
*/
|
|
93
119
|
createMessages(options: {
|
|
@@ -96,6 +122,7 @@ export declare class LLMService {
|
|
|
96
122
|
presetId?: string;
|
|
97
123
|
providerId?: string;
|
|
98
124
|
modelId?: string;
|
|
125
|
+
settings?: Partial<LLMSettings>;
|
|
99
126
|
}): Promise<CreateMessagesResult>;
|
|
100
127
|
/**
|
|
101
128
|
* Gets information about registered adapters
|
package/dist/llm/LLMService.js
CHANGED
|
@@ -143,32 +143,42 @@ class LLMService {
|
|
|
143
143
|
object: "error",
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
+
// Validate API key format if adapter supports it
|
|
147
|
+
if (clientAdapter.validateApiKey && !clientAdapter.validateApiKey(apiKey)) {
|
|
148
|
+
return {
|
|
149
|
+
provider: providerId,
|
|
150
|
+
model: modelId,
|
|
151
|
+
error: {
|
|
152
|
+
message: `Invalid API key format for provider '${providerId}'. Please check your API key.`,
|
|
153
|
+
code: "INVALID_API_KEY",
|
|
154
|
+
type: "authentication_error",
|
|
155
|
+
},
|
|
156
|
+
object: "error",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
146
159
|
console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${providerId}`);
|
|
147
160
|
const result = await clientAdapter.sendMessage(internalRequest, apiKey);
|
|
148
|
-
// Post-process for thinking
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
effectiveOnMissing = isNativeReasoningActive ? 'ignore' : 'error';
|
|
162
|
-
}
|
|
163
|
-
// Step 2: Process the response
|
|
161
|
+
// Post-process for thinking tag fallback
|
|
162
|
+
// This feature extracts reasoning from XML tags when native reasoning is not active.
|
|
163
|
+
// It's a fallback mechanism for models without native reasoning or when native is disabled.
|
|
164
|
+
const fallbackSettings = internalRequest.settings.thinkingTagFallback;
|
|
165
|
+
if (result.object === 'chat.completion' && fallbackSettings && fallbackSettings.enabled !== false) {
|
|
166
|
+
const tagName = fallbackSettings.tagName || 'thinking';
|
|
167
|
+
// Check if native reasoning is active for this request
|
|
168
|
+
const isNativeReasoningActive = modelInfo.reasoning?.supported === true &&
|
|
169
|
+
(internalRequest.settings.reasoning?.enabled === true ||
|
|
170
|
+
(modelInfo.reasoning?.enabledByDefault === true &&
|
|
171
|
+
internalRequest.settings.reasoning?.enabled !== false) ||
|
|
172
|
+
modelInfo.reasoning?.canDisable === false);
|
|
173
|
+
// Process the response - extract thinking tags if present
|
|
164
174
|
const choice = result.choices[0];
|
|
165
175
|
if (choice?.message?.content) {
|
|
166
176
|
const { extracted, remaining } = (0, parser_1.extractInitialTaggedContent)(choice.message.content, tagName);
|
|
167
177
|
if (extracted !== null) {
|
|
178
|
+
// Success: thinking tag found
|
|
168
179
|
console.log(`Extracted <${tagName}> block from response.`);
|
|
169
|
-
// Handle the edge case: append to existing reasoning if present.
|
|
180
|
+
// Handle the edge case: append to existing reasoning if present (e.g., native reasoning + thinking tags)
|
|
170
181
|
const existingReasoning = choice.reasoning || '';
|
|
171
|
-
// Only add a separator when appending to existing reasoning
|
|
172
182
|
if (existingReasoning) {
|
|
173
183
|
// Use a neutral markdown header that works for any consumer (human or AI)
|
|
174
184
|
choice.reasoning = `${existingReasoning}\n\n#### Additional Reasoning\n\n${extracted}`;
|
|
@@ -180,17 +190,24 @@ class LLMService {
|
|
|
180
190
|
choice.message.content = remaining;
|
|
181
191
|
}
|
|
182
192
|
else {
|
|
183
|
-
// Tag was not found
|
|
184
|
-
if (
|
|
193
|
+
// Tag was not found
|
|
194
|
+
// Enforce only if: (1) enforce: true AND (2) native reasoning is NOT active
|
|
195
|
+
if (fallbackSettings.enforce === true && !isNativeReasoningActive) {
|
|
196
|
+
const nativeReasoningCapable = modelInfo.reasoning?.supported === true;
|
|
185
197
|
return {
|
|
186
198
|
provider: providerId,
|
|
187
199
|
model: modelId,
|
|
188
200
|
error: {
|
|
189
|
-
message: `
|
|
190
|
-
|
|
191
|
-
`Either ensure your prompt instructs the model to use <${tagName}> tags, or enable native reasoning if supported.`,
|
|
192
|
-
code: "MISSING_EXPECTED_TAG",
|
|
201
|
+
message: `Model response missing required <${tagName}> tags.`,
|
|
202
|
+
code: "THINKING_TAGS_MISSING",
|
|
193
203
|
type: "validation_error",
|
|
204
|
+
param: nativeReasoningCapable && !isNativeReasoningActive
|
|
205
|
+
? `You disabled native reasoning for this model (${modelId}). ` +
|
|
206
|
+
`To see its reasoning, you must prompt it to use <${tagName}> tags. ` +
|
|
207
|
+
`Example: "Write your step-by-step reasoning in <${tagName}> tags before answering."`
|
|
208
|
+
: `This model (${modelId}) does not support native reasoning. ` +
|
|
209
|
+
`To get reasoning, you must prompt it to use <${tagName}> tags. ` +
|
|
210
|
+
`Example: "Write your step-by-step reasoning in <${tagName}> tags before answering."`,
|
|
194
211
|
},
|
|
195
212
|
object: "error",
|
|
196
213
|
partialResponse: {
|
|
@@ -203,10 +220,7 @@ class LLMService {
|
|
|
203
220
|
}
|
|
204
221
|
};
|
|
205
222
|
}
|
|
206
|
-
|
|
207
|
-
console.warn(`Expected <${tagName}> tag was not found in the response from model ${modelId}.`);
|
|
208
|
-
}
|
|
209
|
-
// If 'ignore', do nothing
|
|
223
|
+
// If enforce: false or native reasoning is active, do nothing
|
|
210
224
|
}
|
|
211
225
|
}
|
|
212
226
|
}
|
|
@@ -262,19 +276,45 @@ class LLMService {
|
|
|
262
276
|
* injection, and role tag parsing into a single, intuitive API. It replaces the need
|
|
263
277
|
* to chain prepareMessage and buildMessagesFromTemplate for model-aware multi-turn prompts.
|
|
264
278
|
*
|
|
279
|
+
* **Model Context Injection:**
|
|
280
|
+
* When a presetId or providerId/modelId is provided, this method automatically injects
|
|
281
|
+
* model context variables into your templates:
|
|
282
|
+
* - `native_reasoning_active`: Whether native reasoning is currently active
|
|
283
|
+
* - `native_reasoning_capable`: Whether the model supports native reasoning
|
|
284
|
+
* - `requires_tags_for_thinking`: Whether thinking tags are needed (true when native reasoning not active)
|
|
285
|
+
* - `model_id`, `provider_id`, `reasoning_effort`, `reasoning_max_tokens`
|
|
286
|
+
*
|
|
287
|
+
* **Best Practice for Thinking Tags:**
|
|
288
|
+
* When adding thinking tag instructions, use requires_tags_for_thinking:
|
|
289
|
+
* `{{ requires_tags_for_thinking ? 'Write your reasoning in <thinking> tags first.' : '' }}`
|
|
290
|
+
*
|
|
265
291
|
* @param options Options for creating messages
|
|
266
|
-
* @returns Promise resolving to parsed messages and
|
|
292
|
+
* @returns Promise resolving to parsed messages, model context, and template settings
|
|
267
293
|
*
|
|
268
294
|
* @example
|
|
269
295
|
* ```typescript
|
|
296
|
+
* // Basic usage
|
|
270
297
|
* const { messages } = await llm.createMessages({
|
|
271
298
|
* template: `
|
|
272
|
-
* <SYSTEM>You are a
|
|
299
|
+
* <SYSTEM>You are a helpful assistant.</SYSTEM>
|
|
273
300
|
* <USER>Help me with {{ task }}</USER>
|
|
274
301
|
* `,
|
|
275
302
|
* variables: { task: 'understanding async/await' },
|
|
276
303
|
* presetId: 'openai-gpt-4.1-default'
|
|
277
304
|
* });
|
|
305
|
+
*
|
|
306
|
+
* // Model-aware template with thinking tags
|
|
307
|
+
* const { messages, modelContext } = await llm.createMessages({
|
|
308
|
+
* template: `
|
|
309
|
+
* <SYSTEM>
|
|
310
|
+
* You are a problem-solving assistant.
|
|
311
|
+
* {{ requires_tags_for_thinking ? 'For complex problems, write your reasoning in <thinking> tags first.' : '' }}
|
|
312
|
+
* </SYSTEM>
|
|
313
|
+
* <USER>{{ question }}</USER>
|
|
314
|
+
* `,
|
|
315
|
+
* variables: { question: 'Explain recursion' },
|
|
316
|
+
* presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
|
|
317
|
+
* });
|
|
278
318
|
* ```
|
|
279
319
|
*/
|
|
280
320
|
async createMessages(options) {
|
|
@@ -290,7 +330,8 @@ class LLMService {
|
|
|
290
330
|
const resolved = this.modelResolver.resolve({
|
|
291
331
|
presetId: options.presetId,
|
|
292
332
|
providerId: options.providerId,
|
|
293
|
-
modelId: options.modelId
|
|
333
|
+
modelId: options.modelId,
|
|
334
|
+
settings: options.settings
|
|
294
335
|
});
|
|
295
336
|
if (resolved.error) {
|
|
296
337
|
// If resolution fails, proceed without model context
|
|
@@ -300,12 +341,15 @@ class LLMService {
|
|
|
300
341
|
const { providerId, modelId, modelInfo, settings } = resolved;
|
|
301
342
|
// Merge settings with model defaults
|
|
302
343
|
const mergedSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, settings || {});
|
|
303
|
-
//
|
|
344
|
+
// Calculate native reasoning status
|
|
345
|
+
const nativeReasoningActive = !!(modelInfo.reasoning?.supported &&
|
|
346
|
+
(mergedSettings.reasoning?.enabled === true ||
|
|
347
|
+
(modelInfo.reasoning?.enabledByDefault && mergedSettings.reasoning?.enabled !== false)));
|
|
348
|
+
// Create model context with new property names
|
|
304
349
|
modelContext = {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
thinking_available: !!modelInfo.reasoning?.supported,
|
|
350
|
+
native_reasoning_active: nativeReasoningActive,
|
|
351
|
+
native_reasoning_capable: !!modelInfo.reasoning?.supported,
|
|
352
|
+
requires_tags_for_thinking: !nativeReasoningActive,
|
|
309
353
|
model_id: modelId,
|
|
310
354
|
provider_id: providerId,
|
|
311
355
|
reasoning_effort: mergedSettings.reasoning?.effort,
|
package/dist/llm/config.js
CHANGED
|
@@ -69,10 +69,10 @@ exports.DEFAULT_LLM_SETTINGS = {
|
|
|
69
69
|
maxTokens: undefined,
|
|
70
70
|
exclude: false,
|
|
71
71
|
},
|
|
72
|
-
|
|
73
|
-
enabled: false,
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
thinkingTagFallback: {
|
|
73
|
+
enabled: false,
|
|
74
|
+
tagName: 'thinking',
|
|
75
|
+
enforce: false
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
78
|
/**
|
|
@@ -34,9 +34,9 @@ class SettingsManager {
|
|
|
34
34
|
...modelDefaults.reasoning,
|
|
35
35
|
...requestSettings?.reasoning,
|
|
36
36
|
},
|
|
37
|
-
|
|
38
|
-
...modelDefaults.
|
|
39
|
-
...requestSettings?.
|
|
37
|
+
thinkingTagFallback: {
|
|
38
|
+
...modelDefaults.thinkingTagFallback,
|
|
39
|
+
...requestSettings?.thinkingTagFallback,
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
// Log the final settings for debugging
|
|
@@ -115,7 +115,7 @@ class SettingsManager {
|
|
|
115
115
|
'supportsSystemMessage',
|
|
116
116
|
'geminiSafetySettings',
|
|
117
117
|
'reasoning',
|
|
118
|
-
'
|
|
118
|
+
'thinkingTagFallback'
|
|
119
119
|
];
|
|
120
120
|
// Check each setting field
|
|
121
121
|
for (const [key, value] of Object.entries(settings)) {
|
|
@@ -195,22 +195,28 @@ class SettingsManager {
|
|
|
195
195
|
}
|
|
196
196
|
continue;
|
|
197
197
|
}
|
|
198
|
-
if (key === '
|
|
198
|
+
if (key === 'thinkingTagFallback' && typeof value === 'object' && value !== null) {
|
|
199
199
|
const thinkingValidated = {};
|
|
200
200
|
if ('enabled' in value && typeof value.enabled !== 'boolean') {
|
|
201
|
-
console.warn(`Invalid
|
|
201
|
+
console.warn(`Invalid thinkingTagFallback.enabled value in template. Must be a boolean.`);
|
|
202
202
|
}
|
|
203
203
|
else if ('enabled' in value) {
|
|
204
204
|
thinkingValidated.enabled = value.enabled;
|
|
205
205
|
}
|
|
206
|
-
if ('
|
|
207
|
-
console.warn(`Invalid
|
|
206
|
+
if ('tagName' in value && typeof value.tagName !== 'string') {
|
|
207
|
+
console.warn(`Invalid thinkingTagFallback.tagName value in template. Must be a string.`);
|
|
208
208
|
}
|
|
209
|
-
else if ('
|
|
210
|
-
thinkingValidated.
|
|
209
|
+
else if ('tagName' in value) {
|
|
210
|
+
thinkingValidated.tagName = value.tagName;
|
|
211
|
+
}
|
|
212
|
+
if ('enforce' in value && typeof value.enforce !== 'boolean') {
|
|
213
|
+
console.warn(`Invalid thinkingTagFallback.enforce value in template. Must be a boolean.`);
|
|
214
|
+
}
|
|
215
|
+
else if ('enforce' in value) {
|
|
216
|
+
thinkingValidated.enforce = value.enforce;
|
|
211
217
|
}
|
|
212
218
|
if (Object.keys(thinkingValidated).length > 0) {
|
|
213
|
-
validated.
|
|
219
|
+
validated.thinkingTagFallback = thinkingValidated;
|
|
214
220
|
}
|
|
215
221
|
continue;
|
|
216
222
|
}
|