Skip to content

chat_template_kwargs only takes booleans, and only enable_thinking reaches the template #259

Closed

Description

@HamedShams

chat_template_kwargs is the standard way to pass per-request flags into a model's chat template (the mlx-lm server takes any JSON object and hands it to apply_chat_template as is). On SwiftLM it works for one key and one type (main @ ce9b717, M1 Max, mlx-community/Qwen2.5-0.5B-Instruct-4bit):

1. Any value that is not a boolean fails the whole request

curl -s localhost:5413/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"hi"}],"max_tokens":16,"chat_template_kwargs":{"reasoning_effort":"low"}}'
# → HTTP 400
# {"error":{"message":"Invalid type for 'chat_template_kwargs.reasoning_effort': Expected to decode Bool but found a string instead.","type":"invalid_request_error","param":"chat_template_kwargs.reasoning_effort"}}

A number fails the same way. The field is declared as [String: Bool]?:

https://github.com/SharpAI/SwiftLM/blob/ce9b717/Sources/SwiftLM/Server.swift#L4924

2. Any key other than enable_thinking is dropped silently

A boolean under another key decodes fine and then goes nowhere. The template context is built from enable_thinking alone, so {"chat_template_kwargs": {"thinking": true}} is accepted with 200 and the template never sees it:

https://github.com/SharpAI/SwiftLM/blob/ce9b717/Sources/SwiftLM/Server.swift#L2582-L2621

Why it matters

enable_thinking is Qwen's and Gemma's name for the switch. Other families SwiftLM runs name theirs differently, and their templates read them straight from the kwargs:

  • DeepSeek-V3.1 reads thinking (bool): {% if not thinking is defined %}{% set thinking = false %} and then opens <think> or closes it. On SwiftLM a V3.1 user cannot switch thinking per request at all.
  • gpt-oss reads reasoning_effort ("low"/"medium"/"high", a string) and model_identity. Both are a 400 here.
  • MiniMax-M2 reads model_identity (string) the same way.

All three families are in mlx-swift-lm's model factory, and mlx-community ships 4-bit conversions of each (DeepSeek-V3.1-4bit, gpt-oss-20b-MXFP4-Q4, MiniMax-M2-4bit). mlx-lm, vLLM and llama-server all take chat_template_kwargs as a plain JSON object, so a request that works against any of them fails or degrades here with nothing in the log.

Fix

Decode chat_template_kwargs as arbitrary JSON (the AnyCodable type tool parameters already use), run it through the existing sanitizeForJinja, and merge it into the template context. enable_thinking keeps its precedence (top-level field, then kwargs, then --thinking) and the Gemma 4 tool-call override stays as it is. Requests that only send enable_thinking see no change.

I'm finishing this with parsing tests and a test that the extra keys reach the template context; PR coming shortly.

Activity

  1. solderzzc commented on Nov 6, 2026

    @solderzzc
    Member

    Thanks for the report. The detailed table of which template families read which kwarg names made the scope clear, and it became #260, which is merged now (7376b6b). Closing this out. Follow-ups: #263 covers the precedence test, #264 covers the low-severity edge cases.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions