Skip to content

fix(providers): tolerate null tool_calls in OpenAI-compatible responses - #144

Merged
gabrielvfonseca merged 2 commits into
mainfrom
fix/openai-null-tool-calls
Nov 12, 2026
Merged

gabrielvfonseca merged 2 commits into
mainfrom
fix/openai-null-tool-calls

Conversation

@gabrielvfonseca

@gabrielvfonseca gabrielvfonseca commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Some OpenAI-compatible inference hosts (e.g. SGLang) send "tool_calls": null in chat-completion responses instead of omitting the field. Serde's #[serde(default)] only substitutes a value for missing keys, so an explicit null failed deserialization with:

json error: invalid type: null, expected a sequence at line 1 column 224

This broke every request (plain prompts and tool calls, streaming and non-streaming) against those hosts, including local SGLang deployments of Qwen3.

Fix

Treat an explicit null as "no tool calls" (empty list) in both response paths:

  • ChatMessage.tool_calls (non-streaming)
  • ChunkDelta.tool_calls (streaming)

via a small generic helper deser_null_as_empty_seq. Fully backward compatible — missing keys still default to empty, and real tool-call arrays deserialize as before.

Verification

  • cargo test -p code-providers --lib — 76/76 pass
  • cargo fmt --check and cargo clippy -p code-providers --lib clean
  • End-to-end against a live SGLang endpoint serving Qwen/Qwen3.8-27B:
    • plain prompt → pong
    • read_file tool call → file read and version returned
    • TS SDK → CLI → model round-trip → pong

Summary by CodeRabbit

  • Bug Fixes
    • OpenAI responses now handle tool-call fields set to null as empty, preventing deserialization errors for both standard and streaming responses.

Some OpenAI-compatible hosts (e.g. SGLang) send "tool_calls": null
instead of omitting the field. Serde's #[serde(default)] only fills
missing keys, so deserialization failed with 'invalid type: null,
expected a sequence' on every request.

Treat an explicit null as an empty tool-call list in both the
non-streaming (ChatMessage) and streaming (ChunkDelta) paths.
Backward compatible: missing keys still default to empty.
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Explain Change →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 53 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 63edff57-35ca-43ce-9fd8-b823a1381aae
Commits

Reviewing files that changed from the base of the PR and between ac72916 and d69df13.

Files selected for processing: 1

  • cli/README.md

No actionable comments were generated in the recent review.

Walkthrough

The OpenAI provider adds a generic deserializer that treats explicit null tool-call fields as empty vectors. It applies to both non-streaming responses and streaming deltas, while retaining defaults for missing fields.

Changes: OpenAI tool-call deserialization

Layer / File(s) Summary
Null handling for tool-call sequences
crates/code-providers/src/providers/openai.rs
A generic deserializer maps explicit null to an empty vector. Both ChatMessage.tool_calls and ChunkDelta.tool_calls use it alongside their missing-field defaults.

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

Change: Bug fix

Merge Risk: Minimal · previous review ac729

This change lets OpenAI-compatible hosts that return null tool_calls work instead of failing to parse. No merge-blocking risk was identified.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: tolerating explicit null tool_calls in OpenAI-compatible responses. It follows the conventional commit format and matches the raw summary, which adds a helper that tre…
Description check Passed The description covers the summary, the root cause (explicit null vs. serde default for missing keys), the fix in both ChatMessage.tool_calls and ChunkDelta.tool_calls, backward compatibility, and ver…
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.
Full details: Title check

Explanation

The title names the main change: tolerating explicit null tool_calls in OpenAI-compatible responses. It follows the conventional commit format and matches the raw summary, which adds a helper that treats null as an empty sequence for both non-streaming and streaming tool_calls fields. It is specific and concise.

Full details: Description check

Explanation

The description covers the summary, the root cause (explicit null vs. serde default for missing keys), the fix in both ChatMessage.tool_calls and ChunkDelta.tool_calls, backward compatibility, and verification steps. It does not follow the template's section structure: the Type of Change checkboxes are not marked (Bug fix is clearly the intended type), the Testing checklist is not ticked, the Checklist section is absent, and Additional Notes is missing. It also does not link a related issue, which the template asks for. Most required information is present, so the check passes, though the template sections should be completed.

Resolution

Optionally mark 'Bug fix' under Type of Change, tick the applicable Testing and Checklist items (for example unit tests pass and added tests, if tests were added), and add a related issue reference or an Additional Notes entry if one exists.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 511754b3-6e69-46a7-82d5-ed9c031ec13d
Commits

Reviewing files that changed from the base of the PR and between 728562d and ac72916.

Files selected for processing: 1

  • crates/code-providers/src/providers/openai.rs

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

@gabrielvfonseca gabrielvfonseca self-assigned this Nov 12, 2026
@gabrielvfonseca
gabrielvfonseca merged commit 0cf6ae7 into main Nov 12, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant