-
Notifications
You must be signed in to change notification settings - Fork 791
feat(evals): add deterministic VQA dataset generation #3488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
+1,734
−4
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b0ca0da
feat(evals): add single-frame VQA generation
ruthwikdasyam 591778a
feat(evals): expand deterministic VQA generation
ruthwikdasyam f7445cf
feat(evals): generate multi-frame VQA datasets
ruthwikdasyam 104ce86
feat(evals): add object-count VQA family
ruthwikdasyam 11b4e45
fix(evals): restore default VQA output path
ruthwikdasyam 0c86ebb
docs(evals): consolidate VQA guide
ruthwikdasyam c6f09f4
fix(evals): harden VQA dataset generation
ruthwikdasyam 9079838
fix(evals): align VQA image encoding
ruthwikdasyam 673ac4f
fix(evals): improve VQA dataset handling
ruthwikdasyam ce9b032
Merge remote-tracking branch 'origin/main' into ruthwik/feat/vqa-gene…
ruthwikdasyam 3b41a03
refactor(cli): centralize VQA commands
ruthwikdasyam b8acb10
feat(evals): use structured VQA responses
ruthwikdasyam 27ec44c
Merge remote-tracking branch 'origin/main' into ruthwik/feat/vqa-gene…
ruthwikdasyam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # Copyright 2026 Dimensional Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| from click import unstyle | ||
| import pytest | ||
| from typer.testing import CliRunner | ||
|
|
||
| from dimos.cli.dimos import main as app | ||
| from dimos.evals import runner as runner_module | ||
| from dimos.evals.types import EvalResult | ||
| from dimos.evals.vqa import generate as generate_module, suite as suite_module | ||
| from dimos.evals.vqa.generate import GenerationRequest, GenerationResult, PublicCase | ||
|
|
||
|
|
||
| def test_vqa_cli_exposes_generate_and_run() -> None: | ||
| result = CliRunner().invoke(app, ["evals", "vqa", "--help"]) | ||
| output = unstyle(result.stdout) | ||
|
|
||
| assert result.exit_code == 0 | ||
| assert "generate" in output | ||
| assert "run" in output | ||
|
|
||
|
|
||
| def test_vqa_generate_cli_declares_single_image_input() -> None: | ||
| result = CliRunner().invoke(app, ["evals", "vqa", "generate", "--help"]) | ||
| output = unstyle(result.stdout) | ||
|
|
||
| assert result.exit_code == 0 | ||
| assert "DATASET" in output | ||
| assert "--image-index" in output | ||
| assert "--start" in output | ||
| assert "--stop" in output | ||
| assert "--stride" in output | ||
| assert "--output" in output | ||
|
|
||
|
|
||
| def test_vqa_run_cli_declares_standalone_dataset_input() -> None: | ||
| result = CliRunner().invoke(app, ["evals", "vqa", "run", "--help"]) | ||
| output = unstyle(result.stdout) | ||
|
|
||
| assert result.exit_code == 0 | ||
| assert "DATASET" in output | ||
| assert "--model" in output | ||
|
|
||
|
|
||
| def test_vqa_generate_cli_runs_generation(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: | ||
| seen: list[GenerationRequest] = [] | ||
|
|
||
| def fake_generate(request: GenerationRequest) -> GenerationResult: | ||
| seen.append(request) | ||
| return GenerationResult( | ||
| output=request.output_directory(), | ||
| cases=( | ||
| PublicCase( | ||
| id="q", | ||
| image="assets/frame.jpg", | ||
| question="Is there a chair?", | ||
| choices=("yes", "no"), | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
| monkeypatch.setattr(generate_module, "generate_dataset", fake_generate) | ||
|
|
||
| result = CliRunner().invoke( | ||
| app, | ||
| [ | ||
| "evals", | ||
| "vqa", | ||
| "generate", | ||
| "recording.db", | ||
| "--image-index", | ||
| "3", | ||
| "--output", | ||
| str(tmp_path), | ||
| ], | ||
| ) | ||
|
|
||
| assert result.exit_code == 0 | ||
| assert seen == [GenerationRequest(dataset="recording.db", image_index=3, output=tmp_path)] | ||
| assert "Generated 1 VQA case" in result.stdout | ||
|
|
||
|
|
||
| def test_vqa_generate_cli_accepts_frame_range( | ||
| monkeypatch: pytest.MonkeyPatch, tmp_path: Path | ||
| ) -> None: | ||
| seen: list[GenerationRequest] = [] | ||
|
|
||
| def fake_generate(request: GenerationRequest) -> GenerationResult: | ||
| seen.append(request) | ||
| return GenerationResult(output=request.output_directory(), cases=()) | ||
|
|
||
| monkeypatch.setattr(generate_module, "generate_dataset", fake_generate) | ||
|
|
||
| result = CliRunner().invoke( | ||
| app, | ||
| [ | ||
| "evals", | ||
| "vqa", | ||
| "generate", | ||
| "recording.db", | ||
| "--start", | ||
| "2", | ||
| "--stop", | ||
| "9", | ||
| "--stride", | ||
| "3", | ||
| "--output", | ||
| str(tmp_path), | ||
| ], | ||
| ) | ||
|
|
||
| assert result.exit_code == 0 | ||
| assert seen == [ | ||
| GenerationRequest( | ||
| dataset="recording.db", | ||
| start=2, | ||
| stop=9, | ||
| stride=3, | ||
| output=tmp_path, | ||
| ) | ||
| ] | ||
|
|
||
|
|
||
| def test_vqa_run_cli_runs_shared_evaluator(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: | ||
| class FakeRunner: | ||
| def __init__(self, **kwargs: object) -> None: | ||
| assert kwargs == {"model": "test-model"} | ||
| self.run_dir = tmp_path / "results" | ||
|
|
||
| def run(self, cases: object) -> list[EvalResult]: | ||
| assert cases == ("case",) | ||
| return [EvalResult(case_id="q", outputs="yes", score=1.0, passed=True)] | ||
|
|
||
| monkeypatch.setattr(suite_module, "load_suite", lambda dataset: ("case",)) | ||
| monkeypatch.setattr(runner_module, "EvalRunner", FakeRunner) | ||
|
|
||
| result = CliRunner().invoke( | ||
| app, | ||
| ["evals", "vqa", "run", str(tmp_path), "--model", "test-model"], | ||
| ) | ||
|
|
||
| assert result.exit_code == 0 | ||
| assert "PASS" in result.stdout | ||
| assert "mean 1.00" in result.stdout |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Copyright 2026 Dimensional Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """CLI commands for generating and evaluating standalone VQA datasets.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| import typer | ||
|
|
||
| app = typer.Typer(help="Generate and evaluate standalone visual question-answering datasets.") | ||
|
|
||
|
|
||
| @app.command("generate") | ||
| def generate( | ||
| dataset: str = typer.Argument(help="Memory dataset name or .db/.mcap path"), | ||
| image_index: int | None = typer.Option(None, min=0, help="Process one color_image index"), | ||
| start: int | None = typer.Option(None, min=0, help="First color_image index in range mode"), | ||
| stop: int | None = typer.Option(None, min=1, help="Exclusive color_image stop index"), | ||
| stride: int | None = typer.Option(None, min=1, help="Frame stride in range mode"), | ||
| output: Path | None = typer.Option(None, help="Override the generated dataset directory"), | ||
| ) -> None: | ||
| """Generate questions for one image or an indexed image range.""" | ||
| # Keep generation's optional model stack out of global CLI startup. | ||
| from dimos.evals.vqa.generate import GenerationRequest, generate_dataset | ||
|
|
||
| request = GenerationRequest( | ||
| dataset=dataset, | ||
| output=output, | ||
| image_index=image_index, | ||
| start=start, | ||
| stop=stop, | ||
| stride=stride, | ||
| ) | ||
| result = generate_dataset(request) | ||
| typer.echo(f"Generated {len(result.cases)} VQA case(s) in {result.output}") | ||
|
|
||
|
|
||
| @app.command("run") | ||
| def run( | ||
| dataset: Path = typer.Argument(help="Generated standalone VQA dataset"), | ||
| model: str = typer.Option("", help="Override chat model"), | ||
| ) -> None: | ||
| """Evaluate a generated standalone VQA dataset.""" | ||
| # Keep evaluation implementation imports out of global CLI startup. | ||
| from dimos.evals.runner import EvalRunner, summarize | ||
| from dimos.evals.vqa.suite import load_suite | ||
|
|
||
| overrides: dict[str, object] = {} | ||
| if model: | ||
| overrides["model"] = model | ||
| runner = EvalRunner(**overrides) | ||
| results = runner.run(load_suite(dataset)) | ||
| for result in results: | ||
| status = "ERROR" if result.error else ("PASS" if result.passed else "fail") | ||
| detail = result.error or f"answer={result.outputs[:60]!r}" | ||
| typer.echo(f"{status:5} {result.case_id:30} {detail}") | ||
| summary = summarize(results) | ||
| typer.echo( | ||
| f"\n{summary.n} cases | mean {summary.mean_score:.2f} | " | ||
| f"pass {summary.pass_rate:.0%} | errors {summary.errors} | {runner.run_dir}" | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, moved to dimos/cli/vqa.py