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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions