fix: strip NSNull from chat templates before Jinja render (#168) - #169
Closed
CodeAndCanvas728 wants to merge 1 commit into
Closed
CodeAndCanvas728 wants to merge 1 commit into
CodeAndCanvas728 wants to merge 1 commit into
Conversation
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>
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #168
Root cause
The crash was not in the model's chat template. It happens before render:
AnyCodabledecodes JSONnull→NSNull(common in opencode/zod tool schemas:"default": null, etc.)Tokenizer.applyChatTemplateconverts tools withJinja.Value.init(any:), which has noNSNullcaseOptional<Any>(type(of:)on theAny?parameter); 2.5.1 correctly reportsNSNullbut still throwsMalformedChatTemplate("defect in the model's chat_template.jinja... not in the request")tools: nil, so it never exercised this pathVerified: without this fix, a tools request with
default: nullagainst Ministral-8B returns 500Cannot convert value of type NSNull to Jinja Value. With the fix, the same request (plusX-SwiftLM-Prefill-Progress) streams cleanly.Changes
A. Root fix —
sanitizeForJinjaRecursively drops
NSNull(and unwraps nested optionals) frommessages,tools, andadditionalContextinsideTransformersTokenizerBridge.applyChatTemplateso 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/completionsstill work).C. Heartbeat payload
ssePrefillChunknow includes"choices": []so opencode's strictChatCompletionChunkunion accepts the namedprefill_progressevent (named event alone was not enough — opencode validates everydata:line).D. Dependency
swift-jinja2.3.5 → 2.5.1 (accurate conversion errors, Optional/BinaryInteger bridging). Alone this does not fix NSNull — A is required.E. Tests
JinjaSanitizerTests(6 cases: nested objects, arrays, optionals, scalars)ServerSSETeststo expect emptychoicesarraytests/test-opencode.shTest 3: tools + prefill-progress combined (closes the Test 1 / Test 2 coverage gap called out in Chat template crash (Optional<Any> Jinja error) only triggers when tools are present — startup probe (#142) misses it #168 / feat: OpenAI-compatible streaming hardening (prefill heartbeat + OpenCode e2e CI) #75)Verification
SwiftLMTestssuite: 174/174 passmlx-community/Ministral-8B-Instruct-2410-4bit→ stream acceptedtests/test-opencode.shfull run (script pinsgemma-4-e4b, not available in this environment)Notes
SwiftBuddyTestsVLM/Audio failures (missing binary at.build/arm64-apple-macosx/debug/SwiftLM) are unrelated.🤖 Generated with Claude Code