feat: 添加风险规则及 agent assets 功能增强

This commit is contained in:
caoxiaozhu
2026-05-19 16:19:03 +00:00
parent d460ee0fe7
commit 54ffef66d3
52 changed files with 26036 additions and 25171 deletions

View File

@@ -177,16 +177,16 @@ SLOT_LABELS = {
}
DATE_TEXT_PATTERN = re.compile(r"(\d{4}[年/-]\d{1,2}[月/-]\d{1,2}日?)")
AMOUNT_TEXT_PATTERN = re.compile(
r"(\d+(?:\.\d+)?)\s*(?:万元|万员|万圆|万园|万块|万元整|元整|块钱|块|元|员|圆|园|万)"
)
AMOUNT_TEXT_PATTERN = re.compile(
r"(\d+(?:\.\d+)?)\s*(?:万元|万员|万圆|万园|万块|万元整|元整|块钱|块|元|员|圆|园|万)"
)
DOCUMENT_AMOUNT_PATTERN = re.compile(
r"(?:价税合计|合计金额|费用合计|订单(?:总)?金额|支付(?:金额)?|实付(?:金额)?|实收(?:金额)?|总(?:额|计|价)|票价|金额|车费|消费金额)"
r"[:\s¥¥人民币]*([0-9]+(?:[.,][0-9]{1,2})?)"
)
DOCUMENT_CURRENCY_AMOUNT_PATTERN = re.compile(r"[¥¥]\s*([0-9]+(?:[.,][0-9]{1,2})?)")
SOURCE_LABELS = {
SOURCE_LABELS = {
"user_text": "用户描述",
"user_form": "用户修改",
"ocr": "票据识别",
@@ -215,7 +215,7 @@ INFERRED_REASON_LABELS = {
"welfare": "员工福利",
"other": "其他费用",
}
SYSTEM_GENERATED_REASON_PREFIXES = (
SYSTEM_GENERATED_REASON_PREFIXES = (
"我上传了",
"请按当前已识别信息",
"请把当前上传的票据",
@@ -225,20 +225,20 @@ SYSTEM_GENERATED_REASON_PREFIXES = (
"我已修改识别信息",
"查看报销草稿",
"请解释一下当前这笔报销的合规风险和待补充项",
)
AMOUNT_UNIT_ALIASES = {
"": "",
"": "",
"": "",
"": "",
"块钱": "",
"元整": "",
"万员": "万元",
"万圆": "万元",
"万园": "万元",
"万块": "万元",
"万元整": "万元",
}
)
AMOUNT_UNIT_ALIASES = {
"": "",
"": "",
"": "",
"": "",
"块钱": "",
"元整": "",
"万员": "万元",
"万圆": "万元",
"万园": "万元",
"万块": "万元",
"万元整": "万元",
}
class UserAgentService:
@@ -1742,7 +1742,7 @@ class UserAgentService:
if is_submitted:
body = (
f"主题:{subject}\n"
f"结论:报销单已提交,当前节点为 {approval_stage or '审批中'}\n"
f"结论:报销单已提交,当前节点为 {approval_stage or '审批中'}\n"
"建议:后续可在个人报销列表中跟踪审批进度,必要时再补充说明或附件。\n"
f"原始问题:{payload.message}"
)
@@ -2381,7 +2381,7 @@ class UserAgentService:
if review_action == "next_step":
if draft_payload is not None and draft_payload.status == "submitted":
stage_text = draft_payload.approval_stage or "审批中"
return f"报销单 {draft_payload.claim_no or ''} 已提交,当前节点为 {stage_text}".strip()
return f"报销单 {draft_payload.claim_no or ''} 已提交,当前节点为 {stage_text}".strip()
if payload.tool_payload.get("submission_blocked"):
return str(payload.tool_payload.get("message") or "").strip() or "当前报销单暂时还不能提交审批。"
return (
@@ -2947,19 +2947,19 @@ class UserAgentService:
"expense_type_code": "",
}
participants: list[str] = []
for item in payload.ontology.entities:
if item.type == "employee" and not values["employee_name"]:
values["employee_name"] = item.value
elif item.type == "customer" and not values["customer"]:
values["customer"] = item.value
elif item.type == "amount" and item.role != "threshold" and not values["amount"]:
normalized_amount = str(item.normalized_value or "").strip()
values["amount"] = f"{normalized_amount}" if normalized_amount else item.value
elif item.type == "expense_type" and not values["expense_type_code"]:
values["expense_type_code"] = item.normalized_value
values["expense_type"] = EXPENSE_TYPE_LABELS.get(
item.normalized_value,
item.value,
for item in payload.ontology.entities:
if item.type == "employee" and not values["employee_name"]:
values["employee_name"] = item.value
elif item.type == "customer" and not values["customer"]:
values["customer"] = item.value
elif item.type == "amount" and item.role != "threshold" and not values["amount"]:
normalized_amount = str(item.normalized_value or "").strip()
values["amount"] = f"{normalized_amount}" if normalized_amount else item.value
elif item.type == "expense_type" and not values["expense_type_code"]:
values["expense_type_code"] = item.normalized_value
values["expense_type"] = EXPENSE_TYPE_LABELS.get(
item.normalized_value,
item.value,
)
elif item.type in {"participant", "person"} and item.value.strip():
participants.append(item.value.strip())
@@ -3189,7 +3189,24 @@ class UserAgentService:
evidence="来源于用户修改后的结构化表单。",
)
inferred_reason = self._infer_reason_from_claim_groups(
claim_groups=claim_groups,
)
reason_value = self._resolve_reason_text(self._resolve_reason_source_text(payload))
if inferred_reason:
return self._build_slot_value(
value=inferred_reason,
raw_value=reason_value or inferred_reason,
normalized_value=inferred_reason,
source="ocr",
confidence=0.82,
evidence=(
"系统已根据票据识别结果预置场景类型;原始描述仍保留为补充说明。"
if reason_value
else "系统已根据票据识别场景补全通用事由,若需更具体说明可继续修改。"
),
)
if reason_value:
return self._build_slot_value(
value=reason_value,
@@ -3199,19 +3216,6 @@ class UserAgentService:
confidence=0.76,
evidence="系统从用户原始描述中提取了本次费用事由,建议继续核对。",
)
inferred_reason = self._infer_reason_from_claim_groups(
claim_groups=claim_groups,
)
if inferred_reason:
return self._build_slot_value(
value=inferred_reason,
raw_value=inferred_reason,
normalized_value=inferred_reason,
source="ocr",
confidence=0.68,
evidence="系统已根据票据识别场景补全通用事由,若需更具体说明可继续修改。",
)
return self._build_slot_value()
def _build_amount_slot(
@@ -3358,17 +3362,17 @@ class UserAgentService:
return self._build_slot_value()
@staticmethod
def _normalize_amount_text(value: str) -> str:
cleaned = str(value or "").strip()
if not cleaned:
return ""
for alias, canonical in sorted(AMOUNT_UNIT_ALIASES.items(), key=lambda item: len(item[0]), reverse=True):
cleaned = cleaned.replace(alias, canonical)
match = AMOUNT_TEXT_PATTERN.search(cleaned)
if not match:
return cleaned
number = float(match.group(1))
return f"{number:.2f}"
def _normalize_amount_text(value: str) -> str:
cleaned = str(value or "").strip()
if not cleaned:
return ""
for alias, canonical in sorted(AMOUNT_UNIT_ALIASES.items(), key=lambda item: len(item[0]), reverse=True):
cleaned = cleaned.replace(alias, canonical)
match = AMOUNT_TEXT_PATTERN.search(cleaned)
if not match:
return cleaned
number = float(match.group(1))
return f"{number:.2f}"
@staticmethod
def _normalize_expense_type_input(value: str) -> tuple[str, str]: