/** * Runtime-agnostic tool-call text parsers for text-only LLM batteries. * * @remarks * **Why this exists.** On-device runtimes like transformers.js and LiteRT-LM (v0.13.1) are * text-in / text-out: they inject tool definitions into the chat template, but the model emits its * tool calls as **raw text in the assistant message**, in a format specific to the model family it * was fine-tuned on. Unlike the OpenAI/Ollama wire batteries — where the provider returns a * structured `tool_calls` array — these batteries must parse the call out of the text themselves. * * This mirrors how vLLM / SGLang / Ollama do it: one **post-hoc** parser per model family, selected * by a flag, run *after* generation (sub-millisecond, fails gracefully, never constrains decoding). * Each family parser is anchored on a literal marker (or, for the weak-signal JSON/pythonic forms, on * the callee name matching a real tool) so cross-family false positives are structurally impossible. * * **Formats are model-specific and drift across versions** (e.g. Gemma has three incompatible tool * formats across its generations; gpt-oss's Harmony channel ordering is an active upstream bug). The * bundled defaults target the small ONNX models that actually run in transformers.js; the `'auto'` * driver fails gracefully and a custom {@link ToolCallParserFn} is the escape hatch for anything else. * * NOT `@module`-tagged: private to the bundled LLM batteries, re-exported through their public * surfaces (`transformers_js`, `litert_lm`). Consumers import from those battery subpaths. */ /** A JSON-serialisable value — the shape of parsed tool-call arguments. */ export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue; }; /** A single tool call extracted from model output. `arguments` is a parsed object, never a string. */ export interface ParsedToolCall { /** The tool name the model called. */ name: string; /** The parsed argument object (never a JSON string). */ arguments: Record; } /** * The result of running a {@link ToolCallParserFn} over assistant text. * * @remarks * `cleanedText` is the prose with every consumed tool-call span removed and trimmed, so the visible * assistant message never carries raw markup. On no-match a parser MUST return * `{ calls: [], cleanedText: rawText }` verbatim — that is the signal the `'auto'` driver uses to * detect "this parser made no claim" and move to the next one. */ export interface ToolCallParseResult { /** The tool calls extracted, in document order. Empty when the parser made no claim. */ calls: ParsedToolCall[]; /** The assistant prose with every consumed tool-call span removed; equals the input on no-match. */ cleanedText: string; } /** Context passed to a parser: the names of the tools actually offered this turn. */ export interface ToolCallParserContext { /** The visible tool names this turn — lets a parser reject calls to tools that don't exist. */ toolNames: ReadonlyArray; } /** A synchronous tool-call text parser. */ export type ToolCallParserFn = (rawText: string, ctx: ToolCallParserContext) => ToolCallParseResult; /** The bundled family parser names, plus `'auto'` (try-all) and `'none'` (disable). */ export type ToolCallParserName = 'auto' | 'hermes' | 'gemma' | 'gpt_oss' | 'pythonic' | 'bare_pythonic' | 'loose_keyed' | 'llama3_json' | 'mistral' | 'qwen3_coder' | 'phi' | 'none'; /** * Parse Hermes-style `{"name":…,"arguments":{…}}` tags. The de-facto standard, * reused by Qwen2.5/Qwen3-Instruct. Anchored on the literal tags — zero collision with bare JSON. * * @remarks * The JSON object after each `` is located by a string-aware balanced-brace scan (not a lazy * `` regex), so an embedded `` or `{`/`}` inside a string argument value (e.g. * `{"text":""}`) survives instead of truncating the call. The literal `` close * is still consumed for span removal when present immediately after the object. */ export declare const hermesToolCallParser: ToolCallParserFn; /** Default {@link hermesToolCallParser}. */ export declare const defaultHermesToolCallParser: ToolCallParserFn; /** * Parse Gemma E2B/E4B tool calls. Accepts the wrapped template form * (`<|tool_call>call:NAME{k:<|"|>v<|"|>}`) AND the decoder-stripped runtime form * (`call:NAME{k:v}`, special tokens removed, scalars unquoted — the shape a real ONNX run emits), * including NESTED argument blocks with curly smart quotes (the form a real E4B `provide_answer` * emits: `{answer:<|“|>…<|”|>,sources:[{path:…}]}`), AND the PREFIX-LESS bare form `NAME{…}` with no * `call:` lead (e.g. `say_i_dont_know{reason: "…"}` — a real E2B/E4B runtime shape), gated on * `ctx.toolNames`. Targets the E2B/E4B form only — Gemma 3 (`tool_code` fences) and FunctionGemma * (``) are out of scope (use a custom {@link ToolCallParserFn}). */ export declare const gemmaToolCallParser: ToolCallParserFn; /** Default {@link gemmaToolCallParser}. */ export declare const defaultGemmaToolCallParser: ToolCallParserFn; /** * Parse gpt-oss Harmony tool calls on the `commentary` channel: * `<|channel|>commentary to=functions.NAME <|constrain|>json<|message|>{…}<|call|>`. The arguments * payload after `<|message|>` is JSON, located by a string-aware balanced-brace scan so an embedded * `<|call|>` / brace inside a string value survives. Anchored on the literal Harmony markers. * * @remarks The Harmony channel/constrain ordering has a documented upstream template-vs-spec drift; * this matches the common ordering. Verify against a real gpt-oss ONNX run when one is available. */ export declare const gptOssToolCallParser: ToolCallParserFn; /** Default {@link gptOssToolCallParser}. */ export declare const defaultGptOssToolCallParser: ToolCallParserFn; /** * Parse pythonic tool calls — `[get_weather(city='SF'), get_time()]`. Requires the **whole trimmed * output** to be the bracketed call list, so it cannot false-positive on incidental prose. Parallel * calls are inherent to the format. * * **Disambiguation, NOT authorization.** The `[fn(args), …]` shape (whole-output) is the structural * signal that this is a call list. It deliberately does NOT check callees against `ctx.toolNames` — * whether a tool is *allowed* is the consumer's call, and the dispatch layer already replies "Tool not * found: … Available tools: …" so the model can self-correct. Dropping an unknown-tool call here would * hide the request and that feedback loop. */ export declare const pythonicToolCallParser: ToolCallParserFn; /** Default {@link pythonicToolCallParser}. */ export declare const defaultPythonicToolCallParser: ToolCallParserFn; /** * Parse a BARE pythonic call — `provide_answer(answer=“…”, sources=[“/x”])` — i.e. the pythonic * `NAME(kwargs)` form WITHOUT the surrounding `[ … ]` list wrapper that {@link pythonicToolCallParser} * requires. Small models (observed: Gemma-4 E2B via transformers.js) emit this for a single call, * sometimes with a leading `/`, a `call:` prefix, or smart quotes in the args. * * Because the bare shape is a WEAK signal (it can resemble incidental prose like "see foo(bar)"), * this parser is gated HARD: it only claims a call whose callee is a real offered tool * (`ctx.toolNames`). That gate is what makes dropping the `[ ]` requirement safe. Runs after the * strict bracketed pythonic parser in the `'auto'` order. */ export declare const barePythonicToolCallParser: ToolCallParserFn; /** Default {@link barePythonicToolCallParser}. */ export declare const defaultBarePythonicToolCallParser: ToolCallParserFn; /** * Parse the DEGENERATE keyed form a small instruct model emits when it ignores every structured * tool-call grammar: the bare tool NAME on its own line, then one or more `argname: value` lines. * * @remarks * Observed verbatim from **Gemma-4 E2B on LiteRT-web** (raw-captured): asked to call an answer tool it * emits, with no `call:`/braces/brackets/JSON at all — * ``` * say_i_dont_know * reason: The documentation does not contain a definition for that. * ``` * No marker-anchored or pythonic/JSON parser claims this, so the "call" leaks into the visible answer as * prose AND the turn looks like a refusal (the tool the model meant to invoke never fires). A 2B can't * be reliably *instructed* into a format (changing the prompt's documented format did not change the * output), so the robust path is to parse the shape it actually produces. * * WEAK SIGNAL, gated HARD (like {@link barePythonicToolCallParser}): it only claims when the FIRST * non-empty line is EXACTLY a real offered tool name (`ctx.toolNames`) and is followed by at least one * `key: value` line. That gate is what keeps it from misreading ordinary prose ("Note: …", a heading * with a colon). Single call only (the degenerate form has no list syntax); runs LAST in `'auto'`. */ export declare const looseKeyedToolCallParser: ToolCallParserFn; /** Default {@link looseKeyedToolCallParser}. */ export declare const defaultLooseKeyedToolCallParser: ToolCallParserFn; /** * Parse bare top-level JSON tool call(s) — `{"name":"x","parameters":{…}}` (or `"arguments"`), * optionally wrapped in a ```` ```json … ``` ```` fence (a common small-model emission — verified via * the real-model matrix on Qwen2.5-Coder-0.5B). The weakest signal, so it is gated hard: after * un-fencing, the output must be ONLY top-level JSON object(s) AND every callee must be a real tool * (`ctx.toolNames`). Runs after every marker-anchored family in `'auto'`. * * @remarks * **Parallel calls.** Small Llama-family / Qwen-Coder models emit MULTIPLE calls as several top-level * objects separated by `,` / `;` / whitespace (verified on the real-model matrix: * Llama-3.2-1B → `{…}; {…}`, Qwen2.5-Coder-0.5B → a fenced `{…},\n{…}`). So we string-aware brace-scan * the (un-fenced) text into successive balanced `{…}` objects, accepting only the separators above * between them — if any NON-separator prose sits between or around the objects, the whole thing declines * (the hard whole-output gate, so this never false-positives on JSON embedded in a sentence). * * **Disambiguation, NOT authorization.** This parser is marker-free, so it must distinguish a tool call * from arbitrary JSON content. It does that STRUCTURALLY: whole-output-is-object(s) + each object has a * string `name`. It deliberately does NOT check the callee against `ctx.toolNames` — whether a requested * tool is *allowed* is the consumer's decision, not the parser's. An unknown-tool call is surfaced like * any other; the dispatch layer already replies "Tool not found: … Available tools: …" so the model can * self-correct. Silently dropping the call here would hide both the request and that feedback loop. */ export declare const llama3JsonToolCallParser: ToolCallParserFn; /** Default {@link llama3JsonToolCallParser}. */ export declare const defaultLlama3JsonToolCallParser: ToolCallParserFn; /** * Parse Mistral tool calls — the `[TOOL_CALLS]` token followed by a JSON array of * `{ name, arguments }`. Anchored on the literal `[TOOL_CALLS]` token. * * @remarks * The array is located by a string-aware balanced-bracket scan from the first `[` after the token (same * approach as {@link phiToolCallParser}), so a `]` inside a string argument value (e.g. * `{"text":"a]b"}`) does not truncate the array the way the old greedy `\[[\s\S]*\]` regex did. */ export declare const mistralToolCallParser: ToolCallParserFn; /** Default {@link mistralToolCallParser}. */ export declare const defaultMistralToolCallParser: ToolCallParserFn; /** * Parse Qwen3-Coder's custom per-parameter XML — `v * …`. Values are taken as trimmed strings (the format is untyped). Anchored on * the literal ``. */ export declare const qwen3CoderToolCallParser: ToolCallParserFn; /** Default {@link qwen3CoderToolCallParser}. */ export declare const defaultQwen3CoderToolCallParser: ToolCallParserFn; /** * Parse Phi-4-mini tool calls: the literal `functools` token followed by a JSON array of * `{ name, arguments }` objects (e.g. `functools[{"name":"get_weather","arguments":{"city":"SF"}}]`). * * @remarks * Anchored on the `functools` begin-of-tool token (vLLM's `phi4_mini_json`). The array is located by * scanning for the first `[` after the token and matching to its balanced closing `]`, so trailing * prose after the call does not break parsing. Declines (no-match) if the payload is not a JSON array * of name-bearing objects. */ export declare const phiToolCallParser: ToolCallParserFn; /** Default {@link phiToolCallParser}. */ export declare const defaultPhiToolCallParser: ToolCallParserFn; /** A parser that never extracts anything — disables tool-call parsing entirely. */ export declare const noneToolCallParser: ToolCallParserFn; /** Default {@link noneToolCallParser}. */ export declare const defaultNoneToolCallParser: ToolCallParserFn; /** The bundled family parsers keyed by name (excluding `'auto'`/`'none'`). */ export declare const BUNDLED_TOOL_CALL_PARSERS: Readonly, ToolCallParserFn>>; /** * The default `'auto'` precedence. Marker-anchored families first (collision-free); the weak-signal * pythonic/llama3_json forms run last and are gated on callee∈toolNames. */ export declare const DEFAULT_TOOL_CALL_PARSER_ORDER: ReadonlyArray>; /** * Compose an `'auto'` parser: run each family parser in `order` until one returns a non-empty * `calls` array; that result wins. Returns no-match if none claim the text. */ export declare const createAutoToolCallParser: (parsers?: Partial, ToolCallParserFn>>, order?: ReadonlyArray>) => ToolCallParserFn; /** Default {@link createAutoToolCallParser}. */ export declare const defaultCreateAutoToolCallParser: (parsers?: Partial, ToolCallParserFn>>, order?: ReadonlyArray>) => ToolCallParserFn; /** * Resolve a `toolCallParser` option (a name, `'auto'`, `'none'`, or a custom fn) to a concrete * {@link ToolCallParserFn}. * * @param option - The option value. Defaults to `'auto'` when undefined. * @param parsers - Override the bundled family parsers (e.g. swap the Gemma parser). */ export declare const resolveToolCallParser: (option: ToolCallParserName | ToolCallParserFn | undefined, parsers?: Partial, ToolCallParserFn>>) => ToolCallParserFn; /** Default {@link resolveToolCallParser}. */ export declare const defaultResolveToolCallParser: (option: ToolCallParserName | ToolCallParserFn | undefined, parsers?: Partial, ToolCallParserFn>>) => ToolCallParserFn;