Files
AgentLab/backend/tests/test_copilot_entity_guide.py
T
ThearchyHelios 9727c21d8d 可点击证据第四期:结论句由模型裁判;只引单元格的问数据图能发成受管
一、结论句裁判

- 报告节点新增 claims: judge:写完、核对完之后,把结论句和它引用的证据摘录交给裁判模型,
  判为支持 / 部分支持 / 不支持 / 不是结论,附一句理由;判定只标注,不改写正文
- 送裁判的句子按固定规则预筛:带引用的、含方向词或因果词的送去判,短句、过渡句不送
- 证据摘录:指标写名称、值和原式;查询给 SQL 前 300 字和被引用的行,被引用的列一定给到;遮罩列一律不给;
  规则里写明「数字已经由系统核对过,不要复算」
- 一份报告合并成一次调用,超出句数就分批;结构化输出失败时退回 JSON 解析,再失败记为未裁判
- 裁判模型的优先级:节点上写的 > 设置里的「证据裁判模型」> Copilot 的模型;思考关闭;
  和写作模型相同时给警告(等于自己审自己)
- rewrite_once(默认关):把不支持的句子和理由交回写作者只改这几句,再裁一次;
  改写稿冒出原稿没有的问题就不采用,保留原稿和原来的判定

二、成本上限

- 每份报告的句数、金额、时长,探索运行每次点击的金额,全局每日金额,每一项都可以自定义,也都可以设成不限;
  默认 40 句、$0.05、30 秒,每次点击 $0.01,每天 $2
- 调用前先估算,超出上限按优先级截断:先判带引用的,再判含方向词、因果词的,其余记为未裁判并写明「已到上限」;
  不报错,也不让报告失败
- 每日花费按本地日期计数,持久化在设置表里;重跑同一次运行不重复计费;价格目录里没有的模型只计次数,并写明估不出金额

三、正式运行与探索运行

- 正式运行在报告节点内裁判,判定写进报告文档、随文档封存;不支持的按 on_unsupported(degrade / withhold)降档或不予出具,
  裁判没跑成记缺口,不能判完整出具
- 探索运行按需裁判:POST /api/runs/{id}/evidence/judge,点开哪句判哪句;判定作为 evidence.judged 事件追加在封存之后,
  标「封存后追加」,封存核对仍然一致;只读封存范围内的工件;同一句不重复判;运行在裁判期间被接着跑时不记录
- 正式运行调这个接口返回 409,写明「正式运行的裁判在节点内完成」

四、治理与自动修复

- G3 放开 judge:受管级别写 judge 必须写预算(judge.max_cost_usd,或显式写 null 表示不限)
- 发布前自动修复:缺预算时给选项(默认金额 / 不限),不替人选;
  从 require_citation 改成 judge 不算降低要求,judge 改回 off、withhold 放宽成 degrade 算降低要求
- Copilot 手册:受管模板可以写 judge 并带预算;问数据按需裁判,不替人打开结论句策略

五、界面

- 结论句句末放徽标:支持 ◆、部分支持 ◇、不支持 ! 整句浅底、未裁判 ?;不用表示「已核对」的绿色;n / N 也跳到不支持的句子
- 证据面板里「模型的解释」:带「模型判断 · 模型名 · 非确定」,封存后追加的另外标出;
  「请模型判断这句」只在探索运行、还没判过时出现,触顶时写明已到上限和怎么调
- 出具横幅多一段结论句计数;设置页新增「证据裁判」一组(裁判模型、各项上限、今日花费),设成不限时写明费用受什么约束;
  报告节点可以配 judge;运行轨迹里裁判相关的事件都有中文标题

六、门禁修正

- 引用模式下,图里没有口径卡(只引查询单元格)的,出具契约不再要求 metrics_from,受管门禁也不再要求 required:
  strict 加 cells 已经保证每个数都点得开、对不上就不予出具;有口径卡的照旧要求。
  以前问数据的图怎么都发不成受管,Copilot 补上契约也会被判「冒出新的问题」作废
2026-09-29 11:10:53 +08:00

