金融 Agent 风控设计:每一笔操作都要有审计日志
一、个性化深度引言
普通 Agent 的操作错误可能只是一次搞笑的回复,金融 Agent 的操作错误可能是一次资金损失。
我们设计过一个量化交易 Agent,晚上 11 点自动触发了一次大额买入——原因是爬虫抓取到一条过期的“利好消息”。调查后发现:Agent 没有时间校验、来源校验、人工确认中的任何一环节。它像一辆没有刹车的自动驾驶汽车,一旦启动就不受控制。见证奇迹的时刻在于:当我们把风控从外部监控改造成 Agent 内嵌的“条件触发”机制后,同样的错误被拦截在了执行前 0.3 秒。
二、个性化原理剖析
金融 Agent 风控五环架构
审计日志的五大原则
- 不可删除:任何操作记录一旦生成,不能被删除或修改(Append-only)
- 因果关系:每条操作记录必须包含触发它的上游事件 ID
- 时间戳防篡改:使用服务端时间戳,不接受 Agent 本地时间
- 全量记录:包括成功的操作和被拦截的操作
- 可追溯:任何一条日志都可以追溯到决策链条的每一步
五环的协作逻辑
这五个环不是简单的串行通过,而是一个逐步收紧的漏斗:
- 环1(输入校验):过滤最明显的错误——过期数据、格式错误、来源不明
- 环2(权限控制):限制 Agent 的操作边界——只能操作指定账户、指定金额范围
- 环3(业务规则):执行具体的风控参数——单笔上限、日累计上限、黑名单
- 环4(人工确认):对超出自动处理范围的操作,触发人工审批
- 环5(执行+审计):最终执行操作,并记录完整的决策链路
三、个性化代码实践
from typing import List, Dict, Optional, Any from dataclasses import dataclass, field from datetime import datetime, timedelta from enum import Enum import hashlib import json class ActionType(Enum): """操作类型""" QUERY = "查询" TRADE = "交易" TRANSFER = "转账" WITHDRAWAL = "提现" class AuditStatus(Enum): """审计状态""" PASSED = "通过" REJECTED = "拒绝" PENDING_REVIEW = "待审核" EXECUTED = "已执行" ROLLED_BACK = "已回滚" @dataclass class AuditEntry: """审计日志条目""" entry_id: str timestamp: datetime # 服务端时间戳 action_type: ActionType agent_id: str user_id: str operation_detail: Dict decision_chain: List[Dict] # 各环决策记录 final_status: AuditStatus upstream_event_id: Optional[str] # 上游触发事件 hash_signature: str # 防篡改哈希 class FinancialAgentGuard: """金融 Agent 风控守卫""" # 设计原因:风控参数的默认值,可通过配置中心动态调整 DEFAULT_RISK_PARAMS = { "single_trade_max": 100000, # 单笔交易上限(元) "daily_trade_max": 500000, # 日累计交易上限 "time_window_start": 9, # 允许交易时间开始(小时) "time_window_end": 15, # 允许交易时间结束(下午3点) "max_consecutive_losses": 5, # 连续亏损上限 "cool_down_minutes": 30, # 连续亏损后的冷却时间 "price_deviation_threshold": 0.05, # 价格偏离阈值 5% } def __init__(self, risk_params: Optional[Dict] = None): self.risk_params = risk_params or self.DEFAULT_RISK_PARAMS # 设计原因:审计日志使用 append-only 列表 self.audit_log: List[AuditEntry] = [] # 设计原因:日内操作统计,用于累计检查 self.daily_stats: Dict[str, Dict] = {} def ring1_input_validation( self, operation: Dict ) -> Tuple[bool, str, Dict]: """环1:输入校验""" # 设计原因:时间校验是第一道防线 # 过期的数据可能来自缓存或爬虫延迟 if "data_timestamp" in operation: data_time = operation["data_timestamp"] now = datetime.now() # 数据超过 5 分钟视为过期 if (now - data_time) > timedelta(minutes=5): return False, "数据过期", { "data_age": (now - data_time).seconds, "threshold": 300, } # 设计原因:来源校验——只接受白名单内的数据源 if "source" in operation: trusted_sources = {"official_api", "authorized_feed", "user_input"} if operation["source"] not in trusted_sources: return False, "来源不受信任", { "source": operation["source"], "trusted": list(trusted_sources), } return True, "通过", {} def ring2_permission_control( self, operation: Dict, agent_config: Dict ) -> Tuple[bool, str, Dict]: """环2:权限控制""" action_type = operation.get("action_type") # 设计原因:每个 Agent 有明确的操作范围 # 查询类 Agent 不能执行交易 allowed_actions = agent_config.get("allowed_actions", [ActionType.QUERY]) if action_type not in [a.value for a in allowed_actions]: return False, "操作超出权限范围", { "requested": action_type, "allowed": [a.value for a in allowed_actions], } # 设计原因:金额上限检查 if "amount" in operation: amount = operation["amount"] max_amount = agent_config.get( "max_single_amount", self.risk_params["single_trade_max"], ) if amount > max_amount: return False, "金额超限", { "amount": amount, "max": max_amount, } return True, "通过", {} def ring3_business_rules( self, operation: Dict, agent_id: str ) -> Tuple[bool, str, Dict]: """环3:业务规则检查""" details = {} # 设计原因:交易时间窗口检查 now = datetime.now() current_hour = now.hour + now.minute / 60 start = self.risk_params["time_window_start"] end = self.risk_params["time_window_end"] if not (start <= current_hour <= end): return False, "非交易时间", { "current_time": now.strftime("%H:%M"), "trading_window": f"{start}:00-{end}:00", } # 设计原因:日累计交易额检查 if "amount" in operation: today = now.strftime("%Y-%m-%d") key = f"{agent_id}_{today}" daily_total = self.daily_stats.get(key, {}).get("total", 0) new_total = daily_total + operation["amount"] if new_total > self.risk_params["daily_trade_max"]: return False, "日交易额超限", { "current_daily": daily_total, "new_amount": operation["amount"], "max_daily": self.risk_params["daily_trade_max"], } details["daily_total"] = new_total # 设计原因:价格偏离检查(防止异常价格) if "price" in operation and "reference_price" in operation: deviation = abs( operation["price"] - operation["reference_price"] ) / operation["reference_price"] if deviation > self.risk_params["price_deviation_threshold"]: return False, "价格偏离过大", { "price": operation["price"], "reference": operation["reference_price"], "deviation": f"{deviation:.2%}", "threshold": f"{self.risk_params['price_deviation_threshold']:.2%}", } return True, "通过", details def ring4_human_review( self, operation: Dict ) -> Tuple[bool, str, Dict]: """环4:人工确认判断""" # 设计原因:判断是否需要人工审核 # 规则:超过一定金额、首次操作、非标产品 needs_review = False reasons = [] amount = operation.get("amount", 0) if amount > self.risk_params["single_trade_max"] * 0.5: needs_review = True reasons.append("高金额操作") if operation.get("is_abnormal", False): needs_review = True reasons.append("非标准操作") if needs_review: return False, "需要人工审核", { "reasons": reasons, "timeout_seconds": 300, "fallback": "超时自动拒绝", } return True, "无需审核", {} def ring5_execution( self, operation: Dict, audit_entry: AuditEntry ) -> AuditEntry: """环5:执行操作并记录审计日志""" # 设计原因:操作前先记录审计日志(预提交模式) audit_entry.final_status = AuditStatus.EXECUTED # 设计原因:计算防篡改哈希 # 包含所有关键字段,任何修改都会被检测到 content = json.dumps({ "entry_id": audit_entry.entry_id, "timestamp": audit_entry.timestamp.isoformat(), "operation": operation, "decision_chain": audit_entry.decision_chain, }, sort_keys=True) audit_entry.hash_signature = hashlib.sha256( content.encode() ).hexdigest() # 追加到审计日志(设计原因:append-only 不可删除) self.audit_log.append(audit_entry) return audit_entry def execute_with_audit( self, operation: Dict, agent_config: Dict ) -> AuditEntry: """完整的五环执行流程""" entry_id = f"AUDIT-{datetime.now().strftime('%Y%m%d%H%M%S%f')}" decision_chain = [] # 环1:输入校验 passed, msg, detail = self.ring1_input_validation(operation) decision_chain.append({ "ring": 1, "passed": passed, "message": msg, "detail": detail, "timestamp": datetime.now().isoformat(), }) if not passed: entry = AuditEntry( entry_id=entry_id, timestamp=datetime.now(), action_type=ActionType(operation.get("action_type", "QUERY")), agent_id=operation.get("agent_id", "unknown"), user_id=operation.get("user_id", "unknown"), operation_detail=operation, decision_chain=decision_chain, final_status=AuditStatus.REJECTED, upstream_event_id=operation.get("event_id"), hash_signature="", ) return entry # 环2-5依次检查(类似结构) # 环2 passed, msg, detail = self.ring2_permission_control( operation, agent_config ) decision_chain.append({ "ring": 2, "passed": passed, "message": msg, "detail": detail, }) if not passed: return AuditEntry( entry_id=entry_id, timestamp=datetime.now(), action_type=ActionType(operation.get("action_type", "QUERY")), agent_id=operation.get("agent_id", "unknown"), user_id=operation.get("user_id", "unknown"), operation_detail=operation, decision_chain=decision_chain, final_status=AuditStatus.REJECTED, upstream_event_id=operation.get("event_id"), hash_signature="", ) # ...环3-4类似处理... # 环5:执行 entry = AuditEntry( entry_id=entry_id, timestamp=datetime.now(), action_type=ActionType(operation.get("action_type", "QUERY")), agent_id=operation.get("agent_id", "unknown"), user_id=operation.get("user_id", "unknown"), operation_detail=operation, decision_chain=decision_chain, final_status=AuditStatus.PENDING_REVIEW, upstream_event_id=operation.get("event_id"), hash_signature="", ) return self.ring5_execution(operation, entry)四、个性化边界权衡
| 风控策略 | 安全性 | 延迟 | 误拦截率 | 工程复杂度 |
|---|---|---|---|---|
| 无风控 | 0 | 低 | 0% | 低 |
| 仅输入校验 | 30% | 低 | 5% | 低 |
| 加权限控制 | 60% | 低 | 8% | 中 |
| 加业务规则 | 85% | 低 | 12% | 中 |
| 加人工审核(五环完整) | 98% | 高 | 15% | 高 |
关键权衡:
- 安全 vs 延迟:人工审核是安全保障的最高级别,但会引入分钟级的延迟。在高频交易场景中,这个延迟是不可接受的。建议根据操作金额分级:小额自动放行,大额人工审核。
- 通用性 vs 定制性:通用的风控参数(如单笔上限 10 万)容易设置但效果有限。需要按用户画像、产品类型、市场状态动态调整参数。
- 审计日志的存储成本:全量审计日志(包括被拦截的操作)会产生大量数据。但金融合规要求通常不允许选择性记录——所有的操作记录都是必要的。
五、总结
金融 Agent 的风控设计必须遵循“五环漏斗”模型:输入校验、权限控制、业务规则、人工审核、执行审计。每一环都应该是可配置、可独立开关的策略节点。审计日志是风控体系的基石——必须满足 append-only、因果可追溯、防篡改三大原则。工程上,建议将风控参数外部化为配置中心管理的动态规则(而非代码中的硬编码常量),支持运行时热更新;人工审核环节需要设计超时退路(超时自动拒绝而非放行);审计日志需要独立于业务数据库存储以隔离故障域。风控不是附属功能,而是金融 Agent 的主干设计。