深度复盘:某银行智能客服 Agent 上线首月的故障排查与性能优化

目标读者

有一定大模型应用落地基础(了解 LangChain/LlamaIndex 等 Agent 框架、RAG 检索、微服务部署),熟悉生产级系统监控与排障流程,负责过金融类 ToB/C 系统稳定性保障的中高级后端工程师、全栈工程师、AI 应用架构师、技术负责人;同时也面向想从实战角度了解大模型 Agent 从测试到生产踩坑的技术爱好者与初级工程师进阶。


引言

痛点引入

202X年是金融大模型应用的“落地元年下半场”——测试环境里能流畅处理95%以上测试用例、PPT里汇报得“神乎其神”的智能客服 Agent,一到真实的银行对公/对私混合业务场景、每天数十万次并发峰值、凌晨还有批量清算触发的API调用,就立刻“原形毕露”:

  • 对公客户经理查授信额度,明明数据库在、RAG源更新了,Agent却半天蹦出个“抱歉,我找不到相关的授信历史,请稍后再试”;
  • 老年用户用方言(带普通话混说)查信用卡还款日,系统要么“听不懂直接转人工”,要么连续转3次导致人工占线率飙升到40%以上;
  • 每月15号的信用卡账单日早上8点-10点,核心接口响应时间从测试的200ms直接涨到8s+,超时率破12%,客服工单量环比激增280%;
  • 更吓人的是,某个敏感的信用卡分期利率调整测试用例(仅在内部UAT用过)被泄露到了外部用户的查询结果里——虽然后续确认是“误加载了UAT分支的向量索引”,但差点引发监管层面的合规风险。

这些问题是不是特别“眼熟”?你可能也在自己的大模型应用上线首周/首月遇到过类似的“生产滑铁卢”:看似完美的“Demo级”产品,一到真实场景的压力、多样性、合规性三重考验下,就漏洞百出。

文章内容概述

作为牵头负责该银行智能客服Agent(代号“智融小宝”)从0.1版本测试到1.0版本稳定上线的AI架构师兼技术负责人,我将在本文用3.2万字+ 的篇幅,深度复盘智融小宝上线首月(202X.09.01-202X.09.30)遇到的12大类、37小类核心问题
敏感数据泄露的合规类问题,到方言识别准确率低、RAG检索漏检的功能性问题,再到核心接口超时、微服务雪崩、向量数据库OOM的性能类问题
不仅会还原每个问题的复现场景、定位过程(从ELK日志→Prometheus监控→Zipkin链路追踪→JVM堆栈分析→向量索引维度排查→LangChain源码调试的全链路排查思路),还会给出经过生产验证的、可落地的解决方案、最佳实践优化代码、以及系统改造后的监控数据对比
最后,还会总结金融类大模型Agent从测试到生产的“踩坑清单”和“避坑指南”,以及未来3-6个月智融小宝的优化方向

读者收益

读完本文,你将获得:

  1. 一套完整的、金融级生产级大模型Agent排障方法论(从问题分类→优先级划分→全链路工具链使用→根因分析框架);
  2. 37个经过生产验证的智融小宝首月故障解决方案(覆盖合规、功能、性能、运维四大维度);
  3. 金融类RAG+Agent的性能优化代码与实践经验(包括向量数据库索引优化、RAG检索链路压缩、LangChain Chain/Agent内存优化、大模型API调用限流与降级、微服务熔断与隔离);
  4. 敏感数据泄露的合规性保障机制与落地案例(从开发环境→测试环境→预发布环境→生产环境的全流程敏感数据隔离,向量数据的脱敏与加密,RAG源的版本控制与灰度发布);
  5. 方言识别、多模态意图理解(虽然本文主要复盘文本+语音转文本,但会带过)、知识召回优化的进阶技巧
  6. 一份金融类大模型Agent从测试到生产的“预上线检查清单”(我自己踩坑后整理的,共8大类、127小项)。

准备工作

技术栈/知识

