从0到1搭建AI智能支付风控助手Stage1-RAG知识库升级 — 元数据让检索更精准

目标:搭建一个能自动评估交易风险、查询商户信息、给出处置建议的AI风控助手

从最简单的RAG问答开始,一步步升级到完整的AI Agent

6个阶段 × AI核心能力

阶段 做什么 能学到什么AI能力 产出
Stage 1 RAG知识库升级(元数据过滤) RAG深度优化 增强版风控知识库
Stage 2 风控评估Prompt设计 Prompt工程、结构化输出 风控评估Prompt模板
Stage 3 模拟支付API工具调用 Function Calling原理 可调用工具的风控助手
Stage 4 ReAct多步推理 Agent架构、决策设计 完整风控Agent
Stage 5 效果评估与指标测量 效果评估三层体系 评估报告+指标
Stage 6 异常处理与成本控制 生产化思维 生产就绪版

前置准备

在终端执行以下命令创建项目目录和知识库文档:

bash 复制代码
mkdir -p /Users/salar/ai-risk-agent/docs

# ========== 知识库文档 ==========

cat > /Users/salar/ai-risk-agent/docs/交易限额规则.txt << 'EOF'
交易限额规则

## 单笔交易限额
- 借记卡:单笔5万元,单日20万元
- 贷记卡:单笔3万元,单日10万元
- 二维码支付:静态码单笔1000元,动态码单笔50万元
- 跨境交易:单笔等值5万美元,需额外审核

## 限额调整规则
- VIP商户可申请提升单笔限额至20万,需满足月交易额100万以上
- 提升限额需风控审批,审批周期3个工作日
- 临时提额有效期7天,到期自动恢复

## 超限处理
- 超出单笔限额:交易直接拒绝,返回错误码"EXCEED_SINGLE_LIMIT"
- 超出单日限额:当日后续交易拒绝,返回错误码"EXCEED_DAILY_LIMIT"
- 疑似拆分交易:多笔接近限额的交易在1小时内出现,触发风控告警
EOF

cat > /Users/salar/ai-risk-agent/docs/商户风险管理.txt << 'EOF'
商户风险管理

## 风险等级分类
商户风险等级分为A/B/C/D四级:
- A级:低风险,历史无欺诈记录,交易稳定
- B级:一般风险,偶发退款率偏高,需关注
- C级:较高风险,退款率超过15%,或存在投诉集中
- D级:高风险,存在欺诈嫌疑或已被列入黑名单

## 风险等级触发条件
- 退款率超过15%:降级为C级
- 单月投诉超过10笔:降级为C级
- 存在欺诈嫌疑(如虚假交易、套现):降级为D级
- 关联黑名单商户(同法人/同IP):降级为D级

## 各等级管控措施
- A级:正常结算T+1,无特殊限制
- B级:正常结算T+1,大额交易(单笔>5万)需人工复核
- C级:降级为T+1结算(如原为T+0则取消),所有大额交易需人工审核,月度巡检
- D级:暂停收单权限,冻结账户余额,启动调查流程,关联商户同步监控

## 黑名单机制
- 失信商户自动加入黑名单,禁止新增交易
- 黑名单商户关联的法人/联系人同步监控
- 黑名单解除需提交申诉材料,审核周期5个工作日
- 申诉通过后降为C级观察,观察期3个月
EOF

cat > /Users/salar/ai-risk-agent/docs/异常交易识别.txt << 'EOF'
异常交易识别

## 行为类异常
- 同一设备1小时内发起超过50笔交易
- 同一IP地址关联超过10个不同商户号
- 交易金额频繁出现在999、4999、9999等接近限额的整数
- 凌晨2-5点高频交易(正常营业时间外)
- 同一商户1小时内相同金额交易超过5笔

## 资金类异常
- 单日退款金额超过交易额的30%
- 退款后立即在同一商户重新交易
- 大额交易后立即小额交易测试
- 资金快进快出:入账后2小时内转出比例超过80%

## 模式类异常
- 拆分交易:将一笔大额交易拆成多笔小额,规避限额
- 循环交易:A→B→C→A的资金闭环
- 预付卡套现:用信用卡购买预付卡再变现
- 虚假退款:实际未发生退款但系统记录退款

## 告警分级
- 一级告警(黄色):单条规则触发,自动记录,次日人工复核
- 二级告警(橙色):2条以上规则同时触发,实时通知风控人员
- 三级告警(红色):3条以上规则触发或涉及D级商户,立即冻结交易
EOF

cat > /Users/salar/ai-risk-agent/docs/反洗钱规则.txt << 'EOF'
反洗钱规则

## 大额交易报告
- 单日现金交易超过5万元人民币,需向反洗钱中心报告
- 单笔转账超过50万元人民币,需提交大额交易报告
- 跨境交易单笔超过等值1万美元,需提交大额申报

