mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:21:58 +00:00
数字标记后面重复写的单字单位也去掉(部署上的「覆盖 8条 条产线」):以前单字的单位后面紧跟着字时一律不动,现在只在它和后面的字连起来是一个更长的单位(人次、个月、台套、元/…,_LONGER_UNIT)时不动,让「人」对「人次」这样的不一致照旧露出来;「条产线」「人参加」照样去掉重复的那个字,流式渲染一致。写作规则加一条 UNIT_RULE([[m:]]、[[v:]] 自带单位,后面不要再写一遍),助手节点手册讲 [[m:]] 的那一行也写明
This commit is contained in:
@@ -229,7 +229,8 @@ NODE_REFERENCE = """\
|
||||
**凡是给人看的、带数字的报告、分析、结论,都用 report,不要用 llm**。它自动收集上游的口径卡指标、查询结果
|
||||
(tool 节点和 agent 查过的库、merge 的合并结果,按查询先后编成 Q1、Q2…)、查库当时的表结构、知识库检索(按先后编成 K1、K2…)
|
||||
和运行输入,模型只能写引用标记,数值由系统从证据里取出来渲染;模型自己写的数字会被打回重写:
|
||||
- [[m:指标id]]:口径卡指标。比率、增幅、占比、差值这类派生计算只能先在口径卡里登记再引用
|
||||
- [[m:指标id]]:口径卡指标。比率、增幅、占比、差值这类派生计算只能先在口径卡里登记再引用。渲染出来自带单位(口径卡里的
|
||||
unit,「8条」),instructions 里不要写成「覆盖 [[m:line_count]] 条产线」这样后面再跟一个单位,写「覆盖 [[m:line_count]] 产线」
|
||||
- [[v:Q1.r0.列名]]:第 1 次查询结果里第 0 行那一列的原值(行号从 0 数)
|
||||
- [[table:Q1 cols=列a,列b rows=0-4]]:把查询结果的几行几列原样生成表格,每一格都点得开出处
|
||||
- [[i:输入字段]]:运行输入
|
||||
|
||||
@@ -1869,6 +1869,10 @@ _UNIT_GAP = 2
|
||||
_UNIT_MAX = 10
|
||||
UNIT_LOOKAHEAD = _UNIT_GAP + _UNIT_MAX + 1
|
||||
_UNIT_KINDS = frozenset({"m", "i", "v"})
|
||||
#: 以一个字的单位打头的更长的单位:口径卡是「人」、正文紧跟着写「人次」,是另一个单位,不当重复的去掉(_tidy)
|
||||
_LONGER_UNIT = re.compile(
|
||||
r"人次|人天|人日|人月|人年|人份|件次|台次|台套|台时|车次|架次|班次|户次|批次|个月|个季度|个工作日|个百分点"
|
||||
r"|小时|分钟|吨公里|公里|公斤|千克|千米|千瓦|万元|亿元|美元|港元|欧元|日元|元/")
|
||||
#: 渲染结果末尾的单位:最后一个数字后面的字(12,345人次 → 人次,4.57万元 → 万元,12.5% → %)
|
||||
_UNIT_TAIL = re.compile(r"\d([^\d\s][^\d]*)$")
|
||||
_BLANK = " \t\u3000"
|
||||
@@ -1924,8 +1928,9 @@ def _tidy(text: str, catalog: dict[str, Any], snaps: _Snapshots, cells_allowed:
|
||||
"""渲染前收拾两处排版,数和引用都不动:
|
||||
|
||||
- 数字标记后面重复写的单位去掉。[[m:visits]] 渲染出来已经带单位(12,345人次),写作者常常
|
||||
还跟着写一遍([[m:visits]] 人次),读者看到的是「12,345人次 人次」。单字的单位后面紧跟着字时
|
||||
不动:写作者写的可能是另一个单位(口径卡是「人」,正文写「人次」),硬删会把不一致藏起来;
|
||||
还跟着写一遍([[m:visits]] 人次),读者看到的是「12,345人次 人次」。单字的单位后面紧跟着字、和它
|
||||
连起来是一个更长的单位时(_LONGER_UNIT)不动:写作者写的是另一个单位(口径卡是「人」,正文写「人次」),
|
||||
硬删会把不一致藏起来;连起来不是单位的(「覆盖 [[m:lines]] 条产线」,口径卡的单位是「条」)照样去掉;
|
||||
- [[see:…]] 渲染成空,它周围留下的空白、括号、标点去掉(_voids、_see_cuts):「最高月份 [[see:Q2]]。」会剩下
|
||||
「最高月份 。」,「结果一致([[see:Q3]],原报告…)」会剩下「结果一致(,原报告…)」。依据(连同去掉的字)两边
|
||||
挨上的字会读成一串的,补一个空格(_glue):「4,919.5[[see:Q1]]212 笔」会读成「4,919.5212 笔」。
|
||||
@@ -1942,7 +1947,8 @@ def _tidy(text: str, catalog: dict[str, Any], snaps: _Snapshots, cells_allowed:
|
||||
hit = re.compile(f"[{_BLANK}]{{0,{_UNIT_GAP}}}" + re.escape(unit)).match(text, marker["end"])
|
||||
if hit:
|
||||
after = text[hit.end():hit.end() + 1]
|
||||
if len(unit) > 1 or not after.isalnum():
|
||||
longer = _LONGER_UNIT.match(text, hit.end() - len(unit))
|
||||
if len(unit) > 1 or not after.isalnum() or not (longer and len(longer.group(0)) > len(unit)):
|
||||
cuts.append((marker["end"], hit.end()))
|
||||
see: list[tuple[int, int]] = []
|
||||
spaces: set[int] = set()
|
||||
@@ -3390,6 +3396,11 @@ SEE_AND_CODE_RULES = (
|
||||
"(「状态为 9」「类型 1」):码值是没有出处的数字,系统会当成裸数字打回重写。"
|
||||
)
|
||||
|
||||
#: [[m:]]、[[v:]] 渲染出来自带单位(部署上撞过「覆盖 8条 条产线」:口径卡的单位是「条」,写作者照着助手的指令又写了一个)。
|
||||
#: 渲染时紧跟着的同一个单位会去掉(_tidy),这里先说给写作者听
|
||||
UNIT_RULE = ("数字标记渲染出来自带单位([[m:…]] 是口径卡里的单位,比如「8条」「12,345人次」),后面不要再写一遍单位:"
|
||||
"写「覆盖 [[m:line_count]] 产线」,不写「覆盖 [[m:line_count]] 条产线」。")
|
||||
|
||||
#: 相对日期(「今天、昨天、上周、本月、去年」)一律按运行当天算(period_rule)。部署上撞过:数据只更新到两周前,查库时拿表里
|
||||
#: 最大的日期往前推七天当「上周」,写作者照着写成「上周产量……」。写作者不知道今天是哪天,就分不出数据有没有覆盖问的那段,
|
||||
#: 所以把运行当天和上周的起止日期直接告诉它:没覆盖时先照实说、写明数据最新到哪天;最近一段的数可以给,但写具体起止日期
|
||||
|
||||
@@ -57,7 +57,7 @@ from app.engine.evidence import (
|
||||
ENTITY_KINDS,
|
||||
MARKER_RULES,
|
||||
SEE_AND_CODE_RULES,
|
||||
period_rule,
|
||||
UNIT_RULE,
|
||||
StreamRenderer,
|
||||
WriterCatalog,
|
||||
build_catalog,
|
||||
@@ -65,6 +65,7 @@ from app.engine.evidence import (
|
||||
compose_doc,
|
||||
describe_violations,
|
||||
ledger_enabled,
|
||||
period_rule,
|
||||
withhold_metrics,
|
||||
writer_catalog,
|
||||
)
|
||||
@@ -646,7 +647,7 @@ def _messages(ctx: NodeContext, state: GraphState, writer: WriterCatalog, claims
|
||||
system = ctx.render_str(ctx.cfg("system", ""), state)
|
||||
instructions = ctx.render_str(ctx.cfg("instructions", ""), state).strip() \
|
||||
or "根据下面的证据写一份简洁的报告,先总后分。"
|
||||
rules = f"{MARKER_RULES}\n{SEE_AND_CODE_RULES}\n{period_rule(run_today())}"
|
||||
rules = f"{MARKER_RULES}\n{SEE_AND_CODE_RULES}\n{UNIT_RULE}\n{period_rule(run_today())}"
|
||||
if writer.guarded:
|
||||
# 写作者看不全证据(目录有省略、有来源的结构化结论为空):「没有查到」这类结论没有依据
|
||||
rules = f"{rules}\n{ABSENCE_RULE}"
|
||||
|
||||
@@ -37,6 +37,7 @@ CARD = {
|
||||
metric("gmv", "销售额", 45678.5, "元", 1),
|
||||
metric("yoy", "同比增幅", 12.5, "%", 1, "plain"),
|
||||
metric("gone", "缺数", None, "人次"),
|
||||
metric("lines", "产线条数", 8, "条"),
|
||||
],
|
||||
}
|
||||
LEDGER = [{"kind": "metric_set", "node_id": "cal", "exec": 1, "artifact": ART, "caliber": "客流口径",
|
||||
@@ -108,6 +109,10 @@ def test_a_definition_with_a_number_is_still_a_claim(catalog):
|
||||
("销售额 [[m:gmv|万]] 万元。", "销售额 4.57万元。"),
|
||||
("同比 [[m:yoy]] %。", "同比 12.5%。"),
|
||||
("客流 [[m:visits]] 人次的水平", "客流 12,345人次的水平"), # 多字单位后面跟着字,照样去掉
|
||||
# 单字的单位后面紧跟着字:和单位连起来不是一个更长的单位(「条产」),照样去掉。部署上撞过「覆盖 8条 条产线」
|
||||
("覆盖 [[m:lines]] 条产线。", "覆盖 8条产线。"),
|
||||
("覆盖[[m:lines]]条产线。", "覆盖8条产线。"),
|
||||
("到访 [[m:heads]] 人参加。", "到访 1,200人参加。"),
|
||||
])
|
||||
def test_a_repeated_unit_is_dropped(catalog, raw, shown):
|
||||
doc = compose_doc(raw, catalog)
|
||||
@@ -117,6 +122,17 @@ def test_a_repeated_unit_is_dropped(catalog, raw, shown):
|
||||
assert verify_doc(doc, catalog)["violations"] == []
|
||||
|
||||
|
||||
def test_the_writer_and_the_copilot_are_told_markers_carry_their_unit():
|
||||
"""助手写报告指令、写作者写正文,都先知道 [[m:]] 渲染出来自带单位、后面不要再写一遍。"""
|
||||
from app.api.copilot import NODE_REFERENCE
|
||||
from app.engine.evidence import UNIT_RULE
|
||||
|
||||
start = NODE_REFERENCE.index("- report:报告撰写")
|
||||
report = NODE_REFERENCE[start:NODE_REFERENCE.index("\n- output 的出具契约", start)]
|
||||
assert "自带单位" in report and "条产线" in report
|
||||
assert "自带单位" in UNIT_RULE and "后面不要再写" in UNIT_RULE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("raw", "shown"), [
|
||||
# 口径卡的单位是「人」,写作者写的是「人次」:不是同一个单位,不动,让不一致露出来
|
||||
("到访 [[m:heads]] 人次。", "到访 1,200人 人次。"),
|
||||
@@ -188,9 +204,10 @@ def _stream(catalog, raw, sizes):
|
||||
|
||||
@pytest.mark.parametrize("size", [1, 2, 3, 5, 8, UNIT_LOOKAHEAD, 40])
|
||||
def test_stream_drops_the_repeated_unit_and_the_blank_like_the_full_render(catalog, size):
|
||||
raw = "客流 [[m:visits]] 人次,为最高月份 [[see:m:visits]]。到访 [[m:heads]] 人次 [[see:m:heads]]\n末尾 [[m:gmv]] 元"
|
||||
raw = ("客流 [[m:visits]] 人次,为最高月份 [[see:m:visits]]。到访 [[m:heads]] 人次 [[see:m:heads]]\n"
|
||||
"覆盖 [[m:lines]] 条产线,末尾 [[m:gmv]] 元")
|
||||
full = render_markers(raw, catalog)
|
||||
assert full == "客流 12,345人次,为最高月份。到访 1,200人 人次\n末尾 45,678.5元"
|
||||
assert full == "客流 12,345人次,为最高月份。到访 1,200人 人次\n覆盖 8条产线,末尾 45,678.5元"
|
||||
out = _stream(catalog, raw, [size] * len(raw))
|
||||
assert "".join(out) == full, out
|
||||
|
||||
@@ -209,7 +226,8 @@ def test_stream_holds_the_blank_before_see_until_it_knows_what_follows(catalog,
|
||||
def _corpus(n):
|
||||
rng = random.Random(20260930)
|
||||
pieces = ["[[m:visits]]", "[[m:gmv|万]]", "[[m:heads]]", "[[m:yoy]]", "[[see:m:visits]]", "[[see:m:yoy]]",
|
||||
"人次", "人", "次", "万元", "元", "%", " ", " ", " ", "\n", "。", ",", "文字", "[[", "]]", "["]
|
||||
"[[m:lines]]", "条", "产线", "人次", "人", "次", "万元", "元", "%", " ", " ", " ", "\n", "。", ",",
|
||||
"文字", "[[", "]]", "["]
|
||||
return ["".join(rng.choice(pieces) for _ in range(rng.randint(1, 16))) for _ in range(n)]
|
||||
|
||||
|
||||
|
||||
@@ -58,3 +58,6 @@ async def test_the_report_prompt_carries_the_run_day(monkeypatch):
|
||||
await finish(graph)
|
||||
prompt = "\n".join(str(m.content) for m in seen[0])
|
||||
assert "今天是 2031-03-12(周三)" in prompt and "上周是 2031-03-03 至 2031-03-09" in prompt
|
||||
from app.engine.evidence import UNIT_RULE
|
||||
|
||||
assert UNIT_RULE in prompt, "[[m:]] 自带单位的那条也在写作规则里"
|
||||
|
||||
Reference in New Issue
Block a user