Skip to content

fix(openresponses): populate Content and accept bare {role,content} items (#10039) - #10040

Merged
mudler merged 3 commits into
mudler:masterfrom
Anai-Guo:fix/openresponses-empty-prompt-when-no-chatmessage-template
May 28, 2026
Merged

fix(openresponses): populate Content and accept bare {role,content} items (#10039)#10040
mudler merged 3 commits into
mudler:masterfrom
Anai-Guo:fix/openresponses-empty-prompt-when-no-chatmessage-template

Conversation

@Anai-Guo

Copy link
Copy Markdown
Contributor

Description

Fixes #10039.

/v1/responses silently returned empty output on any model whose YAML didn't include a Go-side template.chat_message block — the handler logged Prompt (before templating) prompt="", hit the 5× empty-retry guard in core/http/endpoints/openai/inference.go:129, and returned a 200 OK response.completed with output: [].

Three cooperating bugs, all small and orthogonal:

# File:line Bug
A core/http/endpoints/openresponses/responses.go:97, :302 convertORInputToMessages populated only StringContent for the string-input case and for the input.Instructions system message — Content (the any field) stayed nil.
B core/templates/evaluator.go:114 contentExists := i.Content != nil && i.StringContent != "" silently dropped messages with StringContent set but Content nil. Every fallback branch in this function reads i.StringContent, not i.Content.
C core/http/endpoints/openresponses/responses.go:312 The array-input switch dispatched on itemMap["type"] with no default. The OpenAI Python SDK helper client.responses.create(input=[{"role":"user","content":"…"}]) sends items without a type discriminator — those fell through and were dropped.

Fix

  • Set both Content and StringContent in the two openresponses sites that only set one.
  • Treat a bare {role, content} item (no type) as type: "message" for OpenAI-SDK compatibility. Items that are neither known typed items nor a {role, content} shape still fall through unchanged (no behavior change for current valid inputs).
  • Gate TemplateMessages fallback rendering on StringContent != "". This is the most defensive form because every downstream branch in that function (fmt.Sprint(r, i.StringContent), content = fmt.Sprint(i.StringContent), the role == "system" suppression check) reads StringContent. Switching to || would have introduced a regression for the (currently impossible) shape Content!=nil && StringContent=="" since the role prefix would then render with empty content.

Notes for review

  • The existing evaluator_test.go cases all populate StringContent only (Content stays nil), but they pass because they configure TemplateConfig.ChatMessage — which is an earlier branch that consumes StringContent directly and short-circuits before contentExists is read. The new regression test exercises the fallback path (no ChatMessage template configured) with a StringContent-only message — the exact shape /v1/responses produces.
  • No behavior change for /v1/chat/completions — its messages already go through middleware that populates both Content and StringContent, so neither fix B nor fix C affects that path.
  • No new dependencies or schema changes.

Test plan

🤖 Generated with Claude Code

Anai-Guo and others added 2 commits May 27, 2026 21:19
…tems (mudler#10039)

Fixes mudler#10039 — `/v1/responses` silently returned empty
output on any model whose YAML doesn't include a Go-side
`template.chat_message` block.

Three cooperating bugs:

* `convertORInputToMessages` populated only `StringContent` for string
  input and for the `input.Instructions` system message, leaving the
  `Content` (any) field nil.
* `TemplateMessages` gated all fallback content-rendering branches on
  `Content != nil && StringContent != ""` — but every branch in that
  function consumes `StringContent`, not `Content`. The `&&` silently
  dropped messages that had StringContent set and Content nil, producing
  an empty prompt that the 5× empty-retry guard then turned into a
  200 OK with `output: []`.
* The array-input branch of `convertORInputToMessages` dispatched on
  `itemMap["type"]` with no default, dropping bare `{role, content}`
  items emitted by the OpenAI Python SDK helper
  `client.responses.create(input=[{...}])`.

Fix:

* Set both `Content` and `StringContent` in the two openresponses
  message-construction sites that only set one.
* Treat a bare `{role, content}` item (no `type`) as
  `type: "message"` for OpenAI-SDK compatibility.
* Gate `TemplateMessages` fallback rendering on `StringContent != ""`,
  which is what every downstream branch in that function actually
  reads.

Regression test added to `evaluator_test.go` covering the fallback
path (no `ChatMessage` template) with a StringContent-only message,
both with and without a role mapping.
…r#10039)

Add regression tests for the two seams the original fix touched but
left uncovered:

* convertORInputToMessages must populate both Content and StringContent
  for plain string input and for bare {role, content} array items (the
  OpenAI SDK shape that omits the type discriminator). Both are
  functional reds against the pre-fix code.
* Messages.ToProto reads Content, not StringContent — this is the path
  UseTokenizerTemplate backends (imported GGUFs) take. The cases pin
  that contract so a future regression on the producer side is caught.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-7 [Claude Code]
@mudler
mudler enabled auto-merge (squash) May 28, 2026 07:02
@mudler

mudler commented May 28, 2026

Copy link
Copy Markdown
Owner

thanks @Anai-Guo , looking good here

@mudler
mudler merged commit 0fd666e into mudler:master May 28, 2026
58 of 59 checks passed
@localai-bot localai-bot added the bug Something isn't working label Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/v1/responses silently returns empty output: convertORInputToMessages leaves Content nil; array items without type:"message" are dropped

3 participants