在正式开始复盘之前,你需要对以下技术栈/知识有一定的了解(如果不熟悉也没关系,我会在相关章节做必要的解释):

  1. 大模型应用相关
    • Agent 框架:LangChain v0.1.x(智融小宝使用的是这个版本,后面会讲为什么没升级到v0.2.x/v1.x);
    • 检索增强生成(RAG):向量检索(FAISS索引优化、Milvus集群部署)、BM25关键词检索、HyDE(假设性文档嵌入)、 rerank(Cohere rerank API/智谱清言 rerank 本地模型);
    • 大模型API:智谱清言 GLM-4(对公业务用的是GLM-4-Pro,对私业务用的是GLM-4-Flash)、阿里云通义千问(备用API,防止智谱宕机);
    • 语音转文本(ASR):阿里云智能语音识别(对私)、腾讯云语音识别(对公,方言识别率更高);
  2. 后端微服务相关
    • 开发语言:Java 17(网关服务、知识库管理服务、计费服务)、Python 3.10(Agent服务、RAG服务、向量数据预处理服务);
    • 微服务框架:Spring Boot 3.x(Java微服务)、FastAPI 0.104.x(Python微服务);
    • 服务注册与发现:Nacos 2.3.x;
    • 服务网关:Spring Cloud Gateway 4.x;
    • 配置中心:Nacos Config 2.3.x;
    • 熔断与限流:Sentinel 1.8.x(Java微服务)、SlowAPI(Python微服务);
  3. 数据存储相关
    • 关系型数据库:MySQL 8.0(用户信息、对话历史、知识库元数据、计费数据);
    • 向量数据库:Milvus 2.3.x(主向量库,生产环境3节点集群)、FAISS(本地向量库,用于测试环境和预发布环境);
    • 文档存储:MinIO 2024.x(知识库原始文档、向量索引备份、脱敏后的对话记录);
    • 缓存:Redis 7.x(用户会话缓存、对话历史缓存、热点RAG源的元数据缓存、限流令牌桶缓存、大模型API调用结果缓存);
  4. 监控与排障相关
    • 日志采集与分析:ELK Stack(Elasticsearch 8.12.x、Logstash 8.12.x、Kibana 8.12.x)、Filebeat 8.12.x;
    • 指标监控:Prometheus 2.50.x、Grafana 10.4.x;
    • 链路追踪:Zipkin 3.3.x、Spring Cloud Sleuth 3.1.x(Java微服务)、OpenTelemetry Python SDK(Python微服务);
    • JVM监控:Arthas 3.7.x;
    • Python内存分析:memory-profiler、py-spy;
  5. 合规与安全相关
    • 金融数据合规:《个人信息保护法》(PIPL)、《数据安全法》(DSL)、《商业银行网络安全管理办法》;
    • 敏感数据脱敏:Java的MyBatis-Plus脱敏插件、Python的Faker库+自定义脱敏规则;
    • 加密传输:HTTPS/TLS 1.3;
    • 身份认证:OAuth 2.0 + JWT(对私)、OAuth 2.0 + SAML 2.0(对公客户经理登录内部系统)。

环境/工具

智融小宝的生产环境部署架构(后面会在进阶探讨章节详细讲系统架构设计)是基于阿里云Kubernetes(ACK) 部署的,如果你想在本地复现部分排障场景,你需要准备:

  1. 本地开发环境
    • 已安装 JDK 17、Maven 3.9.x、Python 3.10、Docker Desktop 4.28.x;
    • 已安装 VS Code/IntelliJ IDEA/PyCharm(推荐 IntelliJ IDEA Ultimate + PyCharm Professional,因为支持 Spring Boot + FastAPI 联合调试、Docker/Kubernetes 插件);
  2. 本地测试环境
    • 可以通过 Docker Compose 快速启动一套精简版的技术栈:
      • Nacos 2.3.x;
      • MySQL 8.0;
      • Redis 7.x;
      • Milvus 2.3.x Standalone;
      • MinIO 2024.x;
      • Elasticsearch 8.12.x + Kibana 8.12.x(可选,用于本地日志分析);
      • Prometheus 2.50.x + Grafana 10.4.x(可选,用于本地指标监控);
      • Zipkin 3.3.x(可选,用于本地链路追踪)。

核心内容:智融小宝上线首月的故障排查与性能优化全流程

0. 智融小宝的产品定位与核心业务流程(必须先看!)

在正式开始复盘故障之前,我必须先明确智融小宝的产品定位与核心业务流程——因为所有的故障都是基于这个场景产生的,脱离场景谈排障和优化都是“纸上谈兵”。

0.1 产品定位

