Skip to content

Feat: Record thinking, effort and beta flags on inference events - #1667

Merged
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:feat/thinking-effort-capture
Nov 5, 2026
Merged

huang195 merged 1 commit into
rossoctl:mainfrom
huang195:feat/thinking-effort-capture

Conversation

@huang195

@huang195 huang195 commented Nov 5, 2026

Copy link
Copy Markdown
Member

Summary

Part of #1494. Each inference event now records what the request asked for and which thinking blocks travelled with it:

Field Source
thinkingType, thinkingDisplay the request's thinking.type and thinking.display
effort output_config.effort (Anthropic), reasoning_effort (chat completions), reasoning.effort (Responses)
anthropicBeta the anthropic-beta header, every value joined with a comma, cleaned and capped as servedModel is
historyThinking, responseThinking counts of the thinking and redacted_thinking blocks in the request history and in the response (buffered or streamed): blocks, redacted, textBytes, signatureBytes, redactedBytes

Why

The archive could not show what thinking setting a turn used, or whether its thinking blocks reached the model. A Claude Code session through a LiteLLM gateway at 1.105.1 sends each earlier thinking block back with a signature and empty text, and that LiteLLM version drops such blocks before Bedrock (BerriAI/litellm#41361). With this change, the request row shows it directly, for example historyThinking: {blocks: 1, signatureBytes: 912}.

Design

  • Counts, not blocks. The history counts do not grow with the conversation the way the blocks would. The text of a block is the model's reasoning, and the signature is opaque.
  • A value of another JSON type leaves the request parsed. Each new value is decoded as raw JSON and read only when it has the expected type. The test TestThinkingFieldsOfAnUnexpectedTypeKeepTheRequestParsed sends 9 such bodies. The base commit parses all of them, and so does this one.
  • Small fields. They are short scalars and counts, so view=summary keeps them (the field guard goes from 29 to 35) and the archive carries them in the event record. The interner and the archive format do not change.
  • No plugin reads them. They are for the session store and its readers only.

Not in this PR

Verification

  • New tests: core/plugins/inferenceparser/thinking_test.go, and one archive round trip in core/session/archive/format_test.go.
  • I broke 9 lines of the new code, one at a time, and a test failed each time, for the right reason.
  • go test passes for inferenceparser, pipeline, sessionapi, session/... and cost/.... go vet and gofmt -l are clean, and agentop builds.
  • Live: I built this branch with the full plugin profile and sent a Claude Code session through it to a LiteLLM gateway. Every request row showed thinkingType: adaptive, effort: high and the anthropicBeta values. The response rows counted the thinking blocks, and the next request counted them in its history with 0 text bytes.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Each inference event now states what the request asked for and which
thinking blocks travelled with it:

- thinkingType and thinkingDisplay, from the request's thinking field
- effort, from output_config.effort (Anthropic), reasoning_effort
  (chat completions) or reasoning.effort (Responses)
- anthropicBeta, the anthropic-beta header with every value joined
- historyThinking and responseThinking, counts of the thinking and
  redacted_thinking blocks in the request history and in the response,
  buffered or streamed: blocks, redacted, textBytes, signatureBytes,
  redactedBytes

The counts show a block that comes back with a signature and no text.
That is the shape a LiteLLM gateway at 1.105.1 drops before Bedrock
(BerriAI/litellm#41361), and until now the archive had no way to show it.

Every new value is decoded as raw JSON and read only when it has the
expected type, so a value of another type leaves the request parsed and
only that field empty. The fields are short scalars and counts, so the
summary view keeps them and the archive carries them in the event record.

Part of rossoctl#1494.

Signed-off-by: Hai Huang <huang195@gmail.com>
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
@huang195
huang195 requested a review from a team as a code owner November 5, 2026 22:42
@coderabbitai

coderabbitai Bot commented Nov 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 40 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bb5c18c5-9992-46da-beef-2bdff89fd447
Commits

Reviewing files that changed from the base of the PR and between e96ba50 and a5e6cf8.

Files selected for processing: 9

  • core/pipeline/extensions.go
  • core/plugins/inferenceparser/anthropic.go
  • core/plugins/inferenceparser/plugin.go
  • core/plugins/inferenceparser/responses.go
  • core/plugins/inferenceparser/served_model.go
  • core/plugins/inferenceparser/thinking_test.go
  • core/session/archive/format_test.go
  • core/sessionapi/summary_test.go
  • docs/session-api.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@clawgenti clawgenti left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

This is clean work. The new fields are all decoded as json.RawMessage and read only when they have the expected type. I ran my own wrong-type probe bodies against head and base, and they give identical results. The bodies cover: object or number thinking text, null thinking/output_config, a numeric thinking.type, stray data/signature on text blocks, wrong-typed buffered response blocks, wrong-typed stream deltas and content_block_start.data, a numeric reasoning_effort, and a list or object reasoning. Messages, completion, usage and finish reason all survive. Streamed thinking counts don't double on a second finalize, because finalize assigns a copy. The buffered OnResponse arm also returns early once the stream has filled Completion. I left one non-blocking suggestion inline.

Author: huang195 (MEMBER — maintainer)
Areas reviewed: Go (pipeline extension, inferenceparser request/buffered/SSE paths, served-model cleaner), tests, docs
Agent/IDE config (.claude/.vscode): none
Commits: 1 commit, all signed-off: yes
CI status: passing (CodeRabbit and Spellcheck skipped)
Local: go test passes for ./plugins/inferenceparser, ./sessionapi, ./session/... and ./pipeline. cost/ledger and cost/usage fail the same DST/tzdata tests on base, so those failures don't come from this PR.

// agent action. Same classification as the OpenAI path.
IsAction: true,

ThinkingType: stringField(req.Thinking, "type"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: AnthropicBeta goes through cleanServedModel (control runes replaced, capped at 256), but ThinkingType, ThinkingDisplay and Effort are stored exactly as the client sent them. All six fields are kept by view=summary and ride in the archive event record. A long or control-character-laden thinking.type would therefore be stored and served on every summary row, uncapped. Agentop doesn't draw them today, but the next reader that prints them would inherit the escape sequences. The same applies to Effort in parseOpenAIRequest and parseResponsesRequest. Model already has this property, so it isn't new, but for these record-only fields running them through cleanServedModel (or a smaller cap) is cheap. Not blocking.

@huang195
huang195 merged commit c861a4a into rossoctl:main Nov 5, 2026
41 checks passed
@huang195
huang195 deleted the feat/thinking-effort-capture branch November 5, 2026 23:14
@github-project-automation github-project-automation Bot moved this from New/ToDo to Done in Rossoctl Issue Prioritization Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants