Skip to content

fix(providers): normalize Responses reasoning items onto assistant messages - #74

Open
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1788552976-responses-reasoning
Open

fix(providers): normalize Responses reasoning items onto assistant messages#74
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1788552976-responses-reasoning

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Reasoning items returned on the Responses path are now normalized onto the assistant message, restoring the readable reasoning string in parquet results that was dropped when the chat-completions client was removed.

What changed?

  • extractReasoning in src/providers/responses-client.ts, beside extractMessageText and extractCitations: walks a Responses output array and maps every type: "reasoning" item to normalized fields — reasoning (readable text) and reasoningDetails (reasoning.text / reasoning.summary / reasoning.encrypted, carrying id, format, signature where present).
  • responsesTurnToModelOutput spreads its result onto the assistant message. responseItems still carries the raw items for replay; nothing else about the turn conversion changes.
  • Tests: unit coverage for the extractor in responses-client.test.ts, provider-path coverage on responsesTurnToModelOutput, and an end-to-end case that pipes a provider-shaped turn through the parquet writer.

The stored parquet payload is unchanged apart from reasoning coming back. reasoningDetails lives on the message only, which is what the chat-completions client did with it — it fed the outbound echo and was never serialized.

Precedence rules, deliberately:

  • reasoning is populated from plaintext reasoning_text parts. When a provider exposes only a summary, the summary becomes the readable text, since that is the whole of the trace it emits.
  • Encrypted content never becomes readable text. It is kept as a reasoning.encrypted detail for replay and is not decrypted.
  • Items carrying no reasoning contribute nothing; unknown/malformed parts are skipped rather than repaired, so a missing trace stays missing rather than being approximated from reasoning_tokens.

Why?

Since generation moved to the Responses path, responsesTurnToModelOutput built the assistant message from visible text plus tool calls plus raw output items and populated neither reasoning field. ModelMessage.reasoning and ModelMessage.reasoningDetails are still declared, and parquet.ts still has a branch that emits reasoning — a declared capability with no producer. Its regression test hand-feeds message.reasoning, so it kept passing while the provider path that should fill it had no coverage.

The result in production: benchmark parquet rows carry non-zero reasoning_tokens but no trace text, for every reasoning model, on every task. Sampled prod result chunks show readable reasoning present in runs through 2026-08-22 and absent from 2026-08-27 onward, which brackets the switch. Traces from those runs are not recoverable from the results bucket; this fix is forward-looking.

The plaintext case is real, not hypothetical: OpenRouter's Responses skin emits GLM reasoning as {"type":"reasoning","content":[{"type":"reasoning_text","text":"..."}]} with no encrypted_content. include: ["reasoning.encrypted_content"] on the request is a multi-turn replay device for opaque-reasoning providers (store: false makes a bare rs_... id unreplayable), not a signal that traces arrive encrypted — so both shapes have to be handled, and both now are.

How to test

bun test src/providers/responses-reasoning.test.ts src/providers/messages-to-responses.test.ts src/results/parquet.test.ts
bun run format:check && bun run check && bun run typecheck && bun test && bun run build

Expected: all green. The end-to-end case in parquet.test.ts (serializes reasoning from a provider Responses turn end to end) fails on main because the serialized message has no reasoning key.

Benchmark impact

No score, dataset, solver, or scorer change. Rows for reasoning models regain a reasoning string, which increases result-file size for those runs.

Reviewer focus

  • The summary-as-readable-text fallback. The alternative is to leave summary-only providers with an empty reasoning field, with the summary reachable only in memory; I chose the fallback because the summary is the only trace those providers expose.
  • Detail objects are normalized to the reasoning.* convention rather than being raw provider items, so they match what the chat-completions client put in reasoningDetails and stay replayable.
  • Deliberately not in scope: the message's raw responseItems are not serialized into the parquet messages column, and the sample-level response_items column reads task state that only agentic solvers set, so it stays null for single-turn benchmarks. Neither was ever stored, so neither is a regression.
  • Repo has no mutation-testing tooling (no stryker config or script), so no mutation score is reported. The extractor's branches are covered by direct unit tests instead.
  • I searched open PRs for existing work on reasoning storage in the Responses path and found none.

Checklist

  • Tests cover changed behavior
  • Public API or configuration changes are backward compatible, or the break is documented
  • Benchmark changes document dataset provenance and licensing — n/a
  • No credentials, private results, or restricted dataset contents are included
  • Documentation is updated where needed — the repo has no document describing the parquet result format

Link to Devin session: https://openrouter.devinenterprise.com/sessions/e69ee5580147481d8371df26bd73582a
Open in Devin Desktop: https://openrouter.devinenterprise.com/desktop/session/e69ee5580147481d8371df26bd73582a?variant=devin
Requested by: @alan-carroll

…ssages

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team as a code owner September 4, 2026 20:17
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author
Original prompt from Alan

SYSTEM:
<latest_message>
Alan Carroll (U0BSEUL9K09) [ts=1788547294.973619]: @Devin do you have GCS read access
</latest_message>

=== BEGIN THREAD HISTORY (in #brain-alan) ===
Alan Carroll (U0BSEUL9K09) [ts=1788547294.973619]: @Devin do you have GCS read access
=== END THREAD HISTORY ===
Channel ID: C0BSQ785ACU
Thread URL: https://openrouter.slack.com/archives/C0BSQ785ACU/p1788547294973619?thread_ts=1788547294.973619&amp;cid=C0BSQ785ACU

The <latest_message> is the message that you should use to guide your goals + task for this session, and you should use the rest of the slack thread as context.
A [ts=...] marker on a Slack message is that message's timestamp. To act on a specific message with the slack tool (e.g. adding an emoji reaction via the reaction command), pass that value as timestamp along with the Channel ID — no extra lookup call is needed.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Alan Carroll and others added 3 commits September 4, 2026 20:34
…tails column

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…t-item extractors

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ning

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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.

0 participants