智融小宝是某国有大型商业银行华东分行推出的首款混合式(对公+对私)智能客服Agent,主要分为两个版本:

  1. 智融小宝·对私版
    • 接入渠道:该行官方APP“华东银行”、官方微信公众号“华东银行微服务”、官方支付宝生活号“华东银行”;
    • 用户群体:该行所有对私客户(约1200万);
    • 核心业务:信用卡账单查询、信用卡还款日提醒、信用卡分期申请、借记卡余额查询、借记卡交易明细查询、理财产品推荐、理财产品购买预约、网点查询、营业时间查询、常见问题解答(FAQ);
    • 非核心业务:天气查询、笑话大全、诗词鉴赏(这部分是为了提升用户粘性,但金融类业务优先级最高);
    • 性能要求:核心接口(信用卡账单查询、借记卡余额查询、理财产品推荐)的P99响应时间≤2s,P95响应时间≤1s,P50响应时间≤300ms,超时率≤0.1%;非核心接口的P99响应时间≤5s,超时率≤1%;
    • 合规要求:所有涉及用户个人敏感信息(姓名、身份证号、手机号、银行卡号、CVV码、交易密码、授信额度、理财产品持有金额)的数据必须严格脱敏(在ELK日志、Zipkin链路追踪、Kibana指标监控里不能出现完整的敏感数据),向量数据库里的敏感数据必须加密存储,外部用户的查询结果里绝对不能出现内部UAT/PAT测试环境的敏感数据
  2. 智融小宝·对公版
    • 接入渠道:该行对公客户经理内部管理系统“华东公易通”;
    • 用户群体:该行华东分行所有对公客户经理(约2000人);
    • 核心业务:企业客户基本信息查询、企业客户授信额度查询、企业客户授信使用情况查询、企业客户贷款余额查询、企业客户贷款还款计划查询、企业客户存款余额查询、企业客户票据贴现利率查询、企业客户常见问题解答(对公FAQ);
    • 非核心业务:内部公文查询、内部培训视频查询(这部分优先级较低,但必须保证内部敏感数据不外泄);
    • 性能要求:核心接口的P99响应时间≤1.5s,P95响应时间≤800ms,P50响应时间≤200ms,超时率≤0.05%;
    • 合规要求:除了满足对私版的所有合规要求之外,不同支行的客户经理只能查询自己负责的企业客户的数据,必须实现严格的数据权限隔离
0.2 核心业务流程(以对私版用户查询信用卡账单为例)

为了让大家更好地理解后面的故障排查过程,我画了一张智融小宝对私版核心业务流程的时序图(用Mermaid绘制):

信用卡账单查询工具 RelDB/Cache 转人工服务(Java) 阿里云/腾讯云TTS服务(如果用户设置了语音播报) 智谱清言GLM-4/通义千问备用API Redis缓存 MySQL关系型数据库 Milvus向量数据库 RAG检索服务(Python) 智融小宝Agent服务(Python/LangChain) 阿里云/腾讯云ASR服务(如果用户用的是语音输入) 会话服务(Java) OAuth 2.0认证服务(Java) Spring Cloud Gateway(服务网关) 华东银行APP/微信公众号/支付宝生活号 用户 信用卡账单查询工具 RelDB/Cache 转人工服务(Java) 阿里云/腾讯云TTS服务(如果用户设置了语音播报) 智谱清言GLM-4/通义千问备用API Redis缓存 MySQL关系型数据库 Milvus向量数据库 RAG检索服务(Python) 智融小宝Agent服务(Python/LangChain) 阿里云/腾讯云ASR服务(如果用户用的是语音输入) 会话服务(Java) OAuth 2.0认证服务(Java) Spring Cloud Gateway(服务网关) 华东银行APP/微信公众号/支付宝生活号 用户 核心业务流程:用户查询信用卡账单(文本输入) alt [缓存命中] [缓存未命中] LangChain Agent开始处理请求 或者Agent直接调用内部工具(Tool):“信用卡账单查询工具”(使用LangChain的StructuredTool) 先查Redis缓存(TTL=10分钟,信用卡账单日/还款日附近TTL=5分钟) alt [缓存命中] [缓存未命中] alt [内部工具调用(智融小宝用的是这个)] [RAG检索SQL/API接口] alt [用户设置了语音播报] 如果Agent无法处理请求(意图识别失败、工具调用失败、返回结果不符合要求),自动转人工 alt [自动转人工] 输入文本:“帮我查一下我上个月的信用卡账单” 1 发送HTTP请求(携带JWT Token) 2 验证JWT Token的有效性 3 返回Token有效,携带用户ID(脱敏后的:138****1234) 4 获取用户的对话历史(如果有的话) 5 查询用户ID对应的对话历史缓存 6 返回对话历史(最近10条,脱敏后的) 7 查询用户ID对应的最近10条对话历史 8 返回对话历史(脱敏后的) 9 将对话历史写入缓存(TTL=30分钟) 10 返回用户ID、对话历史 11 发送HTTP请求(携带用户ID、对话历史、当前查询文本) 12 发送意图识别请求(使用ZeroShotAgent/OpenAIFunctionsAgent) 13 返回意图:“查询信用卡账单”,实体:“上个月” 14 发送RAG检索请求(查询:“查询信用卡账单的SQL语句/API接口文档”,如果是用结构化数据查询的话) 15 调用内部工具:“信用卡账单查询工具”,携带用户ID、实体“上个月” 16 返回脱敏后的信用卡账单数据 17 查询脱敏后的信用卡账单数据 18 返回数据 19 将数据写入缓存 20 返回数据 21 查询向量索引(最近5条相关的SQL/API文档) 22 返回向量检索结果 23 用Cohere/智谱清言rerank模型对结果进行重排序(Top3) 24 返回重排序后的Top3结果 25 发送请求,基于Top3结果生成SQL/API调用 26 返回SQL/API调用 27 执行SQL/API调用 28 返回数据 29 发送请求,基于对话历史、当前查询文本、返回的信用卡账单数据生成自然语言回复 30 返回自然语言回复(脱敏后的) 31 返回自然语言回复 32 发送自然语言回复 33 返回语音文件URL 34 返回自然语言回复+语音文件URL 35 显示自然语言回复,播放语音 36 返回自然语言回复 37 显示自然语言回复 38 发送转人工请求(携带用户ID、对话历史、当前查询文本、失败原因) 39 返回人工坐席排队信息 40 返回排队信息 41 显示排队信息 42
0.3 上线首月的核心数据指标(故障发生前vs故障排查优化后)

为了让大家更直观地看到故障排查和优化的效果,我先把上线首月第1周(故障高发期)上线首月第4周(优化稳定期) 的核心数据指标对比列出来(后面会在每个故障章节详细讲具体指标的变化):

指标类别 指标名称 第1周(故障高发期)数据 第4周(优化稳定期)数据 优化幅度
功能性指标 对私版意图识别准确率 87.2% 96.8% +9.6%
功能性指标 对公版意图识别准确率 82.5% 95.2% +12.7%
功能性指标 方言识别准确率(带普通话混说) 68.3% 91.5% +23.2%
功能性指标 RAG检索漏检率(对公FAQ) 18.7% 3.2% -15.5%
功能性指标 自动转人工率 22.3% 7.8% -14.5%
性能指标(对私版核心) P50响应时间 387ms 212ms -45.2%
性能指标(对私版核心) P95响应时间 1.87s 789ms -57.8%
性能指标(对私版核心) P99响应时间 8.23s 1.89s -77.0%
性能指标(对私版核心) 超时率 12.7% 0.08% -99.4%
性能指标(对公版核心) P50响应时间 298ms 178ms -40.3%
性能指标(对公版核心) P95响应时间 1.52s 698ms -54.1%
性能指标(对公版核心) P99响应时间 6.87s 1.45s -78.9%
性能指标(对公版核心) 超时率 8.3% 0.04% -99.5%
合规性指标 敏感数据泄露次数 1次(差点引发监管风险) 0次 -100%
运维指标 每日告警次数 327次 12次 -96.3%
运维指标 每日人工介入排障次数 17次 1次(主要是例行检查) -94.1%
业务指标 客服工单量环比(与上月纯人工比) +280% -32% -312%
业务指标 用户满意度 3.2分(满分5分) 4.7分(满分5分) +46.9%

1. 合规类故障:内部UAT测试数据泄露到外部用户查询结果

1.1 核心概念