## 可疑交易识别
- 资金快进快出:入账后2小时内转出比例超过80%
- 交易规模与商户经营规模明显不符
- 无真实贸易背景的频繁资金划转
- 利用多个账户分散转入、集中转出
- 频繁更换结算账户

## KYC要求
- 商户入网需完成KYB认证(企业资质审核)
- 个人商户需身份证+人脸识别+银行卡三要素验证
- 企业商户需营业执照+法人身份证+对公账户验证
- 跨境商户需额外提供进出口资质或MSO牌照

## 客户尽职调查
- 高风险客户:每6个月进行一次尽职调查更新
- 中等风险客户:每年进行一次尽职调查更新
- 低风险客户:每2年进行一次尽职调查更新
- 政治公众人物(PEP):需经高级管理层审批后方可建立业务关系
EOF

cat > /Users/salar/ai-risk-agent/docs/结算规则.txt << 'EOF'
结算规则

## 结算周期
- T+0结算:交易日当天完成资金划拨,需支付机构垫资,仅限A级商户
- T+1结算:交易日次日划款到商户账户,为默认结算方式
- D+1结算:按自然日次日结算,不区分工作日和节假日
- T+0升T+1:C级商户自动从T+0降级为T+1

## 结算模式
- 净额结算:对当日所有交易进行轧差,仅划转净差额,适用于收单场景
- 全额结算:每笔交易单独结算,适用于大额交易场景
- 分账结算:平台分账比例上限30%,分账方不超过5个,必须在交易成功后发起

## 对账规则
- 每日T+1自动对账,核对支付通道流水与商户订单
- 差异订单需在3个工作日内完成核查与调账
- 长尾差异(单笔<1元)可按月汇总处理
- 对账失败超过3次自动告警

## 手续费
- 借记卡:0.5%(上限20元)
- 贷记卡:0.6%(无上限)
- 跨境交易附加:0.3%跨境费
- T+0垫资费:0.1%/笔
EOF

Stage 1: RAG知识库升级 --- 元数据让检索更精准

能理解什么

之前RAG实操项目里,文档是"裸"的------检索时只能靠语义相似度。但真实场景中,知识库可能有几百份文档,涵盖不同类别。加元数据(metadata)后,可以先按类别过滤,再做语义检索,大幅提升准确率。

这就是非常理解的:RAG优化不只是算法的事,数据组织方式同样重要。

运行代码

bash 复制代码
cat > /Users/salar/ai-risk-agent/stage1_rag.py << 'PYEOF'
import os
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings, Document
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.zhipuai import ZhipuAI

Settings.embed_model = HuggingFaceEmbedding(model_name="/Users/salar/bge-small-zh-v1.5")
Settings.llm = ZhipuAI(model="glm-4-flash", temperature=0.1, api_key=os.environ.get("ZHIPUAI_API_KEY"))

# ====== 1. 加载文档并添加元数据 ======
print("正在加载文档...")
doc_dir = "/Users/salar/ai-risk-agent/docs"
raw_docs = SimpleDirectoryReader(doc_dir).load_data()

# 给每个文档添加类别元数据
category_map = {
    "交易限额规则": "限额规则",
    "商户风险管理": "商户风险",
    "异常交易识别": "异常识别",
    "反洗钱规则": "反洗钱",
    "结算规则": "结算",
}
for doc in raw_docs:
    for key, cat in category_map.items():
        if key in doc.metadata.get("filename", ""):
            doc.metadata["category"] = cat
            break
    else:
        doc.metadata["category"] = "其他"

print(f"加载了 {len(raw_docs)} 个文档")
for doc in raw_docs:
    print(f"  - {doc.metadata.get('filename', 'unknown')} → 类别: {doc.metadata.get('category')}")

