From 4df80c0053a6ca56801b0a515dca66c7a40d1cf2 Mon Sep 17 00:00:00 2001 From: qiaoqichao Date: Wed, 30 Sep 2026 17:38:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(v2):=20=E4=BF=AE=E5=A4=8D=E9=A2=84=E5=AE=A1?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=AD=97=E6=AE=B5=E8=A1=A8=E4=B8=89=E5=A4=84?= =?UTF-8?q?=E7=BC=BA=E9=99=B7=E5=B9=B6=E7=95=99=E5=87=BA=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=A2=84=E7=AE=97=E4=BD=99=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补 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 --- backend/services/assistant_materials.py | 12 ++++++++++-- backend/services/assistant_model.py | 13 ++++++++++--- backend/services/component_registry.py | 4 ++-- backend/services/project_assistant.py | 4 ++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/services/assistant_materials.py b/backend/services/assistant_materials.py index 18fdc79..b19b16d 100644 --- a/backend/services/assistant_materials.py +++ b/backend/services/assistant_materials.py @@ -20,8 +20,16 @@ FIELDS = { "financial_assets": "financial.total_assets", "credit_overdue_count": "credit.overdue_count", "credit_report_date": "credit.report_date", "facility_term": "facility.term", + # 补齐原生方法文档声明、而 S1 字段表原本缺失的字段族:还款来源、涉诉、增信、保证金、保险、客户名称。 + # 依据:skills/*/SKILL.md 的 inputs 声明,以及 REVIEW_TOPICS 已预设的 customer./repayment./legal./guarantee. 前缀。 + "customer_name": "customer.name", + "repayment_source_type": "repayment.source_type", "repayment_annual_amount": "repayment.annual_amount", + "legal_case_count": "legal.case_count", "legal_amount": "legal.amount", + "guarantee_amount": "guarantee.amount", "deposit_amount": "deposit.amount", + "insurance_amount": "insurance.amount", } -NUMERIC = set(FIELDS.values()) - {"collection.dependency", "collection.evidence", "credit.report_date"} +NUMERIC = set(FIELDS.values()) - {"collection.dependency", "collection.evidence", "credit.report_date", + "customer.name", "repayment.source_type"} LIMIT = 10 * 1024 * 1024 @@ -30,7 +38,7 @@ def scalar(field, value): return None if field in NUMERIC: result = number(value) - if field in ("lease.duplicate_count", "credit.overdue_count") and (result < 0 or result != result.to_integral_value()): + if field in ("lease.duplicate_count", "credit.overdue_count", "legal.case_count") and (result < 0 or result != result.to_integral_value()): raise ValueError("数量必须为非负整数") if field in ("facility.amount", "lease.total_value") and result <= 0: raise ValueError("融资金额和租赁物价值必须为正数") diff --git a/backend/services/assistant_model.py b/backend/services/assistant_model.py index 9a14e23..a3110d9 100644 --- a/backend/services/assistant_model.py +++ b/backend/services/assistant_model.py @@ -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"]} diff --git a/backend/services/component_registry.py b/backend/services/component_registry.py index 58a91e6..3104575 100644 --- a/backend/services/component_registry.py +++ b/backend/services/component_registry.py @@ -52,8 +52,8 @@ def seed_components(): {"id": "judgment", "title": "待决策事项", "dependencies": ["*"]}]}, ] items += [ - {"id": "corporate.default", "kind": "plan", "name": "对公送审前检查(兼容)", "audience": ["manager"], "run_kind": "pre_review", "skills": [s["id"] for s in skills], "rules": ["demo.coverage"], "template": "template.due-diligence", "legacy_plan_id": "plan-default", "budget": {"timeout_seconds": 120, "max_tool_calls": 12}, "dependencies": []}, - {"id": "corporate.pre-review", "kind": "plan", "name": "客户经理AI预审", "audience": ["manager"], "run_kind": "pre_review", "skills": [s["id"] for s in skills], "rules": ["demo.coverage"], "template": "template.ai-pre-review", "legacy_plan_id": "plan-default", "budget": {"timeout_seconds": 120, "max_tool_calls": 12}, "dependencies": []}, + {"id": "corporate.default", "kind": "plan", "version": "1.1.0", "name": "对公送审前检查(兼容)", "audience": ["manager"], "run_kind": "pre_review", "skills": [s["id"] for s in skills], "rules": ["demo.coverage"], "template": "template.due-diligence", "legacy_plan_id": "plan-default", "budget": {"timeout_seconds": 180, "max_tool_calls": 18}, "dependencies": []}, + {"id": "corporate.pre-review", "kind": "plan", "version": "1.1.0", "name": "客户经理AI预审", "audience": ["manager"], "run_kind": "pre_review", "skills": [s["id"] for s in skills], "rules": ["demo.coverage"], "template": "template.ai-pre-review", "legacy_plan_id": "plan-default", "budget": {"timeout_seconds": 180, "max_tool_calls": 18}, "dependencies": []}, {"id": "corporate.independent-review", "kind": "plan", "name": "评审员独立复核", "audience": ["reviewer"], "run_kind": "independent_review", "skills": [s["id"] for s in skills if s["id"] != "corporate.analysis"] + ["review.analysis"], "rules": ["demo.coverage"], "template": "template.independent-review", "legacy_plan_id": "plan-default", "budget": {"timeout_seconds": 180, "max_tool_calls": 18}, "dependencies": []}, {"id": "corporate.business-sql", "kind": "plan", "name": "多表业务库AI预审", "audience": ["manager"], "run_kind": "pre_review", "skills": [s["id"] for s in skills if s["id"] != "corporate.coverage"], "rules": ["demo.coverage"], "template": "template.ai-pre-review", "legacy_plan_id": "plan-business-sql", "budget": {"timeout_seconds": 120, "max_tool_calls": 12}, "dependencies": []}, ] diff --git a/backend/services/project_assistant.py b/backend/services/project_assistant.py index 9fafbee..c458d7d 100644 --- a/backend/services/project_assistant.py +++ b/backend/services/project_assistant.py @@ -21,8 +21,8 @@ TERMINAL = {"completed", "partial", "failed", "cancelled"} REVIEW_TOPICS = ( {"id": "subject", "title": "主体信息", "prefixes": ("project.", "customer.", "corporate.")}, {"id": "financial", "title": "财务与现金流", "prefixes": ("financial.", "due_diligence.", "metrics.")}, - {"id": "lease_asset", "title": "租赁物", "prefixes": ("lease.", "asset.")}, - {"id": "enhancement", "title": "增信措施", "prefixes": ("guarantee.", "enhancement.")}, + {"id": "lease_asset", "title": "租赁物", "prefixes": ("lease.", "asset.", "insurance.")}, + {"id": "enhancement", "title": "增信措施", "prefixes": ("guarantee.", "enhancement.", "deposit.")}, {"id": "credit_legal", "title": "征信与涉诉", "prefixes": ("credit.", "legal.", "lawsuit.")}, {"id": "transaction", "title": "交易结构与还款", "prefixes": ("facility.", "collection.", "repayment.")}, )