@contentgrowth/llm-service 0.6.92 → 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,44 +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
|
-
let toolCalls = null;
|
|
141
|
-
if (result.candidates?.[0]?.content?.parts) {
|
|
142
|
-
const functionCallParts = result.candidates[0].content.parts.filter(p => p.functionCall);
|
|
143
|
-
if (functionCallParts.length > 0) {
|
|
144
|
-
toolCalls = functionCallParts.map(p => p.functionCall);
|
|
145
|
-
}
|
|
137
|
+
if (systemPrompt) {
|
|
138
|
+
requestOptions.systemInstruction = systemPrompt;
|
|
146
139
|
}
|
|
147
140
|
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
if (tools && tools.length > 0) {
|
|
142
|
+
requestOptions.tools = [{ functionDeclarations: tools.map(t => t.function) }];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log('[GeminiProvider] generateContent request:', JSON.stringify(requestOptions, null, 2));
|
|
146
|
+
|
|
147
|
+
let response;
|
|
150
148
|
try {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parts = candidate.content?.parts || [];
|
|
165
|
+
|
|
166
|
+
// Extract text and function calls
|
|
167
|
+
let textContent = '';
|
|
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);
|
|
159
177
|
}
|
|
160
|
-
} catch (e) {
|
|
161
|
-
// This is expected behavior for tool-only responses
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
// Validate that we have EITHER content OR tool calls
|
|
165
181
|
if (!textContent && (!toolCalls || toolCalls.length === 0)) {
|
|
166
182
|
console.error('[GeminiProvider] Model returned empty response (no text, no tool calls)');
|
|
167
|
-
console.error('[GeminiProvider]
|
|
183
|
+
console.error('[GeminiProvider] Contents:', JSON.stringify(contents, null, 2));
|
|
168
184
|
throw new LLMServiceException(
|
|
169
185
|
'Model returned empty response. This usually means the prompt or schema is confusing the model.',
|
|
170
186
|
500
|
|
@@ -197,15 +213,17 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
197
213
|
|
|
198
214
|
const schema = typeof options.responseFormat === 'object'
|
|
199
215
|
? options.responseFormat.schema
|
|
200
|
-
: null;
|
|
216
|
+
: options.responseSchema || null;
|
|
201
217
|
|
|
202
218
|
if (formatType === 'json' || formatType === 'json_schema') {
|
|
203
219
|
config.responseMimeType = 'application/json';
|
|
204
220
|
|
|
205
221
|
// CRITICAL: Must provide schema for "Strict Mode" to avoid markdown wrappers
|
|
206
222
|
if (schema) {
|
|
223
|
+
// Use responseSchema for strict structured output
|
|
224
|
+
// Must convert to Gemini Schema format (Uppercase types)
|
|
207
225
|
config.responseSchema = this._convertToGeminiSchema(schema);
|
|
208
|
-
console.log('[GeminiProvider] Using Strict JSON mode with schema');
|
|
226
|
+
console.log('[GeminiProvider] Using Strict JSON mode with schema (responseSchema)');
|
|
209
227
|
} else {
|
|
210
228
|
console.warn('[GeminiProvider] Using legacy JSON mode without schema - may produce markdown wrappers');
|
|
211
229
|
}
|
|
@@ -216,25 +234,15 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
216
234
|
}
|
|
217
235
|
|
|
218
236
|
_convertToGeminiSchema(jsonSchema) {
|
|
219
|
-
// SchemaType constants for Gemini schema conversion
|
|
220
|
-
const SchemaType = {
|
|
221
|
-
STRING: 'STRING',
|
|
222
|
-
NUMBER: 'NUMBER',
|
|
223
|
-
INTEGER: 'INTEGER',
|
|
224
|
-
BOOLEAN: 'BOOLEAN',
|
|
225
|
-
ARRAY: 'ARRAY',
|
|
226
|
-
OBJECT: 'OBJECT'
|
|
227
|
-
};
|
|
228
|
-
|
|
229
237
|
const convertType = (type) => {
|
|
230
238
|
switch (type) {
|
|
231
|
-
case 'string': return
|
|
232
|
-
case 'number': return
|
|
233
|
-
case 'integer': return
|
|
234
|
-
case 'boolean': return
|
|
235
|
-
case 'array': return
|
|
236
|
-
case 'object': return
|
|
237
|
-
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';
|
|
238
246
|
}
|
|
239
247
|
};
|
|
240
248
|
|
|
@@ -348,23 +356,33 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
348
356
|
}
|
|
349
357
|
|
|
350
358
|
// Use the new @google/genai API
|
|
351
|
-
const
|
|
359
|
+
const requestOptions = {
|
|
352
360
|
model: modelName,
|
|
353
361
|
contents: [{
|
|
354
362
|
role: "user",
|
|
355
363
|
parts: parts
|
|
356
364
|
}],
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
365
|
+
config: generationConfig
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
if (systemPrompt) {
|
|
369
|
+
requestOptions.systemInstruction = systemPrompt;
|
|
370
|
+
}
|
|
360
371
|
|
|
361
|
-
|
|
362
|
-
|
|
372
|
+
console.log('[GeminiProvider] imageGeneration request:', JSON.stringify(requestOptions, null, 2));
|
|
373
|
+
|
|
374
|
+
const response = await this.client.models.generateContent(requestOptions);
|
|
375
|
+
|
|
376
|
+
const imagePart = response.candidates?.[0]?.content?.parts?.find(
|
|
363
377
|
part => part.inlineData && part.inlineData.mimeType?.startsWith('image/')
|
|
364
378
|
);
|
|
365
379
|
|
|
366
380
|
if (!imagePart || !imagePart.inlineData) {
|
|
367
|
-
|
|
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
|
+
}
|
|
368
386
|
throw new Error('No image data in response');
|
|
369
387
|
}
|
|
370
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 {
|