# 从 RAG 到 Agent:社保智能客服的进化(上)——意图识别与状态机
从 RAG 到 Agent:社保智能客服的进化(上)——意图识别与状态机
一、从上一篇说起
上一篇 我们搭了一套基于 Milvus + Ollama + DeepSeek 的 RAG 智能客服:用户问什么,系统从知识库检索相关内容,喂给大模型生成回答。一条直线:问题 → 向量化 → 检索 → 生成 → 回答。
部署之后开始真的有人用了,问题来了:很多人不是来问政策的,是来办事的。
“我要查养老金发了多少”——这不只是问答,是查询类业务,需要收集身份证号、调接口。
“我要把社保从杭州转到北京”——要收三个字段、确认、提交。
“帮我做个养老金资格认证”——要拍照、要人脸识别,光打字不够。
RAG 管得了"说",管不了"做"。所以我们要从 RAG 升级到 Agent。
这不是换模型,是加一层脑子。
二、Agent 比 RAG 多了什么
如果把 RAG 比作一个图书管理员——你问什么,他翻书告诉你——那 Agent 是一个柜台办事员:
-
判断你要干什么(问答、查询、还是办事)
-
一步步向你要材料(字段收集)
-
调用外部工具(拍照、人脸识别、调业务接口)
-
记住你跟他说过什么(多轮对话状态)
-
确认后提交,告诉你结果
技术对应:
| RAG | Agent |
|-----|-------|
| 无状态,一问一答 | 多轮会话状态机 |
| 单一 prompt | 按状态切换 Function Calling 工具集 |
| 只读:检索知识库 | 读写:收集信息 → 调 API → 返回结果 |
| 文本输入输出 | 文本 + 拍照 + 人脸 + 语音 |
三、整体架构
用户(文本/语音/拍照/人脸)
  │
  ▼
  ┌─────────────┐
  │ Flask 服务 │ ← JWT 鉴权 + HTTPS
  └──────┬──────┘
  │
  ┌──────┴──────────────────────────┐
  │ Agent Core │
  │ │
  │ ┌────────────┐ ┌────────────┐ │
  │ │ IntentEngine│ │FlowController│ │
  │ │ (意图识别) │─▶│ (状态机编排) │ │
  │ └─────┬──────┘ └──────┬─────┘ │
  │ │ │ │
  │ DeepSeek ┌───┴───┐ │
  │ Function │ │ │
  │ Calling SessionStore │ │
  │ (Redis/内存) │ │
  │ │ │
  │ ┌──────────┐ ┌──────────┐ │ │
  │ │PhotoHandler│ │ApiCaller │ │ │
  │ │(身份证OCR) │ │(业务接口) │ │ │
  │ └──────────┘ └──────────┘ │ │
  │ ┌──────────┐ │ │
  │ │FaceHandler│ │ │
  │ │(人脸核验) │ │ │
  │ └──────────┘ │ │
  └─────────────────────────────┘
  │
  ┌──────┴──────┐
  │ RAG 通道 │ ← 退路:知识问答
  │ (Milvus+LLM) │
  └─────────────┘
Agent 不是替代 RAG,是 RAG 的上层建筑。用户来了先走 Agent 流程;Agent 判断这只是一个知识问题,退回 RAG 通道处理。
四、Function Calling:三种上下文,三套工具
Agent 的"脑子"是 IntentEngine。它基于 OpenAI 兼容的 Function Calling,根据用户当前所处的状态,给 LLM 提供不同的工具集。
4.1 空闲状态(idle)——判断你要干什么
系统刚启动时,用户处于 idle 状态。LLM 有两把工具:
tools = [
  {
  "type": "function",
  "function": {
  "name": "match_business",
  "description": "识别用户想要办理或查询的社保业务类型",
  "parameters": {
  "properties": {
  "business_id": {
  "enum": ["pension_query", "insurance_record",
  "social_transfer", "card_apply", ...]
  }
  }
  }
  }
  },
  {
  "type": "function",
  "function": {
  "name": "answer_question",
  "description": "用户在进行社保知识问答咨询",
  "parameters": {
  "properties": {
  "suggested_business_id": {
  "description": "如果用户咨询暗示可能想办理某项业务,填写对应ID"
  }
  }
  }
  }
  }
]
关键设计:answer_question 里藏了一个 suggested_business_id。用户问"社保转移要什么材料",本质上是想办转移。LLM 回答完知识后,系统会自动附一句:“您可能需要办理社保关系转移,回复’办理’即可开始”。
这就是 Agent 比 RAG 多出来的主动性。
4.2 收集状态(collecting)——一步步要材料
进入业务办理流程后,LLM 的工具变成三把:
tools = [
  {"name": "provide_field"}, # 用户提供了字段值
  {"name": "cancel_flow"}, # 用户想取消
  {"name": "answer_question"}, # 用户问了无关问题
]
provide_field 不穷举字段。LLM 从当前 system prompt 里知道正在收集哪个字段,自行从用户输入里提取值。用户说了"320102199001011234",LLM 判断这是身份证号,自动填入。
对话历史也在上下文里。用户上一轮说了"我要查缴费记录",这一轮说"2020年",LLM 能结合历史推断这是开始年份。
4.3 确认状态(confirming)——确认 / 取消 / 修改
信息收齐后进入确认状态:
tools = [
  {"name": "confirm_submit"},
  {"name": "cancel_flow"},
  {"name": "modify_info", "parameters": {"field_key": "要修改的字段"}},
]
用户说"手机号写错了" → LLM 调用 modify_info → 系统跳回 collecting 状态让用户重填。用户说"确认" → confirm_submit → 调接口。
五、状态机:七种状态,一条流转线
FlowController 是一个有限状态机。业务类型定义了字段或步骤,控制器按顺序推进:
  ┌──────────────────┐
  │ idle │ ← 等待用户表达意图
  └────────┬─────────┘
  │ match_business
  ▼
  ┌──────────────────┐
  │ collecting │ ← 逐字段收集信息
  └────────┬─────────┘
  │ 字段收齐
  ┌────────┴─────────┐
  ▼ ▼
  ┌──────────────┐ ┌──────────────────┐
  │ confirming │ │ step_executing │ ← 多步流程(拍照/人脸)
  └──────┬───────┘ └────────┬─────────┘
  │ confirm_submit │ 步骤完成
  ▼ ▼
  ┌──────────────────────────────────┐
  │ executing │ ← 调用业务API
  └────────────────┬─────────────────┘
  │
  ▼
  ┌──────────────────┐
  │ done │ ← 返回结果
  └──────────────────┘
两种流程分支:
-
字段流(fields):大部分业务走这条路。养老金查询收身份证号,社保转移收四个字段,一个个来。
-
步骤流(steps):需要拍照或人脸的业务(如养老金资格认证),先走身份证 OCR,再走人脸核验,最后确认提交。
状态机的好处是任何时候用户说"取消",都能从任意状态跳回 idle。不会出现"卡在流程里退不出来"的情况。
六、Session 管理:记住每一轮说过什么
RAG 是无状态的——每个请求独立,不记得上一句说了什么。Agent 必须记住上下文。
SessionStore 用 Redis 存储每个用户的会话(Redis 不可用时自动降级为内存存储),TTL 30 分钟。每个 session 包含:
{
  "user_id": "user_001",
  "state": "collecting", # 当前状态
  "business_id": "social_transfer", # 当前业务
  "current_field_index": 2, # 收到第几个字段了
  "collected": { # 已收集的字段值
  "from_city": "杭州",
  "to_city": "北京"
  },
  "history": [ # 对话历史(取最近6轮送LLM)
  {"role": "user", "content": "我要转社保"},
  {"role": "assistant", "content": "好的,请提供转出城市..."},
  {"role": "user", "content": "杭州"},
  {"role": "assistant", "content": "请提供转入城市..."},
  ],
  "step_results": {}, # 拍照/人脸的结果
}
对话历史只截取最近 6 轮送入 LLM prompt,控制 token 消耗的同时保持足够的上下文连贯性。
七、Config 驱动:加业务不加代码
这是整个设计里最值钱的决定。所有 12 种业务类型、字段定义、校验规则、API 映射,全在 config.yaml 里:
business_types:
  - id: "social_transfer"
  name: "社保关系转移"
  type: "handle"
  keywords: ["社保转移","跨省转移","换工作","关系转移"]
  fields:
  - key: "from_city"
  label: "转出城市"
  type: "city_select"
  prompt: "请输入您原来参保的城市"
  validate: "^[u4e00-u9fa5]{2,10}(?:市|区|县)?$"
  error_msg: "请输入正确的城市名称"
  - key: "to_city"
  label: "转入城市"
  type: "city_select"
  prompt: "请输入您要转入的城市"
  - key: "id_number"
  label: "身份证号"
  type: "idcard"
  prompt: "请输入您的身份证号"
  - key: "name"
  label: "姓名"
  type: "text"
  prompt: "请输入您的姓名"
  api:
  url: "${SOCIAL_TRANSFER_URL}"
  method: "POST"
  timeout: 30
  require_confirm: true
  confirm_text: "请确认以上信息无误后,点击确认提交社保关系转移申请。"
