universal-llm-client 4.5.0 → 4.5.2
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/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/dist/ai-model.d.ts +0 -1
- package/dist/ai-model.js +0 -1
- package/dist/auditor.d.ts +0 -1
- package/dist/auditor.js +0 -1
- package/dist/client.d.ts +0 -1
- package/dist/client.js +0 -1
- package/dist/gemma-channel.d.ts +0 -1
- package/dist/gemma-channel.js +0 -1
- package/dist/gemma-diffusion.d.ts +0 -1
- package/dist/gemma-diffusion.js +0 -1
- package/dist/http.d.ts +0 -1
- package/dist/http.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/interfaces.d.ts +0 -1
- package/dist/interfaces.js +0 -1
- package/dist/mcp.d.ts +0 -1
- package/dist/mcp.js +0 -1
- package/dist/providers/anthropic.d.ts +0 -1
- package/dist/providers/anthropic.js +0 -1
- package/dist/providers/google.d.ts +0 -1
- package/dist/providers/google.js +0 -1
- package/dist/providers/index.d.ts +0 -1
- package/dist/providers/index.js +0 -1
- package/dist/providers/ollama.d.ts +0 -1
- package/dist/providers/ollama.js +11 -1
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.js +322 -85
- package/dist/router.d.ts +0 -1
- package/dist/router.js +0 -1
- package/dist/stream-decoder.d.ts +0 -1
- package/dist/stream-decoder.js +0 -1
- package/dist/structured-output.d.ts +0 -1
- package/dist/structured-output.js +0 -1
- package/dist/thinking.d.ts +12 -1
- package/dist/thinking.js +34 -1
- package/dist/tools.d.ts +0 -1
- package/dist/tools.js +0 -1
- package/dist/zod-adapter.d.ts +0 -1
- package/dist/zod-adapter.js +0 -1
- package/package.json +1 -2
- package/dist/ai-model.d.ts.map +0 -1
- package/dist/ai-model.js.map +0 -1
- package/dist/auditor.d.ts.map +0 -1
- package/dist/auditor.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/gemma-channel.d.ts.map +0 -1
- package/dist/gemma-channel.js.map +0 -1
- package/dist/gemma-diffusion.d.ts.map +0 -1
- package/dist/gemma-diffusion.js.map +0 -1
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interfaces.d.ts.map +0 -1
- package/dist/interfaces.js.map +0 -1
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/ollama.d.ts.map +0 -1
- package/dist/providers/ollama.js.map +0 -1
- package/dist/providers/openai.d.ts.map +0 -1
- package/dist/providers/openai.js.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js.map +0 -1
- package/dist/stream-decoder.d.ts.map +0 -1
- package/dist/stream-decoder.js.map +0 -1
- package/dist/structured-output.d.ts.map +0 -1
- package/dist/structured-output.js.map +0 -1
- package/dist/thinking.d.ts.map +0 -1
- package/dist/thinking.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/zod-adapter.d.ts.map +0 -1
- package/dist/zod-adapter.js.map +0 -1
- package/src/ai-model.ts +0 -400
- package/src/auditor.ts +0 -213
- package/src/client.ts +0 -402
- package/src/debug/debug-google-streaming.ts +0 -97
- package/src/debug/debug-tool-execution.ts +0 -86
- package/src/debug/test-lmstudio-tools.ts +0 -155
- package/src/demos/README.md +0 -47
- package/src/demos/basic/universal-llm-examples.ts +0 -161
- package/src/demos/diffusion-gemma/.env +0 -29
- package/src/demos/diffusion-gemma/.env.example +0 -27
- package/src/demos/diffusion-gemma/CLAUDE.md +0 -95
- package/src/demos/diffusion-gemma/README.md +0 -59
- package/src/demos/diffusion-gemma/canvas.ts +0 -1606
- package/src/demos/diffusion-gemma/docker-compose.yml +0 -29
- package/src/demos/diffusion-gemma/probe-stream.ts +0 -51
- package/src/demos/diffusion-gemma/probe-tools.ts +0 -55
- package/src/demos/diffusion-gemma/server.ts +0 -1205
- package/src/demos/diffusion-gemma/start-vllm.sh +0 -98
- package/src/demos/mcp/astrid-memory-demo.ts +0 -295
- package/src/demos/mcp/astrid-persona-memory.ts +0 -357
- package/src/demos/mcp/mcp-mongodb-demo.ts +0 -275
- package/src/demos/mcp/simple-astrid-memory.ts +0 -148
- package/src/demos/mcp/simple-mcp-demo.ts +0 -68
- package/src/demos/mcp/working-mcp-demo.ts +0 -62
- package/src/demos/model-alias-demo.ts +0 -0
- package/src/demos/tools/RAG_MEMORY_INTEGRATION.md +0 -267
- package/src/demos/tools/astrid-memory-demo.ts +0 -270
- package/src/demos/tools/astrid-production-memory-clean.ts +0 -785
- package/src/demos/tools/astrid-production-memory.ts +0 -558
- package/src/demos/tools/basic-translation-test.ts +0 -66
- package/src/demos/tools/chromadb-similarity-tuning.ts +0 -390
- package/src/demos/tools/clean-multilingual-conversation.ts +0 -209
- package/src/demos/tools/clean-translation-test.ts +0 -119
- package/src/demos/tools/clean-universal-multilingual-test.ts +0 -131
- package/src/demos/tools/complete-rag-demo.ts +0 -369
- package/src/demos/tools/complete-tool-demo.ts +0 -132
- package/src/demos/tools/demo-tool-calling.ts +0 -124
- package/src/demos/tools/dynamic-language-switching-test.ts +0 -251
- package/src/demos/tools/hybrid-thinking-test.ts +0 -154
- package/src/demos/tools/memory-integration-test.ts +0 -420
- package/src/demos/tools/multilingual-memory-system.ts +0 -802
- package/src/demos/tools/ondemand-translation-demo.ts +0 -655
- package/src/demos/tools/production-tool-demo.ts +0 -245
- package/src/demos/tools/revolutionary-multilingual-test.ts +0 -151
- package/src/demos/tools/rigorous-language-analysis.ts +0 -218
- package/src/demos/tools/test-universal-memory-system.ts +0 -126
- package/src/demos/tools/translation-integration-guide.ts +0 -346
- package/src/demos/tools/universal-memory-system.ts +0 -560
- package/src/gemma-channel.ts +0 -47
- package/src/gemma-diffusion.ts +0 -167
- package/src/http.ts +0 -261
- package/src/index.ts +0 -180
- package/src/interfaces.ts +0 -843
- package/src/mcp.ts +0 -345
- package/src/providers/anthropic.ts +0 -796
- package/src/providers/google.ts +0 -840
- package/src/providers/index.ts +0 -8
- package/src/providers/ollama.ts +0 -503
- package/src/providers/openai.ts +0 -587
- package/src/router.ts +0 -785
- package/src/stream-decoder.ts +0 -535
- package/src/structured-output.ts +0 -759
- package/src/test-scripts/test-advanced-tools.ts +0 -310
- package/src/test-scripts/test-google-deep-research.ts +0 -33
- package/src/test-scripts/test-google-streaming-enhanced.ts +0 -147
- package/src/test-scripts/test-google-streaming.ts +0 -63
- package/src/test-scripts/test-google-system-prompt-comprehensive.ts +0 -189
- package/src/test-scripts/test-google-thinking.ts +0 -46
- package/src/test-scripts/test-mcp-config.ts +0 -28
- package/src/test-scripts/test-mcp-connection.ts +0 -29
- package/src/test-scripts/test-system-message-positions.ts +0 -163
- package/src/test-scripts/test-system-prompt-improvement-demo.ts +0 -83
- package/src/test-scripts/test-tool-calling.ts +0 -231
- package/src/test-scripts/test-vllm-qwen36.ts +0 -256
- package/src/tests/ai-model.test.ts +0 -1614
- package/src/tests/auditor.test.ts +0 -224
- package/src/tests/gemma-diffusion.test.ts +0 -115
- package/src/tests/http.test.ts +0 -200
- package/src/tests/interfaces.test.ts +0 -117
- package/src/tests/providers/anthropic.test.ts +0 -118
- package/src/tests/providers/google.test.ts +0 -841
- package/src/tests/providers/ollama.test.ts +0 -1034
- package/src/tests/providers/openai.test.ts +0 -1511
- package/src/tests/router.test.ts +0 -254
- package/src/tests/stream-decoder.test.ts +0 -263
- package/src/tests/structured-output.test.ts +0 -1450
- package/src/tests/thinking.test.ts +0 -65
- package/src/tests/tools.test.ts +0 -175
- package/src/thinking.ts +0 -73
- package/src/tools.ts +0 -246
- package/src/zod-adapter.ts +0 -72
package/dist/providers/openai.js
CHANGED
|
@@ -5,12 +5,206 @@
|
|
|
5
5
|
* Works with: OpenAI, OpenRouter, LM Studio, LlamaCpp, vLLM, Groq, Together.
|
|
6
6
|
*/
|
|
7
7
|
import { BaseLLMClient } from '../client.js';
|
|
8
|
-
import { resolveThinking, isOpenAIReasoningModel } from '../thinking.js';
|
|
8
|
+
import { resolveThinking, isOpenAIReasoningModel, supportsChatTemplateKwargs } from '../thinking.js';
|
|
9
9
|
import { httpRequest, httpStream, parseSSE, buildHeaders } from '../http.js';
|
|
10
|
-
import { StandardChatDecoder } from '../stream-decoder.js';
|
|
10
|
+
import { createDecoder, StandardChatDecoder } from '../stream-decoder.js';
|
|
11
11
|
import { normalizeJsonSchema, getJsonSchemaFromConfig, } from '../structured-output.js';
|
|
12
12
|
import { isGemmaDiffusionModel, parseGemmaDiffusionOutput } from '../gemma-diffusion.js';
|
|
13
|
+
const VLLM_AUTO_TOOL_CHOICE_HINT = 'vLLM rejected automatic tool choice. Retrying with text-level tool calling. To use native tool_calls, start vLLM with --enable-auto-tool-choice and --tool-call-parser <parser>.';
|
|
14
|
+
function normalizeMessagesForOpenAICompat(messages) {
|
|
15
|
+
let sawNonSystem = false;
|
|
16
|
+
return messages.map(message => {
|
|
17
|
+
if (message.role !== 'system') {
|
|
18
|
+
sawNonSystem = true;
|
|
19
|
+
return {
|
|
20
|
+
...message,
|
|
21
|
+
content: message.content ?? '',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (!sawNonSystem) {
|
|
25
|
+
return {
|
|
26
|
+
...message,
|
|
27
|
+
content: message.content ?? '',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...message,
|
|
32
|
+
role: 'user',
|
|
33
|
+
content: `[SYSTEM MESSAGE]\n${stringifyMessageContent(message.content)}`,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function stringifyMessageContent(content) {
|
|
38
|
+
if (typeof content === 'string')
|
|
39
|
+
return content;
|
|
40
|
+
return content
|
|
41
|
+
.map(part => {
|
|
42
|
+
if (part.type === 'text')
|
|
43
|
+
return part.text;
|
|
44
|
+
if (part.type === 'image_url')
|
|
45
|
+
return `[Image: ${part.image_url.url}]`;
|
|
46
|
+
if (part.type === 'audio')
|
|
47
|
+
return `[Audio: ${part.audio.mimeType}]`;
|
|
48
|
+
return '';
|
|
49
|
+
})
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.join('\n');
|
|
52
|
+
}
|
|
53
|
+
function hasToolDefinitions(body) {
|
|
54
|
+
const tools = body['tools'];
|
|
55
|
+
return Array.isArray(tools) && tools.length > 0;
|
|
56
|
+
}
|
|
57
|
+
function isVllmAutoToolChoiceError(value) {
|
|
58
|
+
const text = value instanceof Error
|
|
59
|
+
? value.message
|
|
60
|
+
: typeof value === 'string'
|
|
61
|
+
? value
|
|
62
|
+
: JSON.stringify(value ?? '');
|
|
63
|
+
const normalized = text.toLowerCase();
|
|
64
|
+
return (normalized.includes('auto')
|
|
65
|
+
&& normalized.includes('tool choice requires --enable-auto-tool-choice')
|
|
66
|
+
&& normalized.includes('--tool-call-parser'));
|
|
67
|
+
}
|
|
68
|
+
async function requestWithVllmToolFallback(url, request, tools, onFallback) {
|
|
69
|
+
try {
|
|
70
|
+
return await httpRequest(url, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: request.headers,
|
|
73
|
+
body: request.body,
|
|
74
|
+
timeout: request.timeout,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (tools?.length
|
|
79
|
+
&& hasToolDefinitions(request.body)
|
|
80
|
+
&& isVllmAutoToolChoiceError(error)) {
|
|
81
|
+
onFallback();
|
|
82
|
+
return httpRequest(url, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: request.headers,
|
|
85
|
+
body: withoutNativeTools(request.body, tools),
|
|
86
|
+
timeout: request.timeout,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function parseJsonObject(text) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(text);
|
|
95
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* not JSON */
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function parseTextToolCallBody(content) {
|
|
105
|
+
const body = content.trim();
|
|
106
|
+
if (!body)
|
|
107
|
+
return [];
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(body);
|
|
110
|
+
const rawCalls = Array.isArray(parsed) ? parsed : [parsed];
|
|
111
|
+
const calls = [];
|
|
112
|
+
for (const rawCall of rawCalls) {
|
|
113
|
+
if (!rawCall || typeof rawCall !== 'object')
|
|
114
|
+
continue;
|
|
115
|
+
const record = rawCall;
|
|
116
|
+
const name = record['name'];
|
|
117
|
+
if (typeof name !== 'string' || !name)
|
|
118
|
+
continue;
|
|
119
|
+
const args = record['arguments'] ?? record['parameters'] ?? record['args'] ?? {};
|
|
120
|
+
calls.push({
|
|
121
|
+
name,
|
|
122
|
+
arguments: typeof args === 'string' ? JSON.stringify(parseJsonObject(args) ?? {}) : JSON.stringify(args ?? {}),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (calls.length > 0)
|
|
126
|
+
return calls;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* not structured JSON */
|
|
130
|
+
}
|
|
131
|
+
const functionCallMatch = /^([@A-Za-z_][@A-Za-z0-9_.:-]*)\s*\(([\s\S]*)\)\s*$/u.exec(body);
|
|
132
|
+
if (functionCallMatch) {
|
|
133
|
+
const rawArgs = functionCallMatch[2].trim();
|
|
134
|
+
const args = rawArgs ? parseJsonObject(rawArgs) : {};
|
|
135
|
+
if (args) {
|
|
136
|
+
return [{ name: functionCallMatch[1], arguments: JSON.stringify(args) }];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const calls = [];
|
|
140
|
+
const funcPattern = /<function=([@A-Za-z_][@A-Za-z0-9_.:-]*)>([\s\S]*?)<\/function>/g;
|
|
141
|
+
let fMatch;
|
|
142
|
+
while ((fMatch = funcPattern.exec(body)) !== null) {
|
|
143
|
+
const args = {};
|
|
144
|
+
const paramPattern = /<parameter=([A-Za-z_][A-Za-z0-9_-]*)>([\s\S]*?)<\/parameter>/g;
|
|
145
|
+
let pMatch;
|
|
146
|
+
while ((pMatch = paramPattern.exec(fMatch[2] ?? '')) !== null) {
|
|
147
|
+
args[pMatch[1]] = pMatch[2].trim();
|
|
148
|
+
}
|
|
149
|
+
calls.push({ name: fMatch[1], arguments: JSON.stringify(args) });
|
|
150
|
+
}
|
|
151
|
+
return calls;
|
|
152
|
+
}
|
|
153
|
+
function recoverToolCallsFromText(content, knownToolNames, generateId) {
|
|
154
|
+
if (!content || content.length < 10)
|
|
155
|
+
return null;
|
|
156
|
+
const calls = [];
|
|
157
|
+
let cleanContent = content;
|
|
158
|
+
const isKnownTool = (name) => knownToolNames.has(name);
|
|
159
|
+
const toolCallPattern = /<tool_call>([\s\S]*?)<\/tool_call>/g;
|
|
160
|
+
let tcMatch;
|
|
161
|
+
while ((tcMatch = toolCallPattern.exec(content)) !== null) {
|
|
162
|
+
const parsedCalls = parseTextToolCallBody(tcMatch[1]);
|
|
163
|
+
let matched = false;
|
|
164
|
+
for (const parsed of parsedCalls) {
|
|
165
|
+
if (!isKnownTool(parsed.name))
|
|
166
|
+
continue;
|
|
167
|
+
matched = true;
|
|
168
|
+
calls.push({
|
|
169
|
+
id: generateId(),
|
|
170
|
+
type: 'function',
|
|
171
|
+
function: { name: parsed.name, arguments: parsed.arguments },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (matched)
|
|
175
|
+
cleanContent = cleanContent.replace(tcMatch[0], '');
|
|
176
|
+
}
|
|
177
|
+
if (calls.length === 0)
|
|
178
|
+
return null;
|
|
179
|
+
return { calls, cleanContent: cleanContent.trim() };
|
|
180
|
+
}
|
|
181
|
+
function toolFallbackInstruction(tools) {
|
|
182
|
+
const toolLines = tools.map(tool => {
|
|
183
|
+
const fn = tool.function;
|
|
184
|
+
return `- ${fn.name}: ${fn.description}\n parameters JSON schema: ${JSON.stringify(fn.parameters)}`;
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
role: 'system',
|
|
188
|
+
content: 'The server does not support native OpenAI tool parsing for this request. '
|
|
189
|
+
+ 'Use this text tool protocol instead.\n\n'
|
|
190
|
+
+ 'When you need a tool, respond with exactly one or more tool calls and no prose:\n'
|
|
191
|
+
+ '<tool_call>tool_name({"argument":"value"})</tool_call>\n\n'
|
|
192
|
+
+ 'After tool results are provided, answer the user normally. Available tools:\n'
|
|
193
|
+
+ toolLines.join('\n'),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function withTextToolFallbackMessages(messages, tools) {
|
|
197
|
+
return [toolFallbackInstruction(tools), ...messages];
|
|
198
|
+
}
|
|
199
|
+
function withoutNativeTools(body, tools) {
|
|
200
|
+
const fallbackBody = { ...body };
|
|
201
|
+
delete fallbackBody['tools'];
|
|
202
|
+
delete fallbackBody['tool_choice'];
|
|
203
|
+
fallbackBody['messages'] = withTextToolFallbackMessages(body['messages'] ?? [], tools);
|
|
204
|
+
return fallbackBody;
|
|
205
|
+
}
|
|
13
206
|
export class OpenAICompatibleClient extends BaseLLMClient {
|
|
207
|
+
warnedVllmToolFallback = false;
|
|
14
208
|
/**
|
|
15
209
|
* DiffusionGemma on trimmed vLLM builds has no server-side reasoning or
|
|
16
210
|
* tool-call parser — the native channel protocol is handled client-side
|
|
@@ -60,13 +254,19 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
60
254
|
}
|
|
61
255
|
super({ ...options, url: base }, auditor);
|
|
62
256
|
}
|
|
257
|
+
warnVllmToolFallback() {
|
|
258
|
+
if (this.warnedVllmToolFallback)
|
|
259
|
+
return;
|
|
260
|
+
this.warnedVllmToolFallback = true;
|
|
261
|
+
console.warn(`[OpenAI] ${VLLM_AUTO_TOOL_CHOICE_HINT}`);
|
|
262
|
+
}
|
|
63
263
|
// ========================================================================
|
|
64
264
|
// Chat
|
|
65
265
|
// ========================================================================
|
|
66
266
|
async chat(messages, options) {
|
|
67
267
|
// Structured output and tools can now be used together.\n // The provider sends both response_format and tools in the request.\n // The Router handles skipping validation when the response contains tool calls.
|
|
68
268
|
const url = this.buildUrl('/chat/completions');
|
|
69
|
-
const tools = options?.tools
|
|
269
|
+
const tools = options?.tools;
|
|
70
270
|
const body = {
|
|
71
271
|
model: this.options.model,
|
|
72
272
|
messages: this.convertMessages(messages),
|
|
@@ -100,12 +300,11 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
100
300
|
provider: 'openai',
|
|
101
301
|
model: this.options.model,
|
|
102
302
|
});
|
|
103
|
-
const response = await
|
|
104
|
-
method: 'POST',
|
|
303
|
+
const response = await requestWithVllmToolFallback(url, {
|
|
105
304
|
headers: buildHeaders(this.options),
|
|
106
305
|
body,
|
|
107
306
|
timeout: this.options.timeout ?? 30000,
|
|
108
|
-
});
|
|
307
|
+
}, tools, () => this.warnVllmToolFallback());
|
|
109
308
|
const data = response.data;
|
|
110
309
|
const choice = data.choices[0];
|
|
111
310
|
if (!choice) {
|
|
@@ -152,6 +351,14 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
152
351
|
}));
|
|
153
352
|
}
|
|
154
353
|
}
|
|
354
|
+
if (!toolCalls?.length && tools?.length && content) {
|
|
355
|
+
const knownToolNames = new Set(tools.map(tool => tool.function.name));
|
|
356
|
+
const recovered = recoverToolCallsFromText(content, knownToolNames, () => this.generateToolCallId());
|
|
357
|
+
if (recovered) {
|
|
358
|
+
toolCalls = recovered.calls;
|
|
359
|
+
content = recovered.cleanContent;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
155
362
|
const result = {
|
|
156
363
|
message: {
|
|
157
364
|
role: 'assistant',
|
|
@@ -177,7 +384,7 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
177
384
|
// ========================================================================
|
|
178
385
|
async *chatStream(messages, options) {
|
|
179
386
|
const url = this.buildUrl('/chat/completions');
|
|
180
|
-
const tools = options?.tools
|
|
387
|
+
const tools = options?.tools;
|
|
181
388
|
const body = {
|
|
182
389
|
model: this.options.model,
|
|
183
390
|
messages: this.convertMessages(messages),
|
|
@@ -202,91 +409,118 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
202
409
|
provider: 'openai',
|
|
203
410
|
model: this.options.model,
|
|
204
411
|
});
|
|
205
|
-
// In gemma-native mode
|
|
206
|
-
//
|
|
412
|
+
// In gemma-native mode, or when a caller selects an explicit decoder,
|
|
413
|
+
// the decoder classifies content into typed events instead of exposing
|
|
414
|
+
// raw deltas with protocol tags still attached.
|
|
207
415
|
const decoderEvents = [];
|
|
208
|
-
const
|
|
416
|
+
const decoderOption = options?.decoder;
|
|
417
|
+
const decoderInstanceProvided = Boolean(decoderOption && typeof decoderOption === 'object');
|
|
418
|
+
const yieldDecoderEvents = !decoderInstanceProvided && (this.gemmaNative || typeof decoderOption === 'string');
|
|
419
|
+
const decoder = typeof decoderOption === 'string'
|
|
420
|
+
? createDecoder(decoderOption, e => decoderEvents.push(e))
|
|
421
|
+
: decoderInstanceProvided
|
|
422
|
+
? decoderOption
|
|
423
|
+
: new StandardChatDecoder(this.gemmaNative ? e => decoderEvents.push(e) : () => { });
|
|
209
424
|
// Track accumulated tool calls across chunks
|
|
210
425
|
const toolCallAccum = new Map();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
headers: buildHeaders(this.options),
|
|
214
|
-
body,
|
|
215
|
-
timeout: this.options.timeout ?? 120000,
|
|
216
|
-
});
|
|
426
|
+
let activeBody = body;
|
|
427
|
+
let retriedWithTextTools = false;
|
|
217
428
|
let usage;
|
|
218
429
|
// Accumulates reasoning deltas from servers that stream a dedicated
|
|
219
430
|
// `reasoning` / `reasoning_content` field (vLLM, DeepSeek-R1, etc.).
|
|
220
431
|
let reasoningBuffer = '';
|
|
221
|
-
|
|
432
|
+
while (true) {
|
|
433
|
+
const stream = httpStream(url, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: buildHeaders(this.options),
|
|
436
|
+
body: activeBody,
|
|
437
|
+
timeout: this.options.timeout ?? 120000,
|
|
438
|
+
});
|
|
222
439
|
try {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (!delta)
|
|
234
|
-
continue;
|
|
235
|
-
// Surface server-side reasoning deltas as thinking events.
|
|
236
|
-
const reasoningDelta = delta.reasoning ?? delta.reasoning_content;
|
|
237
|
-
if (reasoningDelta) {
|
|
238
|
-
reasoningBuffer += reasoningDelta;
|
|
239
|
-
yield { type: 'thinking', content: reasoningDelta };
|
|
240
|
-
}
|
|
241
|
-
if (delta.content) {
|
|
242
|
-
decoder.push(delta.content);
|
|
243
|
-
if (this.gemmaNative) {
|
|
244
|
-
while (decoderEvents.length)
|
|
245
|
-
yield decoderEvents.shift();
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
yield { type: 'text', content: delta.content };
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
// Accumulate streamed tool calls
|
|
252
|
-
if (delta.tool_calls) {
|
|
253
|
-
for (const tc of delta.tool_calls) {
|
|
254
|
-
const existing = toolCallAccum.get(tc.index);
|
|
255
|
-
if (!existing) {
|
|
256
|
-
toolCallAccum.set(tc.index, {
|
|
257
|
-
id: tc.id || this.generateToolCallId(),
|
|
258
|
-
type: 'function',
|
|
259
|
-
function: {
|
|
260
|
-
name: tc.function?.name || '',
|
|
261
|
-
arguments: tc.function?.arguments || '',
|
|
262
|
-
},
|
|
263
|
-
});
|
|
440
|
+
for await (const { data } of parseSSE(stream)) {
|
|
441
|
+
try {
|
|
442
|
+
const parsed = JSON.parse(data);
|
|
443
|
+
if (parsed.usage) {
|
|
444
|
+
usage = {
|
|
445
|
+
inputTokens: parsed.usage.prompt_tokens,
|
|
446
|
+
outputTokens: parsed.usage.completion_tokens,
|
|
447
|
+
totalTokens: parsed.usage.total_tokens,
|
|
448
|
+
cachedTokens: parsed.usage.prompt_tokens_details?.cached_tokens,
|
|
449
|
+
};
|
|
264
450
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
451
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
452
|
+
if (!delta)
|
|
453
|
+
continue;
|
|
454
|
+
// Surface server-side reasoning deltas as thinking events.
|
|
455
|
+
const reasoningDelta = delta.reasoning ?? delta.reasoning_content;
|
|
456
|
+
if (reasoningDelta) {
|
|
457
|
+
reasoningBuffer += reasoningDelta;
|
|
458
|
+
yield { type: 'thinking', content: reasoningDelta };
|
|
459
|
+
}
|
|
460
|
+
if (delta.content) {
|
|
461
|
+
decoder.push(delta.content);
|
|
462
|
+
if (yieldDecoderEvents) {
|
|
463
|
+
while (decoderEvents.length)
|
|
464
|
+
yield decoderEvents.shift();
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
yield { type: 'text', content: delta.content };
|
|
268
468
|
}
|
|
269
|
-
|
|
270
|
-
|
|
469
|
+
}
|
|
470
|
+
// Accumulate streamed tool calls
|
|
471
|
+
if (delta.tool_calls) {
|
|
472
|
+
for (const tc of delta.tool_calls) {
|
|
473
|
+
const existing = toolCallAccum.get(tc.index);
|
|
474
|
+
if (!existing) {
|
|
475
|
+
toolCallAccum.set(tc.index, {
|
|
476
|
+
id: tc.id || this.generateToolCallId(),
|
|
477
|
+
type: 'function',
|
|
478
|
+
function: {
|
|
479
|
+
name: tc.function?.name || '',
|
|
480
|
+
arguments: tc.function?.arguments || '',
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
if (tc.function?.arguments) {
|
|
486
|
+
existing.function.arguments += tc.function.arguments;
|
|
487
|
+
}
|
|
488
|
+
if (tc.function?.name) {
|
|
489
|
+
existing.function.name += tc.function.name;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// Emit tool calls when stream finishes
|
|
495
|
+
if (parsed.choices?.[0]?.finish_reason === 'tool_calls' || parsed.choices?.[0]?.finish_reason === 'stop') {
|
|
496
|
+
if (toolCallAccum.size > 0) {
|
|
497
|
+
const calls = Array.from(toolCallAccum.values())
|
|
498
|
+
.map(tc => this.normalizeToolCall(tc));
|
|
499
|
+
yield { type: 'tool_call', calls };
|
|
271
500
|
}
|
|
272
501
|
}
|
|
273
502
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (parsed.choices?.[0]?.finish_reason === 'tool_calls' || parsed.choices?.[0]?.finish_reason === 'stop') {
|
|
277
|
-
if (toolCallAccum.size > 0) {
|
|
278
|
-
const calls = Array.from(toolCallAccum.values())
|
|
279
|
-
.map(tc => this.normalizeToolCall(tc));
|
|
280
|
-
yield { type: 'tool_call', calls };
|
|
503
|
+
catch {
|
|
504
|
+
// Skip unparseable SSE data
|
|
281
505
|
}
|
|
282
506
|
}
|
|
507
|
+
break;
|
|
283
508
|
}
|
|
284
|
-
catch {
|
|
285
|
-
|
|
509
|
+
catch (error) {
|
|
510
|
+
if (!retriedWithTextTools
|
|
511
|
+
&& tools?.length
|
|
512
|
+
&& hasToolDefinitions(activeBody)
|
|
513
|
+
&& isVllmAutoToolChoiceError(error)) {
|
|
514
|
+
this.warnVllmToolFallback();
|
|
515
|
+
activeBody = withoutNativeTools(activeBody, tools);
|
|
516
|
+
retriedWithTextTools = true;
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
throw error;
|
|
286
520
|
}
|
|
287
521
|
}
|
|
288
522
|
decoder.flush();
|
|
289
|
-
if (
|
|
523
|
+
if (yieldDecoderEvents) {
|
|
290
524
|
while (decoderEvents.length)
|
|
291
525
|
yield decoderEvents.shift();
|
|
292
526
|
}
|
|
@@ -332,6 +566,15 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
332
566
|
yield { type: 'tool_call', calls: finalToolCalls };
|
|
333
567
|
}
|
|
334
568
|
}
|
|
569
|
+
if (!finalToolCalls?.length && tools?.length && cleanContent) {
|
|
570
|
+
const knownToolNames = new Set(tools.map(tool => tool.function.name));
|
|
571
|
+
const recovered = recoverToolCallsFromText(cleanContent, knownToolNames, () => this.generateToolCallId());
|
|
572
|
+
if (recovered) {
|
|
573
|
+
finalToolCalls = recovered.calls;
|
|
574
|
+
cleanContent = recovered.cleanContent;
|
|
575
|
+
yield { type: 'tool_call', calls: finalToolCalls };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
335
578
|
return {
|
|
336
579
|
message: {
|
|
337
580
|
role: 'assistant',
|
|
@@ -400,12 +643,7 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
400
643
|
// Internals
|
|
401
644
|
// ========================================================================
|
|
402
645
|
convertMessages(messages) {
|
|
403
|
-
|
|
404
|
-
return messages.map(msg => ({
|
|
405
|
-
...msg,
|
|
406
|
-
// Ensure content is never null/undefined
|
|
407
|
-
content: msg.content ?? '',
|
|
408
|
-
}));
|
|
646
|
+
return normalizeMessagesForOpenAICompat(messages);
|
|
409
647
|
}
|
|
410
648
|
buildRequestParams(options) {
|
|
411
649
|
const params = {
|
|
@@ -423,16 +661,16 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
423
661
|
// A user-supplied value (via parameters) always wins.
|
|
424
662
|
const thinking = resolveThinking(options?.thinking, this.options.thinking);
|
|
425
663
|
if (thinking) {
|
|
426
|
-
const isOfficialOpenAI = (this.options.url ?? '').includes('api.openai.com');
|
|
427
664
|
if (isOpenAIReasoningModel(this.options.model)) {
|
|
428
665
|
if (params['reasoning_effort'] === undefined) {
|
|
429
666
|
params['reasoning_effort'] = thinking.enabled ? (thinking.level ?? 'medium') : 'minimal';
|
|
430
667
|
}
|
|
431
668
|
}
|
|
432
|
-
else if (
|
|
433
|
-
// `chat_template_kwargs` is a vLLM/Qwen extension.
|
|
434
|
-
//
|
|
435
|
-
//
|
|
669
|
+
else if (supportsChatTemplateKwargs(this.options.url)) {
|
|
670
|
+
// `chat_template_kwargs` is a self-hosted vLLM/Qwen extension.
|
|
671
|
+
// Official OpenAI and hosted OpenAI-compatible gateways (Cerebras,
|
|
672
|
+
// Groq, Fireworks, …) reject unknown body fields with HTTP 400,
|
|
673
|
+
// so only send it to endpoints not on the strict-host list.
|
|
436
674
|
const existing = params['chat_template_kwargs'] ?? {};
|
|
437
675
|
params['chat_template_kwargs'] = { enable_thinking: thinking.enabled, ...existing };
|
|
438
676
|
}
|
|
@@ -478,4 +716,3 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
478
716
|
};
|
|
479
717
|
}
|
|
480
718
|
}
|
|
481
|
-
//# sourceMappingURL=openai.js.map
|
package/dist/router.d.ts
CHANGED
package/dist/router.js
CHANGED
package/dist/stream-decoder.d.ts
CHANGED
|
@@ -148,4 +148,3 @@ export declare function getRegisteredDecoders(): string[];
|
|
|
148
148
|
* @throws Error if the decoder type is not registered
|
|
149
149
|
*/
|
|
150
150
|
export declare function createDecoder(type: DecoderType | string, callback: DecoderCallback, options?: DecoderOptions): StreamDecoder;
|
|
151
|
-
//# sourceMappingURL=stream-decoder.d.ts.map
|
package/dist/stream-decoder.js
CHANGED
package/dist/thinking.d.ts
CHANGED
|
@@ -23,6 +23,18 @@ export interface ResolvedThinking {
|
|
|
23
23
|
export declare function resolveThinking(perCall: boolean | ThinkingLevel | undefined, config: boolean | ThinkingLevel | undefined): ResolvedThinking | undefined;
|
|
24
24
|
/** Heuristic: OpenAI reasoning models use `reasoning_effort` (o-series, GPT-5). */
|
|
25
25
|
export declare function isOpenAIReasoningModel(model: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether an OpenAI-compatible endpoint accepts the vLLM/SGLang
|
|
28
|
+
* `chat_template_kwargs` extension (used to toggle `enable_thinking`).
|
|
29
|
+
*
|
|
30
|
+
* This is a self-hosted-server feature. Commercial hosted gateways that expose
|
|
31
|
+
* an OpenAI-compatible surface (OpenAI, Cerebras, Groq, Fireworks, Together,
|
|
32
|
+
* Mistral, DeepSeek, OpenRouter, …) reject unknown body fields, so we only send
|
|
33
|
+
* it to endpoints not on the strict list (assumed self-hosted vLLM/Qwen). When
|
|
34
|
+
* the URL is unknown/empty we default to permissive (self-hosted) behavior to
|
|
35
|
+
* preserve the prior contract for local servers.
|
|
36
|
+
*/
|
|
37
|
+
export declare function supportsChatTemplateKwargs(url: string | undefined): boolean;
|
|
26
38
|
/**
|
|
27
39
|
* Gemini 2.5 `thinkingBudget` for a level. 0 disables, -1 is dynamic, and the
|
|
28
40
|
* Flash range is 0–24576. A bare `true` (no level) maps to dynamic (-1).
|
|
@@ -33,4 +45,3 @@ export declare function geminiThinkingBudget(level: ThinkingLevel | undefined):
|
|
|
33
45
|
* API minimum) and < `maxTokens` (the API requires headroom for the answer).
|
|
34
46
|
*/
|
|
35
47
|
export declare function anthropicThinkingBudget(level: ThinkingLevel | undefined, maxTokens: number): number;
|
|
36
|
-
//# sourceMappingURL=thinking.d.ts.map
|
package/dist/thinking.js
CHANGED
|
@@ -24,6 +24,40 @@ export function resolveThinking(perCall, config) {
|
|
|
24
24
|
export function isOpenAIReasoningModel(model) {
|
|
25
25
|
return /^(o\d|gpt-5)/i.test(model);
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Hosted, OpenAI-*compatible* gateways that validate the request body strictly
|
|
29
|
+
* and reject unknown fields with HTTP 400 — just like official OpenAI. The
|
|
30
|
+
* `chat_template_kwargs` knob (below) is a self-hosted vLLM/SGLang extension and
|
|
31
|
+
* must NOT be sent to these. Matched as case-insensitive host substrings.
|
|
32
|
+
*/
|
|
33
|
+
const STRICT_OPENAI_COMPAT_HOSTS = [
|
|
34
|
+
'api.openai.com',
|
|
35
|
+
'api.cerebras.ai',
|
|
36
|
+
'api.groq.com',
|
|
37
|
+
'api.fireworks.ai',
|
|
38
|
+
'api.together.xyz',
|
|
39
|
+
'api.together.ai',
|
|
40
|
+
'api.mistral.ai',
|
|
41
|
+
'api.deepseek.com',
|
|
42
|
+
'api.perplexity.ai',
|
|
43
|
+
'openrouter.ai',
|
|
44
|
+
'api.x.ai',
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* Whether an OpenAI-compatible endpoint accepts the vLLM/SGLang
|
|
48
|
+
* `chat_template_kwargs` extension (used to toggle `enable_thinking`).
|
|
49
|
+
*
|
|
50
|
+
* This is a self-hosted-server feature. Commercial hosted gateways that expose
|
|
51
|
+
* an OpenAI-compatible surface (OpenAI, Cerebras, Groq, Fireworks, Together,
|
|
52
|
+
* Mistral, DeepSeek, OpenRouter, …) reject unknown body fields, so we only send
|
|
53
|
+
* it to endpoints not on the strict list (assumed self-hosted vLLM/Qwen). When
|
|
54
|
+
* the URL is unknown/empty we default to permissive (self-hosted) behavior to
|
|
55
|
+
* preserve the prior contract for local servers.
|
|
56
|
+
*/
|
|
57
|
+
export function supportsChatTemplateKwargs(url) {
|
|
58
|
+
const u = (url ?? '').toLowerCase();
|
|
59
|
+
return !STRICT_OPENAI_COMPAT_HOSTS.some((host) => u.includes(host));
|
|
60
|
+
}
|
|
27
61
|
/**
|
|
28
62
|
* Gemini 2.5 `thinkingBudget` for a level. 0 disables, -1 is dynamic, and the
|
|
29
63
|
* Flash range is 0–24576. A bare `true` (no level) maps to dynamic (-1).
|
|
@@ -49,4 +83,3 @@ export function anthropicThinkingBudget(level, maxTokens) {
|
|
|
49
83
|
: 2048; // bare `true`
|
|
50
84
|
return Math.max(1024, Math.min(base, maxTokens - 1024));
|
|
51
85
|
}
|
|
52
|
-
//# sourceMappingURL=thinking.js.map
|
package/dist/tools.d.ts
CHANGED
package/dist/tools.js
CHANGED
package/dist/zod-adapter.d.ts
CHANGED
package/dist/zod-adapter.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-llm-client",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A universal LLM client with transparent provider failover, streaming tool execution, pluggable reasoning, and native observability.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"dist",
|
|
44
|
-
"src",
|
|
45
44
|
"README.md",
|
|
46
45
|
"CHANGELOG.md",
|
|
47
46
|
"LICENSE"
|
package/dist/ai-model.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-model.d.ts","sourceRoot":"","sources":["../src/ai-model.ts"],"names":[],"mappings":"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"}
|