Skip to content

feat(providers): add reasoning_echo_policy field#90

Merged
houko merged 1 commit into
mainfrom
feat/reasoning-echo-policy
May 10, 2026
Merged

feat(providers): add reasoning_echo_policy field#90
houko merged 1 commit into
mainfrom
feat/reasoning-echo-policy

Conversation

@houko
Copy link
Copy Markdown
Contributor

@houko houko commented May 10, 2026

Refs librefang/librefang#4842.

Summary

Long-term replacement for the substring match in the LibreFang OpenAI driver that decides how to handle reasoning_content on historical assistant turns. Three provider-specific behaviours, now expressed as catalog metadata:

Policy Provider/model Why
strip DeepSeek R1 / deepseek-reasoner API rejects requests carrying reasoning_content on previous assistant messages
echo DeepSeek V4 Flash Thinking mode is on by default; API returns 400 when tool_calls turns don't echo back the original reasoning text (the librefang#4842 bug)
empty_string Moonshot / Kimi K2 family Field must be present (empty) on tool_calls turns, with thinking disabled wire-side
none (default) Everyone else Field is omitted entirely

V4 Pro is intentionally NOT marked echo — librefang#4842 reports it working out-of-the-box; the substring guard is narrow on purpose. Flip later when there's an empirical reproducer.

Files

  • schema.toml — registers the field, four options, default "none". #[serde(default)] semantics on the consumer side keep existing TOML parsing unchanged.
  • providers/deepseek.tomldeepseek-v4-flashecho; deepseek-reasonerstrip
  • providers/moonshot.tomlkimi-k2.6, kimi-k2.5, kimi-k2empty_string
  • providers/kimi-coding.tomlkimi-for-codingempty_string
  • providers/byteplus-coding.tomlkimi-k2.5empty_string
  • providers/novita.tomlmoonshotai/kimi-k2-thinkingempty_string
  • scripts/validate.pyVALID_REASONING_ECHO_POLICIES constant + per-model enum check

Test plan

  • python3 scripts/validate.py — passes (267 models, no regressions)
  • Negative test: hand-injected reasoning_echo_policy = "bogus" on deepseek-v4-flash → validator fails with Model 'deepseek-v4-flash' invalid reasoning_echo_policy 'bogus' (valid: echo, empty_string, none, strip)
  • All affected provider files re-read after validate to confirm intended values stuck

Backward compatibility

  • Field is required = false with default = "none" and consumers will read it via #[serde(default)]. Older librefang releases that don't know the field will simply ignore it; their existing substring-based driver behaviour is unchanged.
  • No models are removed or renamed; only metadata added. Pricing / token-window fields untouched.

Follow-up (in librefang/librefang)

A separate PR will add a matching ReasoningEchoPolicy enum to librefang-types::ModelCatalogEntry, plumb it through CompletionRequest, and replace the OpenAI driver's is_deepseek_reasoner / is_deepseek_v4_thinking_with_tools / kimi_needs_reasoning_content substring helpers with a single match on the catalog field. Substring matching is kept as a fallback for unknown / user-defined models.

…asoning_content handling

Refs librefang/librefang#4842 — long-term replacement for the substring
match that the OpenAI driver currently uses to decide how to handle
`reasoning_content` on historical assistant turns.

Three provider-specific behaviours that the driver must distinguish at
wire time, now expressed as catalog metadata:

* `strip`  — DeepSeek R1 / deepseek-reasoner. The API rejects requests
  that carry reasoning_content on previous assistant messages.
* `echo`   — DeepSeek V4 Flash. Thinking mode is on by default and the
  API rejects multi-turn requests when assistant turns containing
  tool_calls don't echo back the original reasoning text. This is the
  bug surfaced in librefang/librefang#4842.
* `empty_string` — Moonshot / Kimi K2 family. The field must be present
  (empty string) on tool_calls turns, with thinking disabled wire-side
  for multi-turn compatibility.
* `none` (default) — most providers; field is omitted entirely.

V4 Pro is intentionally NOT marked `echo` — librefang#4842 reports it
working out-of-the-box; flip when there's an empirical reproducer.

Marks affected models:

  providers/deepseek.toml
    deepseek-v4-flash → echo
    deepseek-reasoner → strip
  providers/moonshot.toml
    kimi-k2.6, kimi-k2.5, kimi-k2 → empty_string
  providers/kimi-coding.toml
    kimi-for-coding → empty_string
  providers/byteplus-coding.toml
    kimi-k2.5 → empty_string
  providers/novita.toml
    moonshotai/kimi-k2-thinking → empty_string

Tooling:

* schema.toml registers the field with the four enum options and a
  `none` default so existing TOML files keep parsing unchanged.
* scripts/validate.py rejects unknown enum values; verified with a
  hand-crafted negative case (`reasoning_echo_policy = "bogus"` →
  validation fails with the expected message).
* `python3 scripts/validate.py` passes (267 models).

The librefang side that consumes this field will land in a follow-up
PR — until then, registry consumers ignore the field via
`#[serde(default)]` and the existing substring fallback continues to
work, so this commit is safe to ship independently.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@houko houko merged commit 6785807 into main May 10, 2026
3 checks passed
@houko houko deleted the feat/reasoning-echo-policy branch May 10, 2026 15:41
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.

1 participant