fix(v2): 修复预审模型字段表三处缺陷并留出工具预算余量

- 补 8 个标准字段(还款来源/涉诉/增信/保证金/保险/客户名称),对齐原生方法文档声明的 inputs
- 字段守卫改为比对标准字段表,不再把"标准字段但本次无数据"误判为未知字段
- 向模型下发 standard_fields 并在提示词约束字段名,避免自造派生指标
- 补 insurance./deposit. 维度前缀;方案预算 12→18、120s→180s,版本升 1.1.0

实测(临时库 + DeepSeek):模型类技能由 failed 转为 partial(部分 unknown 属如实标注),
降级记录为空,工具调用 11 次。字段表三处为代码层改动,对既有库即时生效。
验证:scripts/verify.py 11/11 PASS,531 项单测。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-30 17:38:28 +08:00
co-authored by Claude Code
parent 962d4250dd
commit 4df80c0053
4 changed files with 24 additions and 9 deletions
+10 -3
View File
@@ -13,6 +13,7 @@ from langchain_openai import ChatOpenAI
from pydantic import BaseModel, ConfigDict, Field, field_validator
from graph.agent import agent_manager
from services.assistant_materials import FIELDS
class ModelCheck(BaseModel):
@@ -82,6 +83,8 @@ PROMPT = """你是兴邦项目核查助手,必须通过受限工具读取本
必须读取 read_project_snapshot、read_checks;每个选中方法都用 read_method 读取。
输出一个JSON对象,只含 findings 和 requirement_response。findings 每项字段为 code(稳定英文码)、title、result
(pass/issue/unknown/not_applicable)、evidence_ids(真实证据ID数组)、fields(标准字段数组)、suggested_action。
fields 每项必须**逐字**取自本次任务给出的 standard_fields 列表;不得自造字段名,也不得使用派生指标名
(如负债率、流动比率);派生值只能引用程序已计算的 metrics.* 。清单里没有的字段,说明该项无法核查。
没有依据不得判定pass/issue。缺失资料标unknown,证据只能引用冻结快照。不得引用其他项目经验中的客户信息。
requirement_response必须是一个普通字符串,不得输出对象或数组;逐项说明是否完成用户要求,无法验证要明确说明。
示例顶层结构:{"findings": [], "requirement_response": "已读取授权事实并完成核查。"}。不要Markdown围栏。"""
@@ -134,6 +137,7 @@ def invoke(skill, snapshot, checks, rules, requirement, experiences, model_confi
available = [read_project_snapshot, read_document, read_checks, read_method, read_experiences]
selected = [t for t in available if t.name in skill["allowed_tools"]]
task = {"instructions": skill.get("instructions"), "requirement": requirement, "methods": list(skill.get("methods", {})),
"standard_fields": sorted(FIELDS.values()),
"documents": [{"id": d["id"], "name": d["name"]} for d in snapshot["documents"]]}
prompt = PROMPT
if feedback:
@@ -153,7 +157,6 @@ def invoke(skill, snapshot, checks, rules, requirement, experiences, model_confi
if feedback and parsed.experience_proposal is None:
raise ValueError("模型未返回结构化经验草稿")
if parsed.experience_proposal:
from services.assistant_materials import FIELDS
proposal = parsed.experience_proposal
if not proposal.conditions or set(proposal.conditions) - set(FIELDS.values()) or proposal.required_field not in FIELDS.values() or proposal.exception_field not in FIELDS.values() or proposal.required_field in proposal.conditions:
raise ValueError("经验草稿的触发条件或执行字段无效")
@@ -168,7 +171,11 @@ def invoke(skill, snapshot, checks, rules, requirement, experiences, model_confi
raise ValueError("模型结论缺少证据")
if any("document:" + document_evidence[e] not in reads for e in finding.evidence_ids if e in document_evidence):
raise ValueError("模型引用了尚未读取的材料片段")
if set(finding.fields) - set(snapshot["values"]):
raise ValueError("模型引用未知字段")
# 白名单比的是**标准字段表**,不是"当前恰好有值的字段":
# 后者会把"标准字段但本次无数据"误判成未知字段,与提示词"缺失资料标 unknown"相矛盾。
# 仍是封闭白名单——表外字段一样被拒,模型不能自造指标。
unknown = sorted(set(finding.fields) - set(FIELDS.values()))
if unknown:
raise ValueError("模型引用未知字段:" + ",".join(unknown))
seen.add(finding.code)
return parsed.model_dump(), {"engine": result["engine"], "tool_calls": count[0], "tools_read": sorted(reads), "model": model_config["name"]}