Tell the drafter to keep each id to its own English, and the critic to check

The drift in the production Chinese is the drafter's, not the aligner's: the
drafter re-flows the passage across paragraph ids wherever Pass 0 split the
original mid-sentence, and the alignment pass cannot see it by construction.
So the prevention belongs in the prompts.

The drafter is now told, explicitly, that each id's translation may cover
only that id's own English - even where the source is cut mid-sentence and
the Chinese therefore reads as a fragment - and that it must not move a
neighbour's content into this id or this id's content into the next. The
instruction gives the concrete case the model actually does: "The chair in
the middle has" followed by "\"Captain\"".

The critic is now asked to check the id-to-text correspondence, with a new
issue type "misalignment" and a block severity. It cannot repair the draft
- the pipeline records critic issues rather than revising the translation -
but it makes the drift visible in the run record instead of silent.

4 new tests, 63 total, ruff clean.
This commit is contained in:
Umbra Tools
2026-11-06 21:35:32 -08:00
parent 11fd8d8060
commit 54336535e3
2 changed files with 64 additions and 2 deletions

View File

@@ -245,6 +245,10 @@ def draft_prompt(
规则:
- 段落 id 必须与原文一一对应,数量和顺序不变。
- 每个 id 只输出一段中文;不要把两段合并成一个 id。
- **每个 id 的译文只能覆盖该 id 自己的英文。** 原文常在句子中间被切成多段
(例如一段是"The chair in the middle has",下一段是'"Captain"'),译文也必须
照这个切分来,哪怕某一段的中文读起来不完整。绝对不要把相邻段落的内容提前
写进前一个 id,也不要把本段的内容挪到下一个 id。宁可让某段译文是半句。
- 只输出译文。绝对不要把段落 id、说话人、类型、括号注释写进译文。
- 不要输出英文原文,不要输出解释。
- 不要把英文单词留在译文里(术语表允许保留的专名和书名除外);每个英文实词都必须译成中文。
@@ -296,7 +300,7 @@ def critic_prompt(
{{
"id": "段落id",
"severity": "block|major|minor",
"type": "meaning|pronoun|irony|term|omission|addition|register",
"type": "meaning|pronoun|irony|term|omission|addition|register|misalignment",
"problem": "中文说明问题",
"source_excerpt": "英文片段(不超过30词)",
"target_excerpt": "中文片段(不超过40字)",
@@ -306,10 +310,17 @@ def critic_prompt(
}}
严重度标准:
- block:事实被改变、整句遗漏或增加、说话人/指代错、关键术语错。
- block:事实被改变、整句遗漏或增加、说话人/指代错、关键术语错、段落错位。
- major:读者会明显理解错、反讽或人物声音被抹平、重要细节丢失。
- minor:细微语气或可选润色;不要为了凑数报风格偏好。
必须检查"段落 id 与译文是否对应"(类型 misalignment,severity 为 block):
原文在句子中间被切成多段时(相邻两段常常都不是完整句子),译文很容易跟着
自然语感重新分段,结果就是某一段的译文讲的是相邻那一段的内容。逐段确认:
这一段的译文讲的是这一段的英文吗?如果某段的译文其实对应前一段或后一段的
原文,报 misalignment,source_excerpt 取本段英文,target_excerpt 取错位的那句
译文。宁可多报,也不要让错位的译文通过审校。
id 必须使用输入中的段落 id(例如 ch-6-p0000),不要用句子序号。
键名必须与上面的 JSON 完全一致,不要自创 english_sentence_part 之类的字段。
没有问题时 issues 为空数组,verdict 为 pass。

View File

@@ -19,9 +19,60 @@ sys.path.insert(0, str(HERE.parent))
from llm import parse_json # noqa: E402
from pipeline import latin_residue, sanitize # noqa: E402
from prompts import critic_prompt, draft_prompt # noqa: E402
from qa import check_chapter # noqa: E402
class PromptInstructionTests(unittest.TestCase):
"""The drafter and the critic must both know about paragraph drift.
Measured on the real Chinese edition: the drafter re-flows the passage
across paragraph ids wherever Pass 0 split the original mid-sentence, and
the alignment pass cannot see it - a one-to-one paragraph is a pure copy,
so there is nothing to divide and nothing to disagree. The prevention is
an instruction to the drafter; the detection is the alignment pass's
``--verify`` stage.
"""
def _draft(self) -> str:
return draft_prompt(
chapter_title="Salvage",
scene_index=0,
chunk_index=0,
chunks_in_scene=1,
paragraphs=[{"id": "ch-6-p0000", "speaker": "narrator", "text": "The chair has"}],
analysis={"summary_zh": "摘要"},
previous_summary="",
glossary={},
)
def _critic(self) -> str:
return critic_prompt(
chapter_title="Salvage",
paragraphs=[{"id": "ch-6-p0000", "speaker": "narrator", "text": "The chair has"}],
draft={"ch-6-p0000": "中间的椅子旁"},
glossary={},
)
def test_the_drafter_is_told_to_keep_each_id_to_its_own_english(self) -> None:
prompt = self._draft()
self.assertIn("每个 id 的译文只能覆盖该 id 自己的英文", prompt)
def test_the_drafter_is_told_not_to_move_content_to_a_neighbour(self) -> None:
prompt = self._draft()
self.assertIn("不要把相邻段落的内容提前", prompt)
self.assertIn("不要把本段的内容挪到下一个 id", prompt)
def test_the_critic_is_asked_to_check_the_id_to_text_correspondence(self) -> None:
prompt = self._critic()
self.assertIn("misalignment", prompt)
self.assertIn("段落 id 与译文是否对应", prompt)
def test_a_misalignment_is_a_blocking_issue(self) -> None:
prompt = self._critic()
self.assertIn("段落错位", prompt)
class SanitizeTests(unittest.TestCase):
def test_strips_dialogue_label(self) -> None:
self.assertEqual(sanitize("(对话;凯因达)“我在游艇上。”"), "“我在游艇上。”")