77 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Copilot 的节点手册要教三期、四期的写法:表名字段名、知识库原话、结论句策略(含模型裁判 judge),以及受管模板的
报告节点怎么配。
手册里没写的配置,Copilot 就不会用;写错的写法,报告节点会一直被打回。所以这里不只查关键词,
还把手册里举的标记例子交给真正的解析器,确认写法和系统认的是同一套。
"""
from __future__ import annotations
import re
from app.api.copilot import _ASSIST_RULES, NODE_REFERENCE, GenerateIn, _user_message
from app.engine.evidence import QUOTE_MIN, SUPPORTED_KINDS, parse_markers
from app.engine.schema import CLAIMS_VALUES, JUDGE_KEYS, JUDGE_ON_UNSUPPORTED
def _report_section() -> str:
start = NODE_REFERENCE.index("- report:")
return NODE_REFERENCE[start:NODE_REFERENCE.index("\n- output", start)]
def test_the_report_entry_teaches_entities_quotes_and_claims():
report = _report_section()
for words in (
["[[t:", "[[c:", "反引号", "可能是编造的名字"], # 表名、字段名
["[[q:K1|", "逐字", f"至少 {QUOTE_MIN} 个字", "[[see:K1]]"], # 知识库原话
["claims", "require_citation", "off", "judge"], # 结论句策略
["judge.max_cost_usd", "null", "不限", "rewrite_once", "on_unsupported", "按需"], # 模型裁判怎么配
["entities", "link"], # 实体层开关
):
assert all(w in report for w in words), [w for w in words if w not in report]
# 结论句策略的取值和校验认的是同一份;judge 已经能用,不能再说「后续版本」「跑不起来」
assert all(value in report for value in CLAIMS_VALUES)
assert all(value in report for value in JUDGE_ON_UNSUPPORTED)
assert "后续版本" not in report and "跑不起来" not in report
# 裁判模型建议和写作模型不同:否则等于自己审自己
assert "不同" in report and "自己审自己" in report
def test_the_judge_keys_in_the_guide_are_the_ones_the_validator_knows():
named = set(re.findall(r"judge\.(\w+)", NODE_REFERENCE))
assert named and named <= set(JUDGE_KEYS), named - set(JUDGE_KEYS)
def test_the_markers_in_the_guide_parse_as_the_system_reads_them():
kinds = {m["kind"] for m in parse_markers(_report_section())}
assert {"t", "c", "q", "m", "v", "table", "i", "see"} <= kinds
assert kinds <= SUPPORTED_KINDS
[quote] = [m for m in parse_markers(_report_section()) if m["kind"] == "q"]
assert quote["ref"] == "K1" and quote["quote"]
def test_governed_templates_spell_out_the_report_settings():
ref = NODE_REFERENCE
governed = ref[ref.index("要按受管级别发布的模板"):]
for words in (['numbers: "strict"', 'on_violation: "fail"', 'claims: "require_citation"'],
['claims: "judge"', "judge.max_cost_usd", "null"],
["report_from", "narrative"], ["cite_fields: true"], ["evidence_role", "source"]):
assert all(w in governed for w in words), [w for w in words if w not in governed]
def test_the_publish_assist_knows_judge_is_the_stricter_one():
"""发布前的 Copilot 兜底只许往严里改:它要知道 judge 比 require_citation 严,不能为了过门禁把 judge 改回去。"""
assert "judge" in _ASSIST_RULES and "max_cost_usd" in _ASSIST_RULES
# update_node 把 judge 整个换掉:补预算只写 {max_cost_usd} 会把 withhold 连同裁判模型一起丢了
rules = " ".join(_ASSIST_RULES.split())
assert re.search(r"on_unsupported.{0,20}withhold.{0,20}不改回\s*degrade", rules), "withhold 不许改回 degrade"
assert re.search(r"补预算.{0,40}整份", rules), "补预算时 judge 要整份带上原来的键"
def test_the_answer_branch_mentions_names_and_quotes():
text = _user_message(GenerateIn(instruction="上周哪张表的退款最多", intent="answer"), patch=False)
assert "[[t:" in text and "反引号" in text and "编造" in text
assert "[[q:K1|" in text and "逐字" in text
# 问数是探索运行:不要替人打开结论句策略(结论句由读的人点开时按需请模型判断)
assert not re.search(r"claims\s*[:=]\s*['\"]?(require_citation|judge)", text)
assert "按需" in text