20年老程序员×AI:2小时搭建社保智能客服系统实战
20年老程序员×AI:2小时搭建社保智能客服系统实战
一、背景
去年用 Python 现学现卖做了一个社保知识 RAG 问答系统——用 Milvus 向量库 + Ollama(BGE-M3) + DeepSeek,用户问政策,系统从知识库里找最像的问题喂给大模型回答。
跑了一段时间发现不对——用户不只是想"问问题",他们想办事:查养老金发了多少、转移社保关系、开参保证明、异地就医备案……这就需要从"单轮 QA"变成"有状态的多轮 Agent"。
下面是我和 AI 一起从头搭建这个 Agent 系统的完整过程。先说结论:一个人,2-3 小时,12 个业务类型,后端+前端+语音+HTTPS 全栈。传统开发哪个 10 人团队不得干两周?
二、核心对话实录
回合1:桩/真分层——开发效率的第一刀
我:后端接口现在先全部用桩模块实现……LLM、Milvus、OCR 真实,人脸服务、业务 API 桩模拟。
这个需求背后的经验判断:如果全真,卡在联调上一个服务不通整体跑不起来;如果全桩,验证不了核心链路。真/假分层后,LLM 的 Function Calling、向量检索、OCR 身份证识别走真实服务,验证核心能力;人脸和业务 API 走桩返回模拟数据,保证流程能从头跑到尾。
AI 改了 2 个文件:face_handler.py去掉 requests 调外部接口,固定返回通过;api_caller.py根据 business_id 返回对应的模拟 JSON。
回合2:字段校验失败跳 QA
我: 43103 输入后没有继续要身份证号,跳去回答问题了。
这是一个典型的边界条件 AI 自己想不到。用户办异地就医备案,我输入43103(不完整的身份证号),系统回答"您好,社会保障号码就是您的身份证号码……"——把残缺输入当成了提问。
原因是_handle_collecting里的逻辑:LLM 把43103识别为answer_question,校验失败后优先走了 QA 路径。修法很简单:校验失败一律返回纠错提示,不跳 QA。
# 去掉这个分支:# if intent == "answer_question":# return self._handle_qa(...)# 验证失败始终:return {"type": "text", "content": validated_value, "next_action": "collecting"}回合3:"办理"没有上下文
我: 系统推荐了"💡 您可能需要办理【异地就医备案】,回复’办理’即可开始",用户回"办理",系统却又让重新说办什么。
用户说"办理"两个字,LLM 不知道办什么——它没看见上一轮的推荐。修法:给 IntentEngine 的 LLM 调用注入最少最近 6 条对话历史作为上下文。
def_build_messages(self,system_content,user_text,session):messages=[{"role":"system","content":system_content}]history=session.get("history",[])ifhistory:forhinhistory[-6:]:messages.append({"role":h.get("role"),"content":h.get("content","")[:300]})messages.append({"role":"user","content":user_text})returnmessages加系统指令:“如果用户说办理、好的、可以等简短确认词,参考对话历史中最近一次推荐的业务来判定。”
效果:有历史→match_business,无历史→answer_question。
回合4:JSON 转卡片
我:前端用户直接看到 JSON,太不友好了。
业务 API(桩)返回的 JSON 被原样展示为代码块。加了一个renderJsonBlocks()函数,解析<pre><code class="language-json">块,转成 key-value 表格卡片。配了 40+ 英译中字段映射表:
varlabelMap={'id_card':'身份证号','monthly_pension':'月养老金','application_id':'申请编号','direct_settlement':'直接结算',...};嵌套 object 不再显示[object Object],转成 JSON 字符串展示。
回合5:聊天历史丢了
我:前端聊天记录历史丢了。
原来是每条新消息直接覆盖#resultdiv。改成追加模式——用户消息蓝色气泡右对齐,AI 回复白色气泡左对齐 + 头像,chat-history容器自动滚底。
回合6-9:语音按钮——4 次迭代踩坑
这是与讯飞 IAT SDK 集成时踩的坑,也是整个开发过程里调最久的部分。
第一版需求:按住不放开始录音,松开提交文本,10 秒无语音自动放弃。
坑1:点了没反应。原因:btn.onclick = null之后才去读origClick,永远是 null → 先把原始点击保存了再清空。
坑2:一直"连接中…“,不到"录音中…”。原因:window.iatWS不存在——index.js里是let iatWS,不在 window 上 → 改typeof iatWS !== 'undefined'直接引用。
坑3:松开后文字为空。原因:松开→立即关 WebSocket,截断了正在回来的识别结果 → 改成松开只停录音,等 WebSocket 收到status=2自然关闭后延迟 300ms 再取文字。
坑4:同事反馈——“要考虑建立通道的时间,不能让客户白说” → 分两段:按住→"连接中…"→WebSocket 就绪→"录音中…"→现在可以说话了。
// 最终流程btn.innerText='连接中...';// 按下的瞬间// checkReady 轮询 iatWS.readyState === 1btn.innerText='录音中...';// 就绪后// 说话...// 松开 → '处理中...' → 等 close → 取文字 → 提交坑5:语音识别结果自带句号。讯飞识别结果 “北京市。” 被^\d{17}[\dXx]$校验拦下 → 加rstrip('\u3002...')去掉中文标点。顺便加了中文日期智能解析:“026年3月5号” →2026-03-05。
回合10:并发安全
我:要支持并发,多个客户的上下文不能混在一起。
Redis key 天然按agent:session:{user_id}隔离。FlowController 单例加双重检查锁:
_flow_lock=threading.Lock()def_get_flow():globalflowifflowisNone:with_flow_lock:ifflowisNone:flow=FlowController(...)returnflowSessionStore 加了 Redis 不可用时的内存回退,不依赖特定环境。
回合11:RAG 老说"根据上下文"
我:回答问题里面出现了根据上下文。
Prompt 调了一轮:从"不要提示根据提供的上下文"改成"严禁说根据上下文、参考以上信息、根据提供的内容之类的话,要像自己本来就知道一样直接回答"。顺手修了一个 bug——for 循环里的q变量名覆盖了用户原始问题。
回合12:可配置化——业务的灵魂
我:业务太少,还要有参保登记、个人信息修改、异地就医备案等——可配置化。
核心设计:业务规则不进代码,只改 YAML。字段定义、校验规则、API 映射全部配置化:
-id:"remote_medical"name:"异地就医备案"type:"handle"fields:-key:"medical_city"label:"就医城市"validate:"^[\\u4e00-\\u9fa5]{2,10}(?:市|区|县)?$"-key:"hospital"label:"就医医院"...require_confirm:trueconfig.yaml 里新增一个业务,api_caller.py加一段桩数据,前端bizNameMap补一行——不写任何业务逻辑代码。从 5 个业务扩到 12 个,纯加配置。
最终 12 个业务:养老金发放查询、缴费记录查询、养老金资格认证、社保关系转移、参保证明开具、参保登记、个人信息修改、异地就医备案、社保卡申领、医疗费用报销、失业保险金申领、生育保险申领。
回合13:身份证后置
我:一个销售同事提的——身份证号是敏感信息,应该最后再要求客户输入,容易获取信赖。我考虑后觉得合理。
12 个业务类型,凡是含id_number字段的,全部调到最后一个。用户先回答城市、医院、日期这些低敏感信息,建立信任感之后,最后才要身份证号。config.yaml 里重新排序即可,代码不动。
回合14:手机端语音需要 HTTPS
我:要有 HTTPS,要不手机端语音功能用不了——
getUserMedia在 HTTP 下被禁止。
OpenSSL 生成自签名证书,启动代码加双模式:
# 默认 HTTPS 443(手机端可用语音)# $env:FLASK_HTTP=1 → HTTP 5000(本地调试)ifos.environ.get("FLASK_HTTP")=="1":app.run(host='0.0.0.0',port=5000,debug=True)else:app.run(host='0.0.0.0',port=443,ssl_context=(cert_path,key_path),debug=True)三、关于 AI 写代码这件事
知道好的代码长什么样,让 AI 写。判断力比编码力更稀缺。
创造从"想要什么"开始,不是从"会什么"开始。AI 把"去找什么"的时间从 3 天压到 3 分钟而已。
四、技术栈
| 层 | 技术 |
|---|---|
| LLM | DeepSeek API (deepseek-chat) |
| 向量化 | Ollama (bge-m3) |
| 向量数据库 | Milvus |
| 会话存储 | Redis (30min TTL, 内存回退) |
| 意图识别 | Function Calling + 对话历史上下文 + 业务推荐 |
| 前端 | Flask + jQuery + AmazeUI + 讯飞 IAT/TTS + marked.js |
| HTTPS | Flask SSL + OpenSSL 自签名证书 |
人脸服务和业务 API 当前用桩实现,联调时替换即可。
五、结语
有人说 AI coding 提升效率 50%,有人说 10%。看看这个项目的实际数据:传统开发 11-17 天,实际 2-3 小时——效率提升至少 80%。原因有两个:
- 项目类型恰好是 AI 最强项:样板代码、配置驱动、CRUD、前端适配
- 经验差决定了 AI 的放大倍数:初级程序员不知道该做什么、不该做什么、哪里会出问题。经验让 AI 从"能跑的代码生成器"变成"精准的工具"
