Skip to content

A tool with no properties is rendered so the model must invent an empty parameter name (--jinja_tools, 1.121) #2510

Description

@firedesiresa-bit

A tool with no properties is rendered so the model must invent an empty parameter name

Environment

KoboldCpp 1.121, koboldcpp-linux-x64
Launch --jinja --jinja_tools --quantkv f16 --contextsize 65536 --batchsize 256 --threads 8 --skiplauncher
Model nemotron-3-nano-4b.gguf (also seen on gemma4-e4b, qwen35-4b/9b, ornith-9b)
API POST /v1/chat/completions, tools array, using_openai_tools: true
Rendered prompt read via --debugmode 1 (the server prints the request, including a prompt field holding the fully rendered text)

Summary

When a tool's JSON Schema declares no properties — {"type": "object", "properties": {}, "required": []} — the rendered prompt declares it as:

<tools>
<function>
<name>eve_vault_index_rebuild</name>
<parameters>
<additionalProperties>False</additionalProperties>
</parameters>
</function>

There is no <parameter> block, and nothing names a parameter. But the same prompt instructs the
model:

If you choose to call a function ONLY reply in the following format with NO suffix:

<tool_call>
<function=example_function_name>
<parameter=example_parameter_1>
value_1
</parameter>
<parameter=example_parameter_2>
…
</parameter>
</function>
</tool_call>

Reminder:
- Function calls MUST follow the specified format: an inner <function=...></function> block must be
  nested within <tool_call></tool_call> XML tags
- Required parameters MUST be specified

So the model is given a format that requires a <parameter=NAME> and a tool that has no name to give
it. It writes <parameter=>, an empty name, and the client receives:

"tool_calls": [{"function": {"name": "eve_vault_index_rebuild", "arguments": "{\"\": …}"}}]

Strict clients reject that. In our harness the call is recorded as
unexpected-argument-key, and the lane's guard — which compares the received arguments to the
declared ones — reads "observed_reason": "unexpected-arguments" and fails the step.

Evidence

1. Deterministic, end to end. 12 runs across four campaigns, all with the same model, lane and
weights: argument_keys=[''] every time. ollama, same GGUF, same lane, renders through the model's
own template and sends argument_keys=[] — accepted. The bench is not the variable: its provider
adapters only observe window and parser, one schema is declared for every provider, and one request
body is composed for all three.

2. The rendered prompt (from --debugmode 1) shows both halves side by side: the <parameters>
block above, and the <parameter=NAME> instruction quoted above.

3. --jinjatemplate does not change it. Forcing the model's own
tokenizer.chat_template (10,504 chars, read straight out of the GGUF) still yields [''], 3 of 3.
The rendering comes from the chat-completions adapter (chatcompletionsadapter: "AutoGuess" in the
exported config), not from the template.

4. What does not reproduce it, measured. Two minimal candidates were falsified, which is why
this report does not claim a minimal repro:

  • a single no-argument tool, nothing else in the conversation → arguments='{}', 3 of 3;
  • a no-argument tool after an assistant call that did carry a parameter, plus its tool result →
    arguments='{}', 3 of 3.

The smallest repro that reproduces it reliably is our bench's own request: five tools including the
deferred-tool bridge (tool_search / tool_describe / tool_call, the last taking calls), a long
system prompt, and a conversation already carrying tool calls and results. That points at the
nested/bridged schema rather than at the bare properties: {} case
— the trigger appears to need
the complex tool surface, so the bridge's own schema is the first place to look.

Impact, stated plainly

Any client that validates a tool call's arguments against the declared schema sees a call that cannot
be valid, for a tool that is in fact being called correctly. Whether that is fatal depends on the
client: ours executes the tool and then fails the step at its guard, so the loss is a rung of the
evaluation rather than a crash.

There is no configuration lever for the tool format — measured

Both levers that look like they should affect this do not:

  • --jinjatemplate <the model's own tokenizer.chat_template> — no change: [''], 3 of 3.

  • --chatcompletionsadapter <JSON> — cannot help. Its help says "force custom instruct tags", and
    the shipped adapter files contain only chat delimiters. AutoGuess.json (≈40 model presets) and
    DeepSeek.json both define exactly:

    { "system_start": …, "system_end": …, "user_start": …, "user_end": …,
      "assistant_start": …, "assistant_end": …, "assistant_gen": … }

    There is no tool, function, or parameter key in any of them.

The <tools>/<function>/<parameters>/<parameter> block and the instruction beside it are therefore
hard-coded in the chat-completions tool handling, and the suggested fix has to land there.

Suggested fix (either is enough)

  1. Express a no-argument tool unambiguously. <parameters/>, empty, with no
    <additionalProperties> line standing where a parameter would go — and a sentence in the
    instruction covering the no-argument case ("if the function takes no parameters, emit an empty
    <function=…></function> block").
  2. Or accept an empty parameter name as "no arguments" in the parser, so <parameter=> maps to
    {} rather than to {"": …}.

Related open issues

Workaround available to us

None on the KoboldCpp side, as measured above — so the only local option is to stop declaring a
no-argument tool: give the governed chain steps a named parameter (one would do), which changes a
declared surface and therefore needs the bench owner's approval and a re-run of the affected lanes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions