mlx-bun 0.0.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/LICENSE +21 -0
- package/README.md +362 -0
- package/THIRD_PARTY_LICENSES.md +41 -0
- package/bin/mlx-bun.mjs +32 -0
- package/docs/curve-designer.html +303 -0
- package/docs/design/adapters-end-to-end.md +201 -0
- package/docs/design/compat-cli-surface-design.md +227 -0
- package/docs/design/docs-reorg-plan.md +152 -0
- package/docs/design/hlg-sampling.md +340 -0
- package/docs/design/memory-system.md +339 -0
- package/docs/design/optimization_plan.md +344 -0
- package/docs/design/parallel-slots.md +515 -0
- package/docs/design/runtime-isolation.md +247 -0
- package/docs/design/segmented-backward-training.md +508 -0
- package/docs/investigations/chunk-finetune-experiment.md +84 -0
- package/docs/investigations/curve-bisector-routing.md +123 -0
- package/docs/investigations/curve-runs/bisector-route.json +98 -0
- package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
- package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
- package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
- package/docs/investigations/curve-terrain.html +105 -0
- package/docs/investigations/expert-offload-single-user-moe.md +292 -0
- package/docs/investigations/hlg-ab.html +34 -0
- package/docs/investigations/hlg-explorer.html +238 -0
- package/docs/investigations/hlg-figs/cliff.svg +1 -0
- package/docs/investigations/hlg-figs/cross.svg +1 -0
- package/docs/investigations/hlg-figs/edges.svg +1 -0
- package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
- package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
- package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
- package/docs/investigations/hlg-figs/terrain.svg +1 -0
- package/docs/investigations/hlg-report.html +262 -0
- package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
- package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
- package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
- package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
- package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
- package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
- package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
- package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
- package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
- package/docs/investigations/hlg-runs/hlg-map.json +383 -0
- package/docs/investigations/hlg-runs/run0-original.log +159 -0
- package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
- package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
- package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
- package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
- package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
- package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
- package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
- package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
- package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
- package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
- package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
- package/docs/investigations/hlg-runs/runN-verify.log +116 -0
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
- package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
- package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
- package/docs/investigations/hlg-sampling-investigation.md +970 -0
- package/docs/investigations/lab-build-journal.md +157 -0
- package/docs/investigations/pi-builtin-investigation.md +308 -0
- package/docs/investigations/starter-model-port-handoff.md +323 -0
- package/docs/planning/IDEAS.md +26 -0
- package/docs/planning/PRODUCT_ROADMAP.md +426 -0
- package/docs/planning/ResearchTopics.md +119 -0
- package/docs/planning/chunk-task-roadmap.md +77 -0
- package/docs/planning/journal.md +155 -0
- package/docs/reference/distribution.md +121 -0
- package/docs/reference/embedding.md +168 -0
- package/docs/reference/library-api.md +153 -0
- package/docs/reference/server-api.md +224 -0
- package/docs/reference/server-config.md +243 -0
- package/docs/segmented-backward-handoff.md +148 -0
- package/package.json +68 -0
- package/src/anthropic.ts +491 -0
- package/src/chat-template.ts +186 -0
- package/src/cli.ts +1075 -0
- package/src/config.ts +229 -0
- package/src/curve-sampler.ts +122 -0
- package/src/dataset/generators.ts +1087 -0
- package/src/dataset/index.ts +12 -0
- package/src/dataset/job.ts +81 -0
- package/src/dataset/llm.ts +71 -0
- package/src/dataset/registry.ts +528 -0
- package/src/download.ts +283 -0
- package/src/eval/README.md +86 -0
- package/src/eval/kl-prompts.ts +29 -0
- package/src/eval/kl.ts +294 -0
- package/src/eval/quality-db.ts +92 -0
- package/src/eval/runner.ts +102 -0
- package/src/eval/score.ts +49 -0
- package/src/eval/tasks/bfcl.ts +500 -0
- package/src/eval/tasks/gsm8k.ts +85 -0
- package/src/eval/tasks/hashhop.ts +109 -0
- package/src/eval/tasks/humaneval.ts +252 -0
- package/src/eval/tasks/ifeval.ts +354 -0
- package/src/eval/tasks/mmlu.ts +145 -0
- package/src/evaldb.ts +199 -0
- package/src/expert-offload-build.ts +77 -0
- package/src/expert-offload.ts +61 -0
- package/src/expert-trace.ts +67 -0
- package/src/fit.ts +254 -0
- package/src/generate.ts +451 -0
- package/src/harness-pi.ts +177 -0
- package/src/hf-push.ts +628 -0
- package/src/index.ts +22 -0
- package/src/jobs/db.ts +139 -0
- package/src/jobs/index.ts +29 -0
- package/src/jobs/job-entry.ts +106 -0
- package/src/jobs/runner.ts +304 -0
- package/src/jobs/sse.ts +114 -0
- package/src/jobs/types.ts +58 -0
- package/src/kv-store.ts +185 -0
- package/src/lora.ts +314 -0
- package/src/md-modules.d.ts +7 -0
- package/src/mlx/array.ts +237 -0
- package/src/mlx/autograd.ts +267 -0
- package/src/mlx/checkpoint.ts +116 -0
- package/src/mlx/compile.ts +158 -0
- package/src/mlx/custom-vjp.ts +140 -0
- package/src/mlx/ffi.ts +433 -0
- package/src/mlx/metal-kernel.ts +130 -0
- package/src/mlx/ops.ts +707 -0
- package/src/mmap.ts +90 -0
- package/src/model/batched-mask.ts +204 -0
- package/src/model/batched-rotating.ts +300 -0
- package/src/model/compiled-decode.ts +681 -0
- package/src/model/factory.ts +27 -0
- package/src/model/fingerprint.ts +33 -0
- package/src/model/flash-attention.ts +674 -0
- package/src/model/fused-decode-kernel.ts +305 -0
- package/src/model/fused-geglu-kernel.ts +87 -0
- package/src/model/gemma4-base.ts +1616 -0
- package/src/model/gemma4.ts +1012 -0
- package/src/model/generated/gemma4-12b.ts +526 -0
- package/src/model/generated/gemma4-26b.ts +500 -0
- package/src/model/generated/gemma4-e4b.ts +757 -0
- package/src/model/generated/index.ts +18 -0
- package/src/model/minicpm5.ts +278 -0
- package/src/model/qwen3-delta.ts +224 -0
- package/src/model/qwen3_5.ts +429 -0
- package/src/model/support.ts +46 -0
- package/src/native-pack.ts +152 -0
- package/src/pi-provider.ts +95 -0
- package/src/pi-terminal.ts +264 -0
- package/src/pi-web.ts +924 -0
- package/src/preflight.ts +123 -0
- package/src/prompt-cache.ts +113 -0
- package/src/quantize/allocator.ts +627 -0
- package/src/quantize/calibration.ts +180 -0
- package/src/quantize/config-writer.ts +132 -0
- package/src/quantize/index.ts +57 -0
- package/src/quantize/job.ts +136 -0
- package/src/quantize/quantizer.ts +462 -0
- package/src/quantize/safetensors-writer.ts +171 -0
- package/src/quantize/sensitivity.ts +234 -0
- package/src/registry.ts +336 -0
- package/src/responses.ts +774 -0
- package/src/safetensors.ts +158 -0
- package/src/sampler.ts +517 -0
- package/src/serve/batch-scheduler.ts +362 -0
- package/src/serve/generation-gateway.ts +160 -0
- package/src/server.ts +1891 -0
- package/src/spec/drafter.ts +261 -0
- package/src/spec/generate.ts +221 -0
- package/src/status-page.html +497 -0
- package/src/tokenizer.ts +38 -0
- package/src/tool-call.ts +242 -0
- package/src/train/dataset.ts +461 -0
- package/src/train/export.ts +32 -0
- package/src/train/forward.ts +229 -0
- package/src/train/index.ts +43 -0
- package/src/train/job.ts +148 -0
- package/src/train/lora-params.ts +194 -0
- package/src/train/loss.ts +342 -0
- package/src/train/merge.ts +195 -0
- package/src/train/optimizer.ts +176 -0
- package/src/train/rank.ts +101 -0
- package/src/train/segmented.ts +520 -0
- package/src/train/trainer.ts +600 -0
- package/src/tui.ts +161 -0
- package/src/vision/embedder.ts +168 -0
- package/src/vision/preprocess.ts +247 -0
- package/src/vision/prompt.ts +158 -0
- package/src/vision/siglip.ts +599 -0
- package/src/web/app.html +2471 -0
- package/src/web/skills/web-research/SKILL.md +44 -0
- package/src/web/skills.ts +59 -0
- package/src/web-tools.ts +426 -0
- package/src/weights.ts +91 -0
package/src/tool-call.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Gemma 4 tool-call parsing — port of mlx-lm tool_parsers/gemma4.py.
|
|
2
|
+
//
|
|
3
|
+
// The model emits <|tool_call>call:name{key:value,...}<tool_call|>
|
|
4
|
+
// where strings are delimited by the <|"|> special token, keys are bare,
|
|
5
|
+
// and numbers/bools/objects/arrays are JSON-ish. After its tool_call
|
|
6
|
+
// blocks the model emits <|tool_response> (token 50, in the EOS set) —
|
|
7
|
+
// the request to the runtime for results.
|
|
8
|
+
//
|
|
9
|
+
// Token ids (tokenizer.json added_tokens; stable for this family):
|
|
10
|
+
// <|tool_call> 48 <tool_call|> 49 <|tool_response> 50 <|"|> 52
|
|
11
|
+
// <|channel> 100 <channel|> 101 (reasoning channel; see CHANNEL_* below)
|
|
12
|
+
|
|
13
|
+
export const TOOL_CALL_START = 48;
|
|
14
|
+
export const TOOL_CALL_END = 49;
|
|
15
|
+
export const TOOL_RESPONSE_START = 50;
|
|
16
|
+
|
|
17
|
+
/** Gemma 4 reasoning channel sentinels. With thinking on, the model wraps its
|
|
18
|
+
* chain-of-thought as <|channel>thought\n…<channel|> before the final
|
|
19
|
+
* answer. soc_token=`<|channel>` (100), eoc_token=`<channel|>` (101). These
|
|
20
|
+
* are special tokens stripped at decode, so reasoning must be split at the
|
|
21
|
+
* TOKEN level (ToolAwareStream), not from decoded text. */
|
|
22
|
+
export const CHANNEL_START = 100;
|
|
23
|
+
export const CHANNEL_END = 101;
|
|
24
|
+
|
|
25
|
+
const QUOTE = '<|"|>';
|
|
26
|
+
|
|
27
|
+
export interface ParsedToolCall {
|
|
28
|
+
name: string;
|
|
29
|
+
/** Parsed argument object (callers serialize for OpenAI's string field). */
|
|
30
|
+
arguments: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ToolSpec {
|
|
34
|
+
function?: { name?: string; parameters?: Record<string, unknown> };
|
|
35
|
+
name?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Convert gemma4 argument syntax to JSON: extract <|"|>-strings to
|
|
39
|
+
* placeholders, quote bare keys, restore strings JSON-escaped. */
|
|
40
|
+
export function gemmaArgsToJson(text: string): string {
|
|
41
|
+
const strings: string[] = [];
|
|
42
|
+
let out = "";
|
|
43
|
+
let i = 0;
|
|
44
|
+
while (i < text.length) {
|
|
45
|
+
const start = text.indexOf(QUOTE, i);
|
|
46
|
+
if (start === -1) {
|
|
47
|
+
out += text.slice(i);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
const end = text.indexOf(QUOTE, start + QUOTE.length);
|
|
51
|
+
if (end === -1) throw new Error("unterminated <|\"|> string");
|
|
52
|
+
out += text.slice(i, start) + `\x00${strings.length}\x00`;
|
|
53
|
+
strings.push(text.slice(start + QUOTE.length, end));
|
|
54
|
+
i = end + QUOTE.length;
|
|
55
|
+
}
|
|
56
|
+
// quote bare keys (after { or ,)
|
|
57
|
+
out = out.replace(/([{,])\s*([\w-]+)\s*:/g, (_, pre, key) => `${pre}${JSON.stringify(key)}:`);
|
|
58
|
+
// restore strings as JSON literals
|
|
59
|
+
out = out.replace(/\x00(\d+)\x00/g, (_, n) => JSON.stringify(strings[Number(n)]!));
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Parse every `call:name{...}` block in a decoded tool-call segment. */
|
|
64
|
+
export function parseToolCalls(text: string): ParsedToolCall[] {
|
|
65
|
+
const calls: ParsedToolCall[] = [];
|
|
66
|
+
let i = 0;
|
|
67
|
+
while (i < text.length) {
|
|
68
|
+
const m = /call:([\w-]+)\{/.exec(text.slice(i));
|
|
69
|
+
if (!m) break;
|
|
70
|
+
const nameEnd = i + m.index + m[0].length - 1; // position of '{'
|
|
71
|
+
// balanced-brace scan, skipping <|"|>-delimited strings
|
|
72
|
+
let depth = 0;
|
|
73
|
+
let j = nameEnd;
|
|
74
|
+
while (j < text.length) {
|
|
75
|
+
if (text.startsWith(QUOTE, j)) {
|
|
76
|
+
const close = text.indexOf(QUOTE, j + QUOTE.length);
|
|
77
|
+
if (close === -1) throw new Error("unterminated <|\"|> string");
|
|
78
|
+
j = close + QUOTE.length;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const c = text[j];
|
|
82
|
+
if (c === "{") depth++;
|
|
83
|
+
else if (c === "}") {
|
|
84
|
+
depth--;
|
|
85
|
+
if (depth === 0) break;
|
|
86
|
+
}
|
|
87
|
+
j++;
|
|
88
|
+
}
|
|
89
|
+
if (depth !== 0) throw new Error("unbalanced braces in tool call");
|
|
90
|
+
const argsBlock = text.slice(nameEnd, j + 1);
|
|
91
|
+
calls.push({
|
|
92
|
+
name: m[1]!,
|
|
93
|
+
arguments: JSON.parse(gemmaArgsToJson(argsBlock)) as Record<string, unknown>,
|
|
94
|
+
});
|
|
95
|
+
i = j + 1;
|
|
96
|
+
}
|
|
97
|
+
return calls;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const TOOL_CALL_BLOCK_RE = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi;
|
|
101
|
+
const XML_FUNCTION_EQUALS_RE = /^\s*<function=([^>\s]+)>\s*([\s\S]*?)\s*<\/function>\s*$/i;
|
|
102
|
+
const XML_PARAMETER_EQUALS_RE = /<parameter=([^>\s]+)>\s*([\s\S]*?)\s*<\/parameter>/gi;
|
|
103
|
+
const XML_FUNCTION_ATTR_RE = /<function\s+name=["']([^"']+)["']\s*>\s*([\s\S]*?)\s*<\/function>/gi;
|
|
104
|
+
// CDATA alternative first so a `</param>` inside a CDATA block never
|
|
105
|
+
// terminates the value early.
|
|
106
|
+
const XML_PARAM_ATTR_RE = /<param\s+name=["']([^"']+)["']\s*>((?:<!\[CDATA\[[\s\S]*?\]\]>|[\s\S])*?)<\/param>/gi;
|
|
107
|
+
|
|
108
|
+
function toolSpecName(tool: ToolSpec): string | null {
|
|
109
|
+
const name = tool.function?.name ?? tool.name;
|
|
110
|
+
return typeof name === "string" && name.trim() ? name.trim() : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function decodeXml(text: string): string {
|
|
114
|
+
return text
|
|
115
|
+
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")
|
|
116
|
+
.replace(/"/g, '"')
|
|
117
|
+
.replace(/'/g, "'")
|
|
118
|
+
.replace(/</g, "<")
|
|
119
|
+
.replace(/>/g, ">")
|
|
120
|
+
.replace(/&/g, "&")
|
|
121
|
+
.trim();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** JSON-schema type names for a parameter (handles `type: [..]` unions). */
|
|
125
|
+
function schemaTypeNames(schema: unknown): Set<string> {
|
|
126
|
+
if (!schema || typeof schema !== "object") return new Set();
|
|
127
|
+
const raw = (schema as Record<string, unknown>).type;
|
|
128
|
+
if (typeof raw === "string") return new Set([raw]);
|
|
129
|
+
if (Array.isArray(raw)) return new Set(raw.filter((t): t is string => typeof t === "string"));
|
|
130
|
+
return new Set();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function toolParameterSchema(
|
|
134
|
+
tools: ToolSpec[], toolName: string, parameterName: string,
|
|
135
|
+
): Record<string, unknown> | null {
|
|
136
|
+
for (const tool of tools) {
|
|
137
|
+
if (toolSpecName(tool) !== toolName) continue;
|
|
138
|
+
const params = tool.function?.parameters ?? (tool as Record<string, any>).parameters;
|
|
139
|
+
const props = params && typeof params === "object"
|
|
140
|
+
? (params as Record<string, any>).properties : null;
|
|
141
|
+
const schema = props && typeof props === "object" ? props[parameterName] : null;
|
|
142
|
+
return schema && typeof schema === "object" ? schema : null;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Oracle `_decode_tool_parameter_value`: string-typed params stay raw
|
|
148
|
+
* text (a path like "2025" must not become a number); everything else
|
|
149
|
+
* is JSON-decoded when possible. */
|
|
150
|
+
function decodeToolValue(value: string, schema: Record<string, unknown> | null): unknown {
|
|
151
|
+
const text = decodeXml(value);
|
|
152
|
+
if (!text) return "";
|
|
153
|
+
const types = schemaTypeNames(schema);
|
|
154
|
+
if (types.size && [...types].every((t) => t === "string" || t === "null")) return text;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(text);
|
|
157
|
+
} catch {
|
|
158
|
+
return text;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseJsonToolCall(block: string): ParsedToolCall | null {
|
|
163
|
+
let payload: unknown;
|
|
164
|
+
try {
|
|
165
|
+
payload = JSON.parse(block);
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (!payload || typeof payload !== "object") throw new Error("JSON tool_call payload must be an object");
|
|
170
|
+
const obj = payload as Record<string, any>;
|
|
171
|
+
const fn = obj.function && typeof obj.function === "object" ? obj.function : obj;
|
|
172
|
+
const name = typeof fn.name === "string" ? fn.name.trim() : "";
|
|
173
|
+
if (!name) throw new Error("JSON tool_call is missing a function name");
|
|
174
|
+
const args = fn.arguments ?? obj.arguments ?? {};
|
|
175
|
+
if (typeof args === "string") {
|
|
176
|
+
const parsed = args.trim() ? JSON.parse(args) : {};
|
|
177
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
178
|
+
throw new Error("tool_call arguments must be a JSON object");
|
|
179
|
+
return { name, arguments: parsed as Record<string, unknown> };
|
|
180
|
+
}
|
|
181
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
182
|
+
throw new Error("tool_call arguments must be a JSON object");
|
|
183
|
+
return { name, arguments: args as Record<string, unknown> };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseXmlEqualsToolCall(block: string, tools: ToolSpec[]): ParsedToolCall | null {
|
|
187
|
+
const match = XML_FUNCTION_EQUALS_RE.exec(block);
|
|
188
|
+
XML_FUNCTION_EQUALS_RE.lastIndex = 0;
|
|
189
|
+
if (!match) return null;
|
|
190
|
+
const name = match[1]!.trim();
|
|
191
|
+
const body = match[2]!;
|
|
192
|
+
const args: Record<string, unknown> = {};
|
|
193
|
+
for (const param of body.matchAll(XML_PARAMETER_EQUALS_RE)) {
|
|
194
|
+
const key = param[1]!.trim();
|
|
195
|
+
if (!key) throw new Error(`tool '${name}' contains an empty parameter name`);
|
|
196
|
+
args[key] = decodeToolValue(param[2]!, toolParameterSchema(tools, name, key));
|
|
197
|
+
}
|
|
198
|
+
XML_PARAMETER_EQUALS_RE.lastIndex = 0;
|
|
199
|
+
return { name, arguments: args };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function parseXmlAttrToolCalls(text: string, tools: ToolSpec[]): ParsedToolCall[] {
|
|
203
|
+
const calls: ParsedToolCall[] = [];
|
|
204
|
+
for (const fn of text.matchAll(XML_FUNCTION_ATTR_RE)) {
|
|
205
|
+
const name = fn[1]!.trim();
|
|
206
|
+
const body = fn[2]!;
|
|
207
|
+
const args: Record<string, unknown> = {};
|
|
208
|
+
for (const param of body.matchAll(XML_PARAM_ATTR_RE)) {
|
|
209
|
+
const key = param[1]!.trim();
|
|
210
|
+
if (!key) throw new Error(`tool '${name}' contains an empty parameter name`);
|
|
211
|
+
args[key] = decodeToolValue(param[2]!, toolParameterSchema(tools, name, key));
|
|
212
|
+
}
|
|
213
|
+
XML_PARAM_ATTR_RE.lastIndex = 0;
|
|
214
|
+
calls.push({ name, arguments: args });
|
|
215
|
+
}
|
|
216
|
+
XML_FUNCTION_ATTR_RE.lastIndex = 0;
|
|
217
|
+
return calls;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** OptiQ-style decoded-text tool parsing. Tool markup is parsed only
|
|
221
|
+
* after generation, and only when tools are active; tokenizer ids are
|
|
222
|
+
* model-family-specific and must not be used globally. Supports the
|
|
223
|
+
* OpenAI JSON `<tool_call>...</tool_call>` contract, Qwen-style
|
|
224
|
+
* `<function=name><parameter=...>`, and MiniCPM5's native
|
|
225
|
+
* `<function name="..."><param name="...">...` template shape. */
|
|
226
|
+
export function parseGeneratedToolCalls(text: string, tools: ToolSpec[]): ParsedToolCall[] {
|
|
227
|
+
if (!tools.length) return [];
|
|
228
|
+
const known = new Set(tools.map(toolSpecName).filter((n): n is string => !!n));
|
|
229
|
+
const calls: ParsedToolCall[] = [];
|
|
230
|
+
for (const block of text.matchAll(TOOL_CALL_BLOCK_RE)) {
|
|
231
|
+
const body = block[1]!.trim();
|
|
232
|
+
const parsed = parseJsonToolCall(body) ?? parseXmlEqualsToolCall(body, tools);
|
|
233
|
+
if (!parsed) throw new Error("unsupported tool_call payload format");
|
|
234
|
+
calls.push(parsed);
|
|
235
|
+
}
|
|
236
|
+
TOOL_CALL_BLOCK_RE.lastIndex = 0;
|
|
237
|
+
if (calls.length === 0) calls.push(...parseXmlAttrToolCalls(text, tools));
|
|
238
|
+
for (const c of calls) {
|
|
239
|
+
if (!known.has(c.name)) throw new Error(`unknown tool '${c.name}'`);
|
|
240
|
+
}
|
|
241
|
+
return calls;
|
|
242
|
+
}
|