Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions milestones/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ repositories that install `docgen` and maintain their own demo bundle. The
library no longer ships an in-repo dogfood; consumers are the integration test
of record.

**Active:** **[image-empty-bytes.md](image-empty-bytes.md)** —
`image-generate` must not write empty PNG bytes as success.
**Active:** **[image-generation-strings.md](image-generation-strings.md)** —
`image_generation.model` / `size` / `quality` must be YAML strings at
config load.

**Shipped:**
- **[image-empty-bytes.md](image-empty-bytes.md)** —
`image-generate` must not write empty PNG bytes as success (#103).
- **[hint-front-matter-yaml.md](hint-front-matter-yaml.md)** —
Invalid `hints/*.md` YAML front matter must fail `yaml-generate`, not skip
hint `visual_map` wiring (#102).
Expand Down
4 changes: 2 additions & 2 deletions milestones/image-empty-bytes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Milestone: image-generate must not write empty assets

**Status:** Active
**PR:** pending
**Status:** Shipped
**PR:** #103
**Depends on:** `milestones/hint-front-matter-yaml.md` (PR #102),
`milestones/tts-empty-audio.md` (PR #99)

Expand Down
35 changes: 35 additions & 0 deletions milestones/image-generation-strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Milestone: image_generation.model / size / quality must be strings

**Status:** Active
**PR:** pending
**Depends on:** `milestones/image-empty-bytes.md` (PR #103),
`milestones/tts-empty-audio.md` (PR #99)

## Problem

`tts.model` / `tts.voice` are already non-empty YAML strings at config
load. `image_generation.model` / `size` / `quality` were not.

`image_generation_config` does `defaults.update(block)`. A YAML **list**
for `model:` (bullet list of model names) overwrote the default string.
`generate_images_for_spec` then did `str(icfg.get("model"))`, sending
`"['gpt-image-1']"` to the Images API.

## Goal

Fail closed at `Config.from_yaml`. Missing keys still use the property
defaults (`gpt-image-1`, `1536x1024`, no quality).

## Done when

- [x] Present `image_generation.model` / `size` / `quality` must be
non-empty YAML strings.
- [x] Tests for list values of those keys.
- [x] `ruff check src/ tests/`
- [x] `pytest tests/`
- [x] `docgen benchmark` (no clock change)

## Out of scope

- Empty mapping `image_generation: {}` remains allowed (defaults apply).
- `wizard.default_guidance` type gating is separate.
7 changes: 7 additions & 0 deletions src/docgen/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ def __post_init__(self) -> None:
)
if wiz.get("llm_model") is not None:
require_yaml_string(wiz["llm_model"], label="wizard.llm_model", source=src)
ig = self._block("image_generation")
if ig.get("model") is not None:
require_yaml_string(ig["model"], label="image_generation.model", source=src)
if ig.get("size") is not None:
require_yaml_string(ig["size"], label="image_generation.size", source=src)
if ig.get("quality") is not None:
require_yaml_string(ig["quality"], label="image_generation.quality", source=src)
ocr = self._sub_block(validation, "ocr", label="validation.ocr")
if ocr.get("error_patterns") is not None:
string_list_block(
Expand Down
21 changes: 21 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,24 @@ def test_from_yaml_mapping_av_sync_anchor_keywords_loads(tmp_path: Path) -> None
cfg = Config.from_yaml(p)
rows = cfg.av_sync_config["anchor_keywords"]["01"]
assert rows[0]["keyword"] == "Flask"


def test_from_yaml_list_image_generation_model_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text("image_generation:\n model:\n - gpt-image-1\n", encoding="utf-8")
with pytest.raises(ConfigError, match="image_generation.model must be a YAML string"):
Config.from_yaml(p)


def test_from_yaml_list_image_generation_size_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text("image_generation:\n size:\n - 1536x1024\n", encoding="utf-8")
with pytest.raises(ConfigError, match="image_generation.size must be a YAML string"):
Config.from_yaml(p)


def test_from_yaml_list_image_generation_quality_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text("image_generation:\n quality:\n - high\n", encoding="utf-8")
with pytest.raises(ConfigError, match="image_generation.quality must be a YAML string"):
Config.from_yaml(p)