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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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