总结

我给 AI 装了一个"大脑":从会聊天,到会干活

银行合规助手项目——Agent

2026-06-22

我给 AI 装了一个”大脑”:从会聊天,到会干活

上篇我们搭了一个 RAG 系统,让 AI 能从银行合规文档里找答案。 但很快我就发现,它只会回答问题——不会查客户信息,也不会提交申请。 这篇文章,我给它加上了真正的”行动能力”。

一、RAG 做不到什么

上次的系统上线后,我测试了几个问题:

“信用卡逾期30天会有什么后果?”

✅ 完美,从合规文档里检索出了答案,还标注了条款来源。

“帮我查一下 C002 客户的账户情况”

❌ AI 开始胡说了。它根本不知道 C002 是谁,只能靠”猜”。

“C002 逾期了,帮我提交一个减免申请”

❌ 更离谱,它编了一个假的申请单号,说”已提交”——但什么都没发生。

RAG 解决的是”知道什么”的问题,解决不了”能做什么”的问题。

这就是 RAG 和 Agent 的本质区别:


二、Agent 比 RAG 多了什么

多了两件事:意图判断工具调用

打个比方——

你走进银行,跟大堂经理说了一句话:

“C002 客户逾期了,帮我提交一个减免申请”

大堂经理需要先判断你想干什么(意图判断),然后去做对应的事(工具调用):

这个”判断 → 执行”的过程,就是 Agent 的核心逻辑。


三、意图路由:给 AI 装一个交通枢纽

在代码里,我把用户的问题分成三类:

用户的问题进来,先经过一个”路由器”——判断是哪种意图,再分发给对应的处理模块:

用户输入
    │
    ▼ 路由器(意图判断)
    ├── 知识型 ──→ RAG 检索 ──→ 带引用的合规回答
    ├── 查询型 ──→ 客户工具 ──→ 客户账户信息
    └── 操作型 ──→ 申请工具 ──→ 待审批申请单

关键问题是:路由器怎么判断意图?

我实现了两种方式,结论很有意思。


四、方式A:让模型自己决定调哪个工具

这是 OpenAI 推广的 Function Calling(工具调用) 方式。

原理:我给模型发一份”工具说明书”,告诉它有三个工具可用:

工具1:rag_search        —— "搜索合规知识库,回答规定类问题"
工具2:query_customer    —— "查询特定客户的账户信息"
工具3:submit_operation  —— "提交需要人工审批的操作申请"

然后把用户的问题一起发过去,让模型自己判断:“我要调用哪个工具,传什么参数?”

模型返回的不是一句话,而是一个结构化的指令:

{
  "tool": "query_customer",
  "arguments": {"customer_id": "C002"}
}

Python 读取这个指令,去查询 C002 的数据,再把结果返回给用户。

优点:参数提取准确,模型直接输出结构化数据。 缺点:依赖模型有没有被专门训练过这种格式——小模型经常”不配合”。


五、方式B:让模型输出 JSON,Python 手动解析

这是更稳妥的降级方案。

我不要求模型调用工具,只要求它输出一个 JSON,告诉我它的判断:

Prompt:根据用户输入判断意图,只输出JSON,格式如下:
{"intent": "知识型/查询型/操作型", "reason": "判断依据", "params": {...}}

用 Ollama 的 format: "json" 模式,强制模型输出合法 JSON(不加这个,小模型会输出花式乱码)。

模型输出:

{
  "intent": "操作型",
  "reason": "用户请求为特定客户提交逾期罚息减免申请",
  "params": {
    "operation_type": "逾期减免",
    "customer_id": "C002",
    "detail": "逾期罚息减免申请"
  }
}

Python 拿到这个 JSON,直接读 intent 字段,决定走哪个分支。

优点:几乎所有模型都能跟着 JSON 指令走,稳定性强。 缺点:多了一层解析代码,遇到模型输出奇怪内容还要兜底处理。


六、11 道测试题,两种方式正面 PK

我准备了 11 道测试题——4 道知识型、3 道查询型、2 道操作型,还故意加了 2 道模糊题

Q10 [模糊] C002 客户最近有没有逾期?如果逾期了按规定要怎么处理?
Q11 [模糊] 账户 C003 上个月还款正常,但他想申请一个利率优惠,帮我走一下流程

两种方式全部跑一遍,结果如下:

两种方式最终正确率相同,但过程差异明显:

tool_calling 有 4 道题”掉链子”了——模型没有调用工具,直接用自然语言回答了(比如 Q04「银行多少天回复投诉」、Q06「客户逾期了几天」)。

不过代码里做了降级兜底:检测到没有工具调用时,自动切换到 json_parse 重新判断,所以结果还是正确的。代价是这 4 道题多跑了一次模型,耗时多了约 800ms。

模糊题的处理方式不同:

Q10「逾期情况 + 怎么处理」—— - json_parse:模型输出”查询型和知识型”(不合法),触发降级,兜底为知识型(走 RAG) - tool_calling:模型选择了查询型(先查客户,再说规定)

Q11「申请利率优惠」——两种方式都判断为操作型,一致。


七、为什么操作型不直接执行

你可能注意到一个细节:当用户说”帮我提交减免申请”时,系统只是生成了一张申请单,而不是直接修改客户数据。

已生成申请单 [REQ-67517237],等待人工审批

这是故意设计的。

在金融场景里,任何涉及账户状态或资金流向的操作,都必须有人工复核这道关卡。原因很简单:

所以正确的设计是:

AI 负责 → 识别意图 + 整理信息 + 生成申请单
人工负责 → 审核 + 最终决策

这有个专业名词叫 Human-in-the-loop(人在环路中),是目前 AI 落地高风险业务的主流方案。


八、整体结构一眼看懂

用户输入
    │
    ▼ router.py(意图路由)
    │  方式A:tool_calling(发工具定义,模型自选)
    │  方式B:json_parse(要求输出JSON,Python解析)
    │
    ├── 知识型 ──→ RAG(chunking + embedding + vectorstore + LLM)
    │                → 带条款引用的合规回答
    │
    ├── 查询型 ──→ tools.py: query_customer_info("C002")
    │                → 客户姓名/额度/逾期状态/信用评分
    │
    └── 操作型 ──→ tools.py: submit_operation_request(...)
                     → 生成申请单,等待人工审批

整套系统: - 路由模型:本地 Ollama qwen2.5:7b(完全免费) - RAG 问答:同上,或切换 Anthropic Claude - Embedding:sentence-transformers(本地,免费) - 向量库:Chroma(本地文件,免费)

全程不需要 OpenAI,不需要任何云端 API Key,跑在自己电脑上。


九、学到了什么

Agent = 意图判断 + 工具调用。说起来简单,但每个环节都有细节要处理。

json_parse 在小模型上比 tool_calling 更稳。不是说 tool_calling 不好,而是小模型的工具调用训练不充分,遇到措辞平实的问题容易”跑偏”。

降级兜底比单点完美更重要。与其追求路由 100% 原生成功,不如把兜底链路做扎实——4 道触发降级的题,用户感知不到任何差异。

模糊问题是真正的分水岭。明确的问题,两种方式都能判对;模糊问题暴露了模型对”多意图”的处理策略差异。这部分没有标准答案,取决于你的业务希望在模糊时”偏向哪个意图”。


从 RAG 到 Agent,代码加了不到 300 行——tools.py、router.py、agent_main.py 三个文件。复杂度没有想象中高,核心是把每一步的职责想清楚。