AlexsJones/llmfit · error

JSON serialization failed

Error message

JSON serialization failed

What it means

An .expect() inside ModelQualityResult::display() (quality.rs) around serde_json::to_string_pretty of a json!-built Value. A Value constructed via the json! macro serializes successfully unless it contains non-finite floats — serde_json rejects NaN/Infinity with 'float must be finite'. Since quality/speed/composite scores are f64 computed from model responses, NaN propagation (e.g. averaging over zero tests, or a rubric score of 0/0) is the realistic trigger for this panic.

Source

Thrown at llmfit-core/src/quality.rs:594

    /// Print results as JSON.
    pub fn display_json(&self) {
        let json = serde_json::json!({
            "quality_benchmark": {
                "model": self.model,
                "provider": self.provider,
                "overall": {
                    "quality": self.overall_quality,
                    "speed": self.overall_speed,
                    "composite": self.overall_composite,
                },
                "role_scores": self.roles,
                "test_results": self.test_results,
            }
        });
        println!(
            "{}",
            serde_json::to_string_pretty(&json).expect("JSON serialization failed")
        );
    }
}

impl RoutingRecommendation {
    /// Print a routing matrix row.
    pub fn display_row(&self) {
        let note_str = self
            .note
            .as_deref()
            .map(|n| format!("  ({})", n))
            .unwrap_or_default();
        println!(
            "  {:<17} -> {:<30}  q={:.1}  s={:.1}  c={:.1}{}",
            self.role, self.model, self.quality, self.speed, self.composite, note_str
        );
    }
}

View on GitHub (pinned to acc7e40c3a)

Solutions

  1. Sanitize scores before display: map non-finite values to 0.0 (or serialize them as null) with a small helper applied to overall_quality/overall_speed/overall_composite and role/test scores
  2. Fix the upstream NaN source — typically a division by a test count that can be zero — so aggregates are computed only over successful tests
  3. Reproduce with `llmfit quality --model ... --json` against the failing endpoint and inspect which score field is NaN before printing

Example fix

// before (quality.rs)
println!("{}", serde_json::to_string_pretty(&json).expect("JSON serialization failed"));

// after — clamp non-finite floats so Value is always serializable
fn finite(v: f64) -> f64 { if v.is_finite() { v } else { 0.0 } }
// ...use finite(self.overall_quality), finite(self.overall_speed), finite(self.overall_composite) when building `json`...
Defensive patterns

Strategy: validation

Validate before calling

// before printing, assert all scores are finite
fn all_finite(r: &ModelQualityResult) -> bool {
    r.overall_quality.is_finite() && r.overall_speed.is_finite() && r.overall_composite.is_finite()
}
assert!(all_finite(&result), "non-finite score — refusing to serialize NaN/Inf");

Type guard

fn finite_score(v: f64) -> Option<f64> {
    v.is_finite().then_some(v)
}

Prevention

When it happens

Trigger: A quality run where a scoring computation produces NaN or Infinity (division by zero when no tests succeeded, an errored test contributing NaN that is then averaged) and display() is called to print the JSON summary.

Common situations: Benchmarking an endpoint that returns empty/garbage responses so role scores degrade to NaN; refactoring the composite-score formula and introducing an unchecked division.

Related errors


AI-assisted analysis of AlexsJones/llmfit@acc7e40c3a (2026-08-17). Data as JSON: /api/errors/9eb66e2c551a8b5e. Report an issue: GitHub.