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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Read an explicit null sequence as an empty one by senamakel · Pull Request #4 · tinyhumansai/tinyinference · GitHub
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

@senamakelsenamakel 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-connectorBot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

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

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-31T11:44:07.414613Zab54e4ePR 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

coderabbitaiBot 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely describes the main change: treating explicit null sequence values as empty sequences during deserialization.
Docstring Coverage✅ PassedDocstring 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✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

@tinysweepertinysweeperBot 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

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 mainAug 31, 2026
13 checks passed
@senamakel
senamakel deleted the tolerate-null-sequences branch August 31, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@senamakel