fix(vision): NaN/inf/bool di uno strumento non diventano più una quota (#16)

runner.run_graph esclude bool e NaN/inf dagli output numerici, con il
motivo in RunOutcome.invalid_outputs. vision_service tratta una quota
mappata con valore None/assente/non finito come "non prodotta" (422),
invece di arrivare a float(None) e sollevare TypeError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:35:53 +02:00
parent 10261b66f0
commit 20957e517d
5 changed files with 137 additions and 4 deletions
+19 -2
View File
@@ -6,6 +6,7 @@ database, no filesystem beyond reading the pinned submodule's commit.
"""
from __future__ import annotations
import math
import os
import subprocess
import time
@@ -34,6 +35,12 @@ class RunOutcome:
failures: list[ToolFailure] = field(default_factory=list)
engine_version: str = ""
duration_ms: int = 0
# A tool that "succeeded" but handed back NaN/inf (vs-measure emits NaN on
# an empty slice - see caliper.py:112) or a bool (a subclass of int, so
# `isinstance(x, (int, float))` alone would let it through as a quote).
# Kept apart from `failures`: the tool did not raise, it just produced a
# number the caller must not measure a part by. Value is the reason.
invalid_outputs: dict[str, str] = field(default_factory=dict)
@lru_cache(maxsize=1)
@@ -109,6 +116,7 @@ def run_graph(
outputs: dict[str, float] = {}
failures: list[ToolFailure] = []
invalid_outputs: dict[str, str] = {}
for result in report:
if not result.ok:
failures.append(ToolFailure(
@@ -118,12 +126,21 @@ def run_graph(
))
continue
for name, value in result.outputs.items():
if isinstance(value, (int, float)):
outputs[f"{result.tool_id}.{name}"] = float(value)
key = f"{result.tool_id}.{name}"
# bool is a subclass of int - a tool that outputs a flag must not
# be read as a quote.
if isinstance(value, bool) or not isinstance(value, (int, float)):
continue
value = float(value)
if not math.isfinite(value):
invalid_outputs[key] = "nan" if value != value else "infinite"
continue
outputs[key] = value
return RunOutcome(
outputs=outputs,
failures=failures,
engine_version=engine_version(),
duration_ms=elapsed_ms,
invalid_outputs=invalid_outputs,
)