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
8 changes: 5 additions & 3 deletions milestones/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
50 changes: 50 additions & 0 deletions milestones/generation-numeric-tunables.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion milestones/hint-segment-create-bool.md
Original file line number Diff line number Diff line change
@@ -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),
Expand Down
35 changes: 35 additions & 0 deletions src/docgen/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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:
Expand Down
60 changes: 60 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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