Skip to content

fix(openai): harden request building for strict OpenAI-compatible backends#3574

Merged
esengine merged 2 commits into
main-v2from
fix/openai-strict-backend-compat
Jun 8, 2026
Merged

fix(openai): harden request building for strict OpenAI-compatible backends#3574
esengine merged 2 commits into
main-v2from
fix/openai-strict-backend-compat

Conversation

@esengine

@esengine esengine commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary

Two robustness/compat fixes for OpenAI-compatible providers, surfaced while investigating #3526 (switching to a strict OpenAI-compatible backend mid-session → HTTP 400 "Expecting ',' delimiter").

1. fix(provider): valid schema for no-param tools

A tool with no parameters (common for MCP tools) produces an empty schema. An empty json.RawMessage makes json.Marshal of the whole chat request fail with unexpected end of JSON input — so a single no-arg tool bricks the entire provider for every request, with a cryptic error. CanonicalizeSchema now maps empty → {"type":"object"}.

2. fix(openai): null content for pure tool-call assistant turns

A pure-tool_calls assistant turn previously serialized "content":"". Strict OpenAI-compatible backends can reject that. null is the OpenAI-spec form for a tool-call turn; we now emit null for that one case and keep the string form (empty included) for every other role/message.

On #3526 specifically

I verified our request serialization is not the cause: a mid-session request (assistant tool_calls + tool results) marshals to valid JSON (json.Valid == true), and Go's json.Marshal either produces valid JSON or errors — it never emits malformed JSON. So the Expecting ',' delimiter 400 is the Agnes/Sapiens server rejecting valid JSON.

Change #2 is a spec-aligned compatibility attempt for that server (the content:""null shape is the most likely thing a strict clone trips on). It is verified non-regressive on DeepSeek (a live multi-tool session, whose history carries the tool-call turns as content:null, completed with no 400). I could not test it against Agnes/Sapiens directly — @reporter, please try a build with this change and confirm.

Testing

  • go test ./internal/provider/... green
  • Live DeepSeek multi-tool run completes (3 tool rounds, history serialized with content:null, no 400)

Refs #3526

reasonix added 2 commits June 8, 2026 03:29
An empty tool schema (common for no-argument MCP tools) is an empty
json.RawMessage, which makes json.Marshal of the whole chat request fail
with "unexpected end of JSON input" — bricking the entire provider for
every request with a cryptic error. Canonicalize empty to {"type":"object"}.
Strict OpenAI-compatible backends can reject an assistant tool_calls
turn whose content is the empty string. null is the OpenAI-spec form and
is accepted by DeepSeek (verified against a live multi-tool session), so
emit null for that one case and keep the string form (empty included)
for every other role. Refs #3526.
@esengine esengine requested a review from SivanCola as a code owner June 8, 2026 10:29
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development provider Model providers & selection (internal/provider) labels Jun 8, 2026
@esengine esengine enabled auto-merge (squash) June 8, 2026 10:30
@esengine esengine merged commit 8feb6b3 into main-v2 Jun 8, 2026
10 checks passed
@esengine esengine deleted the fix/openai-strict-backend-compat branch June 8, 2026 10:37
dorokuma pushed a commit to dorokuma/DeepSeek-Reasonix that referenced this pull request Jun 10, 2026
…kends (esengine#3574)

* fix(provider): emit a valid schema for no-param tools

An empty tool schema (common for no-argument MCP tools) is an empty
json.RawMessage, which makes json.Marshal of the whole chat request fail
with "unexpected end of JSON input" — bricking the entire provider for
every request with a cryptic error. Canonicalize empty to {"type":"object"}.

* fix(openai): send null content for pure tool-call assistant turns

Strict OpenAI-compatible backends can reject an assistant tool_calls
turn whose content is the empty string. null is the OpenAI-spec form and
is accepted by DeepSeek (verified against a live multi-tool session), so
emit null for that one case and keep the string form (empty included)
for every other role. Refs esengine#3526.

---------

Co-authored-by: reasonix <reasonix@deepseek.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

provider Model providers & selection (internal/provider) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant