Skip to content

serve/ cannot express tool calls for a chat.json container #34

Description

@marcobambini

python3 -m serve refuses to start on any container that is not K3. Reported from a
Kimi-Linear container on a 16 GB MacBook, where waste run and waste chat both work:

WASTE 0.6.6 (container v0, backend NEON, crc32 armv8, arm64)
model kimi_linear — kimi-linear, 27 layers, 256 experts, ctx 4096
<|open|> is not a single token in this container (got 5): its specials.json
does not carry K3's XTML markers: unsupported

What is happening

ServerCore.__init__ resolves the four XTML control tokens up front
(serve/server.py:85serve/engine.py:566), and raises WASTE_E_UNSUPPORTED
unless each of <|open|>, <|sep|>, <|close|>, <|end_of_msg|> encodes to
exactly one token. Kimi-Linear's tokenizer has none of them — measured on a
converted container:

$ ./waste tokenize ~/models/kimi-linear.waste '<|open|>'
27 91 4454 91 29
$ ./waste tokenize ~/models/kimi-linear.waste '<|im_user|>'
163587

The refusal is correct and should stay: absent markers encode as ordinary
text, so the model would read its own turn structure as prose and answer
plausibly and wrongly. The bug is not that it refuses; it is that XTML is the
only format serve/ can speak, so a container that has a perfectly good chat
format of its own has no HTTP path at all.

Two consequences worth separating:

  1. Kimi-Linear cannot be served. serve/xtml.py is a port of K3's
    encoding_k3.py, and serve/regions.py reads replies back by matching XTML
    token ids. Neither has a second format. chat.json is not read by serve/
    at all, so there is no way to configure around it.
  2. /v1/completions is unreachable too, which is the smaller and more
    annoying half. It is raw continuation and needs no chat template, but the
    failure is in the constructor, so the whole server is down before any
    endpoint is dispatched.

What Kimi-Linear does carry

From its specials.json — enough for a conversation and for tool calls, but
there is no think channel, so reasoning_content has no equivalent and thinking
would simply be off for it:

token id
<|im_system|> 163594
<|im_user|> 163587
<|im_assistant|> 163588
<|im_middle|> 163601
<|im_end|> 163586
<|tool_calls_section_begin|> 163595
<|tool_call_begin|> 163597
<|tool_call_argument_begin|> 163598
<|tool_call_end|> 163599
<|tool_calls_section_end|> 163596

Shape of a fix

Not a small change, and worth doing in two steps that are independently useful:

  • Let the server start. Move marker resolution out of __init__ to the
    point of use, so a container without XTML serves /v1/completions and returns
    a clear 400 on /v1/chat/completions instead of failing to boot.
  • A second renderer and a second reply parser. regions.py decides
    structure from token ids rather than text, which is the right design and also
    what makes this more than a template swap: it needs a marker vocabulary per
    format, not a hardcoded four. Kimi-Linear's tool-call markers mean tool_calls
    is reachable; reasoning_content is not.

Whether the renderer is driven by the container's chat.json or by a second
Python module in serve/ is the open design question. chat.json is four
prefix/suffix strings and cannot express tool definitions, so it covers plain
chat only — which may be the right first cut.

Workaround today

waste run and waste chat, which read chat.json and are unaffected. A
Kimi-Linear container has no chat.json unless the converter put one there, and
without it the CLI says so and falls back to raw continuation — which is why the
same report also had an instruct model producing odd answers. Write this into
the container directory, next to manifest.json:

{
 "system": ["<|im_system|>system<|im_middle|>", "<|im_end|>"],
 "user": ["<|im_user|>user<|im_middle|>", "<|im_end|>"],
 "assistant": ["<|im_assistant|>assistant<|im_middle|>", "<|im_end|>"],
 "open": "<|im_assistant|>assistant<|im_middle|>"
}

Do not copy examples/chat.json for this: it is generic ChatML, and
<|im_start|> is not in Kimi's vocabulary — it encodes as six ordinary tokens,
which is the same silent failure described above. The converter now installs the
right one per architecture and refuses to install a template whose markup the
release does not carry.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions