Force JSON output from the VLM with schema-in-user-prompt + response_format
Claude
Unverified signature
360f285
The Ising VLM was ignoring the system prompt's "return JSON only"
instruction and replying with markdown bullets ("**Experiment type**:
Clearly a Rabi oscillation..."), which then failed JSON parsing. Two
changes, either of which would likely fix it alone:
1. Repeat the schema in the user prompt and explicitly demand the reply
begin with `{` and end with `}`. Some NIM deployments strip or
de-emphasize system prompts on multimodal calls, so anchoring the
JSON demand in the user message makes it much harder to ignore.
2. Add `response_format: {"type": "json_object"}` to the chat request
(OpenAI API param supported by NIM / vLLM). On backends where it
works, this constrains decoding to valid JSON at the sampler level.
Leaves the system prompt in place as belt-and-suspenders; removing it
would be a regression if/when NIM starts honoring system prompts again.
Surface JSON parse failures in analyzer output
Claude
Unverified signature
9b0eac9
When the NIM endpoint returns 200 but the body isn't valid JSON (e.g.
the VLM writes prose like "this looks like tabular data, not a
calibration plot"), AnalysisResult.parsed ends up as {} and every
field rendered as "n/a" with no indication of what went wrong — the
exact silent-failure mode that had us chasing ghosts during Space
testing.
Render a distinct "could not be parsed" block with the raw model
response (truncated to 1200 chars) so the next failure is actionable
instead of mysterious. Also nudges the user toward in-distribution
inputs + a retry.
Render CSV uploads as plots, not table screenshots
Claude
Unverified signature
3c65610
The Ising Calibration VLM is trained on calibration plots (Rabi traces,
T1 decays, readout IQ scatter, etc.), not on images of numeric tables.
When a user uploaded a 2-column CSV via the Gradio app, data.load_payload
was rendering the dataframe as a matplotlib table grid — the VLM would
see "a table with headers x,y and 25 numeric rows" and respond with
"no clear Rabi oscillations observed" at confidence 0.2, which then
failed JSON parsing sporadically and always produced useless metrics.
Add _render_csv_for_vlm that dispatches by CSV shape:
* 2 numeric columns → line plot (covers rabi/ramsey/t1/t2)
* columns named i + q → IQ scatter
* 1 numeric column → line plot vs row index
* anything else → fall back to the table screenshot
Verified end-to-end on the synthetic rabi.csv the user was testing
with: the rendered image now shows a clear damped oscillation instead
of a grid of raw floats.
Ship qcal-copilot pip package (Phase 1)
Claude
Unverified signature
d7214cb
Reshape the repo into a src/qcal pip package so real calibration engineers
can drop qcal into their workflow without cloning the HF Space. The existing
Gradio app and decoder stage keep working as-is.
* src/ layout + pyproject.toml with [ml], [decoder], [gui], [all] extras and
a `qcal = qcal.cli:app` console script.
* qcal.fit: scipy.optimize curve fits for Rabi (damped sine), Ramsey (damped
cosine w/ detuning), T1 / T2-echo (exp decay). All return a FitResult
dataclass with R^2; recover synthesized params with R^2≈1.0.
* qcal.data: from_array / from_npy / from_npz adapters. Render matplotlib
plots (line / heatmap / scatter) per experiment type, overlay the fit
curve, and wire numeric summaries + fit summaries into the VLM prompt.
* qcal.analyzer: new analyze_payload() convenience wrapper; AnalysisResult
now carries fit_params, fit, and source, and exposes _repr_markdown_.
* qcal.decoder: _repr_markdown_ on DecoderResult for Jupyter.
* qcal.config: TOML-backed config under…