# ====== 2. 分块并保留元数据 ======
splitter = SentenceSplitter(chunk_size=256, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(raw_docs)
print(f"\n分块后节点数: {len(nodes)}")
for i, node in enumerate(nodes[:3]):
    print(f"  节点{i}: 类别={node.metadata.get('category')} | 内容={node.text[:50]}...")

# ====== 3. 构建向量索引 ======
print("\n正在构建向量索引...")
index = VectorStoreIndex(nodes)

# ====== 4. 对比:无过滤 vs 手动过滤 ======
test_queries = [
    "商户单笔交易限额是多少",
    "退款率高的商户怎么处理",
    "什么是拆分交易",
]

print("\n" + "="*60)
print("【对比测试:无过滤检索 vs 按类别过滤后检索】")
print("="*60)

for query in test_queries:
    print(f"\n{'─'*50}")
    print(f"❓ 问题: {query}")
    
    # 无过滤:纯语义检索
    retriever = index.as_retriever(similarity_top_k=3)
    all_nodes = retriever.retrieve(query)
    print(f"\n  [无过滤] Top3 来源类别: {[n.metadata.get('category') for n in all_nodes]}")
    print(f"  Top1 相似度: {all_nodes[0].score:.4f} | 内容: {all_nodes[0].text[:80]}...")
    
    # 手动过滤:只保留相关类别的结果
    # 先判断这个问题最相关哪个类别
    query_category_map = {
        "限额": "限额规则",
        "退款": "商户风险",
        "拆分": "异常识别",
    }
    target_cat = None
    for kw, cat in query_category_map.items():
        if kw in query:
            target_cat = cat
            break
    
    if target_cat:
        filtered_nodes = [n for n in all_nodes if n.metadata.get("category") == target_cat]
        if filtered_nodes:
            print(f"  [过滤后] 只保留类别={target_cat},共{len(filtered_nodes)}条")
            print(f"  Top1 相似度: {filtered_nodes[0].score:.4f} | 内容: {filtered_nodes[0].text[:80]}...")
        else:
            print(f"  [过滤后] 类别={target_cat} 无结果,需放宽过滤条件")
    else:
        print(f"  [过滤后] 无法自动判断类别,回退到无过滤")

# ====== 5. 生成答案对比 ======
print("\n\n" + "="*60)
print("【最终答案对比:有没有元数据引导,答案质量差多少】")
print("="*60)

query = "退款率超过15%的商户会怎样"
print(f"\n❓ 问题: {query}")

# 用完整知识库回答
engine_all = index.as_query_engine(similarity_top_k=3)
resp_all = engine_all.query(query)
print(f"\n[全库检索回答]\n{resp_all}")

# 只用商户风险相关文档回答
merchant_nodes = [n for n in nodes if n.metadata.get("category") == "商户风险"]
if merchant_nodes:
    merchant_index = VectorStoreIndex(merchant_nodes)
    engine_filtered = merchant_index.as_query_engine(similarity_top_k=3)
    resp_filtered = engine_filtered.query(query)
    print(f"\n[过滤后检索回答]\n{resp_filtered}")

print("\n" + "="*60)
print("💡 思考:两个回答有什么区别?过滤后的回答是否更聚焦、更少无关信息?")
print("💡 这就是元数据的价值------在检索前缩小范围,减少噪音干扰。")
print("💡 在生产环境中,知识库可能有几百份文档,元数据过滤是必需品而非锦上添花。")
PYEOF
bash 复制代码
cd /Users/salar/ai-risk-agent && python stage1_rag.py

跑完之后对比数据,会有不少的差异

  1. 无过滤检索时,Top3结果里会有来自不相关类别的(比如问限额,结果里混进了结算规则)
  2. 过滤后的答案更精准,也没有丢失信息。
  3. 目前测试的知识库只有一点点数据,如果知识库从5份变成500份,元数据过滤的价值会很大。
    元数据过滤三大落地价值(结合本次测试印证)
  • 降噪:从源头屏蔽跨业务无关文档,避免结算、反洗钱内容干扰限额 / 商户风险类问题;
  • 聚焦:生成回答只保留对应分类核心信息,减少额外无关业务条款,回答简洁精准;
  • 性能优化:参与向量计算的节点变少,知识库越大,检索耗时优化越明显。

那结合着之前RAG实操的,整体的流程就完整了

  • 文件 → unstructured 解析文本 → LlamaIndex 语义分块 → 人工自定义绑定 metadata (category 等) → LlamaIndex 调用 sentence-transformers 生成向量 → 写入 Qdrant 向量库 → 检索时元数据过滤 + 向量相似度召回 → 组装上下文给大模型输出答案
相关推荐
武子康1 小时前
调查研究-199 MCP Zero-Touch OAuth:为什么它是 MCP 进入企业生产的关键门槛?
人工智能·agent·mcp
冬奇Lab1 小时前
每日一个开源项目(第144篇):ai-website-cloner-template - 一条命令、多 Agent 并行,把任意网站逆向成 Next.js 代码
前端·人工智能·开源
冬奇Lab1 小时前
AI 原生组织不是买工具,而是让等待消失
人工智能·工作流引擎
半个落月1 小时前
从数据集划分理解大模型的数据工程
人工智能
用户8299792943932 小时前
一文带你彻底搞懂claude code中的上下文压缩
人工智能
IT_陈寒2 小时前
Vue的这个响应式陷阱让我熬到凌晨三点
前端·人工智能·后端
冬奇Lab12 小时前
Workflow 系列(01):基础理论——三种执行模型与 Anthropic 5 种模式
人工智能·agent·工作流引擎
冬奇Lab12 小时前
每日一个开源项目(第143篇):page-agent - 纯 JS 的网页 GUI Agent,无需截图、无需插件、无需后端
前端·人工智能·agent
程序员cxuan14 小时前
虽迟但到!GPT-5.6 终于来了!
人工智能·后端·程序员