在讲这个故障之前,我们需要先了解几个金融类大模型应用合规性保障的核心概念

  1. 环境隔离(Environment Isolation):将开发环境(DEV)、测试环境(TEST)、用户验收测试环境(UAT)、预发布环境(PRE)、生产环境(PROD)完全隔离开来,包括:
    • 网络隔离:不同环境使用不同的VPC(虚拟私有云),之间没有直接的网络连接,只能通过特定的跳板机/堡垒机进行访问;
    • 数据隔离:不同环境使用不同的数据库实例、向量数据库实例、MinIO存储桶、Redis缓存集群;
    • 配置隔离:不同环境使用不同的配置中心命名空间/分组,配置项(比如大模型API Key、向量数据库连接地址、数据库连接地址)完全不同;
    • 代码隔离:不同环境使用不同的Git分支(比如dev分支对应DEV环境,test分支对应TEST环境,uat分支对应UAT环境,pre分支对应PRE环境,main/master分支对应PROD环境),代码合并需要经过严格的代码评审(Code Review)和测试;
  2. 向量数据脱敏与加密(Vector Data Anonymization & Encryption):向量数据库里存储的是文档的向量嵌入(Embedding),虽然向量嵌入看起来是一串“无意义”的浮点数,但通过向量逆向工程(Vector Inversion) 或者向量相似度攻击(Vector Similarity Attack),攻击者有可能从向量嵌入中还原出部分原始文档的内容——尤其是如果原始文档里包含敏感数据的话。因此,金融类大模型应用的向量数据必须:
    • 先对原始文档里的敏感数据进行结构化脱敏(比如把身份证号123456789012345678变成123456********5678,把手机号13812345678变成138****5678),再生成向量嵌入;
    • 对生成的向量嵌入进行加密存储(比如使用AES-256-GCM对称加密算法),只有在需要检索的时候才解密;
  3. RAG源版本控制与灰度发布(RAG Source Version Control & Canary Release):RAG源(比如FAQ文档、API接口文档、产品手册)是不断更新的,如果直接把更新后的RAG源向量索引部署到生产环境,可能会导致检索结果不准确——因此,金融类大模型应用的RAG源必须:
    • 进行严格的版本控制(比如使用Git LFS存储大的PDF/Word文档,使用Milvus的Collection别名(Alias)功能管理不同版本的向量索引);
    • 进行灰度发布(比如先把新的向量索引部署到10%的生产流量上,观察24小时的检索准确率和用户满意度,如果没问题再逐步扩大到50%、100%);
  4. 数据权限隔离(Data Permission Isolation):对于对公版的智能客服Agent,不同支行的客户经理只能查询自己负责的企业客户的数据——因此,必须在Agent服务的内部工具调用层关系型数据库的查询层向量数据库的检索层都实现严格的数据权限过滤
1.2 问题背景

202X.09.03(上线第3天)下午3点17分,我们的运维监控群里突然弹出了一条来自客服部门负责人的紧急消息:

“刚才有个对私客户在APP上查‘信用卡分期利率是多少’,智融小宝返回的结果里居然出现了‘UAT测试环境下的分期利率优惠活动:企业客户员工专享,3期分期利率0.1%,6期分期利率0.2%,12期分期利率0.3%——这个活动只在内部UAT测试过,从未对外宣传过,更没有上线!请立刻排查原因,防止引发监管风险!”

收到这条消息后,我们整个技术团队(AI架构组、后端组、运维组、安全组)立刻进入了“一级响应状态”——因为这不仅仅是一个普通的技术故障,更是一个严重的金融数据合规风险:如果客户把这个截图发到社交媒体上,或者投诉到银保监会,我们分行可能会面临巨额罚款,甚至会影响到分行的年度评级

1.3 问题描述

我们先来看一下故障复现的完整截图(为了保护客户隐私和银行机密,我已经对截图里的敏感数据进行了二次脱敏):
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
(注:因为是示例博客,我无法提供真实的截图——大家可以想象一下:用户问“信用卡分期利率是多少”,智融小宝先返回了正常的生产环境分期利率,然后又突然加了一段“UAT测试环境下的企业客户员工专享分期利率优惠活动”的内容。)

1.4 问题排查过程(从ELK日志→Zipkin链路追踪→向量索引维度排查→Git仓库排查→配置中心排查的全链路)

收到故障报警后,我们按照金融级生产系统故障排查的标准流程(问题分类→优先级划分→应急处置→根因分析→解决方案→验证修复→复盘总结)开始排查:

1.4.1 应急处置(10分钟内完成)

首先,我们必须立刻阻止故障的进一步扩散——因此,我们做了以下3件事:

  1. 关闭智融小宝对私版的“信用卡分期业务”相关功能:通过Nacos配置中心,把智融小宝Agent服务里的“信用卡分期查询工具”、“信用卡分期申请工具”的enabled配置项设置为false——这样用户再问信用卡分期相关的问题,智融小宝就会自动转人工,不会再返回任何可能包含敏感数据的内容;
  2. 临时下线生产环境的所有向量索引:通过Milvus的控制台,把生产环境的所有Collection(包括对私FAQ Collection、对公FAQ Collection、API接口文档Collection、产品手册Collection)的状态设置为Offline——这样RAG检索服务就无法从向量数据库里检索到任何内容,只能依赖内部工具和大模型的通用知识;
  3. 启动“备用人工坐席预案”:联系客服部门,启动所有备用的人工坐席(约500人),防止因为自动转人工率飙升导致人工占线率过高。
