Skip to content

feat(evals): add deterministic VQA dataset generation - #3488

Open
ruthwikdasyam wants to merge 8 commits into
mainfrom
ruthwik/feat/vqa-generation-1
Open

feat(evals): add deterministic VQA dataset generation#3488
ruthwikdasyam wants to merge 8 commits into
mainfrom
ruthwik/feat/vqa-generation-1

Conversation

@ruthwikdasyam

@ruthwikdasyam ruthwikdasyam commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Contribution path

Closes DIM-1418

Problem

DimOS needs reproducible visual-question datasets generated from recorded camera frames.

Solution

Add dimos evals vqa generate and dimos evals vqa run workflows for deterministic multiple-choice VQA datasets. Questions are constrained by family,
answers come from private Moondream evidence, and
outputs include lossless PNG assets plus audit metadata.

Added 3 deterministic families - presence, horizontal_detection, object_count -> which are pre-built using primitive methods (moondream here) - so can just call the method to get the solution - making it deterministic.

How to Test

dimos evals vqa generate go2_short.db --image-index 100

dimos evals vqa run ~/.local/state/dimos/datasets/vqa/go2_short-frames --model gpt-4o-mini

uv run --no-sync pytest dimos/evals/vqa

AI assistance

OpenCode with GPT-5.6 Sol assisted with implementation, tests, documentation, and review.

Checklist

  • I have read and approved the CLA.

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.44193% with 70 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/evals/vqa/generate.py 80.00% 29 Missing and 5 partials ⚠️
dimos/evals/vqa/suite.py 67.53% 16 Missing and 9 partials ⚠️
dimos/evals/vqa/families.py 91.54% 3 Missing and 3 partials ⚠️
dimos/evals/vqa/author.py 91.30% 1 Missing and 1 partial ⚠️
dimos/evals/vqa/primitives/moondream.py 71.42% 2 Missing ⚠️
dimos/evals/vqa/cli.py 96.00% 0 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #3488      +/-   ##
==========================================
+ Coverage   74.05%   76.14%   +2.08%     
==========================================
  Files        1283     1234      -49     
  Lines      124704   119584    -5120     
  Branches    11141    10734     -407     
==========================================
- Hits        92349    91054    -1295     
+ Misses      29493    25431    -4062     
- Partials     2862     3099     +237     
Flag Coverage Δ
OS-ubuntu-24.04-arm 70.57% <89.44%> (+0.14%) ⬆️
OS-ubuntu-latest 72.35% <89.44%> (+0.12%) ⬆️
Py-3.10 72.34% <89.44%> (+0.12%) ⬆️
Py-3.11 72.34% <89.44%> (+0.13%) ⬆️
Py-3.12 72.34% <89.44%> (+0.13%) ⬆️
Py-3.13 72.34% <89.44%> (+0.12%) ⬆️
Py-3.14 72.34% <89.44%> (+0.12%) ⬆️
Py-3.14t 72.33% <89.44%> (+0.11%) ⬆️
SelfHosted-Large 29.78% <33.18%> (+0.07%) ⬆️
SelfHosted-Linux 35.81% <33.18%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/evals/cli.py 38.46% <100.00%> (+5.12%) ⬆️
dimos/evals/vqa/test_cli.py 100.00% <100.00%> (ø)
dimos/evals/vqa/test_contracts.py 100.00% <100.00%> (ø)
dimos/models/vl/base.py 49.55% <100.00%> (ø)
dimos/evals/vqa/cli.py 96.00% <96.00%> (ø)
dimos/evals/vqa/author.py 91.30% <91.30%> (ø)
dimos/evals/vqa/primitives/moondream.py 71.42% <71.42%> (ø)
dimos/evals/vqa/families.py 91.54% <91.54%> (ø)
dimos/evals/vqa/suite.py 67.53% <67.53%> (ø)
dimos/evals/vqa/generate.py 80.00% <80.00%> (ø)

... and 87 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mintlify

mintlify Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Aug 17, 2026, 5:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds standalone VQA dataset generation and evaluation commands with constrained question families, detector-derived labels, frame assets, and audit metadata. It also registers the CLI commands and corrects OpenAI vision data URLs to identify JPEG-encoded images.

The exercised lifecycle failure hypotheses were disproved: generation successfully replaced an existing empty output directory; generated public cases and private labels loaded and evaluated through the shared runner with a score of 1.0; and an injected failure on a later frame did not publish a partial dataset.

No actionable defects were found. The change is safe to merge.

Confidence Score: 5/5

The generated VQA artifact lifecycle and evaluation handoff behaved correctly in the exercised scenarios.

A deterministic harness generated a dataset into an existing empty directory, loaded its cases and labels into the shared evaluator, obtained a successful result, and confirmed that a later-frame failure left no partial output.

