豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

[BUG] Responses API: undocumented reasoning+message pairing constraint breaks multi-turn conversations #1050

@achandmsft

Description

@achandmsft

Reasoning+message items must appear as consecutive pairs in input, but nothing documents this. The most common pattern — filtering response.output to keep only messages — silently produces orphaned items → 400 on the next turn.

This broke OpenClaw (64.9k forks) on gpt-5.3-codex. They had to add downgradeOpenAIReasoningBlocks() to strip orphan reasoning items.

400: Item 'msg_...' of type 'message' was provided without its required preceding item of type 'reasoning'

Tested in .NET, JS, Python, Go, Java — identical results across all 5 SDKs. This is an API-level constraint, not SDK-specific. Confirmed via curl (see gist). The SDK types don't prevent building input arrays that violate it.

The .NET SDK uses shared ResponseItem types for both input and output — output items pass back as input without compile errors, which makes the broken pattern especially easy to write.

Model A (all items) B (msgs only)
gpt-5.3-codex (reasoning=high) PASS FAIL
o4-mini PASS FAIL*

* Nondeterministic — o4-mini sometimes returns reasoning-only output (no message to orphan). Codex with reasoning=high reliably returns both items.

Workaround: PreviousResponseId. For manual history, always pass reasoning+message pairs together.

Related:

To Reproduce

using System.ClientModel;
using OpenAI;
using OpenAI.Responses;

#pragma warning disable OPENAI001

var client = new ResponsesClient(
    new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!));

var prompts = new[] { "Write a Python prime checker.", "Add type hints.", "Add docstrings." };
var conversation = new List<ResponseItem>();

for (int i = 0; i < prompts.Length; i++)
{
    Console.WriteLine($"\n> {prompts[i]}");
    conversation.Add(ResponseItem.CreateUserMessageItem(prompts[i]));
    var options = new CreateResponseOptions("gpt-5.3-codex", conversation)
    {
        MaxOutputTokenCount = 300,
        ReasoningOptions = new ResponseReasoningOptions { ReasoningEffortLevel = ResponseReasoningEffortLevel.High },
    };
    try
    {
        var response = await client.CreateResponseAsync(options);
        Console.WriteLine($"  {response.Value.GetOutputText()?[..Math.Min(80, response.Value.GetOutputText()?.Length ?? 0)]}...");
        // Common pattern: keep only messages, discard reasoning
        foreach (var item in response.Value.OutputItems)
            if (item is MessageResponseItem)
                conversation.Add(item);  // orphan message → 400 on next turn
    }
    catch (Exception e)
    {
        Console.WriteLine($"  ERROR: {e.Message[..Math.Min(120, e.Message.Length)]}");
        break;
    }
}
// Turn 2 → 400: Item 'msg_...' was provided without its required preceding item

Reproduced on o4-mini and gpt-5.3-codex. Full cross-language repro (JS, Python, .NET, curl): https://gist.github.com/achandmsft/57886350885cec3af8ef3f456ed529cf

Environment

  • OpenAI .NET SDK v2.9.1
  • .NET 9
  • Windows 11, also reproduced on Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions