mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 14:21:58 +00:00
整篇包在 ``markdown 里、中间再套一段一样长的 ``sql 时照样去掉外层围栏:带信息串的一行开、不带的一行收,两两配对。以前一样长的内层围栏一律不去,模型最常见的这种写法仍是 0 句结论,裁判和反方审稿没有句子可审,正式运行照样出具
规则:中间和外层同种符号的围栏行,比外层短的跳过,比外层长的不去(规格原文),一样长的按配对看——带信息串的开一个代码块,不带的收掉它;没有可收的 ``` 说明外层在这里就收了(好几个代码块),开着时又来一个 ```sql、扫到最后还开着的,都不去。去掉外层后 compose_doc 和前端每见到一行 ``` 开合一次,配好的一对正好切成一个代码块,原 docstring「见到 ``` 就收」的理由不成立,改准确了。 一样长的行只认 compose_doc 和前端认得的写法(信息串是紧跟着的一个词):``` sql 这种它们不认,算一行正文。不然它会和后面的 ``` 配成一对、去掉外层,而后面那个 ``` 在它们眼里成了开头,把结论句吞进代码块。比外层长的照旧按行首判,````sql title 这样的也不去。 外层反引号里套 ~~~、四个以上反引号的代码块照常去掉外层:compose_doc 和前端只认 ``` 围栏,这段按正文核对,和模型不包外层、直接这么写时一样,比整篇一个代码块强;docstring 写明,测试钉住。 测试:same-fence-inside 挪到去的一边,补多段内层代码块、~~~ 里套 ~~~、反引号里套 ~~~;不去的补没有可收的 ```、开着时又来 ```python、内层没收、``` sql 开头、更长的带词围栏。compose_doc 一级补 ```markdown 套 ```sql(SQL 仍是代码块,结论句 2 句)和 ~~~ 块按正文核对。端到端(正式运行,裁判 + 反方审稿 + 出具契约)加一组套 ```sql 的写法:两句都判、都审,完整出具。裁判退回改写(rewrite_once)那一稿也包在代码块里时照样去掉:改写稿被采用,第二轮判到改过的那句,以前这条路没有测试守着。每条判断各做过一次去掉、测试变红。
This commit is contained in:
@@ -443,8 +443,11 @@ def _messages(ctx: NodeContext, state: GraphState, writer: WriterCatalog, claims
|
||||
_OPEN_FENCE = re.compile(r"(`{3,}|~{3,})[ \t]*([^\s`]*)[ \t]*")
|
||||
#: 最后一行:行首最多缩进三格(同 CommonMark),缩进四格的是一行缩进代码,不算收尾
|
||||
_CLOSE_FENCE = re.compile(r" {0,3}(`{3,}|~{3,})")
|
||||
#: 正文里像围栏的行:行首空白不讲究,前端和 compose_doc 的切块(evidence._FENCE)就是这样认 ``` 的
|
||||
_INNER_FENCE = re.compile(r"\s*(`{3,}|~{3,})")
|
||||
#: 正文里以围栏开头的行(行首空白不讲究):和外层同种符号、比外层长的,分不清哪一行收尾
|
||||
_FENCE_RUN = re.compile(r"\s*(`{3,}|~{3,})")
|
||||
#: 其中 compose_doc 的切块(evidence._FENCE)和前端 Markdown.tsx 认作围栏的:信息串只能是紧跟着的一个词(字母、数字、
|
||||
#: 下划线)。去掉外层之后正文按它们切,``` sql、```sql title 这样的行它们不认,是一行正文
|
||||
_INNER_FENCE = re.compile(r"\s*(`{3,}|~{3,})([A-Za-z0-9_]*)\s*")
|
||||
#: 信息串只认这几个(大小写不敏感):写成 sql、json 的是模型真要贴一段代码
|
||||
_PROSE_INFO = frozenset({"", "markdown", "md"})
|
||||
|
||||
@@ -454,9 +457,20 @@ def _unfenced(raw: str) -> str:
|
||||
|
||||
不去的话整篇在 compose_doc 里是一个代码块:裸数字照样标出,可一句结论都切不出来,裁判、反方审稿没有句子可审,
|
||||
界面上整篇显示成代码。只在拿得准的时候去:去掉首尾空白后,第一行是围栏、信息串为空或 markdown / md,最后一行
|
||||
是同一道围栏(同种符号、同样长、不带信息串),中间没有哪一行像同种、不比它短的围栏。有这样一行,就是好几个代码块,
|
||||
或者分不清哪一行才是收尾(前端和 compose_doc 的切块见到 ``` 就收);正文自己的代码块要套在里面,外层得用更长的
|
||||
围栏或者换一种符号。sql、json 这类信息串、围栏外还有前言结语的,都不是整篇报告被包了起来,原样交给 compose_doc。
|
||||
是同一道围栏(同种符号、同样长、不带信息串)。sql、json 这类信息串、围栏外还有前言结语的,都不是整篇报告被包了
|
||||
起来,原样交给 compose_doc。
|
||||
|
||||
正文自己的代码块可以套在里面,中间的围栏行这样看(只看和外层同种符号的):
|
||||
- 比外层短:收不掉外层,外层收在哪一行没有疑问,跳过
|
||||
- 比外层长:分不清哪一行收尾,不去
|
||||
- 和外层一样长:模型最常见的写法是 ```markdown 里再套 ```sql … ```。带信息串的一行开一个代码块,不带的一行收掉它,
|
||||
两两配对;去掉外层后,compose_doc 和前端每见到一行 ``` 开合一次,配好的这一对正好切成一个代码块。配不成对的
|
||||
不去:没有代码块可收的 ``` 说明外层在这里就收了,后面是围栏外的字(好几个代码块);开着的代码块里又来一个
|
||||
```sql,compose_doc 和前端会拿它当收尾;扫到最后还开着的,后半篇会被吞进代码块
|
||||
|
||||
换一种符号的(外层反引号、里面 ~~~)也跳过,照常去掉外层。跳过的这些行去掉外层后怎么切,和模型不包外层、直接
|
||||
这么写时一样:compose_doc 和前端只认 ``` 围栏,里面 ~~~、四个以上反引号的代码块按正文切,当一段话核对——比整篇
|
||||
一个代码块强,真正的结论句至少有得审。
|
||||
"""
|
||||
text = (raw or "").replace("\r\n", "\n").replace("\r", "\n").strip()
|
||||
lines = text.split("\n")
|
||||
@@ -465,10 +479,20 @@ def _unfenced(raw: str) -> str:
|
||||
if head is None or tail is None or head.group(2).lower() not in _PROSE_INFO or tail.group(1) != head.group(1):
|
||||
return raw
|
||||
fence = head.group(1)
|
||||
if any((inner := _INNER_FENCE.match(line)) and inner.group(1)[0] == fence[0] and len(inner.group(1)) >= len(fence)
|
||||
for line in lines[1:-1]):
|
||||
return raw
|
||||
return "\n".join(lines[1:-1])
|
||||
nested = False # 正文自己的、和外层一样长的代码块开着
|
||||
for line in lines[1:-1]:
|
||||
run = _FENCE_RUN.match(line)
|
||||
if run is None or run.group(1)[0] != fence[0] or len(run.group(1)) < len(fence):
|
||||
continue
|
||||
if len(run.group(1)) > len(fence):
|
||||
return raw
|
||||
if (inner := _INNER_FENCE.fullmatch(line)) is None:
|
||||
continue # ``` sql 这样的行 compose_doc 和前端不认,去掉外层后是一行正文
|
||||
# 一样长的:带信息串的只能开(已经开着就配不成对),不带的只能收(没有开着的就是外层在这里收了)
|
||||
if bool(inner.group(2)) == nested:
|
||||
return raw
|
||||
nested = not nested
|
||||
return raw if nested else "\n".join(lines[1:-1])
|
||||
|
||||
|
||||
async def _store(doc: dict[str, Any], ctx: NodeContext) -> str | None:
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
没有句子可审(反方审稿记 challenge_skipped / no_claims),正式运行照样出具,界面上整篇显示成代码。
|
||||
|
||||
- 去:去掉首尾空白后整篇正好是一个围栏代码块(``` 或 ~~~,收尾围栏和开头一字不差),信息串为空、markdown 或 md
|
||||
(大小写不敏感),围栏外没有别的字;重写那几轮的稿子同样处理
|
||||
- 不去:信息串是别的语言(sql、json……)、围栏外还有字、有多个代码块、开闭围栏对不上、里面嵌着同样长或更长的同种
|
||||
围栏——这些是模型真要贴一段代码,或者分不清哪一行收尾,原样交给 compose_doc
|
||||
(大小写不敏感),围栏外没有别的字;重写、裁判退回改写的稿子同样处理。正文自己的代码块(```sql … ```)可以套在
|
||||
里面,和外层一样长也行
|
||||
- 不去:信息串是别的语言(sql、json……)、围栏外还有字、有多个代码块、开闭围栏对不上、里面嵌着更长的同种围栏、
|
||||
里面和外层一样长的围栏配不成对——这些是模型真要贴一段代码,或者分不清哪一行收尾,原样交给 compose_doc
|
||||
- doc["source"] 记去掉围栏之后的正文:markdown 就是从它渲染出来的,出口契约复核取回的也是这一份文档
|
||||
|
||||
模型一律是 MockChatModel 剧本,数据源是现造的 SQLite 文件,区域、金额都是虚构的。
|
||||
@@ -68,11 +69,22 @@ UNWRAPPED = [
|
||||
pytest.param(f"\n\n```markdown\n{BODY}\n```\n\n", BODY, id="blank-lines-around"),
|
||||
pytest.param(f"```markdown\r\n{BODY}\r\n```\r\n".replace("\n\n", "\r\n\r\n"), BODY, id="crlf"),
|
||||
pytest.param(f"```markdown\n{BODY}\n ```", BODY, id="indented-close"),
|
||||
# 正文里自己的代码块:外层用更长的同种围栏、或者换一种符号,才是一个代码块里套着另一个,里面的原样留下
|
||||
# 正文里自己的代码块套在里面:外层更长、换一种符号的,外层收在哪一行没有疑问;和外层一样长的,带信息串的一行
|
||||
# (```sql)开、不带的一行收,两两配对——去掉外层后 compose_doc 和前端也是这样切的。里面的原样留下
|
||||
pytest.param("````markdown\n## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。\n````",
|
||||
"## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。", id="shorter-fence-inside"),
|
||||
pytest.param("~~~markdown\n## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。\n~~~",
|
||||
"## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。", id="other-fence-inside"),
|
||||
pytest.param("```markdown\n## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。\n```",
|
||||
"## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。", id="same-fence-inside"),
|
||||
pytest.param("```markdown\n华东增长最快。\n\n```sql\nSELECT 1\n```\n\n华南次之。\n\n```python\nprint(2)\n```\n```",
|
||||
"华东增长最快。\n\n```sql\nSELECT 1\n```\n\n华南次之。\n\n```python\nprint(2)\n```",
|
||||
id="same-fence-blocks-inside"),
|
||||
pytest.param("~~~md\n## 口径\n\n~~~sql\nSELECT 1\n~~~\n\n结论如上。\n~~~",
|
||||
"## 口径\n\n~~~sql\nSELECT 1\n~~~\n\n结论如上。", id="same-tilde-inside"),
|
||||
# 外层反引号里套 ~~~:照样去(compose_doc 不认 ~~~,这段按正文核对,见 test_a_tilde_block_inside_reads_as_prose)
|
||||
pytest.param("```markdown\n## 口径\n\n~~~sql\nSELECT 1\n~~~\n\n结论如上。\n```",
|
||||
"## 口径\n\n~~~sql\nSELECT 1\n~~~\n\n结论如上。", id="tilde-inside-backticks"),
|
||||
]
|
||||
|
||||
KEPT = [
|
||||
@@ -90,7 +102,15 @@ KEPT = [
|
||||
pytest.param(f"```markdown\n{BODY}", id="unclosed"),
|
||||
pytest.param(f"```markdown\n{BODY}\n ```", id="close-indented-as-code"),
|
||||
pytest.param("```markdown\n## 口径\n\n````sql\nSELECT 1\n````\n\n结论如上。\n```", id="longer-fence-inside"),
|
||||
pytest.param("```markdown\n## 口径\n\n```sql\nSELECT 1\n```\n\n结论如上。\n```", id="same-fence-inside"),
|
||||
pytest.param("```markdown\n## 口径\n\n````sql title\nSELECT 1\n\n结论如上。\n```", id="longer-fence-with-words-inside"),
|
||||
# 和外层一样长的围栏配不成对:没有代码块可收的 ```(外层在这里就收了,后面是围栏外的字)、开着的代码块里又来一个
|
||||
# ```python(compose_doc 会拿它当收尾)、代码块开了没收、开头写成 ``` sql(compose_doc 不认,后面那个 ``` 反倒
|
||||
# 成了开头,把结论句吞进代码块)
|
||||
pytest.param("```markdown\n华东增长最快。\n```\n华南次之。\n```", id="bare-fence-inside"),
|
||||
pytest.param("```markdown\n## 口径\n\n```sql\nSELECT 1\n```python\nprint(2)\n```\n\n结论如上。\n```",
|
||||
id="opener-inside-open-block"),
|
||||
pytest.param("```markdown\n## 口径\n\n```sql\nSELECT 1\n\n结论如上。\n```", id="inner-block-left-open"),
|
||||
pytest.param("```markdown\n## 口径\n\n``` sql\nSELECT 1\n```\n\n结论如上。\n```", id="inner-opener-unreadable"),
|
||||
pytest.param("```markdown```", id="one-line"),
|
||||
pytest.param(BODY, id="plain"),
|
||||
pytest.param("", id="empty"),
|
||||
@@ -121,6 +141,39 @@ def test_the_unwrapped_body_reads_as_sentences():
|
||||
assert doc["source"] == "## 本周概览\n\n华东销售额 1500,居各区域之首。华南销售额超过华东。"
|
||||
|
||||
|
||||
def test_a_code_block_inside_stays_a_code_block():
|
||||
"""模型常见的写法:```markdown 里再套一段 ```sql,内外围栏一样长。去掉外层后,compose_doc 每见到一行 ``` 开合一次,
|
||||
```sql … ``` 正好切成一个代码块,前后两句是结论;不去的话外层被第一个 ``` 收掉,两句结论都落在代码块里。"""
|
||||
from app.engine.evidence import compose_doc
|
||||
|
||||
sql = "```sql\nSELECT region, SUM(amount) FROM sales GROUP BY region\n```"
|
||||
body = f"## 本周概览\n\n华东销售额 1500,居各区域之首。\n\n{sql}\n\n华南销售额超过华东。"
|
||||
raw = f"```markdown\n{body}\n```"
|
||||
wrapped = compose_doc(raw, {}, node_id="write")
|
||||
assert [b["type"] for b in wrapped["blocks"]] == ["code", "paragraph", "code"], "背景:不去时结论句都在代码块里"
|
||||
assert wrapped["stats"]["claims"] == 0
|
||||
doc = compose_doc(report_mod._unfenced(raw), {}, node_id="write")
|
||||
assert [(b["type"], b.get("lang")) for b in doc["blocks"]] == [
|
||||
("heading", None), ("paragraph", None), ("code", "sql"), ("paragraph", None)]
|
||||
assert doc["stats"]["claims"] == 2 and doc["source"] == body
|
||||
assert [(v["code"], v["text"]) for v in doc["violations"]] == [("uncited_number", "1500")]
|
||||
|
||||
|
||||
def test_a_tilde_block_inside_reads_as_prose():
|
||||
"""外层反引号里套一段 ~~~sql:照样去掉外层,真正的结论句才有得审。compose_doc 和前端的切块只认 ``` 围栏,这段 ~~~
|
||||
按正文切,和模型不包外层、直接这么写时一样:SQL 当一段话核对(会送去裁判、反方审稿),数字照样查。
|
||||
compose_doc 认得 ~~~ 以后这里跟着改。"""
|
||||
from app.engine.evidence import compose_doc
|
||||
|
||||
raw = "```markdown\n## 口径\n\n~~~sql\nSELECT region FROM sales WHERE amount > 1000\n~~~\n\n华东销售额超过华南。\n```"
|
||||
doc = compose_doc(report_mod._unfenced(raw), {}, node_id="write")
|
||||
assert [b["type"] for b in doc["blocks"]] == ["heading", "paragraph", "paragraph"]
|
||||
md = doc["markdown"]
|
||||
claims = [md[u["span"][0]:u["span"][1]] for _, u in iter_units(doc) if u["kind"] == "claim"]
|
||||
assert "华东销售额超过华南。" in claims
|
||||
assert [(v["code"], v["text"]) for v in doc["violations"]] == [("uncited_number", "1000")]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 报告节点:写作、重写都经过这一步
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -269,6 +322,12 @@ NET = ("SELECT region AS 区域, SUM(amount) AS 净销售额 FROM sales WHERE re
|
||||
"ORDER BY 净销售额 DESC")
|
||||
TEXT = "华东销售额 [[v:Q1.r0.销售额]],居各区域之首。[[see:Q1]]华南销售额超过华北。[[see:Q1]]"
|
||||
EAST, SOUTH = "华东销售额 1,500,居各区域之首。", "华南销售额超过华北。"
|
||||
SQL = "```sql\nSELECT region AS 区域, SUM(amount) AS 销售额 FROM sales GROUP BY region\n```"
|
||||
#: 两句结论中间贴一段 SQL,外面再包 ```markdown:内外围栏一样长
|
||||
NESTED = f"华东销售额 [[v:Q1.r0.销售额]],居各区域之首。[[see:Q1]]\n\n{SQL}\n\n华南销售额超过华北。[[see:Q1]]"
|
||||
#: 裁判退回后的改写稿:只改了华南那句,补上数字
|
||||
REWRITTEN = "华东销售额 [[v:Q1.r0.销售额]],居各区域之首。[[see:Q1]]华南销售额 [[v:Q1.r1.销售额]],高于华北。[[see:Q1]]"
|
||||
NEW_SOUTH = "华南销售额 900,高于华北。"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -294,20 +353,26 @@ async def shop(tmp_path):
|
||||
|
||||
|
||||
class Models:
|
||||
"""写作者、裁判、反查 agent 三份剧本。写作者回 writer;裁判把收到的每句都判「证据支持」;反查 agent 每句先查一次
|
||||
扣除退款后的销售额,再交「经受住」。judges 记下裁判收到的请求,reviewed 记下反查 agent 审过的句子。"""
|
||||
"""写作者、裁判、反查 agent 三份剧本。写作者依次回 writer 里的几段(用完了一直回最后一段);裁判按句子里的字给判定
|
||||
(verdicts:{句中片段: 判定},没对上的都判「证据支持」);反查 agent 每句先查一次扣除退款后的销售额,再交「经受住」。
|
||||
writes 记下写作者每次收到的消息,judges 记下裁判收到的请求,reviewed 记下反查 agent 审过的句子。"""
|
||||
|
||||
def __init__(self, monkeypatch, source: str, writer: str):
|
||||
def __init__(self, monkeypatch, source: str, *writer: str, verdicts: dict[str, str] | None = None):
|
||||
self.writes: list[list] = []
|
||||
self.judges: list[str] = []
|
||||
self.reviewed: list[str] = []
|
||||
verdicts = verdicts or {}
|
||||
|
||||
def decide(model, messages):
|
||||
system = str(messages[0].content)
|
||||
if JUDGE_ROLE in system:
|
||||
request = str(messages[-1].content)
|
||||
self.judges.append(request)
|
||||
items = [{"unit": uid, "verdict": "supported", "rationale": "与证据一致。", "used": []}
|
||||
for uid, _ in re.findall(r"^\[(u\d+)\]([^\n]*)$", request, re.M)]
|
||||
items = []
|
||||
for uid, line in re.findall(r"^\[(u\d+)\]([^\n]*)$", request, re.M):
|
||||
status = next((s for frag, s in verdicts.items() if frag in line), "supported")
|
||||
items.append({"unit": uid, "verdict": status, "used": [],
|
||||
"rationale": "与证据一致。" if status == "supported" else "证据不足以支持这句话。"})
|
||||
return AIMessage(content=json.dumps({"items": items}, ensure_ascii=False))
|
||||
if challenge_mod.CHALLENGE_SYSTEM in system:
|
||||
claim = next((c for c in (EAST, SOUTH) if c in str(messages[1].content)), "")
|
||||
@@ -320,7 +385,8 @@ class Models:
|
||||
return AIMessage(content="查一下。", tool_calls=[call])
|
||||
return AIMessage(content="查完了。")
|
||||
if report_mod.ROLE in system:
|
||||
return AIMessage(content=writer)
|
||||
self.writes.append(list(messages))
|
||||
return AIMessage(content=writer[min(len(self.writes), len(writer)) - 1])
|
||||
raise AssertionError(f"没想到的模型调用:{system[:60]}")
|
||||
|
||||
monkeypatch.setattr(MockChatModel, "_decide", decide)
|
||||
@@ -332,25 +398,38 @@ class Models:
|
||||
monkeypatch.setattr(verify_agent, "get_chat_model", priced)
|
||||
|
||||
|
||||
def audited(source: str) -> dict:
|
||||
"""取数 → 报告(逐句裁判)→ 反方审稿 → 成果(引用模式的出具契约,按报告文档复核)。"""
|
||||
def audited(source: str, *, challenge: bool = True, **report) -> dict:
|
||||
"""取数 → 报告(逐句裁判,report 是另加的节点配置)→ 反方审稿(challenge=False 时不要)→ 成果(引用模式的出具
|
||||
契约,按报告文档复核)。"""
|
||||
return chain(
|
||||
node("start", "input"),
|
||||
node("fetch", "tool", tool=f"db_query__{source}", args={"sql": QUERY}),
|
||||
node("write", "report", "写周报", instructions="写周报", claims="judge"),
|
||||
node("chal", "challenge", "审稿", report="write"),
|
||||
node("write", "report", "写周报", instructions="写周报", claims="judge", **report),
|
||||
*([node("chal", "challenge", "审稿", report="write")] if challenge else []),
|
||||
node("out", "output", fields=[{"name": "周报", "value": "{{ nodes.write.text }}"}],
|
||||
contract={"report_from": "write"}),
|
||||
)
|
||||
|
||||
|
||||
async def test_the_judge_and_the_challenge_get_sentences_to_review(monkeypatch, shop):
|
||||
models = Models(monkeypatch, shop, fenced(TEXT))
|
||||
def judged(doc: dict) -> dict[str, str]:
|
||||
"""{句子原文: 裁判的判定},只列判过的句子(代码块不送裁判)。"""
|
||||
md = doc["markdown"]
|
||||
return {md[u["span"][0]:u["span"][1]]: u["verdict"]["status"] for _, u in iter_units(doc) if u.get("verdict")}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body, markdown, blocks", [
|
||||
pytest.param(TEXT, EAST + SOUTH, [("paragraph", None)], id="plain"),
|
||||
pytest.param(NESTED, f"{EAST}\n\n{SQL}\n\n{SOUTH}", [("paragraph", None), ("code", "sql"), ("paragraph", None)],
|
||||
id="sql-inside"),
|
||||
])
|
||||
async def test_the_judge_and_the_challenge_get_sentences_to_review(monkeypatch, shop, body, markdown, blocks):
|
||||
models = Models(monkeypatch, shop, fenced(body))
|
||||
row = await finish(audited(shop), run_class="formal")
|
||||
assert row.status == "succeeded", row.error
|
||||
|
||||
checked, doc = await report_of(row.id)
|
||||
assert doc["source"] == TEXT and doc["markdown"] == EAST + SOUTH
|
||||
assert doc["source"] == body and doc["markdown"] == markdown
|
||||
assert [(b["type"], b.get("lang")) for b in doc["blocks"]] == blocks, "贴的那段 SQL 仍是代码块"
|
||||
assert doc["stats"]["claims"] == 2
|
||||
# 单元格引用照常解析:渲染成快照里的值,带着出处
|
||||
[cell] = [s for s in iter_segments(doc) if s.get("cite")]
|
||||
@@ -359,9 +438,7 @@ async def test_the_judge_and_the_challenge_get_sentences_to_review(monkeypatch,
|
||||
# 裁判:两句都送去判了,判定写进文档
|
||||
[request] = models.judges
|
||||
assert EAST in request and SOUTH in request
|
||||
md = doc["markdown"]
|
||||
judged = {md[u["span"][0]:u["span"][1]]: (u.get("verdict") or {}).get("status") for _, u in iter_units(doc)}
|
||||
assert judged == {EAST: "supported", SOUTH: "supported"}
|
||||
assert judged(doc) == {EAST: "supported", SOUTH: "supported"}
|
||||
assert checked["judge"]["counts"]["supported"] == 2 and checked["judge"]["complete"] is True
|
||||
|
||||
# 反方审稿:两句都审了,不是「没有可审的结论句」
|
||||
@@ -375,3 +452,22 @@ async def test_the_judge_and_the_challenge_get_sentences_to_review(monkeypatch,
|
||||
issuance = row.output["_issuance"]
|
||||
assert issuance["tier"] == "formal" and issuance["gaps"] == [], issuance
|
||||
assert (await verify_manifest(row.id))["ok"] is True
|
||||
|
||||
|
||||
async def test_the_rewrite_the_judge_asks_for_is_unwrapped_too(monkeypatch, shop):
|
||||
"""rewrite_once:裁判把华南那句判证据不足,退回写作者改写,改写稿也包在 ```markdown 里——照样去掉围栏,改写稿被采用,
|
||||
第二轮裁判判到的是改过的那句。改写稿要是没去围栏,整篇是一个代码块,第二轮一句可判的都没有。"""
|
||||
models = Models(monkeypatch, shop, fenced(TEXT), fenced(REWRITTEN), verdicts={SOUTH: "insufficient"})
|
||||
row = await finish(audited(shop, challenge=False, judge={"rewrite_once": True}), run_class="formal")
|
||||
assert row.status == "succeeded", row.error
|
||||
assert len(models.writes) == 2 and len(models.judges) == 2
|
||||
assert SOUTH in str(models.writes[1][-1].content), "退回去的是判作证据不足的那句"
|
||||
assert NEW_SOUTH in models.judges[1] and EAST not in models.judges[1], "第二轮只判改过的句子"
|
||||
|
||||
checked, doc = await report_of(row.id)
|
||||
rewrite = checked["judge"]["rewrite"]
|
||||
assert rewrite["applied"] is True and rewrite["sentences"] == [SOUTH]
|
||||
assert doc["source"] == REWRITTEN and doc["markdown"] == EAST + NEW_SOUTH
|
||||
assert [b["type"] for b in doc["blocks"]] == ["paragraph"] and doc["stats"]["claims"] == 2
|
||||
assert judged(doc) == {EAST: "supported", NEW_SOUTH: "supported"}
|
||||
assert row.output["周报"] == EAST + NEW_SOUTH
|
||||
|
||||
Reference in New Issue
Block a user