diff --git a/milestones/README.md b/milestones/README.md index fbdaef2..99ea9fd 100644 --- a/milestones/README.md +++ b/milestones/README.md @@ -5,11 +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:** **[hint-segment-create-bool.md](hint-segment-create-bool.md)** — -hint `docgen.segment.create` must be a YAML boolean (`"false"` used to -still insert the segment). +**Active:** **[generation-numeric-tunables.md](generation-numeric-tunables.md)** — +narration / scene-generation temperature and context-byte tunables must +be YAML numbers (`true` used to become `1.0` / 1-byte context). **Shipped:** +- **[hint-segment-create-bool.md](hint-segment-create-bool.md)** — + hint `docgen.segment.create` must be a YAML boolean (#116). - **[numeric-config-tunables.md](numeric-config-tunables.md)** — timestamps / compose / manim / validation numeric tunables must be YAML numbers (#115). diff --git a/milestones/generation-numeric-tunables.md b/milestones/generation-numeric-tunables.md new file mode 100644 index 0000000..2bbfd06 --- /dev/null +++ b/milestones/generation-numeric-tunables.md @@ -0,0 +1,50 @@ +# Milestone: LLM generation numeric tunables must be YAML numbers + +**Status:** Active +**PR:** [#117](https://github.com/jmjava/documentation-generator/pull/117) +**Depends on:** `milestones/hint-segment-create-bool.md` (PR #116), +`milestones/numeric-config-tunables.md` (PR #115), +`milestones/generation-model-strings.md` (PR #107) + +## Problem + +`narration-generate` / `scene-spec-generate` wrap generation tunables +in ``float()`` / ``int()``. A YAML **bool** is a subclass of ``int``, +so: + +1. ``narration_from_source.temperature: true`` became temperature + **1.0**. +2. ``max_context_bytes: true`` became a **1-byte** context window. +3. ``max_whisper_words_in_prompt: true`` became **1** (truncate the + word stream). + +A YAML list or string raised ``TypeError`` / ``ValueError`` at generate +time, not ``ConfigError`` at load. + +## Goal + +Fail closed at ``Config.from_yaml``. Present values of: + +- ``narration_from_source.temperature`` / ``max_context_bytes`` +- ``manim_scene_generation.temperature`` / ``max_context_bytes`` +- ``max_whisper_segments_in_prompt`` / ``max_whisper_words_in_prompt`` / + ``max_whisper_segment_text_chars`` + +must be YAML numbers (int or float, not bool). Missing keys keep +defaults. + +## Done when + +- [x] Present tunables must be YAML numbers. +- [x] Tests for bool temperature, list ``max_context_bytes``, quoted + whisper cap, and valid numbers. +- [x] `ruff check src/ tests/` +- [x] `pytest tests/` (677 passed, 1 skipped) +- [x] `docgen benchmark` (no clock change; meets baseline) + +## Out of scope + +- Nested validation numerics (``ocr.sample_interval_sec``, …). +- Per-segment ``temperature`` keys that generation settings do not + currently read. +- Coercing numeric strings into numbers. diff --git a/milestones/hint-segment-create-bool.md b/milestones/hint-segment-create-bool.md index 92f1a2d..f220191 100644 --- a/milestones/hint-segment-create-bool.md +++ b/milestones/hint-segment-create-bool.md @@ -1,6 +1,6 @@ # Milestone: hint segment create must be a YAML boolean -**Status:** Active +**Status:** Shipped **PR:** [#116](https://github.com/jmjava/documentation-generator/pull/116) **Depends on:** `milestones/numeric-config-tunables.md` (PR #115), `milestones/hint-front-matter-yaml.md` (PR #102), diff --git a/src/docgen/config.py b/src/docgen/config.py index a5749ce..7f064ba 100644 --- a/src/docgen/config.py +++ b/src/docgen/config.py @@ -485,6 +485,18 @@ def __post_init__(self) -> None: label="narration_from_source.system_prompt", source=src, ) + if nfs.get("temperature") is not None: + require_yaml_number( + nfs["temperature"], + label="narration_from_source.temperature", + source=src, + ) + if nfs.get("max_context_bytes") is not None: + require_yaml_number( + nfs["max_context_bytes"], + label="narration_from_source.max_context_bytes", + source=src, + ) msg = self._block("manim_scene_generation") if msg.get("model") is not None: require_yaml_string( @@ -502,6 +514,29 @@ def __post_init__(self) -> None: label="manim_scene_generation.scene_spec_system_prompt", source=src, ) + if msg.get("temperature") is not None: + require_yaml_number( + msg["temperature"], + label="manim_scene_generation.temperature", + source=src, + ) + if msg.get("max_context_bytes") is not None: + require_yaml_number( + msg["max_context_bytes"], + label="manim_scene_generation.max_context_bytes", + source=src, + ) + for wkey in ( + "max_whisper_segments_in_prompt", + "max_whisper_words_in_prompt", + "max_whisper_segment_text_chars", + ): + if msg.get(wkey) is not None: + require_yaml_number( + msg[wkey], + label=f"manim_scene_generation.{wkey}", + source=src, + ) if self.raw.get("env_file") is not None: require_yaml_string(self.raw["env_file"], label="env_file", source=src) if self.raw.get("repo_root") is not None: diff --git a/tests/test_config.py b/tests/test_config.py index 19ba33b..5b2a8ff 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -888,3 +888,63 @@ def test_from_yaml_numeric_tunables_allowed(tmp_path: Path) -> None: assert c.ffmpeg_timeout_sec == 120 assert c.max_drift_sec == 3.0 assert c.max_freeze_ratio == 0.4 + + +def test_from_yaml_bool_nfs_temperature_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("narration_from_source:\n temperature: true\n", encoding="utf-8") + with pytest.raises( + ConfigError, match="narration_from_source.temperature must be a YAML number" + ): + Config.from_yaml(p) + + +def test_from_yaml_list_nfs_max_context_bytes_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text( + "narration_from_source:\n max_context_bytes:\n - 120000\n", + encoding="utf-8", + ) + with pytest.raises( + ConfigError, + match="narration_from_source.max_context_bytes must be a YAML number", + ): + Config.from_yaml(p) + + +def test_from_yaml_bool_msg_temperature_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("manim_scene_generation:\n temperature: true\n", encoding="utf-8") + with pytest.raises( + ConfigError, match="manim_scene_generation.temperature must be a YAML number" + ): + Config.from_yaml(p) + + +def test_from_yaml_string_max_whisper_words_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text( + 'manim_scene_generation:\n max_whisper_words_in_prompt: "0"\n', + encoding="utf-8", + ) + with pytest.raises( + ConfigError, + match="manim_scene_generation.max_whisper_words_in_prompt must be a YAML number", + ): + Config.from_yaml(p) + + +def test_from_yaml_generation_numeric_tunables_allowed(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text( + "narration_from_source:\n temperature: 0.5\n max_context_bytes: 90000\n" + "manim_scene_generation:\n temperature: 0.4\n max_context_bytes: 80000\n" + " max_whisper_segments_in_prompt: 0\n max_whisper_words_in_prompt: 12\n" + " max_whisper_segment_text_chars: 200\n", + encoding="utf-8", + ) + c = Config.from_yaml(p) + assert c.raw["narration_from_source"]["temperature"] == 0.5 + assert c.raw["narration_from_source"]["max_context_bytes"] == 90000 + assert c.raw["manim_scene_generation"]["temperature"] == 0.4 + assert c.raw["manim_scene_generation"]["max_whisper_words_in_prompt"] == 12