Skip to content

Read an explicit null sequence as an empty one - #4

Merged
senamakel merged 1 commit into
mainfrom
tolerate-null-sequences
Aug 31, 2026
Merged

Read an explicit null sequence as an empty one#4
senamakel merged 1 commit into
mainfrom
tolerate-null-sequences

Conversation

@senamakel

@senamakel senamakel commented Aug 31, 2026

Copy link
Copy Markdown
Member

What changed

Every sequence field an OpenAI-compatible provider can send is now deserialized through Option<Vec<_>>, so an explicit null reads as an empty list instead of failing the decode.

#[serde(default)] covers an absent key and nothing else. A key present with the value null still reaches the Vec visitor and fails the whole response with invalid type: null, expected a sequence.

That is not hypothetical. Mistral-family endpoints spell "the model did not call a tool" as "tool_calls": null on every plain-text completion:

{"choices":[{"index":0,"finish_reason":"stop",
  "message":{"role":"assistant","tool_calls":null,"content":"Hi!"}}]}

Any agent role sitting on such a rung could not complete a single turn — the model answered perfectly and the caller saw a transport fault. Found on a Lean-specialised rung (labs-leanstral-1-5), where a sub-agent delegation failed 100% of the time with serialization error: invalid type: null, expected a sequence.

This is the same tolerance deserialize_arguments already applies one level down to a tool call's arguments, and for the same reason recorded in its doc comment: one provider's unexpected spelling of "nothing" must not fail the decode of everything around it.

Fields covered: choices and message.tool_calls (unary), choices and delta.tool_calls (streaming), the Responses API's output / content / summary, and the model listing's data.

Public API or behavior changes

None to the API. One behavior change, deliberate: a body carrying "choices": null now fails with Model("openai response contained no choices") rather than a serde error — the provider fact the caller can act on, rather than the transport being named for a body it read perfectly. Pinned by a test.

Validation

cargo fmt --all -- --check      ok
cargo clippy --all-targets -- -D warnings   ok, 0 warnings
cargo test -p tinyinference --lib           ok, 266 passed

cargo test --doc has 5 pre-existing failures in src/embeddings/ — present on main before this branch and untouched by it.

Three new tests: a_null_tool_calls_array_is_read_as_no_tool_calls, a_null_choices_array_is_read_as_no_choices, a_null_tool_calls_delta_is_read_as_no_fragments.

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility with OpenAI-compatible providers that return null for optional list fields.
    • Prevented deserialization failures for response choices, tool calls, streamed content, and model listings.
    • Improved error reporting when response candidates are unavailable.
  • Tests

    • Added coverage for null-valued tool calls, choices, and streaming response fields.

Several OpenAI-compatible endpoints spell "no tool calls" as
"tool_calls": null rather than by omitting the key; Mistral-family
models do it on every plain-text completion. #[serde(default)] covers
only an absent key, so such a body failed the whole decode with
"invalid type: null, expected a sequence" and a model that simply
answered in prose surfaced as a transport fault.

Deserialize every sequence the provider sends through Option, the same
tolerance deserialize_arguments already applies to a tool call's
arguments: choices, tool_calls on both the unary and streaming paths,
the Responses API's output/content/summary, and the model listing.

Co-authored-by: Medulla <medulla@tinyhumans.ai>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T11:44:07.414613Z ab54e4e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: be808545-ab20-48f1-ab11-93c16fef95b0

📥 Commits

Reviewing files that changed from the base of the PR and between cc8aca4 and ab54e4e.

📒 Files selected for processing (3)
  • crates/tinyinference/src/providers/openai/responses.rs
  • crates/tinyinference/src/providers/openai/test.rs
  • crates/tinyinference/src/providers/openai/types.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

OpenAI response collection fields now deserialize explicit JSON null values as empty vectors. Tests cover non-streaming messages, choices, streaming tool-call deltas, response outputs, and model lists.

Changes

OpenAI null collection handling

Layer / File(s) Summary
Null collection deserialization
crates/tinyinference/src/providers/openai/types.rs, crates/tinyinference/src/providers/openai/responses.rs
Adds deserialize_null_as_empty and applies it to OpenAI choices, tool calls, model data, response output, content, and summary fields.
Null response coverage
crates/tinyinference/src/providers/openai/test.rs
Adds tests for null tool calls, null choices, and null streaming tool-call deltas.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to ab54e

This localized change makes explicit null sequence fields decode as empty collections, with targeted tests and passing checks; no actionable merge-blocking risk remains, so it is merge-ready after normal checks.

Poem

A rabbit hops through JSON rain
Null arrays leave no broken chain
Empty vectors fill the tray
Tests keep stray decode errors away
OpenAI paths now parse the way

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

             $0.0140 · 85,077 in / 1,741 out · 20,745 cached (24%) · deepseek/deepseek-v4-flash, openrouter/openai/text-embedding-3-small, z-ai/glm-5.2 · 302 embedded
critique:    $0.0031 · 33,958 in / 365 out   · 0 cached (0%)       · deepseek/deepseek-v4-flash
security:    $0.0070 · 32,603 in / 433 out   · 17,145 cached (53%) · deepseek/deepseek-v4-flash, z-ai/glm-5.2
tests:       $0.0011 · 12,537 in / 114 out   · 0 cached (0%)       · deepseek/deepseek-v4-flash
description: $0.0026 · 4,821 in  / 534 out   · 3,600 cached (75%)  · z-ai/glm-5.2

@tinysweeper

tinysweeper Bot commented Aug 31, 2026

Copy link
Copy Markdown

How this change flows

2 changed behaviours across 15 relationships. 6 surrounding behaviours are shown (60 graph nodes walked). 43 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["...400_unions_with_existing_baseline_degrade<br/>changed"]:::changed
  n1["ChatCompletionChunk<br/>changed"]:::changed
  n2["...ne_knobs_default_to_supported_wire_shapes"]:::impacted
  n3["translates_request_to_openai_json_shape"]:::impacted
  n4["with_response_format"]:::impacted
  n5["ingest"]:::impacted
  n6["model"]:::impacted
  n7["translate_request"]:::impacted
  n0 -->|calls| n4
  n0 -->|tests| n4
  n2 -->|calls| n4
  n2 -->|tests| n4
  n2 -->|calls| n6
  n2 -->|tests| n6
  n2 -->|calls| n7
  n2 -->|tests| n7
  n3 -->|calls| n4
  n3 -->|tests| n4
  n3 -->|calls| n6
  n3 -->|tests| n6
  n3 -->|calls| n7
  n3 -->|tests| n7
  n5 -->|uses| n1
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@senamakel
senamakel merged commit dbf7897 into main Aug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant