genai-lite 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +508 -30
- package/dist/config/presets.json +121 -17
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -3
- package/dist/llm/LLMService.createMessages.test.d.ts +4 -0
- package/dist/llm/LLMService.createMessages.test.js +364 -0
- package/dist/llm/LLMService.d.ts +49 -47
- package/dist/llm/LLMService.js +208 -303
- package/dist/llm/LLMService.original.d.ts +147 -0
- package/dist/llm/LLMService.original.js +656 -0
- package/dist/llm/LLMService.prepareMessage.test.d.ts +1 -0
- package/dist/llm/LLMService.prepareMessage.test.js +303 -0
- package/dist/llm/LLMService.sendMessage.preset.test.d.ts +1 -0
- package/dist/llm/LLMService.sendMessage.preset.test.js +153 -0
- package/dist/llm/LLMService.test.js +275 -0
- package/dist/llm/clients/AnthropicClientAdapter.js +64 -10
- package/dist/llm/clients/AnthropicClientAdapter.test.js +11 -1
- package/dist/llm/clients/GeminiClientAdapter.js +70 -11
- package/dist/llm/clients/GeminiClientAdapter.test.js +125 -1
- package/dist/llm/clients/MockClientAdapter.js +9 -3
- package/dist/llm/clients/MockClientAdapter.test.js +11 -1
- package/dist/llm/clients/OpenAIClientAdapter.js +26 -10
- package/dist/llm/clients/OpenAIClientAdapter.test.js +11 -1
- package/dist/llm/config.js +117 -2
- package/dist/llm/config.test.js +17 -0
- package/dist/llm/services/AdapterRegistry.d.ts +59 -0
- package/dist/llm/services/AdapterRegistry.js +113 -0
- package/dist/llm/services/AdapterRegistry.test.d.ts +1 -0
- package/dist/llm/services/AdapterRegistry.test.js +239 -0
- package/dist/llm/services/ModelResolver.d.ts +35 -0
- package/dist/llm/services/ModelResolver.js +116 -0
- package/dist/llm/services/ModelResolver.test.d.ts +1 -0
- package/dist/llm/services/ModelResolver.test.js +158 -0
- package/dist/llm/services/PresetManager.d.ts +27 -0
- package/dist/llm/services/PresetManager.js +50 -0
- package/dist/llm/services/PresetManager.test.d.ts +1 -0
- package/dist/llm/services/PresetManager.test.js +210 -0
- package/dist/llm/services/RequestValidator.d.ts +31 -0
- package/dist/llm/services/RequestValidator.js +122 -0
- package/dist/llm/services/RequestValidator.test.d.ts +1 -0
- package/dist/llm/services/RequestValidator.test.js +159 -0
- package/dist/llm/services/SettingsManager.d.ts +32 -0
- package/dist/llm/services/SettingsManager.js +223 -0
- package/dist/llm/services/SettingsManager.test.d.ts +1 -0
- package/dist/llm/services/SettingsManager.test.js +266 -0
- package/dist/llm/types.d.ts +107 -0
- package/dist/prompting/builder.d.ts +4 -0
- package/dist/prompting/builder.js +12 -61
- package/dist/prompting/content.js +3 -9
- package/dist/prompting/index.d.ts +2 -3
- package/dist/prompting/index.js +4 -5
- package/dist/prompting/parser.d.ts +80 -0
- package/dist/prompting/parser.js +133 -0
- package/dist/prompting/parser.test.js +348 -0
- package/dist/prompting/template.d.ts +8 -0
- package/dist/prompting/template.js +89 -6
- package/dist/prompting/template.test.js +116 -0
- package/package.json +3 -2
- package/src/config/presets.json +122 -17
package/dist/llm/LLMService.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// AI Summary: Main process service for LLM operations, integrating with ApiKeyProvider for secure key access.
|
|
3
3
|
// Orchestrates LLM requests through provider-specific client adapters with proper error handling.
|
|
4
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
-
};
|
|
7
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
5
|
exports.LLMService = void 0;
|
|
9
|
-
const MockClientAdapter_1 = require("./clients/MockClientAdapter");
|
|
10
6
|
const config_1 = require("./config");
|
|
11
|
-
const
|
|
7
|
+
const template_1 = require("../prompting/template");
|
|
8
|
+
const parser_1 = require("../prompting/parser");
|
|
9
|
+
// Import the extracted services
|
|
10
|
+
const PresetManager_1 = require("./services/PresetManager");
|
|
11
|
+
const AdapterRegistry_1 = require("./services/AdapterRegistry");
|
|
12
|
+
const RequestValidator_1 = require("./services/RequestValidator");
|
|
13
|
+
const SettingsManager_1 = require("./services/SettingsManager");
|
|
14
|
+
const ModelResolver_1 = require("./services/ModelResolver");
|
|
12
15
|
/**
|
|
13
16
|
* Main process service for LLM operations
|
|
14
17
|
*
|
|
@@ -23,55 +26,12 @@ const presets_json_1 = __importDefault(require("../config/presets.json"));
|
|
|
23
26
|
class LLMService {
|
|
24
27
|
constructor(getApiKey, options = {}) {
|
|
25
28
|
this.getApiKey = getApiKey;
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (mode === 'replace') {
|
|
33
|
-
// Replace Mode: Only use custom presets.
|
|
34
|
-
for (const preset of customPresets) {
|
|
35
|
-
finalPresets.set(preset.id, preset);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
// Extend Mode: Load defaults first, then add/override.
|
|
40
|
-
for (const preset of presets_json_1.default) {
|
|
41
|
-
finalPresets.set(preset.id, preset);
|
|
42
|
-
}
|
|
43
|
-
for (const preset of customPresets) {
|
|
44
|
-
finalPresets.set(preset.id, preset);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
this.presets = Array.from(finalPresets.values());
|
|
48
|
-
// Dynamically register client adapters based on configuration
|
|
49
|
-
let registeredCount = 0;
|
|
50
|
-
const successfullyRegisteredProviders = [];
|
|
51
|
-
for (const provider of config_1.SUPPORTED_PROVIDERS) {
|
|
52
|
-
const AdapterClass = config_1.ADAPTER_CONSTRUCTORS[provider.id];
|
|
53
|
-
if (AdapterClass) {
|
|
54
|
-
try {
|
|
55
|
-
const adapterConfig = config_1.ADAPTER_CONFIGS[provider.id];
|
|
56
|
-
const adapterInstance = new AdapterClass(adapterConfig);
|
|
57
|
-
this.registerClientAdapter(provider.id, adapterInstance);
|
|
58
|
-
registeredCount++;
|
|
59
|
-
successfullyRegisteredProviders.push(provider.id);
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
console.error(`LLMService: Failed to instantiate adapter for provider '${provider.id}'. This provider will use the mock adapter. Error:`, error);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
console.warn(`LLMService: No adapter constructor found for supported provider '${provider.id}'. This provider will use the mock adapter as a fallback.`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (registeredCount > 0) {
|
|
70
|
-
console.log(`LLMService: Initialized with ${registeredCount} dynamically registered adapter(s) for: ${successfullyRegisteredProviders.join(", ")}.`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
console.log(`LLMService: No real adapters were dynamically registered. All providers will use the mock adapter.`);
|
|
74
|
-
}
|
|
29
|
+
// Initialize services
|
|
30
|
+
this.presetManager = new PresetManager_1.PresetManager(options.presets, options.presetMode);
|
|
31
|
+
this.adapterRegistry = new AdapterRegistry_1.AdapterRegistry();
|
|
32
|
+
this.requestValidator = new RequestValidator_1.RequestValidator();
|
|
33
|
+
this.settingsManager = new SettingsManager_1.SettingsManager();
|
|
34
|
+
this.modelResolver = new ModelResolver_1.ModelResolver(this.presetManager);
|
|
75
35
|
}
|
|
76
36
|
/**
|
|
77
37
|
* Gets list of supported LLM providers
|
|
@@ -91,11 +51,11 @@ class LLMService {
|
|
|
91
51
|
async getModels(providerId) {
|
|
92
52
|
console.log(`LLMService.getModels called for provider: ${providerId}`);
|
|
93
53
|
// Validate provider exists
|
|
94
|
-
|
|
54
|
+
const models = (0, config_1.getModelsByProvider)(providerId);
|
|
55
|
+
if (models.length === 0) {
|
|
95
56
|
console.warn(`Requested models for unsupported provider: ${providerId}`);
|
|
96
57
|
return [];
|
|
97
58
|
}
|
|
98
|
-
const models = (0, config_1.getModelsByProvider)(providerId);
|
|
99
59
|
console.log(`Found ${models.length} models for provider: ${providerId}`);
|
|
100
60
|
return [...models]; // Return a copy to prevent external modification
|
|
101
61
|
}
|
|
@@ -106,146 +66,145 @@ class LLMService {
|
|
|
106
66
|
* @returns Promise resolving to either success or failure response
|
|
107
67
|
*/
|
|
108
68
|
async sendMessage(request) {
|
|
109
|
-
console.log(`LLMService.sendMessage called
|
|
69
|
+
console.log(`LLMService.sendMessage called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
|
|
110
70
|
try {
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return
|
|
115
|
-
provider: request.providerId,
|
|
116
|
-
model: request.modelId,
|
|
117
|
-
error: {
|
|
118
|
-
message: `Unsupported provider: ${request.providerId}. Supported providers: ${config_1.SUPPORTED_PROVIDERS.map((p) => p.id).join(", ")}`,
|
|
119
|
-
code: "UNSUPPORTED_PROVIDER",
|
|
120
|
-
type: "validation_error",
|
|
121
|
-
},
|
|
122
|
-
object: "error",
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
// Validate model
|
|
126
|
-
if (!(0, config_1.isModelSupported)(request.modelId, request.providerId)) {
|
|
127
|
-
const availableModels = (0, config_1.getModelsByProvider)(request.providerId).map((m) => m.id);
|
|
128
|
-
console.warn(`Unsupported model ${request.modelId} for provider ${request.providerId}. Available: ${availableModels.join(", ")}`);
|
|
129
|
-
return {
|
|
130
|
-
provider: request.providerId,
|
|
131
|
-
model: request.modelId,
|
|
132
|
-
error: {
|
|
133
|
-
message: `Unsupported model: ${request.modelId} for provider: ${request.providerId}. Available models: ${availableModels.join(", ")}`,
|
|
134
|
-
code: "UNSUPPORTED_MODEL",
|
|
135
|
-
type: "validation_error",
|
|
136
|
-
},
|
|
137
|
-
object: "error",
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
// Get model info for additional validation or settings
|
|
141
|
-
const modelInfo = (0, config_1.getModelById)(request.modelId, request.providerId);
|
|
142
|
-
if (!modelInfo) {
|
|
143
|
-
// This shouldn't happen if validation above passed, but defensive programming
|
|
144
|
-
console.error(`Model info not found for validated model: ${request.modelId}`);
|
|
145
|
-
return {
|
|
146
|
-
provider: request.providerId,
|
|
147
|
-
model: request.modelId,
|
|
148
|
-
error: {
|
|
149
|
-
message: `Internal error: Model configuration not found for ${request.modelId}`,
|
|
150
|
-
code: "MODEL_CONFIG_ERROR",
|
|
151
|
-
type: "internal_error",
|
|
152
|
-
},
|
|
153
|
-
object: "error",
|
|
154
|
-
};
|
|
71
|
+
// Resolve model information from preset or direct IDs
|
|
72
|
+
const resolved = this.modelResolver.resolve(request);
|
|
73
|
+
if (resolved.error) {
|
|
74
|
+
return resolved.error;
|
|
155
75
|
}
|
|
76
|
+
const { providerId, modelId, modelInfo, settings: resolvedSettings } = resolved;
|
|
77
|
+
// Create a proper LLMChatRequest with resolved values
|
|
78
|
+
const resolvedRequest = {
|
|
79
|
+
...request,
|
|
80
|
+
providerId: providerId,
|
|
81
|
+
modelId: modelId,
|
|
82
|
+
};
|
|
156
83
|
// Validate basic request structure
|
|
157
|
-
const structureValidationResult = this.validateRequestStructure(
|
|
84
|
+
const structureValidationResult = this.requestValidator.validateRequestStructure(resolvedRequest);
|
|
158
85
|
if (structureValidationResult) {
|
|
159
86
|
return structureValidationResult;
|
|
160
87
|
}
|
|
161
|
-
// Validate settings if provided
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
model: request.modelId,
|
|
168
|
-
error: {
|
|
169
|
-
message: `Invalid settings: ${settingsValidationErrors.join(", ")}`,
|
|
170
|
-
code: "INVALID_SETTINGS",
|
|
171
|
-
type: "validation_error",
|
|
172
|
-
},
|
|
173
|
-
object: "error",
|
|
174
|
-
};
|
|
88
|
+
// Validate settings if provided
|
|
89
|
+
const combinedSettings = { ...resolvedSettings, ...request.settings };
|
|
90
|
+
if (combinedSettings) {
|
|
91
|
+
const settingsValidation = this.requestValidator.validateSettings(combinedSettings, providerId, modelId);
|
|
92
|
+
if (settingsValidation) {
|
|
93
|
+
return settingsValidation;
|
|
175
94
|
}
|
|
176
95
|
}
|
|
177
96
|
// Apply model-specific defaults and merge with user settings
|
|
178
|
-
const finalSettings = this.mergeSettingsForModel(
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const paramsToExclude = new Set();
|
|
184
|
-
// Add provider-level exclusions
|
|
185
|
-
if (providerInfo?.unsupportedParameters) {
|
|
186
|
-
providerInfo.unsupportedParameters.forEach((param) => paramsToExclude.add(param));
|
|
97
|
+
const finalSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, combinedSettings);
|
|
98
|
+
// Validate reasoning settings for model capabilities
|
|
99
|
+
const reasoningValidation = this.requestValidator.validateReasoningSettings(modelInfo, finalSettings.reasoning, resolvedRequest);
|
|
100
|
+
if (reasoningValidation) {
|
|
101
|
+
return reasoningValidation;
|
|
187
102
|
}
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
103
|
+
// Get provider info for parameter filtering
|
|
104
|
+
const providerInfo = (0, config_1.getProviderById)(providerId);
|
|
105
|
+
if (!providerInfo) {
|
|
106
|
+
return {
|
|
107
|
+
provider: providerId,
|
|
108
|
+
model: modelId,
|
|
109
|
+
error: {
|
|
110
|
+
message: `Provider information not found: ${providerId}`,
|
|
111
|
+
code: "PROVIDER_ERROR",
|
|
112
|
+
type: "server_error",
|
|
113
|
+
},
|
|
114
|
+
object: "error",
|
|
115
|
+
};
|
|
194
116
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// (it might have been undefined initially and thus not part of finalSettings depending on merge logic)
|
|
198
|
-
// Using 'in' operator is robust for checking presence of properties, including those from prototype chain.
|
|
199
|
-
// For direct properties of an object, hasOwnProperty is more specific.
|
|
200
|
-
// Given finalSettings is Required<LLMSettings>, all keys should be present, potentially as undefined.
|
|
201
|
-
if (param in filteredSettings) {
|
|
202
|
-
console.log(`LLMService: Removing excluded parameter '${String(param)}' for provider '${request.providerId}', model '${request.modelId}'. Value was:`, filteredSettings[param]);
|
|
203
|
-
delete filteredSettings[param]; // Cast to allow deletion
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
// This case should ideally not happen if finalSettings truly is Required<LLMSettings>
|
|
207
|
-
// and mergeSettingsForModel ensures all keys are present (even if undefined).
|
|
208
|
-
console.log(`LLMService: Parameter '${String(param)}' marked for exclusion was not found in settings for provider '${request.providerId}', model '${request.modelId}'.`);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
117
|
+
// Filter out unsupported parameters
|
|
118
|
+
const filteredSettings = this.settingsManager.filterUnsupportedParameters(finalSettings, modelInfo, providerInfo);
|
|
211
119
|
const internalRequest = {
|
|
212
|
-
...
|
|
120
|
+
...resolvedRequest,
|
|
213
121
|
settings: filteredSettings,
|
|
214
122
|
};
|
|
215
123
|
console.log(`Processing LLM request with (potentially filtered) settings:`, {
|
|
216
|
-
provider:
|
|
217
|
-
model:
|
|
124
|
+
provider: providerId,
|
|
125
|
+
model: modelId,
|
|
218
126
|
settings: filteredSettings,
|
|
219
|
-
messageCount:
|
|
127
|
+
messageCount: resolvedRequest.messages.length,
|
|
220
128
|
});
|
|
221
|
-
console.log(`Processing LLM request: ${request.messages.length} messages, model: ${request.modelId}`);
|
|
222
129
|
// Get client adapter
|
|
223
|
-
const clientAdapter = this.
|
|
130
|
+
const clientAdapter = this.adapterRegistry.getAdapter(providerId);
|
|
224
131
|
// Use ApiKeyProvider to get the API key and make the request
|
|
225
132
|
try {
|
|
226
|
-
const apiKey = await this.getApiKey(
|
|
133
|
+
const apiKey = await this.getApiKey(providerId);
|
|
227
134
|
if (!apiKey) {
|
|
228
135
|
return {
|
|
229
|
-
provider:
|
|
230
|
-
model:
|
|
136
|
+
provider: providerId,
|
|
137
|
+
model: modelId,
|
|
231
138
|
error: {
|
|
232
|
-
message: `API key for provider '${
|
|
139
|
+
message: `API key for provider '${providerId}' could not be retrieved. Ensure your ApiKeyProvider is configured correctly.`,
|
|
233
140
|
code: "API_KEY_ERROR",
|
|
234
141
|
type: "authentication_error",
|
|
235
142
|
},
|
|
236
143
|
object: "error",
|
|
237
144
|
};
|
|
238
145
|
}
|
|
239
|
-
console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${
|
|
146
|
+
console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${providerId}`);
|
|
240
147
|
const result = await clientAdapter.sendMessage(internalRequest, apiKey);
|
|
241
|
-
|
|
148
|
+
// Post-process for thinking extraction
|
|
149
|
+
if (result.object === 'chat.completion' && internalRequest.settings.thinkingExtraction?.enabled) {
|
|
150
|
+
const settings = internalRequest.settings.thinkingExtraction;
|
|
151
|
+
const tagName = settings.tag || 'thinking';
|
|
152
|
+
// Step 1: Resolve the effective onMissing strategy
|
|
153
|
+
let effectiveOnMissing = settings.onMissing || 'auto';
|
|
154
|
+
if (effectiveOnMissing === 'auto') {
|
|
155
|
+
// Check if native reasoning is active
|
|
156
|
+
const isNativeReasoningActive = modelInfo.reasoning?.supported === true &&
|
|
157
|
+
(internalRequest.settings.reasoning?.enabled === true ||
|
|
158
|
+
modelInfo.reasoning?.enabledByDefault === true ||
|
|
159
|
+
modelInfo.reasoning?.canDisable === false); // Always-on models
|
|
160
|
+
effectiveOnMissing = isNativeReasoningActive ? 'ignore' : 'error';
|
|
161
|
+
}
|
|
162
|
+
// Step 2: Process the response
|
|
163
|
+
const choice = result.choices[0];
|
|
164
|
+
if (choice?.message?.content) {
|
|
165
|
+
const { extracted, remaining } = (0, parser_1.extractInitialTaggedContent)(choice.message.content, tagName);
|
|
166
|
+
if (extracted !== null) {
|
|
167
|
+
console.log(`Extracted <${tagName}> block from response.`);
|
|
168
|
+
// Handle the edge case: append to existing reasoning if present.
|
|
169
|
+
const existingReasoning = choice.reasoning || '';
|
|
170
|
+
const separator = existingReasoning ? '\n\n' : '';
|
|
171
|
+
// Add a comment to indicate the source of this reasoning block.
|
|
172
|
+
const newReasoning = `<!-- Extracted by genai-lite from <${tagName}> tag -->\n${extracted}`;
|
|
173
|
+
// Update the choice object
|
|
174
|
+
choice.reasoning = `${existingReasoning}${separator}${newReasoning}`;
|
|
175
|
+
choice.message.content = remaining;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Tag was not found, enforce the effective strategy
|
|
179
|
+
if (effectiveOnMissing === 'error') {
|
|
180
|
+
return {
|
|
181
|
+
provider: providerId,
|
|
182
|
+
model: modelId,
|
|
183
|
+
error: {
|
|
184
|
+
message: `The model (${modelId}) response was expected to start with a <${tagName}> tag but it was not found. ` +
|
|
185
|
+
`This is enforced because the model does not have native reasoning active. ` +
|
|
186
|
+
`Either ensure your prompt instructs the model to use <${tagName}> tags, or enable native reasoning if supported.`,
|
|
187
|
+
code: "MISSING_EXPECTED_TAG",
|
|
188
|
+
type: "validation_error",
|
|
189
|
+
},
|
|
190
|
+
object: "error",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
else if (effectiveOnMissing === 'warn') {
|
|
194
|
+
console.warn(`Expected <${tagName}> tag was not found in the response from model ${modelId}.`);
|
|
195
|
+
}
|
|
196
|
+
// If 'ignore', do nothing
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
console.log(`LLM request completed successfully for model: ${modelId}`);
|
|
242
201
|
return result;
|
|
243
202
|
}
|
|
244
203
|
catch (error) {
|
|
245
204
|
console.error("Error in LLMService.sendMessage:", error);
|
|
246
205
|
return {
|
|
247
|
-
provider:
|
|
248
|
-
model:
|
|
206
|
+
provider: providerId,
|
|
207
|
+
model: modelId,
|
|
249
208
|
error: {
|
|
250
209
|
message: error instanceof Error
|
|
251
210
|
? error.message
|
|
@@ -261,8 +220,8 @@ class LLMService {
|
|
|
261
220
|
catch (error) {
|
|
262
221
|
console.error("Error in LLMService.sendMessage (outer):", error);
|
|
263
222
|
return {
|
|
264
|
-
provider: request.providerId,
|
|
265
|
-
model: request.modelId,
|
|
223
|
+
provider: request.providerId || request.presetId || 'unknown',
|
|
224
|
+
model: request.modelId || request.presetId || 'unknown',
|
|
266
225
|
error: {
|
|
267
226
|
message: error instanceof Error
|
|
268
227
|
? error.message
|
|
@@ -276,121 +235,98 @@ class LLMService {
|
|
|
276
235
|
}
|
|
277
236
|
}
|
|
278
237
|
/**
|
|
279
|
-
*
|
|
238
|
+
* Gets all configured model presets
|
|
280
239
|
*
|
|
281
|
-
* @
|
|
282
|
-
* @returns LLMFailureResponse if validation fails, null if valid
|
|
240
|
+
* @returns Array of model presets
|
|
283
241
|
*/
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (!request.messages ||
|
|
287
|
-
!Array.isArray(request.messages) ||
|
|
288
|
-
request.messages.length === 0) {
|
|
289
|
-
return {
|
|
290
|
-
provider: request.providerId,
|
|
291
|
-
model: request.modelId,
|
|
292
|
-
error: {
|
|
293
|
-
message: "Request must contain at least one message",
|
|
294
|
-
code: "INVALID_REQUEST",
|
|
295
|
-
type: "validation_error",
|
|
296
|
-
},
|
|
297
|
-
object: "error",
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
// Validate message structure
|
|
301
|
-
for (let i = 0; i < request.messages.length; i++) {
|
|
302
|
-
const message = request.messages[i];
|
|
303
|
-
if (!message.role || !message.content) {
|
|
304
|
-
return {
|
|
305
|
-
provider: request.providerId,
|
|
306
|
-
model: request.modelId,
|
|
307
|
-
error: {
|
|
308
|
-
message: `Message at index ${i} must have both 'role' and 'content' properties`,
|
|
309
|
-
code: "INVALID_MESSAGE",
|
|
310
|
-
type: "validation_error",
|
|
311
|
-
},
|
|
312
|
-
object: "error",
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
if (!["user", "assistant", "system"].includes(message.role)) {
|
|
316
|
-
return {
|
|
317
|
-
provider: request.providerId,
|
|
318
|
-
model: request.modelId,
|
|
319
|
-
error: {
|
|
320
|
-
message: `Invalid message role '${message.role}' at index ${i}. Must be 'user', 'assistant', or 'system'`,
|
|
321
|
-
code: "INVALID_MESSAGE_ROLE",
|
|
322
|
-
type: "validation_error",
|
|
323
|
-
},
|
|
324
|
-
object: "error",
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return null; // Request is valid
|
|
242
|
+
getPresets() {
|
|
243
|
+
return this.presetManager.getPresets();
|
|
329
244
|
}
|
|
330
245
|
/**
|
|
331
|
-
*
|
|
246
|
+
* Creates messages from a template with role tags and model-aware variable substitution
|
|
332
247
|
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* @returns Complete settings object with all required fields
|
|
337
|
-
*/
|
|
338
|
-
mergeSettingsForModel(modelId, providerId, requestSettings) {
|
|
339
|
-
// Get model-specific defaults
|
|
340
|
-
const modelDefaults = (0, config_1.getDefaultSettingsForModel)(modelId, providerId);
|
|
341
|
-
// Merge with user-provided settings (user settings take precedence)
|
|
342
|
-
const mergedSettings = {
|
|
343
|
-
temperature: requestSettings?.temperature ?? modelDefaults.temperature,
|
|
344
|
-
maxTokens: requestSettings?.maxTokens ?? modelDefaults.maxTokens,
|
|
345
|
-
topP: requestSettings?.topP ?? modelDefaults.topP,
|
|
346
|
-
stopSequences: requestSettings?.stopSequences ?? modelDefaults.stopSequences,
|
|
347
|
-
frequencyPenalty: requestSettings?.frequencyPenalty ?? modelDefaults.frequencyPenalty,
|
|
348
|
-
presencePenalty: requestSettings?.presencePenalty ?? modelDefaults.presencePenalty,
|
|
349
|
-
user: requestSettings?.user ?? modelDefaults.user,
|
|
350
|
-
supportsSystemMessage: requestSettings?.supportsSystemMessage ??
|
|
351
|
-
modelDefaults.supportsSystemMessage,
|
|
352
|
-
geminiSafetySettings: requestSettings?.geminiSafetySettings ??
|
|
353
|
-
modelDefaults.geminiSafetySettings,
|
|
354
|
-
};
|
|
355
|
-
// Log the final settings for debugging
|
|
356
|
-
console.log(`Merged settings for ${providerId}/${modelId}:`, {
|
|
357
|
-
temperature: mergedSettings.temperature,
|
|
358
|
-
maxTokens: mergedSettings.maxTokens,
|
|
359
|
-
topP: mergedSettings.topP,
|
|
360
|
-
hasStopSequences: mergedSettings.stopSequences.length > 0,
|
|
361
|
-
frequencyPenalty: mergedSettings.frequencyPenalty,
|
|
362
|
-
presencePenalty: mergedSettings.presencePenalty,
|
|
363
|
-
hasUser: !!mergedSettings.user,
|
|
364
|
-
geminiSafetySettingsCount: mergedSettings.geminiSafetySettings.length,
|
|
365
|
-
});
|
|
366
|
-
return mergedSettings;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Gets the appropriate client adapter for a provider
|
|
248
|
+
* This unified method combines the functionality of template rendering, model context
|
|
249
|
+
* injection, and role tag parsing into a single, intuitive API. It replaces the need
|
|
250
|
+
* to chain prepareMessage and buildMessagesFromTemplate for model-aware multi-turn prompts.
|
|
370
251
|
*
|
|
371
|
-
* @param
|
|
372
|
-
* @returns
|
|
373
|
-
*/
|
|
374
|
-
getClientAdapter(providerId) {
|
|
375
|
-
// Check for registered real adapters first
|
|
376
|
-
const registeredAdapter = this.clientAdapters.get(providerId);
|
|
377
|
-
if (registeredAdapter) {
|
|
378
|
-
console.log(`Using registered adapter for provider: ${providerId}`);
|
|
379
|
-
return registeredAdapter;
|
|
380
|
-
}
|
|
381
|
-
// Fall back to mock adapter for unsupported providers
|
|
382
|
-
console.log(`No real adapter found for ${providerId}, using mock adapter`);
|
|
383
|
-
return this.mockClientAdapter;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Registers a client adapter for a specific provider
|
|
252
|
+
* @param options Options for creating messages
|
|
253
|
+
* @returns Promise resolving to parsed messages and model context
|
|
387
254
|
*
|
|
388
|
-
* @
|
|
389
|
-
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* const { messages } = await llm.createMessages({
|
|
258
|
+
* template: `
|
|
259
|
+
* <SYSTEM>You are a {{ thinking_enabled ? "thoughtful" : "helpful" }} assistant.</SYSTEM>
|
|
260
|
+
* <USER>Help me with {{ task }}</USER>
|
|
261
|
+
* `,
|
|
262
|
+
* variables: { task: 'understanding async/await' },
|
|
263
|
+
* presetId: 'openai-gpt-4.1-default'
|
|
264
|
+
* });
|
|
265
|
+
* ```
|
|
390
266
|
*/
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
267
|
+
async createMessages(options) {
|
|
268
|
+
console.log('LLMService.createMessages called');
|
|
269
|
+
// NEW: Step 1 - Parse the template for metadata and content
|
|
270
|
+
const { metadata, content: templateContent } = (0, parser_1.parseTemplateWithMetadata)(options.template);
|
|
271
|
+
// Validate the settings from the template
|
|
272
|
+
const templateSettings = this.settingsManager.validateTemplateSettings(metadata.settings || {});
|
|
273
|
+
// Step 2: Get model context if model information is provided
|
|
274
|
+
let modelContext = null;
|
|
275
|
+
if (options.presetId || (options.providerId && options.modelId)) {
|
|
276
|
+
// Resolve model information
|
|
277
|
+
const resolved = this.modelResolver.resolve({
|
|
278
|
+
presetId: options.presetId,
|
|
279
|
+
providerId: options.providerId,
|
|
280
|
+
modelId: options.modelId
|
|
281
|
+
});
|
|
282
|
+
if (resolved.error) {
|
|
283
|
+
// If resolution fails, proceed without model context
|
|
284
|
+
console.warn('Model resolution failed, proceeding without model context:', resolved.error);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const { providerId, modelId, modelInfo, settings } = resolved;
|
|
288
|
+
// Merge settings with model defaults
|
|
289
|
+
const mergedSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, settings || {});
|
|
290
|
+
// Create model context
|
|
291
|
+
modelContext = {
|
|
292
|
+
thinking_enabled: !!(modelInfo.reasoning?.supported &&
|
|
293
|
+
(mergedSettings.reasoning?.enabled === true ||
|
|
294
|
+
(modelInfo.reasoning?.enabledByDefault && mergedSettings.reasoning?.enabled !== false))),
|
|
295
|
+
thinking_available: !!modelInfo.reasoning?.supported,
|
|
296
|
+
model_id: modelId,
|
|
297
|
+
provider_id: providerId,
|
|
298
|
+
reasoning_effort: mergedSettings.reasoning?.effort,
|
|
299
|
+
reasoning_max_tokens: mergedSettings.reasoning?.maxTokens,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Step 2: Combine variables with model context
|
|
304
|
+
// Model context comes first so user variables can override
|
|
305
|
+
const allVariables = {
|
|
306
|
+
...(modelContext || {}),
|
|
307
|
+
...options.variables
|
|
308
|
+
};
|
|
309
|
+
// Step 3: Render the template with all variables
|
|
310
|
+
let renderedTemplate;
|
|
311
|
+
try {
|
|
312
|
+
// Use templateContent which is the template without the <META> block
|
|
313
|
+
renderedTemplate = (0, template_1.renderTemplate)(templateContent, allVariables);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
throw new Error(`Template rendering failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
317
|
+
}
|
|
318
|
+
// Step 4: Parse role tags from the rendered template
|
|
319
|
+
const parsedMessages = (0, parser_1.parseRoleTags)(renderedTemplate);
|
|
320
|
+
// Step 5: Convert to LLMMessage format
|
|
321
|
+
const messages = parsedMessages.map(({ role, content }) => ({
|
|
322
|
+
role: role,
|
|
323
|
+
content
|
|
324
|
+
}));
|
|
325
|
+
return {
|
|
326
|
+
messages,
|
|
327
|
+
modelContext,
|
|
328
|
+
settings: templateSettings // NEW: Add the extracted settings
|
|
329
|
+
};
|
|
394
330
|
}
|
|
395
331
|
/**
|
|
396
332
|
* Gets information about registered adapters
|
|
@@ -398,15 +334,7 @@ class LLMService {
|
|
|
398
334
|
* @returns Map of provider IDs to adapter info
|
|
399
335
|
*/
|
|
400
336
|
getRegisteredAdapters() {
|
|
401
|
-
|
|
402
|
-
for (const [providerId, adapter] of this.clientAdapters.entries()) {
|
|
403
|
-
adapterInfo.set(providerId, {
|
|
404
|
-
providerId,
|
|
405
|
-
hasAdapter: true,
|
|
406
|
-
adapterInfo: adapter.getAdapterInfo?.() || { name: "Unknown Adapter" },
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
return adapterInfo;
|
|
337
|
+
return this.adapterRegistry.getRegisteredAdapters();
|
|
410
338
|
}
|
|
411
339
|
/**
|
|
412
340
|
* Gets a summary of available providers and their adapter status
|
|
@@ -414,30 +342,7 @@ class LLMService {
|
|
|
414
342
|
* @returns Summary of provider availability
|
|
415
343
|
*/
|
|
416
344
|
getProviderSummary() {
|
|
417
|
-
|
|
418
|
-
const unavailableProviders = [];
|
|
419
|
-
for (const provider of config_1.SUPPORTED_PROVIDERS) {
|
|
420
|
-
if (this.clientAdapters.has(provider.id)) {
|
|
421
|
-
availableProviders.push(provider.id);
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
unavailableProviders.push(provider.id);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
totalProviders: config_1.SUPPORTED_PROVIDERS.length,
|
|
429
|
-
providersWithAdapters: availableProviders.length,
|
|
430
|
-
availableProviders,
|
|
431
|
-
unavailableProviders,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Gets all configured model presets
|
|
436
|
-
*
|
|
437
|
-
* @returns Array of model presets
|
|
438
|
-
*/
|
|
439
|
-
getPresets() {
|
|
440
|
-
return [...this.presets]; // Return a copy to prevent external modification
|
|
345
|
+
return this.adapterRegistry.getProviderSummary();
|
|
441
346
|
}
|
|
442
347
|
}
|
|
443
348
|
exports.LLMService = LLMService;
|