feat(providers): add reasoning_echo_policy field for OpenAI-compat reasoning_content handling (#90)
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.
This commit is contained in:
7 files changed
+31
No files matched your search
@@ -35,6 +35,7 @@ except ImportError:
|
||||
|
||||
VALID_TIERS = {"frontier", "smart", "balanced", "fast", "local"}
|
||||
VALID_MODALITIES = {"text", "image", "audio", "video", "music"}
|
||||
VALID_REASONING_ECHO_POLICIES = {"none", "strip", "echo", "empty_string"}
|
||||
VALID_HAND_CATEGORIES = {
|
||||
"communication", "content", "data", "development",
|
||||
"devops", "finance", "productivity", "research", "social",
|
||||
@@ -102,6 +103,13 @@ def validate_provider_file(filepath: Path) -> list[str]:
|
||||
if tier is not None and tier not in VALID_TIERS:
|
||||
errors.append(f"{filepath.name}: Model '{label}' invalid tier '{tier}'")
|
||||
|
||||
policy = model.get("reasoning_echo_policy")
|
||||
if policy is not None and policy not in VALID_REASONING_ECHO_POLICIES:
|
||||
errors.append(
|
||||
f"{filepath.name}: Model '{label}' invalid reasoning_echo_policy "
|
||||
f"'{policy}' (valid: {', '.join(sorted(VALID_REASONING_ECHO_POLICIES))})"
|
||||
)
|
||||
|
||||
for cost_field in (
|
||||
"input_cost_per_m",
|
||||
"output_cost_per_m",
|
||||
|
||||
Reference in new issue
Block a user