Files Needing Attention: No files need follow-up based on the exercised VQA generation, artifact loading, and evaluation paths.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the standalone VQA flow validation script in the prepared environment and observed a 1.0 score from the shared evaluator after loading matching public cases and private labels.
  • Injected a detector exception on the second frame and confirmed the output directory remained empty, indicating no partial artifacts were published.
  • Verified the exact harness script trex-artifacts/vqa-standalone-flow-check.py is executable and deterministic, designed to exercise empty-directory replacement, artifact loading, and partial-write handling.
  • Confirmed the command output log trex-artifacts/vqa-standalone-flow-check-output.log shows a successful run with generated artifacts, matched public/private IDs, a 1.0 result, and an empty output directory after the induced failure.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(evals): align VQA image encoding" | Re-trigger Greptile

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Aug 19, 2026
Comment thread dimos/models/vl/openai.py
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{img_base64}"},
"image_url": {"url": f"data:image/jpeg;base64,{img_base64}"},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why jpeg?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to_base64 actually encodes as JPEG so it looks like using image/png was a previous bug.

Comment thread dimos/evals/vqa/author.py
"Do not duplicate a family/object pair. Do not answer questions or add fields. "
f"Available families: {json.dumps(family_shapes)}"
)
payload: object = self._model.query_json(image, prompt)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
payload: object = self._model.query_json(image, prompt)
payload = self._model.query_json(image, prompt)

since you enforce list type just below


model_config = ConfigDict(extra="forbid", frozen=True)

family: Literal["presence", "horizontal_direction", "object_count"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread dimos/evals/vqa/suite.py
f"{self.inputs}\nChoices: {json.dumps(self.choices)}\nAnswer with exactly one choice."
)
outputs = rig.ask(context, prompt)
answer = _parse_choice(outputs, self.choices)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should always ask llm to provide structured output (like a json) and validate against them uniformly to avoid these custom cleanup/parsing logic

Comment thread dimos/evals/vqa/suite.py

def evaluate(self, rig: EvalRig) -> EvalResult:
image = Image.from_file(self.image_path)
context = [] if rig.blind else cast("list[dict[str, Any]]", image.agent_encode())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point of doing a vqa with the blind option? basically no information provided?

Comment thread dimos/evals/vqa/suite.py
return suite


def _read_jsonl(path: Path) -> list[Any]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use this which provides lazy jsonl loading. jsonl can be very huge and drain too much memory if you load directly. a lazy iterator would be much safer: https://jsonlines.readthedocs.io/en/latest/

Comment thread dimos/evals/vqa/suite.py
Comment on lines +74 to +82
case_by_id = _unique_by_id(cases, "case")
label_by_id = _unique_by_id(labels, "label")
if case_by_id.keys() != label_by_id.keys():
missing_labels = sorted(case_by_id.keys() - label_by_id.keys())
missing_cases = sorted(label_by_id.keys() - case_by_id.keys())
raise ValueError(
f"VQA case/label IDs do not match: missing_labels={missing_labels}, "
f"missing_cases={missing_cases}"
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole checking seems to imply that there's a 1-1 correspondance between case and label. in that case why not just use 1 jsonl instead of 2?

from dimos.perception.detection.type.detection2d.imageDetections2D import ImageDetections2D


class MoondreamObjectDetector:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use existing mooondrealVLModel any need to wrap it?

Comment thread dimos/evals/vqa/cli.py

import typer

app = typer.Typer(help="Generate and evaluate standalone visual question-answering datasets.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all cli change should be in dimos/cli so we can track everything

Comment on lines +298 to +302
def _write_frame(output: Path, frame: _GeneratedFrame) -> None:
(output / "assets").mkdir(parents=True, exist_ok=True)
frame_audit = output / "audit" / f"frame-{frame.index:06d}"
frame_audit.mkdir(parents=True, exist_ok=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hard coded path generation / making directories. this will work for a git cloned dimos but not for a library installed dimos. Needs to be done properly and save VQA sets to .local/ the same place mem2 does.

Comment thread dimos/models/vl/openai.py
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{self._prepare_image(img)[0].to_base64()}"
"url": f"data:image/jpeg;base64,{self._prepare_image(img)[0].to_base64()}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats this for

Comment thread docs/usage/vqa.md
title: "Visual Question Answering"
---

# Visual Question Answering

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this add the title twice?

Comment thread docs/usage/vqa.md
# Visual Question Answering

The VQA tools generate deterministic questions from recorded images and evaluate them through the
shared DimOS evaluation runner.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All docs use dimOS now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can add a CI check for this lol

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point: #3561

Comment thread docs/usage/vqa.md
Comment on lines +75 to +85

## Architecture

- `author.py` proposes constrained family inputs from an image.
- `families.py` owns question text, choices, and deterministic answer rules.
- `primitives/moondream.py` supplies private object detections.
- `generate.py` loads frames, reuses models, and writes datasets atomically.
- `suite.py` validates generated artifacts and creates shared evaluation cases.

The initial implementation intentionally defers negative-presence policy, retries, and resume
behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Architecture
- `author.py` proposes constrained family inputs from an image.
- `families.py` owns question text, choices, and deterministic answer rules.
- `primitives/moondream.py` supplies private object detections.
- `generate.py` loads frames, reuses models, and writes datasets atomically.
- `suite.py` validates generated artifacts and creates shared evaluation cases.
The initial implementation intentionally defers negative-presence policy, retries, and resume
behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants