mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 07:02:00 +00:00
60be16a0bb
渲染(evidence._tidy):
- 上一轮只沿着去掉的字往两边找粘连:依据前后没有空白、括号、标点时没有切口,「华东 [[v:Q1.r0.销售额]][[see:Q1]]
[[v:Q1.r0.订单数]] 笔」渲染、组装都是「4,919.5212 笔」,「[[v:…]][[see:Q1]]2 月」是「4,919.52 月」,「**华东 [[v:…]]**
[[see:Q1]]2 月」组装出「华东 4,919.52 月」,verify_doc 都不报。现在每个依据(连着写的一串、只装着依据的括号都算)
两边都判(_glue 看所有不显示的字,不只看切口),会读成一串的补一个空格:这一片有去掉的字的照旧换掉头一个,没有的
插在头一个依据前面(粗体收尾的星号外面)
- 只装着依据的括号认【】、[](全角半角混用也算)、〔〕、[],和()一样连括号去掉,外面悬着的停顿标点照原规则收拾;
依据在这些括号开头、末尾紧挨着分隔标点的,同()一样收拾。核查 agent 拿到的查询结果以「【证据 Qn】」开头,模型照着
写成「【[[see:Q1]]】」,以前渲染成「【】」
- 右边是写作者紧贴着下文写的半角逗号的(「1,200([[see:Q1]]),900」去掉括号读成「1,200,900」),空格补在逗号后面
(同「212,[[see:Q1]],5」→「212, 5」)。上一轮把这一类当成可以不管的「),5」写法;和基线 58a6759 逐份比,结构化组合里
有 2481 份是上一轮去掉括号之后才并成一个数的(「增长 12([[see:Q1]]),212 笔」→「增长 12,212 笔」),现在都隔开了
- 找挨上的字时也越过删除线的两个波浪线:前端照样画出删去的数,挨着后面的数读
- 流式渲染把这几种括号、半角方括号、波浪线也攒到后面的字来了再放;半角「]」攒着时,标记收尾的「]]」和标记里末尾的
空白不算,标记整个放出去,从它后面攒起
不含依据的文档渲染结果、内容哈希逐字不变:随机 1.6 万份不含依据的文档和基线逐份比过,BEFORE 两份哈希照旧。
测试:依据两边什么都没有(数、标记、粗体、斜体、删除线、连着写的一串)、几种括号(只装着依据、全角半角混用、套着、
和别的括号连着写、开头末尾带标点、不含依据的不动)、半角逗号的用例;结构化组合加上两种括号,中间只有依据的也算;流式
渲染、随机语料加上几种括号和波浪线;审批质询、核查 agent、反方审稿、双盲复算各补一个端到端用例。15 处改动逐一去掉实现,
相关测试都变红(第 2 轮的 7 处也重跑过,照样变红)。另在测试之外:复核用的抽查脚本 6000 份 × 6 个种子只剩写作者自己
写成「212,,2」的一份;结构化组合 29.4 万份、随机含依据的 1.6 万份和基线比没有新并成的数,流式逐字、随机分块都等于整篇
渲染。
118 lines
6.4 KiB
Python
118 lines
6.4 KiB
Python
"""审批质询的回答:依据写进括号不再断句,收尾超时记时长上限,占位词不当回答(期 5 真实模型验收 M1)。
|
||
|
||
真实模型验收里,质询 agent 写的「结果一致([[see:Q3]],原报告 SQL 未按 status 过滤)」「合计 [[v:Q1.r1.金额合计]]
|
||
([[see:Q1]])」渲染成「(,原报告…)」「()」,随正式运行封存;另一次回答查完了,收尾那次调用超过时长上限,报错写着
|
||
「回答超过时长上限(180 秒)」,answer.limit 却是 null,界面上缺了「已达时长上限」。
|
||
|
||
回答只写了依据(「([[see:Q1]])」)时显示出来是空的:以前照样交回,封存之后的追问末尾补上「注:以上回答依据的是数据源
|
||
当前的数据…」,回答正文只剩这句系统说明、却记成回答成功。现在按没给回答记。回答就是一个「无。」的,照常是回答。
|
||
|
||
模型一律是 MockChatModel 剧本,区域、金额都是虚构的。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from app.core import artifact_store
|
||
from app.engine import verify_agent
|
||
from app.engine.inquiry import NO_ANSWER
|
||
from tests.test_inquiry_api import ( # noqa: F401 - 夹具按名字引入
|
||
EAST,
|
||
NO_TEST,
|
||
QUESTION,
|
||
Models,
|
||
Play,
|
||
ask,
|
||
client,
|
||
engine_up,
|
||
events,
|
||
finish,
|
||
pause,
|
||
pending,
|
||
report_doc,
|
||
set_verify,
|
||
shop,
|
||
unit_of,
|
||
)
|
||
from tests.test_verify_rationale_rules import slow_final
|
||
|
||
#: 依据写进了括号:开头跟着逗号的、只有依据的
|
||
BRACKETED = ("仍居各区域之首([[see:Q1]],已排除测试账号):华东为 [[v:Q1.r0.销售额]]([[see:Q1]]),"
|
||
"华南为 [[v:Q1.r1.销售额]](同口径,[[see:Q1]])。")
|
||
BRACKETED_TEXT = "仍居各区域之首(已排除测试账号):华东为 1,200,华南为 900(同口径)。"
|
||
#: 两个只装着依据的括号连着写、数字包着粗体(复核第 2 轮):去掉括号之后两个数不能挨成一个(「1,200900」),句末前悬着的
|
||
#: 逗号也去掉
|
||
TOGETHER = ("华东、华南分别为 **[[v:Q1.r0.销售额]]**([[see:Q1]])([[see:Q1]])[[v:Q1.r1.销售额]],"
|
||
"华东仍居各区域之首,([[see:Q1]])([[see:Q1]])。")
|
||
TOGETHER_TEXT = "华东、华南分别为 1,200 900,华东仍居各区域之首。"
|
||
#: 依据直接夹在两个数之间(两边什么都没有)、写进方头括号(复核第 3 轮):两个数不能挨成一个,括号不能剩下「【】」
|
||
BARE = "华东、华南分别为 [[v:Q1.r0.销售额]][[see:Q1]][[v:Q1.r1.销售额]],华东仍居各区域之首【[[see:Q1]]】。"
|
||
BARE_TEXT = "华东、华南分别为 1,200 900,华东仍居各区域之首。"
|
||
|
||
|
||
async def _asked(client, shop) -> tuple[dict, list]:
|
||
paused = await pause(shop)
|
||
[approval] = await pending(paused.id)
|
||
r = await ask(client, paused.id, report="write", unit=None, question=QUESTION, approval_id=approval.id)
|
||
assert r.status_code == 200, r.text
|
||
return r.json()["inquiry"], await events(paused.id, "approval.inquiry")
|
||
|
||
|
||
@pytest.mark.parametrize(("said", "shown"), [(BRACKETED, BRACKETED_TEXT), (TOGETHER, TOGETHER_TEXT), (BARE, BARE_TEXT)],
|
||
ids=["bracketed", "together", "bare"])
|
||
async def test_see_in_brackets_does_not_break_the_answer(monkeypatch, shop, client, said, shown):
|
||
Models(monkeypatch, shop, plays={"排除测试账号": Play([NO_TEST], said)})
|
||
item, [event] = await _asked(client, shop)
|
||
answer = item["answer"]
|
||
assert answer["ok"] is True and answer["text"] == shown
|
||
doc = artifact_store.load(answer["doc_artifact"])
|
||
assert doc["markdown"] == shown and doc["source"] == said
|
||
assert doc["violations"] == []
|
||
assert event.data["answer_text"] == shown
|
||
|
||
|
||
async def test_a_final_answer_that_runs_out_of_time_reports_the_time_limit(monkeypatch, shop, client):
|
||
"""质询 agent 查完了、自己停下,交回答那次调用超过时长上限:报错说时长上限,limit 也是 timeout_s(事件里同样)。"""
|
||
await set_verify({"inquiry": {"timeout_s": 0.5}})
|
||
monkeypatch.setattr(verify_agent, "SETTLE_GRACE_S", 0.3)
|
||
models = Models(monkeypatch, shop)
|
||
slow_final(monkeypatch)
|
||
item, [event] = await _asked(client, shop)
|
||
answer = item["answer"]
|
||
assert answer["ok"] is False and answer["text"] == "" and answer["doc_artifact"] is None
|
||
assert answer["error"] == "回答超过时长上限(0.5 秒),未能给出回答,可缩小问题范围后重新提问"
|
||
assert answer["limit"] == "timeout_s"
|
||
assert [q["sql"] for q in item["queries"]] == [NO_TEST], "查到的照记"
|
||
assert (event.data["limit"], event.data["error"]) == ("timeout_s", answer["error"])
|
||
assert models.finals == [], "交回答那次没等到回复"
|
||
|
||
|
||
async def test_a_placeholder_answer_is_no_answer(monkeypatch, shop, client):
|
||
Models(monkeypatch, shop, plays={"排除测试账号": Play([NO_TEST], "placeholder")})
|
||
item, _ = await _asked(client, shop)
|
||
answer = item["answer"]
|
||
assert (answer["ok"], answer["text"], answer["limit"]) == (False, "", None)
|
||
assert answer["error"] == NO_ANSWER
|
||
|
||
|
||
async def test_an_answer_that_shows_nothing_is_no_answer_even_after_the_seal(monkeypatch, shop, client):
|
||
"""封存之后的追问:回答只写了依据,显示出来是空的。以前回答文档照样组装,末尾补上「查的是现在的数据」那句说明,
|
||
回答正文只剩系统说明、却记成回答成功。"""
|
||
Models(monkeypatch, shop, plays={"排除测试账号": Play([NO_TEST], "([[see:Q1]])")})
|
||
done = await finish(shop)
|
||
assert done.status == "succeeded", done.error
|
||
r = await ask(client, done.id, report="write", unit=unit_of(await report_doc(done.id), EAST), question=QUESTION)
|
||
assert r.status_code == 200, r.text
|
||
answer = r.json()["inquiry"]["answer"]
|
||
assert (answer["ok"], answer["text"], answer["doc_artifact"]) == (False, "", None)
|
||
assert answer["error"] == NO_ANSWER
|
||
|
||
|
||
async def test_a_one_word_answer_is_an_answer(monkeypatch, shop, client):
|
||
"""问的是「有没有」,答「无。」:这是回答,不是占位词,不能报「模型没有给出回答内容」让提问人重问、再花一次钱。"""
|
||
Models(monkeypatch, shop, plays={"排除测试账号": Play([NO_TEST], "无。[[see:Q1]]")})
|
||
item, [event] = await _asked(client, shop)
|
||
answer = item["answer"]
|
||
assert (answer["ok"], answer["text"], answer["error"]) == (True, "无。", None)
|
||
assert event.data["answer_text"] == "无。"
|