1.4.2 问题分类与优先级划分

根据故障的影响范围严重程度紧急程度,我们把这个故障划分为:

  • 问题类别:合规类故障(最高优先级类别);
  • 严重程度:S1级(最严重的级别,定义为“影响到金融数据合规,可能引发监管风险或重大声誉损失”);
  • 紧急程度:P0级(最紧急的级别,定义为“必须立刻处理,24小时内必须找到根因并修复”)。
1.4.3 根因分析(从ELK日志→Zipkin链路追踪→向量索引维度排查→Git仓库排查→配置中心排查,耗时约4小时)

应急处置完成后,我们开始深入排查故障的根因——我们的排查思路是:“既然故障是返回了UAT测试环境的内容,那我们就从‘智融小宝的回复内容是从哪里来的’这个问题入手,一步步倒推。”

智融小宝的回复内容主要来自以下3个地方:

  1. 内部工具调用返回的数据
  2. RAG检索服务返回的内容
  3. 大模型的通用知识

我们先从第1个地方:内部工具调用返回的数据开始排查——因为内部工具调用的数据都是从生产环境的MySQL数据库和Redis缓存里来的,而我们的生产环境和UAT环境的数据库是完全隔离的,所以大概率不是这里的问题。

我们通过ELK的Kibana控制台,搜索了故障发生时间(202X.09.03 15:15:00-15:20:00)、用户ID(脱敏后的:139****5678)的所有日志——关键词是“信用卡分期查询工具”、“工具调用返回数据”。
搜索结果显示:用户的这次查询根本没有触发内部工具调用——因为用户的问题是“信用卡分期利率是多少”,这是一个FAQ类的问题,不需要调用内部工具查询结构化数据,而是应该触发RAG检索服务检索对私FAQ Collection。

那我们接下来排查第2个地方:RAG检索服务返回的内容——这是最有可能出问题的地方,因为RAG检索服务的内容来自向量数据库,而向量数据库的索引是我们手动上传的。

我们继续通过Kibana控制台,搜索了同一时间、同一用户ID的所有RAG检索服务的日志——关键词是“RAG检索请求”、“向量检索结果”、“rerank结果”、“返回给Agent的内容”。
搜索结果显示:RAG检索服务返回给Agent的Top3 rerank结果里,第1条和第2条是正常的生产环境对私FAQ内容,第3条居然是UAT测试环境的“企业客户员工专享分期利率优惠活动”的FAQ内容!

那为什么UAT测试环境的FAQ内容会出现在生产环境的对私FAQ Collection里呢?我们接下来排查第3个地方:向量数据库的索引维度——我们需要看一下生产环境的对私FAQ Collection里到底存储了哪些内容。

我们通过Milvus的Attu控制台(Milvus的官方可视化管理工具),连接到了生产环境的Milvus集群,然后打开了对私FAQ Collection(Collection名称是faq_private_prod_v1.0.0),然后用故障发生时用户的查询文本“信用卡分期利率是多少”的向量嵌入做了一次暴力检索(不使用索引,直接扫描所有向量)——暴力检索的结果显示:对私FAQ Collection里居然存储了127条UAT测试环境的FAQ内容!

那这些UAT测试环境的FAQ内容是怎么被上传到生产环境的对私FAQ Collection里的呢?我们接下来排查第4个地方:向量数据预处理服务的代码和配置——因为向量数据预处理服务是负责把原始FAQ文档转换成向量嵌入,然后上传到对应的Milvus Collection里的。

首先,我们通过Git仓库(代码托管在GitHub Enterprise上),查看了向量数据预处理服务的main/master分支(对应生产环境)的最近提交记录——最近的一次提交是202X.09.01(上线当天)早上8点32分,提交人是AI架构组的小李,提交信息是“更新对私FAQ Collection到v1.0.0版本”。
然后,我们通过Git的diff命令,查看了这次提交的具体内容——内容显示:小李修改了向量数据预处理服务的config.py文件里的MILVUS_COLLECTION_NAME配置项,把原来的faq_private_uat_v0.9.9改成了faq_private_prod_v1.0.0——但是,他忘记修改MINIO_BUCKET_NAME配置项了!
原来的MINIO_BUCKET_NAME配置项是faq-private-uat(对应UAT环境的MinIO存储桶),而生产环境的MinIO存储桶应该是faq-private-prod——所以,当小李运行向量数据预处理服务的上传脚本时,服务从UAT环境的MinIO存储桶里下载了UAT测试环境的FAQ文档,然后转换成向量嵌入,上传到了生产环境的对私FAQ Collection里!

