@contentgrowth/llm-service 0.7.2 → 0.7.4
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
package/src/index.js
CHANGED
|
@@ -5,3 +5,5 @@ export { MODEL_CONFIGS } from './llm/config-manager.js';
|
|
|
5
5
|
export { OpenAIProvider } from './llm/providers/openai-provider.js';
|
|
6
6
|
export { GeminiProvider } from './llm/providers/gemini-provider.js';
|
|
7
7
|
export { extractJsonFromResponse } from './llm/json-utils.js';
|
|
8
|
+
export { FINISH_REASONS } from './llm/providers/base-provider.js';
|
|
9
|
+
|
|
@@ -94,7 +94,7 @@ export class DefaultConfigProvider extends BaseConfigProvider {
|
|
|
94
94
|
apiKey: tenantConfig.api_key,
|
|
95
95
|
models: MODEL_CONFIGS[tenantConfig.provider],
|
|
96
96
|
temperature: parseFloat(env.DEFAULT_TEMPERATURE || '0.7'),
|
|
97
|
-
maxTokens: parseInt(env.DEFAULT_MAX_TOKENS || '
|
|
97
|
+
maxTokens: parseInt(env.DEFAULT_MAX_TOKENS || '16384'),
|
|
98
98
|
capabilities: tenantConfig.capabilities || { chat: true, image: false, video: false },
|
|
99
99
|
isTenantOwned: true
|
|
100
100
|
};
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standardized finish reasons across all LLM providers.
|
|
3
|
+
* Providers map their native values to these standard constants.
|
|
4
|
+
*/
|
|
5
|
+
export const FINISH_REASONS = {
|
|
6
|
+
COMPLETED: 'completed', // Normal completion (OpenAI: stop, Gemini: STOP, Anthropic: end_turn)
|
|
7
|
+
TRUNCATED: 'truncated', // Hit max tokens (OpenAI: length, Gemini: MAX_TOKENS, Anthropic: max_tokens)
|
|
8
|
+
CONTENT_FILTER: 'content_filter', // Content was filtered
|
|
9
|
+
TOOL_CALL: 'tool_call', // Stopped for tool call
|
|
10
|
+
UNKNOWN: 'unknown', // Unknown/unmapped reason
|
|
11
|
+
};
|
|
12
|
+
|
|
1
13
|
/**
|
|
2
14
|
* Abstract base class for LLM Providers.
|
|
3
15
|
* Defines the standard interface that all providers must implement.
|
|
@@ -7,6 +19,39 @@ export class BaseLLMProvider {
|
|
|
7
19
|
this.config = config;
|
|
8
20
|
}
|
|
9
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Normalize provider-specific finish reason to standard value.
|
|
24
|
+
* Override in subclass if provider uses different values.
|
|
25
|
+
* @param {string} providerReason - The provider's native finish reason
|
|
26
|
+
* @returns {string} Standardized finish reason from FINISH_REASONS
|
|
27
|
+
*/
|
|
28
|
+
normalizeFinishReason(providerReason) {
|
|
29
|
+
// Default mappings - providers can override
|
|
30
|
+
const upperReason = (providerReason || '').toUpperCase();
|
|
31
|
+
|
|
32
|
+
// Completed mappings
|
|
33
|
+
if (['STOP', 'END_TURN'].includes(upperReason)) {
|
|
34
|
+
return FINISH_REASONS.COMPLETED;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Truncated mappings
|
|
38
|
+
if (['LENGTH', 'MAX_TOKENS'].includes(upperReason)) {
|
|
39
|
+
return FINISH_REASONS.TRUNCATED;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Content filter mappings
|
|
43
|
+
if (['CONTENT_FILTER', 'SAFETY'].includes(upperReason)) {
|
|
44
|
+
return FINISH_REASONS.CONTENT_FILTER;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Tool call mappings
|
|
48
|
+
if (['TOOL_CALLS', 'TOOL_USE', 'FUNCTION_CALL'].includes(upperReason)) {
|
|
49
|
+
return FINISH_REASONS.TOOL_CALL;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return FINISH_REASONS.UNKNOWN;
|
|
53
|
+
}
|
|
54
|
+
|
|
10
55
|
/**
|
|
11
56
|
* Simple chat interface for single-turn conversations
|
|
12
57
|
* @param {string} userMessage
|
|
@@ -230,9 +230,14 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
230
230
|
|
|
231
231
|
// console.log('Gemini returns:', textContent);
|
|
232
232
|
// Return with parsed JSON if applicable
|
|
233
|
+
// Normalize the finish reason to standard value for consistent handling
|
|
234
|
+
const normalizedFinishReason = this.normalizeFinishReason(candidate.finishReason);
|
|
235
|
+
|
|
233
236
|
return {
|
|
234
237
|
content: textContent,
|
|
235
238
|
tool_calls: toolCalls ? (Array.isArray(toolCalls) ? toolCalls : [toolCalls]).map(fc => ({ type: 'function', function: fc })) : null,
|
|
239
|
+
finishReason: normalizedFinishReason, // Standardized: 'completed', 'truncated', etc.
|
|
240
|
+
_rawFinishReason: candidate.finishReason, // Keep original for debugging
|
|
236
241
|
_responseFormat: options.responseFormat,
|
|
237
242
|
...(options.responseFormat && this._shouldAutoParse(options) ? {
|
|
238
243
|
parsedContent: this._safeJsonParse(textContent)
|
|
@@ -68,10 +68,16 @@ export class OpenAIProvider extends BaseLLMProvider {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// Normalize the finish reason to standard value for consistent handling
|
|
72
|
+
const rawFinishReason = response.choices[0].finish_reason;
|
|
73
|
+
const normalizedFinishReason = this.normalizeFinishReason(rawFinishReason);
|
|
74
|
+
|
|
71
75
|
// Return with parsed JSON if applicable
|
|
72
76
|
return {
|
|
73
77
|
content: message.content,
|
|
74
78
|
tool_calls: message.tool_calls,
|
|
79
|
+
finishReason: normalizedFinishReason, // Standardized: 'completed', 'truncated', etc.
|
|
80
|
+
_rawFinishReason: rawFinishReason, // Keep original for debugging
|
|
75
81
|
// Add metadata about response format
|
|
76
82
|
_responseFormat: options.responseFormat,
|
|
77
83
|
// Auto-parse JSON if requested
|
package/src/llm-service.js
CHANGED
|
@@ -174,12 +174,12 @@ export class LLMService {
|
|
|
174
174
|
options
|
|
175
175
|
);
|
|
176
176
|
|
|
177
|
-
let { content, tool_calls, parsedContent } = initialResponse;
|
|
177
|
+
let { content, tool_calls, parsedContent, finishReason, _rawFinishReason } = initialResponse;
|
|
178
178
|
|
|
179
179
|
// Tool execution loop with safety limit
|
|
180
180
|
while (tool_calls && iteration < MAX_ITERATIONS) {
|
|
181
181
|
iteration++;
|
|
182
|
-
console.log(`[Tool Call] Iteration ${iteration}/${MAX_ITERATIONS}: Assistant wants to use tools:`, tool_calls);
|
|
182
|
+
console.log(`[Tool Call] Iteration ${iteration}/${MAX_ITERATIONS} with finish reason ${finishReason}: Assistant wants to use tools:`, tool_calls);
|
|
183
183
|
currentMessages.push({ role: 'assistant', content: content || '', tool_calls });
|
|
184
184
|
|
|
185
185
|
// Execute tools using the provider's helper (which formats results for that provider)
|
|
@@ -196,6 +196,8 @@ export class LLMService {
|
|
|
196
196
|
content = nextResponse.content;
|
|
197
197
|
tool_calls = nextResponse.tool_calls;
|
|
198
198
|
parsedContent = nextResponse.parsedContent; // Preserve parsedContent from final response
|
|
199
|
+
finishReason = nextResponse.finishReason; // Preserve finishReason from final response
|
|
200
|
+
_rawFinishReason = nextResponse._rawFinishReason; // Preserve raw finish reason for debugging
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
if (iteration >= MAX_ITERATIONS) {
|
|
@@ -203,7 +205,7 @@ export class LLMService {
|
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
// Return both content and parsedContent (if available)
|
|
206
|
-
return { content, parsedContent, toolCalls: tool_calls };
|
|
208
|
+
return { content, parsedContent, toolCalls: tool_calls, finishReason, _rawFinishReason };
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
/**
|