要新增一种业务?加一段 YAML。不动一行 Python 代码。字段类型(idcard、phone、year、enum、city_select)是内置校验器,idcard 甚至做了校验位计算。keywords 辅助 LLM 做意图匹配,prompt 是发给用户的话术,require_confirm 控制是否需要二次确认。
这个设计让 Agent 的业务方可以自己配,不需要每次加业务都找开发改代码。落地场景里,这点比技术本身重要。
八、业务推荐:从问答到办事的无缝衔接
再回头说 suggested_business_id 这个设计。
用户在 idle 状态问了一个知识类问题——比如"社保转移需要什么材料"——LLM 调用 answer_question,同时判断这个问题暗示了 social_transfer 业务。系统正常走 RAG 生成回答,但在回答末尾自动追加:
💡 您可能需要办理【社保关系转移】,回复"办理"即可开始。
用户回一句"办理",LLM 看到对话历史里有上一次推荐的业务,直接匹配 match_business。从"咨询"到"办事",用户不需要重新描述一遍需求。
这个看起来小的功能,在体验上是质变——从被动答疑变成了主动引导。
九、关键代码:IntentEngine 的一轮调用
def _call_llm(self, messages, tools):
  payload = {
  "model": "deepseek-chat",
  "messages": messages,
  "tools": tools,
  "tool_choice": "auto",
  "temperature": 0.3,
  "max_tokens": 4096
  }
  resp = requests.post(self._url, json=payload, headers=self._headers, timeout=60)
  result = resp.json()
 
  choice = result["choices"][0]
  msg = choice.get("message", {})
 
  if msg.get("tool_calls"):
  tool_call = msg["tool_calls"][0]
  func_name = tool_call["function"]["name"]
  func_args = json.loads(tool_call["function"]["arguments"])
  return {
  "intent": func_name,
  "arguments": func_args,
  "raw_message": msg.get("content", "")
  }
 
  # LLM 没调工具,当普通问答处理
  return {
  "intent": "answer_question",
  "arguments": {"question": msg.get("content", "")},
  }
DeepSeek 的 Function Calling 完全兼容 OpenAI 格式。tool_choice: "auto" 让模型自己决定调不调工具、调哪个。如果 LLM 正常回了一句文本而不是 tool_call,说明它认为不需要工具——退回 RAG 通道处理。
十、小结
上篇讲的是 Agent 的"脑子":
-
Function Calling 按状态分三套工具——idle 判断意图,collecting 收集字段,confirming 确认提交
-
七态状态机——从 idle 到 done,支持字段流和步骤流两种分支,任何时候可取消
-
Session 持久化——Redis 存状态,对话历史送 LLM,30 分钟 TTL
-
Config 驱动——加业务只加 YAML,不动代码
-
业务推荐——问答可无缝转化为办事
下篇我们看 Agent 怎么"伸手够真实世界"——身份证 OCR、人脸核验,以及一个完整案例从头走到尾。
相关链接
- DeepSeek API 文档:https://platform.deepseek.com/api-docs
- 项目源码:https://github.com/xxx/si_agent
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)