那为什么小李会忘记修改MINIO_BUCKET_NAME配置项呢? 我们接下来排查第5个地方:配置中心的配置——因为向量数据预处理服务的配置项本来应该是放在Nacos配置中心里的,而不是放在代码里的config.py文件里的!
原来,为了方便测试,小李在向量数据预处理服务的代码里加了一个config.py文件,里面硬编码了所有的配置项(包括MILVUS_COLLECTION_NAMEMINIO_BUCKET_NAMENACOS_SERVER_ADDR等等)——而且,他还在上传脚本里加了一个--use-local-config的参数,如果使用这个参数,服务就会优先读取本地的config.py文件,而不是Nacos配置中心的配置!
上线当天早上,小李因为时间紧急(上线时间定在早上9点,他8点30分才到公司),所以直接运行了上传脚本,并加上了--use-local-config的参数——而且,他只修改了MILVUS_COLLECTION_NAME配置项,忘记修改MINIO_BUCKET_NAME配置项了!

1.5 问题解决(从临时修复到永久修复,耗时约8小时)

找到根因后,我们立刻开始修复故障——我们的修复方案分为临时修复永久修复两部分:

1.5.1 临时修复(1小时内完成)

临时修复的目标是尽快恢复智融小宝的正常服务,同时确保没有敏感数据泄露——我们做了以下4件事:

  1. 删除生产环境对私FAQ Collection里的所有UAT测试环境的FAQ内容:通过Milvus的Python SDK,写了一个临时的删除脚本——先把生产环境对私FAQ Collection里的所有向量数据遍历一遍,然后通过元数据(metadata)里的environment字段(我们在上传向量数据的时候,会在元数据里加一个environment字段,用来标识数据是来自哪个环境的)筛选出所有environment=uat的数据,然后批量删除;
  2. 重新上传生产环境的对私FAQ Collection:通过向量数据预处理服务的上传脚本,不使用--use-local-config参数,而是直接读取Nacos配置中心的生产环境配置——这次服务从生产环境的MinIO存储桶里下载了正常的FAQ文档,然后转换成向量嵌入,上传到了生产环境的对私FAQ Collection里;
  3. 重新上线生产环境的所有向量索引:通过Milvus的Attu控制台,把生产环境的所有Collection的状态设置为Online
  4. 重新开启智融小宝对私版的“信用卡分期业务”相关功能:通过Nacos配置中心,把智融小宝Agent服务里的“信用卡分期查询工具”、“信用卡分期申请工具”的enabled配置项设置为true

临时修复完成后,我们做了3轮验证测试

  1. AI架构组的内部测试:用故障发生时的用户查询文本“信用卡分期利率是多少”做了100次测试,每次都返回了正常的生产环境内容;
  2. 后端组的接口测试:用Postman做了所有核心接口的测试,所有接口都正常返回;
  3. 客服部门的UAT测试:邀请了20名客服人员做了1000次测试,没有发现任何敏感数据泄露的问题。

验证测试通过后,我们在202X.09.03下午7点30分(应急处置完成后4小时)全面恢复了智融小宝的正常服务

1.5.2 永久修复(6小时内完成,加上后续的代码评审和测试,总共耗时约3天)

临时修复只是“治标不治本”——我们必须从制度层面流程层面技术层面三个维度来彻底解决这个问题,防止类似的故障再次发生。

我们的永久修复方案如下:

1.5.2.1 制度层面
  1. 制定《金融类大模型应用环境隔离管理制度》:明确规定了不同环境的网络隔离、数据隔离、配置隔离、代码隔离的具体要求,以及违反制度的处罚措施;
  2. 制定《金融类大模型应用RAG源版本控制与灰度发布管理制度》:明确规定了RAG源的版本控制流程、灰度发布流程、以及回滚流程;
  3. 制定《金融类大模型应用向量数据预处理服务上线审批流程》:明确规定了向量数据预处理服务的上传脚本必须经过AI架构组组长后端组组长安全组组长运维组组长四个人的共同审批才能运行;
  4. 制定《金融类大模型应用上线前预检查清单》:明确规定了上线前必须检查的所有项目(共8大类、127小项),比如“配置项是否正确”、“环境隔离是否到位”、“敏感数据是否脱敏”、“向量索引是否正确”等等——上线前必须由四个人共同签字确认才能上线。
