@contentgrowth/llm-service 0.6.91 → 0.6.94
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/package.json
CHANGED
|
@@ -127,58 +127,60 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Use the new @google/genai API
|
|
130
|
-
|
|
130
|
+
// Use the new @google/genai API
|
|
131
|
+
const requestOptions = {
|
|
131
132
|
model: modelName,
|
|
132
133
|
contents: contents,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
tools: tools ? [{ functionDeclarations: tools.map(t => t.function) }] : undefined,
|
|
136
|
-
});
|
|
134
|
+
config: generationConfig,
|
|
135
|
+
};
|
|
137
136
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
if (systemPrompt) {
|
|
138
|
+
requestOptions.systemInstruction = systemPrompt;
|
|
139
|
+
}
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
if (tools && tools.length > 0) {
|
|
142
|
+
requestOptions.tools = [{ functionDeclarations: tools.map(t => t.function) }];
|
|
143
|
+
}
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
console.log('[GeminiProvider] generateContent request:', JSON.stringify(requestOptions, null, 2));
|
|
146
|
+
|
|
147
|
+
let response;
|
|
148
|
+
try {
|
|
149
|
+
response = await this.client.models.generateContent(requestOptions);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('[GeminiProvider] generateContent failed:', error);
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// In @google/genai, the response is returned directly (no .response property)
|
|
156
|
+
// And helper methods like .text() or .functionCalls() might not exist on the raw object
|
|
157
|
+
// So we extract manually from candidates
|
|
158
|
+
|
|
159
|
+
const candidate = response.candidates?.[0];
|
|
160
|
+
if (!candidate) {
|
|
161
|
+
throw new LLMServiceException('No candidates returned from model', 500);
|
|
157
162
|
}
|
|
158
163
|
|
|
159
|
-
|
|
164
|
+
const parts = candidate.content?.parts || [];
|
|
165
|
+
|
|
166
|
+
// Extract text and function calls
|
|
160
167
|
let textContent = '';
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
textContent
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.map(p => p.text)
|
|
171
|
-
.join('');
|
|
168
|
+
let toolCalls = null;
|
|
169
|
+
|
|
170
|
+
for (const part of parts) {
|
|
171
|
+
if (part.text) {
|
|
172
|
+
textContent += part.text;
|
|
173
|
+
}
|
|
174
|
+
if (part.functionCall) {
|
|
175
|
+
if (!toolCalls) toolCalls = [];
|
|
176
|
+
toolCalls.push(part.functionCall);
|
|
172
177
|
}
|
|
173
|
-
} catch (e) {
|
|
174
|
-
// response.text() throws if there is no text content (e.g. only tool calls)
|
|
175
|
-
// This is expected behavior for tool-only responses
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
// Validate that we have EITHER content OR tool calls
|
|
179
181
|
if (!textContent && (!toolCalls || toolCalls.length === 0)) {
|
|
180
182
|
console.error('[GeminiProvider] Model returned empty response (no text, no tool calls)');
|
|
181
|
-
console.error('[GeminiProvider]
|
|
183
|
+
console.error('[GeminiProvider] Contents:', JSON.stringify(contents, null, 2));
|
|
182
184
|
throw new LLMServiceException(
|
|
183
185
|
'Model returned empty response. This usually means the prompt or schema is confusing the model.',
|
|
184
186
|
500
|
|
@@ -211,15 +213,17 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
211
213
|
|
|
212
214
|
const schema = typeof options.responseFormat === 'object'
|
|
213
215
|
? options.responseFormat.schema
|
|
214
|
-
: null;
|
|
216
|
+
: options.responseSchema || null;
|
|
215
217
|
|
|
216
218
|
if (formatType === 'json' || formatType === 'json_schema') {
|
|
217
219
|
config.responseMimeType = 'application/json';
|
|
218
220
|
|
|
219
221
|
// CRITICAL: Must provide schema for "Strict Mode" to avoid markdown wrappers
|
|
220
222
|
if (schema) {
|
|
223
|
+
// Use responseSchema for strict structured output
|
|
224
|
+
// Must convert to Gemini Schema format (Uppercase types)
|
|
221
225
|
config.responseSchema = this._convertToGeminiSchema(schema);
|
|
222
|
-
console.log('[GeminiProvider] Using Strict JSON mode with schema');
|
|
226
|
+
console.log('[GeminiProvider] Using Strict JSON mode with schema (responseSchema)');
|
|
223
227
|
} else {
|
|
224
228
|
console.warn('[GeminiProvider] Using legacy JSON mode without schema - may produce markdown wrappers');
|
|
225
229
|
}
|
|
@@ -230,25 +234,15 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
230
234
|
}
|
|
231
235
|
|
|
232
236
|
_convertToGeminiSchema(jsonSchema) {
|
|
233
|
-
// SchemaType constants for Gemini schema conversion
|
|
234
|
-
const SchemaType = {
|
|
235
|
-
STRING: 'STRING',
|
|
236
|
-
NUMBER: 'NUMBER',
|
|
237
|
-
INTEGER: 'INTEGER',
|
|
238
|
-
BOOLEAN: 'BOOLEAN',
|
|
239
|
-
ARRAY: 'ARRAY',
|
|
240
|
-
OBJECT: 'OBJECT'
|
|
241
|
-
};
|
|
242
|
-
|
|
243
237
|
const convertType = (type) => {
|
|
244
238
|
switch (type) {
|
|
245
|
-
case 'string': return
|
|
246
|
-
case 'number': return
|
|
247
|
-
case 'integer': return
|
|
248
|
-
case 'boolean': return
|
|
249
|
-
case 'array': return
|
|
250
|
-
case 'object': return
|
|
251
|
-
default: return
|
|
239
|
+
case 'string': return 'STRING';
|
|
240
|
+
case 'number': return 'NUMBER';
|
|
241
|
+
case 'integer': return 'INTEGER';
|
|
242
|
+
case 'boolean': return 'BOOLEAN';
|
|
243
|
+
case 'array': return 'ARRAY';
|
|
244
|
+
case 'object': return 'OBJECT';
|
|
245
|
+
default: return 'STRING';
|
|
252
246
|
}
|
|
253
247
|
};
|
|
254
248
|
|
|
@@ -362,22 +356,33 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
362
356
|
}
|
|
363
357
|
|
|
364
358
|
// Use the new @google/genai API
|
|
365
|
-
const
|
|
359
|
+
const requestOptions = {
|
|
366
360
|
model: modelName,
|
|
367
361
|
contents: [{
|
|
368
362
|
role: "user",
|
|
369
363
|
parts: parts
|
|
370
364
|
}],
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
365
|
+
config: generationConfig
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
if (systemPrompt) {
|
|
369
|
+
requestOptions.systemInstruction = systemPrompt;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
console.log('[GeminiProvider] imageGeneration request:', JSON.stringify(requestOptions, null, 2));
|
|
373
|
+
|
|
374
|
+
const response = await this.client.models.generateContent(requestOptions);
|
|
374
375
|
|
|
375
|
-
const response = result.response;
|
|
376
376
|
const imagePart = response.candidates?.[0]?.content?.parts?.find(
|
|
377
377
|
part => part.inlineData && part.inlineData.mimeType?.startsWith('image/')
|
|
378
378
|
);
|
|
379
379
|
|
|
380
380
|
if (!imagePart || !imagePart.inlineData) {
|
|
381
|
+
// Fallback: Check if it returned a URI or other format, or just text
|
|
382
|
+
const textPart = response.candidates?.[0]?.content?.parts?.find(p => p.text);
|
|
383
|
+
if (textPart) {
|
|
384
|
+
console.warn('[GeminiProvider] Model returned text instead of image:', textPart.text);
|
|
385
|
+
}
|
|
381
386
|
throw new Error('No image data in response');
|
|
382
387
|
}
|
|
383
388
|
|
|
@@ -93,7 +93,7 @@ export class OpenAIProvider extends BaseLLMProvider {
|
|
|
93
93
|
|
|
94
94
|
const schema = typeof options.responseFormat === 'object'
|
|
95
95
|
? options.responseFormat.schema
|
|
96
|
-
: null;
|
|
96
|
+
: options.responseSchema || null;
|
|
97
97
|
|
|
98
98
|
switch (formatType) {
|
|
99
99
|
case 'json':
|
|
@@ -115,7 +115,7 @@ export class OpenAIProvider extends BaseLLMProvider {
|
|
|
115
115
|
|
|
116
116
|
case 'json_schema':
|
|
117
117
|
if (!schema) {
|
|
118
|
-
throw new Error('
|
|
118
|
+
throw new Error('responseSchema required when using json_schema format');
|
|
119
119
|
}
|
|
120
120
|
console.log('[OpenAIProvider] Using Strict JSON mode with schema');
|
|
121
121
|
return {
|