Commit 7289cfe
authored
* fix(compiler): enable JSON mode + harden plan handling (closes #71)
Concept generation can fail in two ways when the LLM emits malformed
JSON for the concepts plan: (a) the parser raises and the function
returns with zero concepts written, (b) a structurally-wrong shape
(nested list, bare strings) slips past the list fallback and crashes
each per-concept task at `concept.get("title")`. Both paths previously
exited via `[OK]`, leaving users to discover an empty `wiki/concepts/`
on their own.
- Pass `response_format={"type": "json_object"}` on the four LLM calls
whose prompt requests a JSON object (summary, plan, concept create,
concept update). Constrains the decoder so providers that support
json mode — OpenAI, DeepSeek, Qwen, Kimi, GLM, MiniMax, Doubao —
can no longer return prose. The existing "Return ONLY valid JSON"
prompt language already satisfies the DeepSeek/Qwen "must mention
json" requirement.
- Add `_filter_concept_items` to drop non-dict entries from the plan
before they reach `_gen_create` / `_gen_update`. Logs the dropped
count and the offending types so the cause is diagnosable.
- Include the first 500 chars of `plan_raw` in the parse-failure
WARNING (was DEBUG-only). Print a stdout `[WARN]` line on both
"plan unparseable" and "planned N, wrote K" so a silent regression
no longer masquerades as `[OK]`.
* fix(compiler): close two crash paths from #71 review
Code review on PR #75 surfaced two function-aborting bugs that the
original defenses did not cover. Both shift the same silent-loss
class one step upstream — `_compile_concepts` raises before any
concept task runs, the v1 summary is never written on the short-doc
path, and the new `[WARN] planned vs written` line never fires.
- `_filter_concept_items` also requires a non-empty string `name`.
Without this, dicts that omit the `name` key (JSON mode constrains
syntax, not schema) reach the `planned_slugs` set comprehension at
line 1014 and raise `KeyError: 'name'`.
- New `_filter_related_slugs` mirrors the same guard for the
`related` list, dropping non-strings. The previous code passed
`parsed.get("related", [])` straight into `_sanitize_concept_name`,
which calls `unicodedata.normalize("NFKC", name)` and raises
`TypeError` on any non-`str` entry.
Verified end-to-end against the original screenwriter EPUB on
deepseek-v4-flash (no regression) and via direct unit-style calls
that feed every mishape into both helpers and observe the expected
drops + WARN messages.
* fix(compiler): address remaining #75 review findings
Three observable-failure-mode improvements from the PR #75 review.
The fourth finding (%r expanding Chinese 3-6×) was refuted — Python 3
repr() preserves printable Unicode 1:1, only control chars are escaped.
- Detect ``finish_reason == "length"`` in ``_llm_call`` /
``_llm_call_async`` and emit both a logger.warning and a stdout
[WARN] line. Truncation was previously silent: ``json_repair`` would
salvage the prefix and parsing succeeded with a smaller-than-intended
plan, with no signal anything was cut off.
- Bump the concepts-plan ``max_tokens`` from 1024 to 2048. ``json_object``
mode adds quoting/escaping overhead, and the cap was tight even for
modest plans; the truncation detector above guards against the 2048
case too.
- Re-add a full DEBUG log of the raw plan on parse failure. The earlier
diff replaced ``logger.debug("Raw: %s", plan_raw)`` with a 500-char
preview embedded in the WARNING, which strictly reduced what DEBUG
users could recover when the actual bad JSON lived past char 500.
Now both: 500-char preview at WARNING, full payload at DEBUG.
- Change "check warnings above" to "see log (stderr)" and include the
failing exception type names inline in the partial-failure [WARN]
line. ``logger.warning`` lands on stderr (Python default); users
capturing only stdout previously got a WARN that pointed at logs
they couldn't see — now the stdout line is self-contained.
Verified end-to-end against the screenwriter EPUB on
deepseek-v4-flash (no regression). Unit-tested ``_warn_if_truncated``
across stop/length/malformed-response inputs.
* fix(compiler): close three silent-loss paths surfaced by max-effort review
The previous round of #75 added a partial-failure [WARN] line but missed
three paths where concept generation still silently produces zero or
broken pages while [OK] prints normally:
- ``_filter_concept_items`` could strip an LLM plan down to nothing
(e.g. ``{"create":[{"foo":"bar"}, "x"]}`` — both rejected). The
early-return at ``if not create_items and not update_items and not
related_items`` then fired with no stdout signal, indistinguishable
from "LLM legitimately had nothing to add". Now compares pre-filter
vs post-filter totals and emits a ``[WARN]`` when filtering wiped a
non-empty plan.
- ``parsed.get("content", raw)`` only used the default when the
``"content"`` key was absent; under ``response_format=json_object``
the LLM can legally return ``{"content": null}`` (refusal /
content-policy hit). ``None`` then propagated into ``pending_writes``
and crashed ``strip_ghost_wikilinks(None, ...)`` mid-batch. Switched
to ``parsed.get("content") or raw`` so null/empty collapses to the
raw fallback.
- An empty or whitespace-only ``content`` sailed through ``pending_writes``
as a successful concept and got committed as a blank Markdown page
with no signal. New ``_require_nonempty_content`` raises ``ValueError``
after parsing/fallback; the gather loop's existing ``failure_types``
collector catches it and the partial-failure ``[WARN]`` surfaces it.
Verified ``_require_nonempty_content`` across normal / null / empty /
whitespace / non-str inputs. Existing tests (70) pass with no regression.
* style(compiler): trim narrative comments from #75 series
Strip multi-paragraph docstrings down to one-line summaries, remove
inline comments that re-stated what the code does, and drop all
issue/PR cross-references — those belong in commit messages and PR
descriptions, not in code that has to age.
Kept the WHYs a cold reader actually needs: the DeepSeek/Qwen prompt
requirement on _JSON_RESPONSE_FORMAT, and the .get("content") or raw
note about JSON-null distinction.
1 parent 5e81196 commit 7289cfe
1 file changed
Lines changed: 129 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
40 | 44 | | |
41 | 45 | | |
42 | 46 | | |
| |||
258 | 262 | | |
259 | 263 | | |
260 | 264 | | |
| 265 | + | |
261 | 266 | | |
262 | 267 | | |
263 | 268 | | |
| |||
274 | 279 | | |
275 | 280 | | |
276 | 281 | | |
| 282 | + | |
277 | 283 | | |
278 | 284 | | |
279 | 285 | | |
| |||
282 | 288 | | |
283 | 289 | | |
284 | 290 | | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
285 | 310 | | |
286 | 311 | | |
287 | 312 | | |
| |||
297 | 322 | | |
298 | 323 | | |
299 | 324 | | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
300 | 368 | | |
301 | 369 | | |
302 | 370 | | |
| |||
911 | 979 | | |
912 | 980 | | |
913 | 981 | | |
914 | | - | |
| 982 | + | |
915 | 983 | | |
916 | 984 | | |
917 | 985 | | |
| |||
935 | 1003 | | |
936 | 1004 | | |
937 | 1005 | | |
938 | | - | |
939 | | - | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
940 | 1018 | | |
941 | 1019 | | |
942 | 1020 | | |
943 | 1021 | | |
944 | 1022 | | |
945 | | - | |
| 1023 | + | |
946 | 1024 | | |
947 | | - | |
| 1025 | + | |
| 1026 | + | |
948 | 1027 | | |
949 | 1028 | | |
950 | | - | |
951 | | - | |
952 | | - | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
953 | 1032 | | |
954 | 1033 | | |
955 | 1034 | | |
956 | 1035 | | |
957 | 1036 | | |
958 | 1037 | | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
959 | 1054 | | |
960 | 1055 | | |
961 | 1056 | | |
| |||
1010 | 1105 | | |
1011 | 1106 | | |
1012 | 1107 | | |
1013 | | - | |
| 1108 | + | |
1014 | 1109 | | |
1015 | 1110 | | |
1016 | 1111 | | |
1017 | | - | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
1018 | 1115 | | |
1019 | 1116 | | |
| 1117 | + | |
1020 | 1118 | | |
1021 | 1119 | | |
1022 | 1120 | | |
| |||
1042 | 1140 | | |
1043 | 1141 | | |
1044 | 1142 | | |
1045 | | - | |
| 1143 | + | |
1046 | 1144 | | |
1047 | 1145 | | |
1048 | 1146 | | |
1049 | | - | |
| 1147 | + | |
1050 | 1148 | | |
1051 | 1149 | | |
| 1150 | + | |
1052 | 1151 | | |
1053 | 1152 | | |
1054 | 1153 | | |
| |||
1066 | 1165 | | |
1067 | 1166 | | |
1068 | 1167 | | |
| 1168 | + | |
1069 | 1169 | | |
1070 | 1170 | | |
1071 | 1171 | | |
| 1172 | + | |
1072 | 1173 | | |
1073 | 1174 | | |
1074 | 1175 | | |
| |||
1077 | 1178 | | |
1078 | 1179 | | |
1079 | 1180 | | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
1080 | 1195 | | |
1081 | 1196 | | |
1082 | 1197 | | |
| |||
1212 | 1327 | | |
1213 | 1328 | | |
1214 | 1329 | | |
1215 | | - | |
| 1330 | + | |
| 1331 | + | |
1216 | 1332 | | |
1217 | 1333 | | |
1218 | 1334 | | |
| |||
0 commit comments