1.5.2.2 流程层面
  1. 优化代码合并流程:所有代码合并到main/master分支(对应生产环境)之前,必须经过至少2名AI架构组的资深工程师的代码评审,以及至少1000次的自动化测试至少20名客服人员的UAT测试
  2. 优化向量数据预处理服务的上传流程
    • 上传脚本必须强制读取Nacos配置中心的配置完全移除本地的config.py文件和--use-local-config参数;
    • 上传脚本必须先进行环境检查——比如检查配置中心的environment配置项是否是prod,检查MinIO存储桶的名称是否是faq-private-prod,检查Milvus Collection的名称是否是faq_private_prod_v1.0.0——如果环境检查不通过,直接退出,不允许上传;
    • 上传脚本必须先进行元数据检查——比如检查所有要上传的向量数据的元数据里的environment字段是否是prod——如果元数据检查不通过,直接退出,不允许上传;
    • 上传脚本必须先进行敏感数据检查——比如检查所有要上传的原始FAQ文档里是否包含完整的敏感数据(身份证号、手机号、银行卡号、CVV码、交易密码、授信额度、理财产品持有金额)——如果敏感数据检查不通过,直接退出,不允许上传;
    • 上传脚本必须生成上传报告——报告里必须包含上传的时间、上传人、上传的环境、上传的Collection名称、上传的MinIO存储桶名称、上传的向量数据数量、上传的元数据检查结果、上传的敏感数据检查结果——报告必须自动发送给AI架构组、后端组、安全组、运维组的所有成员;
  3. 优化应急处置流程:明确规定了不同级别故障的应急处置流程、责任人、以及完成时间——比如S1级/P0级故障的应急处置流程必须在10分钟内完成,责任人是技术负责人,完成时间是10分钟内。
1.5.2.3 技术层面
  1. 完全移除向量数据预处理服务的本地配置:删除了代码里的config.py文件和--use-local-config参数,上传脚本必须强制读取Nacos配置中心的配置;
  2. 添加上传脚本的环境检查功能:写了一个check_environment()函数,用来检查配置中心的environment配置项、MinIO存储桶的名称、Milvus Collection的名称是否正确——下面是这个函数的Python源代码示例:
import os
from minio import Minio
from pymilvus import connections, Collection
import nacos

# 初始化Nacos配置中心客户端
NACOS_SERVER_ADDR = os.getenv("NACOS_SERVER_ADDR", "nacos-prod.example.com:8848")
NACOS_NAMESPACE = os.getenv("NACOS_NAMESPACE", "prod")
NACOS_GROUP = os.getenv("NACOS_GROUP", "ZHIRONGXIAOBAO_GROUP")
NACOS_DATA_ID = os.getenv("NACOS_DATA_ID", "zhirongxiaobao-vector-preprocessing-service-prod.yaml")

client = nacos.NacosClient(NACOS_SERVER_ADDR, namespace=NACOS_NAMESPACE)
config = client.get_config(NACOS_DATA_ID, NACOS_GROUP)

# 解析YAML配置
import yaml
config_dict = yaml.safe_load(config)

# 环境检查函数
def check_environment():
    print("开始环境检查...")
    # 1. 检查配置中心的environment配置项
    expected_environment = "prod"
    actual_environment = config_dict.get("environment", "unknown")
    if actual_environment != expected_environment:
        raise Exception(f"环境检查失败:配置中心的environment配置项应该是{expected_environment},但实际是{actual_environment}")
    print("✅ 配置中心的environment配置项检查通过")

    # 2. 检查MinIO存储桶的名称
    expected_minio_bucket_name = "faq-private-prod"
    actual_minio_bucket_name = config_dict.get("minio", {}).get("bucket_name", "unknown")
    if actual_minio_bucket_name != expected_minio_bucket_name:
        raise Exception(f"环境检查失败:MinIO存储桶的名称应该是{expected_minio_bucket_name},但实际是{actual_minio_bucket_name}")
    # 再检查一下MinIO存储桶是否真的存在
    minio_client = Minio(
        config_dict.get("minio", {}).get("endpoint", "unknown"),
        access_key=config_dict.get("minio", {}).get("access_key", "unknown"),
        secret_key=config_dict.get("minio", {}).get("secret_key", "unknown"),
        secure=True
    )
    if not minio_client.bucket_exists(actual_minio_bucket_name):
        raise Exception(f"环境检查失败:MinIO存储桶{actual_minio_bucket_name}不存在")
    print("✅ MinIO存储桶的名称检查通过")

    # 3. 检查Milvus Collection的名称
    expected_milvus_collection_name = "faq_private_prod_v1.0.0"
    actual_milvus_collection_name = config_dict.get("
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