Skip to content

fix(openai): ensure reasoning_content is present in all assistant messages - #1755

Open
ifdog wants to merge 1 commit into
looplj:unstablefrom
ifdog:fix-reasoning-assistant
Open

fix(openai): ensure reasoning_content is present in all assistant messages#1755
ifdog wants to merge 1 commit into
looplj:unstablefrom
ifdog:fix-reasoning-assistant

Conversation

@ifdog

@ifdog ifdog commented May 31, 2026

Copy link
Copy Markdown

When reasoning_content is enabled (ReasoningFieldContent or ReasoningFieldAll), ensure all assistant messages have a reasoning_content field.

Some thinking-enabled providers (e.g., Kimi, DeepSeek) require reasoning_content to be present in every assistant message, even if it's an empty string. Without this, multi-round tool-calling conversations fail with:

thinking is enabled but reasoning_content is missing in assistant tool call message

This aligns with the existing fix already present in the DeepSeek transformer.

…sages

When reasoning_content is enabled (ReasoningFieldContent or ReasoningFieldAll),
ensure all assistant messages have a reasoning_content field.

Some thinking-enabled providers (e.g., Kimi, DeepSeek) require reasoning_content
to be present in every assistant message, even if it's an empty string.
Without this, multi-turn tool-calling conversations fail with:
"thinking is enabled but reasoning_content is missing in assistant tool call message".

This aligns with the existing fix in the DeepSeek transformer.
@greptile-apps

greptile-apps Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR injects reasoning_content: \"\" into every assistant message that lacks the field whenever the transformer's reasoningField is ReasoningFieldContent or ReasoningFieldAll. It mirrors an identical loop already present in the DeepSeek-specific transformer, extending it to the shared OpenAI outbound transformer for other reasoning-capable providers such as Kimi.

  • Adds a post-processing loop after RequestFromLLM that sets an empty-string ReasoningContent pointer on any \"assistant\" message where the field is nil, guarding against the \"thinking is enabled but reasoning_content is missing\" API error from providers that require the field on every turn.
  • The guard is conditional on reasoningField being ReasoningFieldContent or ReasoningFieldAll; ReasoningFieldNone and ReasoningFieldReasoning are unaffected.

Confidence Score: 4/5

Safe to merge; the change is a narrow, additive guard that injects an empty string into a pointer field only when that field is absent, matching an already-deployed pattern in the DeepSeek transformer.

The logic is correct for the primary use-case (ReasoningFieldContent). The only open question is whether the ReasoningFieldAll path should also patch the sibling reasoning field, but no known provider currently relies on that combination and the change does not regress existing paths.

llm/transformer/openai/outbound.go — specifically the ReasoningFieldAll branch of the new loop

Important Files Changed

Filename Overview
llm/transformer/openai/outbound.go Adds a post-RequestFromLLM loop that ensures reasoning_content: "" is present on assistant messages when ReasoningFieldContent or ReasoningFieldAll is active; logic is sound but there is a gap for ReasoningFieldAll (only reasoning_content is ensured, reasoning is left nil) and no test exercises the new path.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant OutboundTransformer
    participant RequestFromLLM
    participant Provider

    Caller->>OutboundTransformer: TransformRequest(llmReq)
    OutboundTransformer->>RequestFromLLM: RequestFromLLM(llmReq, reasoningField)
    RequestFromLLM-->>OutboundTransformer: oaiReq (assistant msgs may have nil ReasoningContent)

    alt "reasoningField == ReasoningFieldContent OR ReasoningFieldAll"
        loop each message in oaiReq.Messages
            alt "role=="assistant" AND ReasoningContent==nil"
                OutboundTransformer->>OutboundTransformer: "set ReasoningContent = ptr("")"
            end
        end
    end

    OutboundTransformer->>Provider: POST /chat/completions (all assistant msgs have reasoning_content)
Loading

Reviews (1): Last reviewed commit: "fix(openai): ensure reasoning_content is..." | Re-trigger Greptile

Comment on lines +196 to +203
if reasoningField == ReasoningFieldContent || reasoningField == ReasoningFieldAll {
for i := range oaiReq.Messages {
if oaiReq.Messages[i].Role == "assistant" && oaiReq.Messages[i].ReasoningContent == nil {
emptyStr := ""
oaiReq.Messages[i].ReasoningContent = &emptyStr
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 ReasoningFieldAll leaves reasoning field unpatched

When reasoningField is ReasoningFieldAll, the loop ensures reasoning_content is non-nil but leaves reasoning as nil on the same message. MessageFromLLMWithConfig only cross-syncs the two fields when at least one is already non-nil (lines 135–140 of outbound_convert.go), so a purely synthetic assistant message (e.g. an injected tool-call turn with no prior reasoning data) ends up with reasoning_content: "" but reasoning: null. Any provider configured with ReasoningFieldAll that applies the same "must be present" contract to the reasoning field would still fail. The DeepSeek transformer avoids this because it always uses ReasoningFieldContent, not ReasoningFieldAll.

Comment on lines +196 to +203
if reasoningField == ReasoningFieldContent || reasoningField == ReasoningFieldAll {
for i := range oaiReq.Messages {
if oaiReq.Messages[i].Role == "assistant" && oaiReq.Messages[i].ReasoningContent == nil {
emptyStr := ""
oaiReq.Messages[i].ReasoningContent = &emptyStr
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 No test covers the new injection path

outbound_reasoning_test.go tests MessageFromLLMWithConfig and RequestFromLLM unit-level behavior, but none of the TransformRequest integration tests verify that an assistant message with a nil ReasoningContent actually exits with reasoning_content: "" in the serialized body. A test case such as "assistant tool-call message with no reasoning becomes reasoning_content: """ would confirm the fix and guard against future regressions.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@looplj

looplj commented Jun 1, 2026

Copy link
Copy Markdown
Owner

ds/mimo 是这样,但是不代表其他都是这样吧,不确定是否有其他兼容性问题。
而且,现在 ds 应该已经修复这个问题了才对

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.

2 participants