Skip to content

fix: serde_json duplicate field error on providers that send both legacy and new usage field names (GPT-5.4) #330

Description

@ajianaz

Problem

Cora fails to parse LLM responses from GPT-5.4 (and potentially other newer models) with:

failed to parse LLM JSON response: duplicate field `prompt_tokens` at line 1 column 2993

Root Cause

GPT-5.4 returns both legacy and new field names in the usage object simultaneously:

"usage": {
  "prompt_tokens": 2615,
  "completion_tokens": 581,
  "total_tokens": 3196,
  "input_tokens": 2615,
  "output_tokens": 581
}

Cora's Usage struct (src/engine/llm.rs:92-99) uses serde aliases to accept both naming conventions:

#[serde(default, alias = "promptTokens", alias = "input_tokens")]
prompt_tokens: u32,

serde_json >= 1.0.120 tracks visited fields during struct deserialization. When input_tokens (alias) maps to prompt_tokens (already set from primary key), it triggers duplicate field error.

Previous models (GPT-4, Claude, etc.) only send one format — GPT-5.4 sends both, triggering the guard.

Fix

Option B: Custom deserialize via serde_json::Value intermediate

Parse the usage field as serde_json::Value first on the ChatResponse struct, then deduplicate field names and convert to the typed Usage struct in post-processing. This handles all three cases:

  1. Legacy only (prompt_tokens) — works as before
  2. New only (input_tokens) — alias resolves correctly
  3. Both (GPT-5.4) — deduplicate before typed conversion

Affected code

  • src/engine/llm.rsUsage struct, ChatResponse struct, chat_completion() function, stream usage extraction

Severity

Medium — blocks all reviews using GPT-5.4 and potentially other providers that send dual usage fields. Workaround: pin to models that only send one format.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions