genai-lite 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +210 -207
- package/dist/config/llm-presets.json +24 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/llm/LLMService.d.ts +30 -1
- package/dist/llm/LLMService.js +42 -4
- package/dist/llm/clients/AnthropicClientAdapter.d.ts +2 -2
- package/dist/llm/clients/AnthropicClientAdapter.js +17 -3
- package/dist/llm/clients/GeminiClientAdapter.d.ts +2 -2
- package/dist/llm/clients/GeminiClientAdapter.js +14 -3
- package/dist/llm/clients/LlamaCppClientAdapter.d.ts +4 -4
- package/dist/llm/clients/LlamaCppClientAdapter.js +118 -15
- package/dist/llm/clients/MistralClientAdapter.d.ts +2 -2
- package/dist/llm/clients/MistralClientAdapter.js +15 -4
- package/dist/llm/clients/MockClientAdapter.d.ts +2 -2
- package/dist/llm/clients/MockClientAdapter.js +36 -19
- package/dist/llm/clients/OpenAIClientAdapter.d.ts +2 -2
- package/dist/llm/clients/OpenAIClientAdapter.js +26 -3
- package/dist/llm/clients/OpenRouterClientAdapter.d.ts +2 -2
- package/dist/llm/clients/OpenRouterClientAdapter.js +81 -10
- package/dist/llm/clients/types.d.ts +18 -1
- package/dist/llm/clients/types.js +4 -0
- package/dist/llm/config.d.ts +9 -2
- package/dist/llm/config.js +697 -33
- package/dist/llm/services/ModelResolver.d.ts +6 -0
- package/dist/llm/services/ModelResolver.js +57 -17
- package/dist/llm/services/SettingsManager.d.ts +1 -1
- package/dist/llm/services/SettingsManager.js +92 -4
- package/dist/llm/types.d.ts +127 -0
- package/dist/prompting/index.d.ts +1 -1
- package/dist/prompting/index.js +2 -1
- package/dist/prompting/parser.d.ts +23 -0
- package/dist/prompting/parser.js +33 -0
- package/dist/shared/adapters/errorUtils.d.ts +15 -0
- package/dist/shared/adapters/errorUtils.js +69 -1
- package/dist/shared/adapters/logprobsUtils.d.ts +13 -0
- package/dist/shared/adapters/logprobsUtils.js +33 -0
- package/dist/shared/services/withRetry.d.ts +50 -0
- package/dist/shared/services/withRetry.js +78 -0
- package/package.json +59 -59
- package/src/config/image-presets.json +212 -212
- package/src/config/llm-presets.json +623 -599
|
@@ -38,4 +38,10 @@ export declare class ModelResolver {
|
|
|
38
38
|
* @returns Resolved model info and settings or error response
|
|
39
39
|
*/
|
|
40
40
|
resolve(options: ModelSelectionOptions): Promise<ModelResolution>;
|
|
41
|
+
/**
|
|
42
|
+
* Asks the llama.cpp adapter which GGUF model the server has loaded and returns
|
|
43
|
+
* its detected capabilities (cached inside the adapter). Returns undefined when
|
|
44
|
+
* the server is unreachable or the model is not recognized.
|
|
45
|
+
*/
|
|
46
|
+
private detectLlamaCppCapabilities;
|
|
41
47
|
}
|
|
@@ -36,7 +36,7 @@ class ModelResolver {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
let modelInfo = (0, config_1.getModelById)(preset.modelId, preset.providerId);
|
|
40
40
|
if (!modelInfo) {
|
|
41
41
|
return {
|
|
42
42
|
error: {
|
|
@@ -51,6 +51,14 @@ class ModelResolver {
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
// Overlay detected GGUF capabilities for llama.cpp presets (the server decides
|
|
55
|
+
// which model is actually loaded, regardless of the preset's modelId)
|
|
56
|
+
if (preset.providerId === 'llamacpp') {
|
|
57
|
+
const detected = await this.detectLlamaCppCapabilities();
|
|
58
|
+
if (detected) {
|
|
59
|
+
modelInfo = { ...modelInfo, ...detected };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
54
62
|
// Merge preset settings with user settings
|
|
55
63
|
const settings = {
|
|
56
64
|
...preset.settings,
|
|
@@ -94,24 +102,36 @@ class ModelResolver {
|
|
|
94
102
|
};
|
|
95
103
|
}
|
|
96
104
|
let modelInfo = (0, config_1.getModelById)(options.modelId, options.providerId);
|
|
97
|
-
|
|
105
|
+
// For llamacpp, try to detect the loaded GGUF model's capabilities from the
|
|
106
|
+
// adapter. This runs even when the registry lookup succeeded (the documented
|
|
107
|
+
// `modelId: 'llamacpp'` usage matches the generic registry entry): the server
|
|
108
|
+
// decides which model is actually loaded, so detected capabilities and vendor
|
|
109
|
+
// default settings must overlay whatever the registry says.
|
|
110
|
+
let detectedCapabilities;
|
|
111
|
+
if (options.providerId === 'llamacpp') {
|
|
112
|
+
detectedCapabilities = await this.detectLlamaCppCapabilities();
|
|
113
|
+
}
|
|
114
|
+
if (modelInfo) {
|
|
115
|
+
if (detectedCapabilities) {
|
|
116
|
+
// Overlay detected capabilities onto the registry entry (detection wins:
|
|
117
|
+
// it reflects the model actually loaded on the server)
|
|
118
|
+
modelInfo = { ...modelInfo, ...detectedCapabilities };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
98
122
|
// Check if provider allows unknown models
|
|
99
123
|
const provider = (0, config_1.getProviderById)(options.providerId);
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
this.logger.warn('Failed to detect GGUF model capabilities:', error);
|
|
113
|
-
// Continue with fallback
|
|
114
|
-
}
|
|
124
|
+
// Unknown OpenRouter models: assume reasoning-capable (optimistic). OpenRouter
|
|
125
|
+
// ignores the reasoning param for models that don't support it, and rejecting
|
|
126
|
+
// here would block reasoning on all unregistered OpenRouter models.
|
|
127
|
+
if (options.providerId === 'openrouter' && !detectedCapabilities) {
|
|
128
|
+
detectedCapabilities = {
|
|
129
|
+
reasoning: {
|
|
130
|
+
supported: true,
|
|
131
|
+
enabledByDefault: false,
|
|
132
|
+
canDisable: true,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
115
135
|
}
|
|
116
136
|
if (provider?.allowUnknownModels) {
|
|
117
137
|
// Flexible provider (e.g., llamacpp) - silent fallback with detected capabilities
|
|
@@ -131,5 +151,25 @@ class ModelResolver {
|
|
|
131
151
|
settings: options.settings
|
|
132
152
|
};
|
|
133
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Asks the llama.cpp adapter which GGUF model the server has loaded and returns
|
|
156
|
+
* its detected capabilities (cached inside the adapter). Returns undefined when
|
|
157
|
+
* the server is unreachable or the model is not recognized.
|
|
158
|
+
*/
|
|
159
|
+
async detectLlamaCppCapabilities() {
|
|
160
|
+
try {
|
|
161
|
+
const adapter = this.adapterRegistry.getAdapter('llamacpp');
|
|
162
|
+
// Check if adapter has the getModelCapabilities method
|
|
163
|
+
if (adapter && typeof adapter.getModelCapabilities === 'function') {
|
|
164
|
+
const capabilities = await adapter.getModelCapabilities();
|
|
165
|
+
return capabilities || undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.logger.warn('Failed to detect GGUF model capabilities:', error);
|
|
170
|
+
// Continue with fallback
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
134
174
|
}
|
|
135
175
|
exports.ModelResolver = ModelResolver;
|
|
@@ -14,7 +14,7 @@ export declare class SettingsManager {
|
|
|
14
14
|
* @param requestSettings - Settings from the request
|
|
15
15
|
* @returns Complete settings object with all required fields
|
|
16
16
|
*/
|
|
17
|
-
mergeSettingsForModel(modelId: string, providerId: ApiProviderId, requestSettings?: Partial<LLMSettings
|
|
17
|
+
mergeSettingsForModel(modelId: string, providerId: ApiProviderId, requestSettings?: Partial<LLMSettings>, modelInfo?: ModelInfo): Required<LLMSettings>;
|
|
18
18
|
/**
|
|
19
19
|
* Filters out unsupported parameters based on model and provider configuration
|
|
20
20
|
*
|
|
@@ -18,9 +18,20 @@ class SettingsManager {
|
|
|
18
18
|
* @param requestSettings - Settings from the request
|
|
19
19
|
* @returns Complete settings object with all required fields
|
|
20
20
|
*/
|
|
21
|
-
mergeSettingsForModel(modelId, providerId, requestSettings) {
|
|
22
|
-
// Get model-specific defaults
|
|
23
|
-
|
|
21
|
+
mergeSettingsForModel(modelId, providerId, requestSettings, modelInfo) {
|
|
22
|
+
// Get model-specific defaults (threading the resolved ModelInfo lets detected
|
|
23
|
+
// GGUF/unknown models contribute capabilities and vendor default settings)
|
|
24
|
+
let modelDefaults = (0, config_1.getDefaultSettingsForModel)(modelId, providerId, modelInfo);
|
|
25
|
+
// Apply the reasoning-mode default overlay when reasoning is effectively on for
|
|
26
|
+
// this request (either explicitly requested or enabled by model default).
|
|
27
|
+
// Per-key precedence: request > reasoningDefaultSettings > defaultSettings.
|
|
28
|
+
const reasoningOn = requestSettings?.reasoning?.enabled ?? modelDefaults.reasoning?.enabled;
|
|
29
|
+
if (reasoningOn === true && modelInfo?.reasoningDefaultSettings) {
|
|
30
|
+
modelDefaults = {
|
|
31
|
+
...modelDefaults,
|
|
32
|
+
...modelInfo.reasoningDefaultSettings,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
24
35
|
// Merge with user-provided settings (user settings take precedence)
|
|
25
36
|
const mergedSettings = {
|
|
26
37
|
temperature: requestSettings?.temperature ?? modelDefaults.temperature,
|
|
@@ -29,6 +40,10 @@ class SettingsManager {
|
|
|
29
40
|
stopSequences: requestSettings?.stopSequences ?? modelDefaults.stopSequences,
|
|
30
41
|
frequencyPenalty: requestSettings?.frequencyPenalty ?? modelDefaults.frequencyPenalty,
|
|
31
42
|
presencePenalty: requestSettings?.presencePenalty ?? modelDefaults.presencePenalty,
|
|
43
|
+
topK: requestSettings?.topK ?? modelDefaults.topK,
|
|
44
|
+
minP: requestSettings?.minP ?? modelDefaults.minP,
|
|
45
|
+
repeatPenalty: requestSettings?.repeatPenalty ?? modelDefaults.repeatPenalty,
|
|
46
|
+
seed: requestSettings?.seed ?? modelDefaults.seed,
|
|
32
47
|
user: requestSettings?.user ?? modelDefaults.user,
|
|
33
48
|
supportsSystemMessage: requestSettings?.supportsSystemMessage ??
|
|
34
49
|
modelDefaults.supportsSystemMessage,
|
|
@@ -48,6 +63,14 @@ class SettingsManager {
|
|
|
48
63
|
},
|
|
49
64
|
openRouterProvider: requestSettings?.openRouterProvider ?? modelDefaults.openRouterProvider,
|
|
50
65
|
structuredOutput: requestSettings?.structuredOutput ?? modelDefaults.structuredOutput,
|
|
66
|
+
logprobs: requestSettings?.logprobs ?? modelDefaults.logprobs,
|
|
67
|
+
topLogprobs: requestSettings?.topLogprobs ?? modelDefaults.topLogprobs,
|
|
68
|
+
llamacpp: requestSettings?.llamacpp || modelDefaults.llamacpp
|
|
69
|
+
? {
|
|
70
|
+
...modelDefaults.llamacpp,
|
|
71
|
+
...requestSettings?.llamacpp,
|
|
72
|
+
}
|
|
73
|
+
: undefined,
|
|
51
74
|
};
|
|
52
75
|
// Log the final settings for debugging
|
|
53
76
|
this.logger.debug(`Merged settings for ${providerId}/${modelId}:`, {
|
|
@@ -121,12 +144,19 @@ class SettingsManager {
|
|
|
121
144
|
'stopSequences',
|
|
122
145
|
'frequencyPenalty',
|
|
123
146
|
'presencePenalty',
|
|
147
|
+
'topK',
|
|
148
|
+
'minP',
|
|
149
|
+
'repeatPenalty',
|
|
150
|
+
'seed',
|
|
124
151
|
'user',
|
|
125
152
|
'supportsSystemMessage',
|
|
126
153
|
'geminiSafetySettings',
|
|
127
154
|
'reasoning',
|
|
128
155
|
'thinkingTagFallback',
|
|
129
|
-
'structuredOutput'
|
|
156
|
+
'structuredOutput',
|
|
157
|
+
'logprobs',
|
|
158
|
+
'topLogprobs',
|
|
159
|
+
'llamacpp'
|
|
130
160
|
];
|
|
131
161
|
// Check each setting field
|
|
132
162
|
for (const [key, value] of Object.entries(settings)) {
|
|
@@ -166,6 +196,64 @@ class SettingsManager {
|
|
|
166
196
|
continue;
|
|
167
197
|
}
|
|
168
198
|
}
|
|
199
|
+
if (key === 'topK') {
|
|
200
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
201
|
+
this.logger.warn(`Invalid topK value in template: ${value}. Must be a non-negative integer.`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (key === 'minP') {
|
|
206
|
+
if (typeof value !== 'number' || value < 0 || value > 1) {
|
|
207
|
+
this.logger.warn(`Invalid minP value in template: ${value}. Must be a number between 0 and 1.`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (key === 'repeatPenalty') {
|
|
212
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
213
|
+
this.logger.warn(`Invalid repeatPenalty value in template: ${value}. Must be a positive number.`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (key === 'seed') {
|
|
218
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
219
|
+
this.logger.warn(`Invalid seed value in template: ${value}. Must be an integer.`);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (key === 'logprobs' && typeof value !== 'boolean') {
|
|
224
|
+
this.logger.warn(`Invalid logprobs value in template. Must be a boolean.`);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (key === 'topLogprobs') {
|
|
228
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > 20) {
|
|
229
|
+
this.logger.warn(`Invalid topLogprobs value in template: ${value}. Must be an integer between 0 and 20.`);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (key === 'llamacpp' && typeof value === 'object' && value !== null) {
|
|
234
|
+
const llamacppValidated = {};
|
|
235
|
+
const v = value;
|
|
236
|
+
if ('grammar' in v && typeof v.grammar !== 'string') {
|
|
237
|
+
this.logger.warn(`Invalid llamacpp.grammar value in template. Must be a string.`);
|
|
238
|
+
}
|
|
239
|
+
else if ('grammar' in v) {
|
|
240
|
+
llamacppValidated.grammar = v.grammar;
|
|
241
|
+
}
|
|
242
|
+
if ('chatTemplateKwargs' in v) {
|
|
243
|
+
if (typeof v.chatTemplateKwargs !== 'object' ||
|
|
244
|
+
v.chatTemplateKwargs === null ||
|
|
245
|
+
Array.isArray(v.chatTemplateKwargs)) {
|
|
246
|
+
this.logger.warn(`Invalid llamacpp.chatTemplateKwargs value in template. Must be an object.`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
llamacppValidated.chatTemplateKwargs = v.chatTemplateKwargs;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (Object.keys(llamacppValidated).length > 0) {
|
|
253
|
+
validated.llamacpp = llamacppValidated;
|
|
254
|
+
}
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
169
257
|
if (key === 'user' && typeof value !== 'string') {
|
|
170
258
|
this.logger.warn(`Invalid user value in template. Must be a string.`);
|
|
171
259
|
continue;
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -284,6 +284,32 @@ export interface LLMSettings {
|
|
|
284
284
|
frequencyPenalty?: number;
|
|
285
285
|
/** Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far */
|
|
286
286
|
presencePenalty?: number;
|
|
287
|
+
/**
|
|
288
|
+
* Limits sampling to the K most likely tokens (integer >= 0; 0 disables top-k filtering).
|
|
289
|
+
* Supported by: Anthropic (top_k), Gemini (topK), llama.cpp (top_k), OpenRouter (top_k).
|
|
290
|
+
* Not supported by OpenAI or Mistral (stripped automatically).
|
|
291
|
+
*/
|
|
292
|
+
topK?: number;
|
|
293
|
+
/**
|
|
294
|
+
* Minimum probability threshold relative to the most likely token (0.0 to 1.0; 0 disables).
|
|
295
|
+
* Note: llama.cpp's server default is 0.05, which matches no vendor recommendation —
|
|
296
|
+
* detected GGUF models get an explicit 0 by default.
|
|
297
|
+
* Supported by: llama.cpp (min_p), OpenRouter (min_p). Stripped for other providers.
|
|
298
|
+
*/
|
|
299
|
+
minP?: number;
|
|
300
|
+
/**
|
|
301
|
+
* Multiplicative repetition penalty over prompt + output tokens (1.0 = disabled).
|
|
302
|
+
* Distinct from presencePenalty (additive, output-only). For Qwen models prefer
|
|
303
|
+
* presencePenalty and keep repeatPenalty at 1.0 (vendor guidance).
|
|
304
|
+
* Supported by: llama.cpp (repeat_penalty), OpenRouter (repetition_penalty). Stripped elsewhere.
|
|
305
|
+
*/
|
|
306
|
+
repeatPenalty?: number;
|
|
307
|
+
/**
|
|
308
|
+
* Seed for (best-effort) deterministic sampling. Integer; llama.cpp treats -1 as random.
|
|
309
|
+
* Supported by: OpenAI (beta; ignored by reasoning models), Gemini, Mistral (randomSeed),
|
|
310
|
+
* llama.cpp, OpenRouter. Not supported by Anthropic (stripped automatically).
|
|
311
|
+
*/
|
|
312
|
+
seed?: number;
|
|
287
313
|
/** A unique identifier representing your end-user, which can help monitor and detect abuse */
|
|
288
314
|
user?: string;
|
|
289
315
|
/** Whether the LLM supports system message (almost all LLMs do nowadays) */
|
|
@@ -328,6 +354,57 @@ export interface LLMSettings {
|
|
|
328
354
|
* @see StructuredOutputSettings
|
|
329
355
|
*/
|
|
330
356
|
structuredOutput?: StructuredOutputSettings;
|
|
357
|
+
/**
|
|
358
|
+
* Request per-token log probabilities on the response.
|
|
359
|
+
* Supported by: llama.cpp, OpenAI, OpenRouter (pass-through; model-dependent).
|
|
360
|
+
* Stripped for Anthropic/Gemini/Mistral. Results land on `choice.logprobs`.
|
|
361
|
+
*/
|
|
362
|
+
logprobs?: boolean;
|
|
363
|
+
/**
|
|
364
|
+
* Number of most-likely alternatives to return per token (0-20).
|
|
365
|
+
* Requires `logprobs: true`.
|
|
366
|
+
*/
|
|
367
|
+
topLogprobs?: number;
|
|
368
|
+
/**
|
|
369
|
+
* llama.cpp-specific settings.
|
|
370
|
+
* Only used when providerId is 'llamacpp'; ignored by other adapters.
|
|
371
|
+
* @see LlamaCppSettings
|
|
372
|
+
*/
|
|
373
|
+
llamacpp?: LlamaCppSettings;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* llama.cpp-specific request settings
|
|
377
|
+
*/
|
|
378
|
+
export interface LlamaCppSettings {
|
|
379
|
+
/**
|
|
380
|
+
* Raw GBNF grammar string for constrained decoding.
|
|
381
|
+
* Mutually exclusive with `structuredOutput` (llama-server rejects both:
|
|
382
|
+
* "Either 'json_schema' or 'grammar' can be specified, but not both").
|
|
383
|
+
* Caveat: current llama.cpp builds may not apply the grammar while thinking
|
|
384
|
+
* is active — prefer grammar with reasoning disabled.
|
|
385
|
+
*/
|
|
386
|
+
grammar?: string;
|
|
387
|
+
/**
|
|
388
|
+
* Extra chat-template kwargs forwarded verbatim to llama-server (requires --jinja).
|
|
389
|
+
* Merged over any library-derived kwargs (e.g. the reasoning toggle's
|
|
390
|
+
* enable_thinking), so explicit values here always win.
|
|
391
|
+
*/
|
|
392
|
+
chatTemplateKwargs?: Record<string, string | number | boolean>;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Per-token log probability entry (OpenAI-compatible shape, also produced by
|
|
396
|
+
* llama.cpp's chat completions endpoint).
|
|
397
|
+
*/
|
|
398
|
+
export interface TokenLogprob {
|
|
399
|
+
/** The generated token text */
|
|
400
|
+
token: string;
|
|
401
|
+
/** Log probability of the token */
|
|
402
|
+
logprob: number;
|
|
403
|
+
/** Most-likely alternative tokens at this position (when topLogprobs requested) */
|
|
404
|
+
topLogprobs?: Array<{
|
|
405
|
+
token: string;
|
|
406
|
+
logprob: number;
|
|
407
|
+
}>;
|
|
331
408
|
}
|
|
332
409
|
/**
|
|
333
410
|
* Request structure for chat completion
|
|
@@ -361,6 +438,8 @@ export interface LLMChoice {
|
|
|
361
438
|
reasoning?: string;
|
|
362
439
|
/** Provider-specific reasoning details that need to be preserved */
|
|
363
440
|
reasoning_details?: any;
|
|
441
|
+
/** Per-token log probabilities (when settings.logprobs was requested and supported) */
|
|
442
|
+
logprobs?: TokenLogprob[];
|
|
364
443
|
/**
|
|
365
444
|
* Parsed JSON content when structuredOutput is enabled and autoParse is true.
|
|
366
445
|
* Contains the parsed object/array from the JSON response.
|
|
@@ -400,6 +479,10 @@ export interface LLMError {
|
|
|
400
479
|
code?: string | number;
|
|
401
480
|
type?: string;
|
|
402
481
|
param?: string;
|
|
482
|
+
/** HTTP status code from the provider, when available */
|
|
483
|
+
status?: number;
|
|
484
|
+
/** Provider-suggested wait before retrying in ms (from a Retry-After header) */
|
|
485
|
+
retryAfterMs?: number;
|
|
403
486
|
providerError?: any;
|
|
404
487
|
}
|
|
405
488
|
/**
|
|
@@ -482,6 +565,50 @@ export interface ModelInfo {
|
|
|
482
565
|
unsupportedParameters?: (keyof LLMSettings)[];
|
|
483
566
|
/** Structured output capabilities */
|
|
484
567
|
structuredOutput?: ModelStructuredOutputCapabilities;
|
|
568
|
+
/**
|
|
569
|
+
* Model-specific default settings (e.g. vendor-recommended sampling parameters).
|
|
570
|
+
* Merged below request settings: DEFAULT < provider < MODEL_DEFAULT_SETTINGS <
|
|
571
|
+
* defaultSettings < request. Used heavily by detected GGUF models, whose vendors
|
|
572
|
+
* publish sampling profiles that differ from llama.cpp's server defaults.
|
|
573
|
+
*/
|
|
574
|
+
defaultSettings?: Partial<LLMSettings>;
|
|
575
|
+
/**
|
|
576
|
+
* Additional default-settings overlay applied only when reasoning/thinking is
|
|
577
|
+
* active for the request (e.g. Qwen recommends a different sampling profile in
|
|
578
|
+
* thinking mode). Per-key precedence: request > reasoningDefaultSettings > defaultSettings.
|
|
579
|
+
*/
|
|
580
|
+
reasoningDefaultSettings?: Partial<LLMSettings>;
|
|
581
|
+
/**
|
|
582
|
+
* Local-model (llama.cpp) reasoning-toggle and output-cleanup metadata.
|
|
583
|
+
* Battle-tested constants sourced from real chat-template behavior.
|
|
584
|
+
*/
|
|
585
|
+
localReasoning?: LocalReasoningMetadata;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Metadata describing how a local (GGUF/llama.cpp) model's chat template handles
|
|
589
|
+
* thinking mode, and how to clean its output.
|
|
590
|
+
*/
|
|
591
|
+
export interface LocalReasoningMetadata {
|
|
592
|
+
/**
|
|
593
|
+
* Name of the chat-template kwarg that toggles thinking (e.g. "enable_thinking").
|
|
594
|
+
* When set, the llama.cpp adapter sends `chat_template_kwargs: { [toggleKwarg]: <bool> }`
|
|
595
|
+
* derived from `settings.reasoning.enabled` (explicitly false when reasoning is not
|
|
596
|
+
* requested). Requires the server to run with `--jinja`. Omit for models whose
|
|
597
|
+
* thinking cannot be toggled (always-on reasoning models).
|
|
598
|
+
*/
|
|
599
|
+
toggleKwarg?: string;
|
|
600
|
+
/**
|
|
601
|
+
* Exact prefix some chat templates inject into response content when thinking is
|
|
602
|
+
* disabled (e.g. Qwen's "<think>\n\n</think>\n\n"). Stripped verbatim (exact
|
|
603
|
+
* startsWith match) from message content before the response is returned.
|
|
604
|
+
*/
|
|
605
|
+
nothinkPrefix?: string;
|
|
606
|
+
/**
|
|
607
|
+
* Open/close marker pair used to extract a reasoning trace from message content
|
|
608
|
+
* when the server does not populate `reasoning_content` (model/template dependent).
|
|
609
|
+
* Only fully-closed pairs are extracted.
|
|
610
|
+
*/
|
|
611
|
+
markers?: [string, string];
|
|
485
612
|
}
|
|
486
613
|
/**
|
|
487
614
|
* IPC channel names for LLM operations
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { renderTemplate } from './template';
|
|
10
10
|
export { countTokens, getSmartPreview, extractRandomVariables } from './content';
|
|
11
|
-
export { parseStructuredContent, extractInitialTaggedContent, parseRoleTags, parseTemplateWithMetadata } from './parser';
|
|
11
|
+
export { parseStructuredContent, extractInitialTaggedContent, extractMarkerDelimitedContent, parseRoleTags, parseTemplateWithMetadata } from './parser';
|
|
12
12
|
export type { TemplateMetadata } from './parser';
|
package/dist/prompting/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Response parsing for extracting structured data from LLM outputs
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.parseTemplateWithMetadata = exports.parseRoleTags = exports.extractInitialTaggedContent = exports.parseStructuredContent = exports.extractRandomVariables = exports.getSmartPreview = exports.countTokens = exports.renderTemplate = void 0;
|
|
11
|
+
exports.parseTemplateWithMetadata = exports.parseRoleTags = exports.extractMarkerDelimitedContent = exports.extractInitialTaggedContent = exports.parseStructuredContent = exports.extractRandomVariables = exports.getSmartPreview = exports.countTokens = exports.renderTemplate = void 0;
|
|
12
12
|
// Template rendering
|
|
13
13
|
var template_1 = require("./template");
|
|
14
14
|
Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
|
|
@@ -21,5 +21,6 @@ Object.defineProperty(exports, "extractRandomVariables", { enumerable: true, get
|
|
|
21
21
|
var parser_1 = require("./parser");
|
|
22
22
|
Object.defineProperty(exports, "parseStructuredContent", { enumerable: true, get: function () { return parser_1.parseStructuredContent; } });
|
|
23
23
|
Object.defineProperty(exports, "extractInitialTaggedContent", { enumerable: true, get: function () { return parser_1.extractInitialTaggedContent; } });
|
|
24
|
+
Object.defineProperty(exports, "extractMarkerDelimitedContent", { enumerable: true, get: function () { return parser_1.extractMarkerDelimitedContent; } });
|
|
24
25
|
Object.defineProperty(exports, "parseRoleTags", { enumerable: true, get: function () { return parser_1.parseRoleTags; } });
|
|
25
26
|
Object.defineProperty(exports, "parseTemplateWithMetadata", { enumerable: true, get: function () { return parser_1.parseTemplateWithMetadata; } });
|
|
@@ -59,6 +59,29 @@ export declare function extractInitialTaggedContent(content: string, tagName: st
|
|
|
59
59
|
extracted: string | null;
|
|
60
60
|
remaining: string;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Extracts content delimited by an arbitrary open/close marker pair from anywhere
|
|
64
|
+
* in a string.
|
|
65
|
+
*
|
|
66
|
+
* Unlike {@link extractInitialTaggedContent}, the markers are free-form strings
|
|
67
|
+
* (not derived from an XML tag name) and may appear anywhere in the content —
|
|
68
|
+
* useful for local-model thinking traces whose markers aren't XML-shaped
|
|
69
|
+
* (e.g. Gemma 4's `<|channel>thought` … `<channel|>`).
|
|
70
|
+
*
|
|
71
|
+
* Only a fully-closed pair is extracted: if either marker is missing, the
|
|
72
|
+
* original content is returned untouched and `extracted` is null. Extracted
|
|
73
|
+
* content that is empty after trimming also yields null.
|
|
74
|
+
*
|
|
75
|
+
* @param content The string to parse.
|
|
76
|
+
* @param openMarker The opening marker (exact match).
|
|
77
|
+
* @param closeMarker The closing marker (exact match, searched after the opener).
|
|
78
|
+
* @returns The content with the delimited block removed (leading whitespace
|
|
79
|
+
* trimmed), and the extracted inner text (or null).
|
|
80
|
+
*/
|
|
81
|
+
export declare function extractMarkerDelimitedContent(content: string, openMarker: string, closeMarker: string): {
|
|
82
|
+
content: string;
|
|
83
|
+
extracted: string | null;
|
|
84
|
+
};
|
|
62
85
|
/**
|
|
63
86
|
* Parses role tags from a template string without rendering variables.
|
|
64
87
|
*
|
package/dist/prompting/parser.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.parseStructuredContent = parseStructuredContent;
|
|
10
10
|
exports.extractInitialTaggedContent = extractInitialTaggedContent;
|
|
11
|
+
exports.extractMarkerDelimitedContent = extractMarkerDelimitedContent;
|
|
11
12
|
exports.parseRoleTags = parseRoleTags;
|
|
12
13
|
exports.parseTemplateWithMetadata = parseTemplateWithMetadata;
|
|
13
14
|
const defaultLogger_1 = require("../logging/defaultLogger");
|
|
@@ -84,6 +85,38 @@ function extractInitialTaggedContent(content, tagName) {
|
|
|
84
85
|
// If the tag pattern is not found at the start, return the original content.
|
|
85
86
|
return { extracted: null, remaining: content };
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Extracts content delimited by an arbitrary open/close marker pair from anywhere
|
|
90
|
+
* in a string.
|
|
91
|
+
*
|
|
92
|
+
* Unlike {@link extractInitialTaggedContent}, the markers are free-form strings
|
|
93
|
+
* (not derived from an XML tag name) and may appear anywhere in the content —
|
|
94
|
+
* useful for local-model thinking traces whose markers aren't XML-shaped
|
|
95
|
+
* (e.g. Gemma 4's `<|channel>thought` … `<channel|>`).
|
|
96
|
+
*
|
|
97
|
+
* Only a fully-closed pair is extracted: if either marker is missing, the
|
|
98
|
+
* original content is returned untouched and `extracted` is null. Extracted
|
|
99
|
+
* content that is empty after trimming also yields null.
|
|
100
|
+
*
|
|
101
|
+
* @param content The string to parse.
|
|
102
|
+
* @param openMarker The opening marker (exact match).
|
|
103
|
+
* @param closeMarker The closing marker (exact match, searched after the opener).
|
|
104
|
+
* @returns The content with the delimited block removed (leading whitespace
|
|
105
|
+
* trimmed), and the extracted inner text (or null).
|
|
106
|
+
*/
|
|
107
|
+
function extractMarkerDelimitedContent(content, openMarker, closeMarker) {
|
|
108
|
+
const start = content.indexOf(openMarker);
|
|
109
|
+
if (start === -1) {
|
|
110
|
+
return { content, extracted: null };
|
|
111
|
+
}
|
|
112
|
+
const end = content.indexOf(closeMarker, start + openMarker.length);
|
|
113
|
+
if (end === -1) {
|
|
114
|
+
return { content, extracted: null };
|
|
115
|
+
}
|
|
116
|
+
const inner = content.substring(start + openMarker.length, end).trim();
|
|
117
|
+
const cleaned = (content.substring(0, start) + content.substring(end + closeMarker.length)).trimStart();
|
|
118
|
+
return { content: cleaned, extracted: inner || null };
|
|
119
|
+
}
|
|
87
120
|
/**
|
|
88
121
|
* Parses role tags from a template string without rendering variables.
|
|
89
122
|
*
|
|
@@ -7,7 +7,22 @@ export interface MappedErrorDetails {
|
|
|
7
7
|
errorMessage: string;
|
|
8
8
|
errorType: string;
|
|
9
9
|
status?: number;
|
|
10
|
+
/** Provider-suggested wait before retrying (parsed from a Retry-After header) */
|
|
11
|
+
retryAfterMs?: number;
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Parses a Retry-After header value (delta-seconds or HTTP-date) into milliseconds.
|
|
15
|
+
*
|
|
16
|
+
* @param value - The raw header value
|
|
17
|
+
* @returns Milliseconds to wait, or undefined when unparseable/negative
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseRetryAfterMs(value: string | null | undefined): number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Extracts a Retry-After value in ms from an SDK error object, tolerating the
|
|
22
|
+
* different places SDKs keep response headers (openai/anthropic `error.headers`
|
|
23
|
+
* as a Headers instance or plain object; Speakeasy-style `error.rawResponse`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractRetryAfterMs(error: any): number | undefined;
|
|
11
26
|
/**
|
|
12
27
|
* Maps common error patterns to standardized error codes and types
|
|
13
28
|
*
|
|
@@ -3,8 +3,53 @@
|
|
|
3
3
|
// Maps common HTTP status codes and network errors to standardized AdapterErrorCode and errorType.
|
|
4
4
|
// Reduces duplication across OpenAI, Anthropic and other provider adapters.
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseRetryAfterMs = parseRetryAfterMs;
|
|
7
|
+
exports.extractRetryAfterMs = extractRetryAfterMs;
|
|
6
8
|
exports.getCommonMappedErrorDetails = getCommonMappedErrorDetails;
|
|
7
9
|
const types_1 = require("../../llm/clients/types");
|
|
10
|
+
/**
|
|
11
|
+
* Parses a Retry-After header value (delta-seconds or HTTP-date) into milliseconds.
|
|
12
|
+
*
|
|
13
|
+
* @param value - The raw header value
|
|
14
|
+
* @returns Milliseconds to wait, or undefined when unparseable/negative
|
|
15
|
+
*/
|
|
16
|
+
function parseRetryAfterMs(value) {
|
|
17
|
+
if (!value)
|
|
18
|
+
return undefined;
|
|
19
|
+
const seconds = Number(value);
|
|
20
|
+
if (!Number.isNaN(seconds)) {
|
|
21
|
+
return seconds >= 0 ? Math.round(seconds * 1000) : undefined;
|
|
22
|
+
}
|
|
23
|
+
const dateMs = Date.parse(value);
|
|
24
|
+
if (!Number.isNaN(dateMs)) {
|
|
25
|
+
const delta = dateMs - Date.now();
|
|
26
|
+
return delta > 0 ? delta : undefined;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extracts a Retry-After value in ms from an SDK error object, tolerating the
|
|
32
|
+
* different places SDKs keep response headers (openai/anthropic `error.headers`
|
|
33
|
+
* as a Headers instance or plain object; Speakeasy-style `error.rawResponse`).
|
|
34
|
+
*/
|
|
35
|
+
function extractRetryAfterMs(error) {
|
|
36
|
+
const headerSources = [error?.headers, error?.rawResponse?.headers];
|
|
37
|
+
for (const headers of headerSources) {
|
|
38
|
+
if (!headers)
|
|
39
|
+
continue;
|
|
40
|
+
let raw;
|
|
41
|
+
if (typeof headers.get === 'function') {
|
|
42
|
+
raw = headers.get('retry-after');
|
|
43
|
+
}
|
|
44
|
+
else if (typeof headers === 'object') {
|
|
45
|
+
raw = headers['retry-after'] ?? headers['Retry-After'];
|
|
46
|
+
}
|
|
47
|
+
const parsed = parseRetryAfterMs(raw);
|
|
48
|
+
if (parsed !== undefined)
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
8
53
|
/**
|
|
9
54
|
* Maps common error patterns to standardized error codes and types
|
|
10
55
|
*
|
|
@@ -25,6 +70,28 @@ function getCommonMappedErrorDetails(error, providerMessageOverride) {
|
|
|
25
70
|
let errorMessage = providerMessageOverride || error?.message || 'Unknown error occurred';
|
|
26
71
|
let errorType = 'server_error';
|
|
27
72
|
let status;
|
|
73
|
+
const retryAfterMs = extractRetryAfterMs(error);
|
|
74
|
+
// Handle user-initiated aborts and client-side timeouts first — the SDKs raise
|
|
75
|
+
// these as named errors (openai: APIUserAbortError/APIConnectionTimeoutError;
|
|
76
|
+
// fetch/undici: AbortError/TimeoutError DOMExceptions)
|
|
77
|
+
if (error &&
|
|
78
|
+
(error.name === 'APIUserAbortError' ||
|
|
79
|
+
error.name === 'AbortError' ||
|
|
80
|
+
(error instanceof DOMException && error.name === 'AbortError'))) {
|
|
81
|
+
return {
|
|
82
|
+
errorCode: types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED,
|
|
83
|
+
errorMessage: providerMessageOverride || error.message || 'Request was aborted',
|
|
84
|
+
errorType: 'abort_error',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (error &&
|
|
88
|
+
(error.name === 'APIConnectionTimeoutError' || error.name === 'TimeoutError')) {
|
|
89
|
+
return {
|
|
90
|
+
errorCode: types_1.ADAPTER_ERROR_CODES.REQUEST_TIMEOUT,
|
|
91
|
+
errorMessage: providerMessageOverride || error.message || 'Request timed out',
|
|
92
|
+
errorType: 'timeout_error',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
28
95
|
// Handle API errors with HTTP status codes
|
|
29
96
|
if (error && typeof error.status === 'number') {
|
|
30
97
|
const httpStatus = error.status;
|
|
@@ -102,6 +169,7 @@ function getCommonMappedErrorDetails(error, providerMessageOverride) {
|
|
|
102
169
|
errorCode,
|
|
103
170
|
errorMessage,
|
|
104
171
|
errorType,
|
|
105
|
-
status
|
|
172
|
+
status,
|
|
173
|
+
...(retryAfterMs !== undefined && { retryAfterMs })
|
|
106
174
|
};
|
|
107
175
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TokenLogprob } from "../../llm/types";
|
|
2
|
+
/**
|
|
3
|
+
* Maps an OpenAI-shaped chat-completions logprobs payload
|
|
4
|
+
* (`choice.logprobs.content[].{token, logprob, top_logprobs[]}`) to the
|
|
5
|
+
* library's normalized {@link TokenLogprob} array.
|
|
6
|
+
*
|
|
7
|
+
* llama-server's chat endpoint produces the same shape, so this mapper is
|
|
8
|
+
* shared across the OpenAI-SDK-based adapters.
|
|
9
|
+
*
|
|
10
|
+
* @param logprobs - The raw `choice.logprobs` object from the provider response
|
|
11
|
+
* @returns Normalized token logprobs, or undefined when absent/empty
|
|
12
|
+
*/
|
|
13
|
+
export declare function mapOpenAIChatLogprobs(logprobs: any): TokenLogprob[] | undefined;
|