@contentgrowth/llm-service 0.7.9 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentgrowth/llm-service",
3
- "version": "0.7.9",
3
+ "version": "0.8.1",
4
4
  "description": "Unified LLM Service for Content Growth",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -113,29 +113,18 @@ export class GeminiProvider extends BaseLLMProvider {
113
113
  break;
114
114
  case 'assistant':
115
115
  role = 'model';
116
- parts = [];
117
-
118
- // Always include text if present (Thought)
119
- if (msg.content) {
120
- parts.push({ text: msg.content });
121
- }
122
-
123
- // Append tool calls if present (Call)
124
116
  if (msg.tool_calls) {
125
- const callParts = msg.tool_calls.map(tc => ({
126
- functionCall: { name: tc.function.name, args: tc.function.arguments }
127
- }));
128
- parts.push(...callParts);
129
- }
130
-
131
- // Safety fallback: if nothing was added, ensure valid structure
132
- // CRITICAL: Newer Gemini models (and SDK 1.34+) REQUIRE a non-empty thought_signature (text)
133
- // before a function call. If we have tool calls but no content, we must fabricate one.
134
- const hasText = parts.some(p => p.text);
135
- if (!hasText && msg.tool_calls) {
136
- parts.unshift({ text: "Processing tool execution..." });
137
- } else if (parts.length === 0) {
138
- parts.push({ text: "" }); // Fallback for truly empty message (shouldn't happen)
117
+ parts = msg.tool_calls.map(tc => {
118
+ const part = {
119
+ functionCall: { name: tc.function.name, args: tc.function.arguments || tc.function.args }
120
+ };
121
+ if (tc.thought_signature) {
122
+ part.thoughtSignature = tc.thought_signature; // camelCase for SDK
123
+ }
124
+ return part;
125
+ });
126
+ } else {
127
+ parts = [{ text: msg.content || '' }];
139
128
  }
140
129
  break;
141
130
  case 'tool':
@@ -228,6 +217,12 @@ export class GeminiProvider extends BaseLLMProvider {
228
217
  }
229
218
  if (part.functionCall) {
230
219
  if (!toolCalls) toolCalls = [];
220
+ // Preserve thought_signature if present (Gemini 3 requirement)
221
+ // Check both snake_case (API) and camelCase (SDK convention)
222
+ const sig = part.thought_signature || part.thoughtSignature;
223
+ if (sig) {
224
+ part.functionCall.thought_signature = sig;
225
+ }
231
226
  toolCalls.push(part.functionCall);
232
227
  }
233
228
  }
@@ -245,6 +240,9 @@ export class GeminiProvider extends BaseLLMProvider {
245
240
  );
246
241
  }
247
242
 
243
+ // Detailed logging as requested
244
+ // console.log('[GeminiProvider] generateContent response candidate:', JSON.stringify(candidate, null, 2));
245
+
248
246
  // console.log('Gemini returns:', textContent);
249
247
  // Return with parsed JSON if applicable
250
248
  // Normalize the finish reason to standard value for consistent handling
@@ -252,7 +250,11 @@ export class GeminiProvider extends BaseLLMProvider {
252
250
 
253
251
  return {
254
252
  content: textContent,
255
- tool_calls: toolCalls ? (Array.isArray(toolCalls) ? toolCalls : [toolCalls]).map(fc => ({ type: 'function', function: fc })) : null,
253
+ tool_calls: toolCalls ? (Array.isArray(toolCalls) ? toolCalls : [toolCalls]).map(fc => ({
254
+ type: 'function',
255
+ function: fc,
256
+ thought_signature: fc.thought_signature
257
+ })) : null,
256
258
  finishReason: normalizedFinishReason, // Standardized: 'completed', 'truncated', etc.
257
259
  _rawFinishReason: candidate.finishReason, // Keep original for debugging
258
260
  _responseFormat: options.responseFormat,