ADR 0003: Vega-Lite (validated subset) over Plotly for chart specs
Status: accepted (v0.1.0)
Context
generate_chart is an ordinary tool the model may call when a result is worth charting. The
model writes the chart spec, so the spec is untrusted structured output that must be validated
before anything treats it as a chart.
Decision
Charts are a subset of Vega-Lite, validated with pydantic (agent/tools/chart.py):
mark: one ofbar,line,point,arc.encoding: a mapping of channel name to{field, type}, wheretypeisquantitative,nominal,ordinalortemporal.data: a mapping from a key to a list of row objects (inline data).
Vega-Lite is a single declarative JSON grammar (mark + encoding + data), so a small fixed schema
covers the common chart shapes. Plotly's figure schema is organised around many trace types, each
with its own attribute set, which is a much larger surface to validate against arbitrary model
output. An invalid spec is rejected and fed back as an observation so the model can retry.
The CLI writes an accepted spec to chart.vega.json.
Consequences and limits
- Validation is structural only. It does not check that
encodingfields exist indata, and it does not validate against the real Vega-Lite JSON Schema. The emitted file has no$schemakey, and itsdatashape (a keyed list of rows rather than{"values": [...]}) may need adapting before a Vega-Lite renderer accepts it. Rendering was not tested. - Unknown keys are dropped, not rejected (pydantic's default
extra="ignore"): a spec withaggregate,scaleor top-level extras validates and the extras vanish from the output. The chart tool therefore rejects wrong shapes and wrong enum values, but does not reject unsupported features. - The chart's data is whatever the model put in the spec. It is not tied to the rows returned by
a SQL query, so a chart can disagree with the query results. Only the newest spec that passes
validation is surfaced (
run._last_valid_chart); a rejected spec is never returned. - No image is produced; consumers render the JSON themselves.