genai-lite 0.4.0 → 0.4.1
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 +67 -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
|
@@ -145,30 +145,27 @@ class LLMService {
|
|
|
145
145
|
}
|
|
146
146
|
console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${providerId}`);
|
|
147
147
|
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
|
|
148
|
+
// Post-process for thinking tag fallback
|
|
149
|
+
// This feature extracts reasoning from XML tags when native reasoning is not active.
|
|
150
|
+
// It's a fallback mechanism for models without native reasoning or when native is disabled.
|
|
151
|
+
const fallbackSettings = internalRequest.settings.thinkingTagFallback;
|
|
152
|
+
if (result.object === 'chat.completion' && fallbackSettings && fallbackSettings.enabled !== false) {
|
|
153
|
+
const tagName = fallbackSettings.tagName || 'thinking';
|
|
154
|
+
// Check if native reasoning is active for this request
|
|
155
|
+
const isNativeReasoningActive = modelInfo.reasoning?.supported === true &&
|
|
156
|
+
(internalRequest.settings.reasoning?.enabled === true ||
|
|
157
|
+
(modelInfo.reasoning?.enabledByDefault === true &&
|
|
158
|
+
internalRequest.settings.reasoning?.enabled !== false) ||
|
|
159
|
+
modelInfo.reasoning?.canDisable === false);
|
|
160
|
+
// Process the response - extract thinking tags if present
|
|
164
161
|
const choice = result.choices[0];
|
|
165
162
|
if (choice?.message?.content) {
|
|
166
163
|
const { extracted, remaining } = (0, parser_1.extractInitialTaggedContent)(choice.message.content, tagName);
|
|
167
164
|
if (extracted !== null) {
|
|
165
|
+
// Success: thinking tag found
|
|
168
166
|
console.log(`Extracted <${tagName}> block from response.`);
|
|
169
|
-
// Handle the edge case: append to existing reasoning if present.
|
|
167
|
+
// Handle the edge case: append to existing reasoning if present (e.g., native reasoning + thinking tags)
|
|
170
168
|
const existingReasoning = choice.reasoning || '';
|
|
171
|
-
// Only add a separator when appending to existing reasoning
|
|
172
169
|
if (existingReasoning) {
|
|
173
170
|
// Use a neutral markdown header that works for any consumer (human or AI)
|
|
174
171
|
choice.reasoning = `${existingReasoning}\n\n#### Additional Reasoning\n\n${extracted}`;
|
|
@@ -180,17 +177,24 @@ class LLMService {
|
|
|
180
177
|
choice.message.content = remaining;
|
|
181
178
|
}
|
|
182
179
|
else {
|
|
183
|
-
// Tag was not found
|
|
184
|
-
if (
|
|
180
|
+
// Tag was not found
|
|
181
|
+
// Enforce only if: (1) enforce: true AND (2) native reasoning is NOT active
|
|
182
|
+
if (fallbackSettings.enforce === true && !isNativeReasoningActive) {
|
|
183
|
+
const nativeReasoningCapable = modelInfo.reasoning?.supported === true;
|
|
185
184
|
return {
|
|
186
185
|
provider: providerId,
|
|
187
186
|
model: modelId,
|
|
188
187
|
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",
|
|
188
|
+
message: `Model response missing required <${tagName}> tags.`,
|
|
189
|
+
code: "THINKING_TAGS_MISSING",
|
|
193
190
|
type: "validation_error",
|
|
191
|
+
param: nativeReasoningCapable && !isNativeReasoningActive
|
|
192
|
+
? `You disabled native reasoning for this model (${modelId}). ` +
|
|
193
|
+
`To see its reasoning, you must prompt it to use <${tagName}> tags. ` +
|
|
194
|
+
`Example: "Write your step-by-step reasoning in <${tagName}> tags before answering."`
|
|
195
|
+
: `This model (${modelId}) does not support native reasoning. ` +
|
|
196
|
+
`To get reasoning, you must prompt it to use <${tagName}> tags. ` +
|
|
197
|
+
`Example: "Write your step-by-step reasoning in <${tagName}> tags before answering."`,
|
|
194
198
|
},
|
|
195
199
|
object: "error",
|
|
196
200
|
partialResponse: {
|
|
@@ -203,10 +207,7 @@ class LLMService {
|
|
|
203
207
|
}
|
|
204
208
|
};
|
|
205
209
|
}
|
|
206
|
-
|
|
207
|
-
console.warn(`Expected <${tagName}> tag was not found in the response from model ${modelId}.`);
|
|
208
|
-
}
|
|
209
|
-
// If 'ignore', do nothing
|
|
210
|
+
// If enforce: false or native reasoning is active, do nothing
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
213
|
}
|
|
@@ -262,19 +263,45 @@ class LLMService {
|
|
|
262
263
|
* injection, and role tag parsing into a single, intuitive API. It replaces the need
|
|
263
264
|
* to chain prepareMessage and buildMessagesFromTemplate for model-aware multi-turn prompts.
|
|
264
265
|
*
|
|
266
|
+
* **Model Context Injection:**
|
|
267
|
+
* When a presetId or providerId/modelId is provided, this method automatically injects
|
|
268
|
+
* model context variables into your templates:
|
|
269
|
+
* - `native_reasoning_active`: Whether native reasoning is currently active
|
|
270
|
+
* - `native_reasoning_capable`: Whether the model supports native reasoning
|
|
271
|
+
* - `requires_tags_for_thinking`: Whether thinking tags are needed (true when native reasoning not active)
|
|
272
|
+
* - `model_id`, `provider_id`, `reasoning_effort`, `reasoning_max_tokens`
|
|
273
|
+
*
|
|
274
|
+
* **Best Practice for Thinking Tags:**
|
|
275
|
+
* When adding thinking tag instructions, use requires_tags_for_thinking:
|
|
276
|
+
* `{{ requires_tags_for_thinking ? 'Write your reasoning in <thinking> tags first.' : '' }}`
|
|
277
|
+
*
|
|
265
278
|
* @param options Options for creating messages
|
|
266
|
-
* @returns Promise resolving to parsed messages and
|
|
279
|
+
* @returns Promise resolving to parsed messages, model context, and template settings
|
|
267
280
|
*
|
|
268
281
|
* @example
|
|
269
282
|
* ```typescript
|
|
283
|
+
* // Basic usage
|
|
270
284
|
* const { messages } = await llm.createMessages({
|
|
271
285
|
* template: `
|
|
272
|
-
* <SYSTEM>You are a
|
|
286
|
+
* <SYSTEM>You are a helpful assistant.</SYSTEM>
|
|
273
287
|
* <USER>Help me with {{ task }}</USER>
|
|
274
288
|
* `,
|
|
275
289
|
* variables: { task: 'understanding async/await' },
|
|
276
290
|
* presetId: 'openai-gpt-4.1-default'
|
|
277
291
|
* });
|
|
292
|
+
*
|
|
293
|
+
* // Model-aware template with thinking tags
|
|
294
|
+
* const { messages, modelContext } = await llm.createMessages({
|
|
295
|
+
* template: `
|
|
296
|
+
* <SYSTEM>
|
|
297
|
+
* You are a problem-solving assistant.
|
|
298
|
+
* {{ requires_tags_for_thinking ? 'For complex problems, write your reasoning in <thinking> tags first.' : '' }}
|
|
299
|
+
* </SYSTEM>
|
|
300
|
+
* <USER>{{ question }}</USER>
|
|
301
|
+
* `,
|
|
302
|
+
* variables: { question: 'Explain recursion' },
|
|
303
|
+
* presetId: 'anthropic-claude-3-7-sonnet-20250219-thinking'
|
|
304
|
+
* });
|
|
278
305
|
* ```
|
|
279
306
|
*/
|
|
280
307
|
async createMessages(options) {
|
|
@@ -290,7 +317,8 @@ class LLMService {
|
|
|
290
317
|
const resolved = this.modelResolver.resolve({
|
|
291
318
|
presetId: options.presetId,
|
|
292
319
|
providerId: options.providerId,
|
|
293
|
-
modelId: options.modelId
|
|
320
|
+
modelId: options.modelId,
|
|
321
|
+
settings: options.settings
|
|
294
322
|
});
|
|
295
323
|
if (resolved.error) {
|
|
296
324
|
// If resolution fails, proceed without model context
|
|
@@ -300,12 +328,15 @@ class LLMService {
|
|
|
300
328
|
const { providerId, modelId, modelInfo, settings } = resolved;
|
|
301
329
|
// Merge settings with model defaults
|
|
302
330
|
const mergedSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, settings || {});
|
|
303
|
-
//
|
|
331
|
+
// Calculate native reasoning status
|
|
332
|
+
const nativeReasoningActive = !!(modelInfo.reasoning?.supported &&
|
|
333
|
+
(mergedSettings.reasoning?.enabled === true ||
|
|
334
|
+
(modelInfo.reasoning?.enabledByDefault && mergedSettings.reasoning?.enabled !== false)));
|
|
335
|
+
// Create model context with new property names
|
|
304
336
|
modelContext = {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
thinking_available: !!modelInfo.reasoning?.supported,
|
|
337
|
+
native_reasoning_active: nativeReasoningActive,
|
|
338
|
+
native_reasoning_capable: !!modelInfo.reasoning?.supported,
|
|
339
|
+
requires_tags_for_thinking: !nativeReasoningActive,
|
|
309
340
|
model_id: modelId,
|
|
310
341
|
provider_id: providerId,
|
|
311
342
|
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
|
}
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -43,28 +43,45 @@ export interface LLMReasoningSettings {
|
|
|
43
43
|
exclude?: boolean;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Settings for extracting
|
|
46
|
+
* Settings for extracting reasoning from XML tags when native reasoning is not active.
|
|
47
|
+
*
|
|
48
|
+
* This is a fallback mechanism for getting reasoning from:
|
|
49
|
+
* 1. Models without native reasoning support (e.g., GPT-4, Claude 3.5)
|
|
50
|
+
* 2. Models with native reasoning disabled (to see the full reasoning trace)
|
|
51
|
+
*
|
|
52
|
+
* **Key use case:** Disable native reasoning on capable models to avoid obfuscation
|
|
53
|
+
* by providers, then prompt the model to use <thinking> tags for full visibility.
|
|
54
|
+
*
|
|
55
|
+
* **Important:** You must explicitly prompt the model to use thinking tags in your prompt.
|
|
56
|
+
* The library only extracts them - it doesn't generate them automatically.
|
|
47
57
|
*/
|
|
48
|
-
export interface
|
|
58
|
+
export interface LLMThinkingTagFallbackSettings {
|
|
49
59
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
60
|
+
* Enable tag extraction fallback.
|
|
61
|
+
* When this object exists, extraction is enabled by default (enabled: true).
|
|
62
|
+
* Set to false to explicitly disable (useful for overriding inherited settings).
|
|
63
|
+
* @default true (when thinkingTagFallback object exists)
|
|
52
64
|
*/
|
|
53
65
|
enabled?: boolean;
|
|
54
66
|
/**
|
|
55
|
-
*
|
|
67
|
+
* Name of the XML tag to extract.
|
|
56
68
|
* @default 'thinking'
|
|
69
|
+
* @example tagName: 'scratchpad' will extract <scratchpad>...</scratchpad>
|
|
57
70
|
*/
|
|
58
|
-
|
|
71
|
+
tagName?: string;
|
|
59
72
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* -
|
|
64
|
-
* -
|
|
65
|
-
*
|
|
73
|
+
* Enforce that thinking tags are present when native reasoning is not active.
|
|
74
|
+
*
|
|
75
|
+
* When true:
|
|
76
|
+
* - If native reasoning is active: No enforcement (model using native)
|
|
77
|
+
* - If native reasoning is NOT active: Error if tags missing (fallback required)
|
|
78
|
+
*
|
|
79
|
+
* This is always "smart" - it automatically detects whether native reasoning
|
|
80
|
+
* is active and only enforces when the model needs to use tags as a fallback.
|
|
81
|
+
*
|
|
82
|
+
* @default false
|
|
66
83
|
*/
|
|
67
|
-
|
|
84
|
+
enforce?: boolean;
|
|
68
85
|
}
|
|
69
86
|
/**
|
|
70
87
|
* Configurable settings for LLM requests
|
|
@@ -91,10 +108,19 @@ export interface LLMSettings {
|
|
|
91
108
|
/** Universal reasoning/thinking configuration */
|
|
92
109
|
reasoning?: LLMReasoningSettings;
|
|
93
110
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
111
|
+
* Extract reasoning from XML tags when native reasoning is not active.
|
|
112
|
+
*
|
|
113
|
+
* This is a fallback mechanism for getting reasoning from:
|
|
114
|
+
* 1. Models without native reasoning support (e.g., GPT-4, Claude 3.5)
|
|
115
|
+
* 2. Models with native reasoning disabled (to see the full reasoning trace)
|
|
116
|
+
*
|
|
117
|
+
* Key use case: Disable native reasoning on capable models to avoid obfuscation
|
|
118
|
+
* by providers, then prompt the model to use <thinking> tags for full visibility.
|
|
119
|
+
*
|
|
120
|
+
* Note: You must explicitly prompt the model to use thinking tags in your prompt.
|
|
121
|
+
* The library only extracts them - it doesn't generate them automatically.
|
|
96
122
|
*/
|
|
97
|
-
|
|
123
|
+
thinkingTagFallback?: LLMThinkingTagFallbackSettings;
|
|
98
124
|
}
|
|
99
125
|
/**
|
|
100
126
|
* Request structure for chat completion
|
|
@@ -252,18 +278,51 @@ export declare const LLM_IPC_CHANNELS: {
|
|
|
252
278
|
*/
|
|
253
279
|
export type LLMIPCChannelName = (typeof LLM_IPC_CHANNELS)[keyof typeof LLM_IPC_CHANNELS];
|
|
254
280
|
/**
|
|
255
|
-
* Model context variables injected into templates
|
|
281
|
+
* Model context variables injected into templates during createMessages()
|
|
282
|
+
*
|
|
283
|
+
* These variables enable templates to adapt based on the model's reasoning capabilities.
|
|
284
|
+
*
|
|
285
|
+
* **Key Usage Pattern:**
|
|
286
|
+
* When adding thinking tag instructions, use requires_tags_for_thinking:
|
|
287
|
+
* ```
|
|
288
|
+
* {{ requires_tags_for_thinking ? 'Write your reasoning in <thinking> tags first.' : '' }}
|
|
289
|
+
* ```
|
|
290
|
+
*
|
|
291
|
+
* This ensures:
|
|
292
|
+
* - Models with active native reasoning get clean prompts
|
|
293
|
+
* - Models without native reasoning get explicit tag instructions
|
|
256
294
|
*/
|
|
257
295
|
export interface ModelContext {
|
|
258
|
-
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
296
|
+
/**
|
|
297
|
+
* Whether native reasoning is CURRENTLY ACTIVE for this request.
|
|
298
|
+
* - true: Model is using built-in reasoning (Claude 4, o4-mini, Gemini with reasoning enabled)
|
|
299
|
+
* - false: No native reasoning is active (model doesn't support it OR it's been disabled)
|
|
300
|
+
*
|
|
301
|
+
* Use in templates when adapting behavior based on whether native reasoning is happening.
|
|
302
|
+
*/
|
|
303
|
+
native_reasoning_active: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Whether the model HAS THE CAPABILITY to use native reasoning.
|
|
306
|
+
* - true: Model supports native reasoning (may or may not be enabled)
|
|
307
|
+
* - false: Model does not support native reasoning
|
|
308
|
+
*
|
|
309
|
+
* Use in templates to check if native reasoning is possible (not necessarily active).
|
|
310
|
+
*/
|
|
311
|
+
native_reasoning_capable: boolean;
|
|
312
|
+
/**
|
|
313
|
+
* Whether this model/request requires thinking tags to produce reasoning.
|
|
314
|
+
* - true: Native reasoning is not active, model needs prompting to use <thinking> tags
|
|
315
|
+
* - false: Native reasoning is active, no need for thinking tags
|
|
316
|
+
*
|
|
317
|
+
* Use in templates for conditional thinking tag instructions:
|
|
318
|
+
* {{ requires_tags_for_thinking ? 'Write your reasoning in <thinking> tags first.' : '' }}
|
|
319
|
+
*/
|
|
320
|
+
requires_tags_for_thinking: boolean;
|
|
262
321
|
/** The resolved model ID */
|
|
263
322
|
model_id: string;
|
|
264
323
|
/** The resolved provider ID */
|
|
265
324
|
provider_id: string;
|
|
266
|
-
/** Reasoning effort level if specified */
|
|
325
|
+
/** Reasoning effort level if specified ('low', 'medium', or 'high') */
|
|
267
326
|
reasoning_effort?: string;
|
|
268
327
|
/** Reasoning max tokens if specified */
|
|
269
328
|
reasoning_max_tokens?: number;
|