Skip to content

fix: strip NSNull from chat templates before Jinja render (#168) - #169

Closed
CodeAndCanvas728 wants to merge 1 commit into
SharpAI:mainfrom
CodeAndCanvas728:fix/168-jinja-tools-null
Closed

CodeAndCanvas728 wants to merge 1 commit into
SharpAI:mainfrom
CodeAndCanvas728:fix/168-jinja-tools-null

Conversation

@CodeAndCanvas728

Copy link
Copy Markdown
Contributor

Fixes #168

Root cause

The crash was not in the model's chat template. It happens before render:

  1. AnyCodable decodes JSON nullNSNull (common in opencode/zod tool schemas: "default": null, etc.)
  2. Tokenizer.applyChatTemplate converts tools with Jinja.Value.init(any:), which has no NSNull case
  3. swift-jinja 2.3.5 reported a misleading Optional<Any> (type(of:) on the Any? parameter); 2.5.1 correctly reports NSNull but still throws
  4. The error was wrapped as MalformedChatTemplate ("defect in the model's chat_template.jinja... not in the request")
  5. The fix: name a malformed chat template instead of failing every request #142 startup probe used tools: nil, so it never exercised this path

Verified: without this fix, a tools request with default: null against Ministral-8B returns 500 Cannot convert value of type NSNull to Jinja Value. With the fix, the same request (plus X-SwiftLM-Prefill-Progress) streams cleanly.

Changes

A. Root fix — sanitizeForJinja

Recursively drops NSNull (and unwraps nested optionals) from messages, tools, and additionalContext inside TransformersTokenizerBridge.applyChatTemplate so every caller is covered.

B. Startup probe — tools case (warn-only)

Second render with a minimal non-empty tools array (parameters include a null). On failure: loud [SwiftLM] ⚠️ warning; server continues (plain chat / /v1/completions still work).

C. Heartbeat payload

ssePrefillChunk now includes "choices": [] so opencode's strict ChatCompletionChunk union accepts the named prefill_progress event (named event alone was not enough — opencode validates every data: line).

D. Dependency

swift-jinja 2.3.5 → 2.5.1 (accurate conversion errors, Optional/BinaryInteger bridging). Alone this does not fix NSNull — A is required.

E. Tests

Verification

  • Full SwiftLMTests suite: 174/174 pass
  • Release build succeeds
  • Baseline (this commit stashed): tools+null → 500 with NSNull error
  • With fix: tools+null+heartbeat against mlx-community/Ministral-8B-Instruct-2410-4bit → stream accepted
  • tests/test-opencode.sh full run (script pins gemma-4-e4b, not available in this environment)

Notes

  • Issue title/message still blames the checkpoint template; after this fix the Tiel template itself is fine for null-bearing tool schemas. A genuine tools-branch parse error would now warn at startup instead of failing every request silently.
  • Pre-existing SwiftBuddyTests VLM/Audio failures (missing binary at .build/arm64-apple-macosx/debug/SwiftLM) are unrelated.

🤖 Generated with Claude Code

JSON nulls in tool schemas (common in opencode zod/effect output) decode
to NSNull via AnyCodable and crash swift-jinja Value.init(any:) before
the template is ever rendered — mislabeled as a broken chat template.
The SharpAI#142 startup probe used tools: nil so it never hit this path, and
opencode also rejected the prefill_progress heartbeat for missing
choices.

- sanitizeForJinja: recursively drop NSNull / unwrap optionals in
  TransformersTokenizerBridge.applyChatTemplate (messages, tools,
  additionalContext)
- extend startup probe with a minimal tools array (warn-only; does not
  abort startup)
- include choices: [] in ssePrefillChunk so strict OpenAI chunk
  validators accept the named heartbeat event
- bump swift-jinja 2.3.5 -> 2.5.1 for accurate conversion errors
- unit tests for the sanitizer + updated SSE expectations
- test-opencode.sh Test 3: tools + prefill-progress combined (SharpAI#75 gap)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CodeAndCanvas728

Copy link
Copy Markdown
Contributor Author

Closing in favor of #170, which contains this commit (ff0c28d) plus the Xcode 26.3 compile fix for (any Sendable)?, per maintainer review.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chat template crash (Optional<Any> Jinja error) only triggers when tools are present — startup probe (#142) misses it

1 participant