HKUDS/Vibe-Trading · error · ValueError
audit must be a list
Error message
audit must be a list
What it means
_coerce_audit_rows accepts None, '' (empty), a JSON string, or a list; anything else that is not a list after JSON decoding raises this. It guards the audit parameter of the goal tool against malformed LLM/tool arguments before row-level validation begins.
Source
Thrown at agent/src/tools/goal_tool.py:42
def _coerce_string_list(value: Any) -> list[str]:
"""Coerce a JSON-schema array-or-string value to a string list."""
if value is None:
return []
if isinstance(value, str):
return [value] if value.strip() else []
if isinstance(value, list):
return [str(item).strip() for item in value if str(item).strip()]
return []
def _coerce_audit_rows(value: Any) -> list[AuditRow]:
"""Coerce model/API-style audit rows into dataclasses."""
if value in (None, ""):
return []
if isinstance(value, str):
value = json.loads(value)
if not isinstance(value, list):
raise ValueError("audit must be a list")
rows: list[AuditRow] = []
for item in value:
if not isinstance(item, dict):
raise ValueError("audit rows must be objects")
criterion_id = str(item.get("criterion_id") or "").strip()
result = str(item.get("result") or "").strip()
if not criterion_id or not result:
raise ValueError("audit rows require criterion_id and result")
rows.append(
AuditRow(
criterion_id=criterion_id,
result=result,
evidence_ids=_coerce_string_list(item.get("evidence_ids")),
notes=str(item.get("notes") or ""),
)
)
return rowsView on GitHub (pinned to 80ffdda44c)
Solutions
- Wrap single row objects in a list: audit=[{...}] or '[{...}]'
- If rows arrive as a dict keyed by criterion, convert with list(rows_dict.values()) before passing
- Catch ValueError from execute and re-prompt the model with the expected schema
Example fix
# before
execute(audit={"criterion_id": "c1", "result": "pass"})
# after
execute(audit=[{"criterion_id": "c1", "result": "pass"}]) Defensive patterns
Strategy: validation
Validate before calling
import json
def normalize_audit(v):
if v in (None, ""):
return []
if isinstance(v, str):
v = json.loads(v)
if isinstance(v, dict):
v = [v]
assert isinstance(v, list), "audit must be a list"
return v Type guard
def is_audit_list(v) -> bool:
import json
if isinstance(v, str):
try:
v = json.loads(v)
except json.JSONDecodeError:
return False
return isinstance(v, list) Try / catch
try:
out = execute(audit=raw)
except ValueError as e:
if str(e) == "audit must be a list":
raw = [raw] if isinstance(raw, dict) else list(raw.values())
out = execute(audit=raw)
raise Prevention
- Always wrap rows in [] even for a single row
- JSON-encode as an array, not an object
- Use pydantic models for tool args to enforce shape at the boundary
When it happens
Trigger: Passing audit as a dict ({"criterion_id": ...}), a number, or a JSON string that decodes to an object rather than an array, e.g. '{"0": {...}}'.
Common situations: LLM emitting a single audit row object instead of an array; JSON string containing an object map keyed by index; hand-written payloads wrapping rows in extra braces.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- audit rows must be objects
- audit rows require criterion_id and result
- legs must be a non-empty array
- invalid alpha_id
- alpha_id not found
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/4f71a53c7353bf07.
Report an issue: GitHub.