☰
RAG故障检测与机器学习二手车估值:算法落地的工程复盘
2026/10/20 6:05:10 网站建设 项目流程

开头先把背景说清楚。我一直在做一个车辆检测与交易方向的算法项目,团队同时接过两个需求:一是用大模型辅助技师做故障诊断,二是给待售二手车打一个靠谱的估值。两个需求原本是分开的,做着做着发现数据是同一批车,业务是同一个入口,于是干脆把RAG故障检测和机器学习估值放在了一套系统里。这个项目做下来,让我对“RAG不是模型问题,是工程问题”和“回归模型没有好数据就是原地打转”这两句话有了非常切身的体会。如果你正准备做类似的算法落地项目,或者想看看RAG在垂直领域到底怎么用,这篇复盘应该能给你一些参考。

1. 项目起底:两类算法怎么塞进同一个业务系统

1.1 两个需求来自同一个业务痛点

当时的业务背景是这样的:团队在做一个面向线下门店的车辆检测SaaS,门店的技师需要快速判断一辆车有没有隐藏故障,同时收车员需要定一个收购价。以前这两件事靠的都是老师傅个人的经验积累,老店长看一眼、听一耳朵就能估个八九不离十,但新人培养周期极长,而且每个门店标准不一。

客户提的需求很直接:能不能让系统自动读故障码、自动看维修记录,然后给出“这辆车大概有什么问题、修起来要花多少钱”的结论。前一半是故障检测,后一半延伸出去就是二手车估值。老板一拍脑袋说那一起做吧,于是这个项目就同时挂上了RAG和机器学习两条线。

标题里“RAG汽车故障检测”对应的是技术方案,“机器学习二手车估值”对应的是业务目标。实际上这两个模块不是简单的并列关系,估值模型里有一组关键特征来自故障检测模块的结果——维修建议里的维修项目、维修等级会成为价格修正因子。所以这不是两个demo拼在一起,而是有真实数据依赖的联动系统。

1.2 技术选型时的三条硬约束

先交代一下我们当初定技术方案时面临的约束,这直接决定了后面所有实施路径。

第一,预算有限。故障检测这块不可能自己训练一个行业大模型,只能站在通用大模型的肩膀上做应用层开发。所以RAG是必然选择:把维修手册、故障码表、历史工单这些垂直资料检索出来,塞进大模型的上下文里,让它基于资料回答。

第二,数据敏感。车辆信息、车架号、车主信息都涉及到隐私合规,公司明确要求数据不能出内网。这意味着向量化和生成环节都得部署在私有化环境,不能动不动调外部云端API。后来我们是自己部署了一版开源向量模型做embedding,LLM用的是公司内网环境部署的对话模型,接口统一走内部网关。

第三,响应时间有硬性要求。门店技师查故障的时候,他手里可能正举着诊断仪等着跟车主解释,一个诊断建议如果转圈转十几秒,他就不用了。我们内部定的目标是端到端2秒左右,其中RAG链路要在700毫秒内完成检索和重排,给生成留出余量。

这三条约束听着简单,实际落地时每一环都逼着你做选择。比如“私有化部署”这四个字,意味着你要自己搞定GPU资源、向量库运维、模型版本管理,而不是在网页上填个API Key那么简单。

1.3 系统数据流与模块边界

整体数据流大概是这样:

店铺OBD设备或人工录入的车况信息进入系统的数据清洗层,标准化成一张车辆信息表。这张表同时流向两条线。

故障检测线:车辆信息中的故障码字符串、故障描述触发RAG检索,从知识库中找到匹配的故障解释、维修方案和历史案例,由大模型生成一段“问题定位+维修建议+风险提示”,最后输出一个结构化的Json。这里有个容易被忽略的细节:生成结果不是一段给客户看的散文,而是必须落成Json里带维修项目编码和维修难度的字段,因为后续估值要直接用。

估值线:车辆基础信息加上故障检测线的维修难度、维修项目数,一起进入特征拼接层,再交给XGBoost/LightGBM这类树模型做价格回归,输出预估价和价格区间。价格区间不仅是给收车员看的,也会在门店给卖方看,所以模型还必须输出置信区间。

模块边界上,我们做了明确分工:RAG故障检测负责回答“车有什么问题、修要花多少钱”,机器学习估值负责回答“这样一辆车现在值多少钱”。故障检测的输出必须是标准字段,估值模型把它当特征用,两者之间不共享模型参数,只共享数据约定。这个设计让两端可以独立迭代,后来证明非常重要——故障检测的知识库需要频繁补充车辆品牌资料,而估值模型主要在生产数据上更新,如果耦合在一起,任何一个更新都会拖累另一个。

2. RAG故障检测最磨人的不是模型,是知识库整理

2.1 文档杂、格式乱:维修资料清洗的第一步

很多人一提RAG就先想用什么embedding模型、用什么向量库,但我在这个项目里最大的教训是:知识库的抽取和清洗才是决定上限的环节。

我们手里的原始资料大致分三类。第一类是车辆维修手册,以PDF为主,有几百本,单本从几十页到上千页都有。第二类是故障码(DTC)对照表,有Excel、CSV、还有直接在网页上贴的表,平均每辆车几千个码,包含触发条件、可能原因、维修建议。第三类是历史维修工单,数据库导出来的txt和Excel混在一起,字段命名在不同门店还不统一。

第一件要做的事是把所有PDF转成可检索文本。这个环节用到了两种工具:能直接复制文本的PDF用解析库抽文本,扫描版的手册就得走OCR。你别小看这个比例,我们这批资料里大概有三成的PDF是扫描件,字体还不统一,OCR出来的结果里“发动机”三个字可能是“发动机”也可能是“发动机”,光清洗OCR脏字符就耗了两个人一周。

清洗规则我们总结下来大概有几条:

  • 统一大小写与全半角,把“error”这种全角字符全部转半角;
  • 对“!”、“?”、“空格”过多的地方做归一化,PDF解析经常在英文单词中间插入奇怪字符;
  • 故障码统一成标准格式,例如把P0301、p0301、P 0301全部转成“P0301”这种无空格大写;
  • 手工建立同义词表,“引擎”和“发动机”、“变速箱”和“变速器”互相映射。

这些清洗工作无法靠大模型自动完成,因为出错后很难追溯。我们是写脚本批量处理,再按车辆品牌抽100条人工审核,确保整体准确率达标才进入下一环节。

2.2 切片策略:按结构切片,不按字数硬切

知识库清洗完之后,紧接着就是切片。切片这个环节很多教程一笔带过,实际上对检索质量的影响极其直接。

一开始我们用的是最常见的固定长度切片:每300个字切一段,带50个字重叠,直接向量化入库。跑完一轮测试发现,很多故障问题检索回来的片段非常破碎。你想问“刹车异响怎么办”,切回来的片段可能是手册里“刹车系统的保养周期”和“制动液更换方法”的拼接段落,这两段内容都有“刹车”二字,但都不是要的“异响诊断”。

后来我们把切片策略改成了按文档结构切:

  • 对维修手册,先解析目录和标题层级,按章、节、小节作为切分单元,超过800字的长节点再二次切;
  • 对故障码表,按“故障码+触发条件+可能原因+维修建议”的完整条目作为一条记录,一个条目就是一个片段;
  • 对历史工单,按“单次维保记录”整体切分,保留车辆型号、故障码、实际维修动作、费用,不拆散。

这么改完之后,同样的检索问题,Top5命中率明显改善。原理也很好理解:RAG检索的核心不是“字面相似”,而是“语义单元完整”。固定长度切片把语义单元拦腰斩断了,embedding即使再好,也挽回不了信息缺失的问题。

2.3 先向量检索,再补BM25混合召回

切片问题解决后,最初版本的检索链路用的是纯向量检索。embedding模型选的是一版开源中文向量模型,维度768,向量库用的开源社区方案,基础配置很顺手,开发阶段确实跑得很快。

但收录完资料做批量评测的时候问题来了。故障诊断里的很多词是专业缩写,比如“EGR阀”、“O2S”、“VVT”,这些缩写词在嵌入空间中经常找不到好的近邻。我拿“EGR阀卡滞导致怠速抖动”去检索,纯向量召回的前几个结果里混入了大量“怠速抖动”相关的通用内容,“EGR阀”这个关键信息反而没被强化。

这个问题的解法是加BM25关键词检索做混合召回。说实话,这个方案在知识管理领域早就是标配,但自己做项目时很容易因为“向量模型能理解语义”而忽略传统检索的价值。我们最后采用的是“双路召回”:一路走向量语义检索,一路走BM25关键词检索,两路结果合并后用RRF(Reciprocal Rank Fusion)做排名融合,再统一进重排阶段。

混合召回的收益肉眼可见。我们用内部构造的40个典型故障问题做评测,Top10召回率从纯向量的68%左右提到了89%。后面我把这个对比数据放在项目周报里,大家才理解为什么不能单靠embedding模型解决垂直领域的检索问题。

2.4 重排环节用了一天的对比测试

混合召回之后是重排(Rerank)。这一步的目标是:把召回回来的20个候选片段,重新精排,选Top5喂给大模型。

重排器的选择上,我们对比了两条路线:一是用更细粒度的交叉编码器模型重排,二是直接用大模型来做一次性精读重排。测试下来的结论是交叉编码器重排的效果最稳,延迟也低,单个片段的精排耗时基本可以控制在百毫秒级。用大模型精读的方案虽然对极个别的疑难故障有惊喜,但耗时不可控,还会占用LLM的生产资源,就放弃了。

这个环节看起来只是“加了一步”,但对生成质量的影响非常大。重排前喂给大模型的Top5片段经常出现前后矛盾的说法;重排后,同一故障相关的正反两面信息基本能够排在前列,生成结果的逻辑一致性明显好了。

3. 故障诊断如何不让大模型“编故障”

3.1 Prompt模板从“自由发挥”到“受约束回答”

RAG链路搭好之后,真正的大坑在生成环节。第一版Prompt我们写得很“开放”,大概是“请根据以下资料,回答用户关于车辆故障的问题”。结果模型经常不满足于资料内容,会把它在预训练阶段见过的、可能匹配但没有被检索到的知识也带进来。

举个具体的例子:问“P0171故障码怎么处理”,资料库里明确写了要先检查进气系统和氧传感器,但模型却答了“建议更换后氧传感器”。这种回答在技术上不能算完全错误,但维修顺序和维修范围的判断标准变了,对门店来说就是不同的备件成本和工时。

我前后迭代了四版Prompt模板,核心思路就是给模型“设限”:

  • 第一,明确只允许使用检索片段内的信息,未经检索到的内容一律不答;
  • 第二,要求优先引用资料原文中的维修顺序,没有明确说明时,必须写“建议进一步检测确认”而不是给出确定的结论;
  • 第三,输出格式固定成Json,包含“故障可能性排序”“建议维修项目编码”“维修难度”和“依据片段ID”四个字段。

第四点值得多说一句:让模型输出“依据片段ID”,是我们在项目中后期加上的。这个字段从用户视角看是“这段话有出处”,从工程视角看其实是给评估系统留了追踪入口。生成结果出错时,我们能直接定位是检索引擎的问题还是Prompt引导的问题,没有这个字段就只能对着黑盒猜。

3.2 置信度提示与兜底逻辑

即便做了Prompt约束,模型在某些情况下还是会给出超出资料的判断。我们当时加入了一个“置信度信号”机制:在Prompt里要求模型评估检索片段与问题的相关度,如果相关度低,输出中的置信度字段就写“low”,并自动触发兜底回答。

兜底回答的模板是统一固定的:“当前资料库中未找到与该故障直接匹配的信息,请结合实车检测进一步判断。”实测下来,在模拟门店使用的环境下,约12%的查询会走兜底。团队里有人说12%是不是太高了,但我认为这个数字恰恰说明系统在说“我不知道”而不是在编,这在故障诊断这种高成本领域是正确选择。

另外要提到生成温度。我们的对话模型支持设置采样温度参数,经过测试,最终把温度调到了比较低的档位。温度低意味着模型输出的随机性小,对技术问答来说“稳定”比“有创造性”重要得多。

3.3 评估方式:专家评审与召回率并用的两面

故障检测模块上线前,我们花了挺长时间搭评估集。最初只是简单准备了一些测试问题,然后人眼看看回答好不好,这远远不够。后来参考了行业内对问答系统的一般评估思路,搭了双轨评估:

一轨是客观指标。对每个评测问题,预先标注“标准答案来自哪个手册章节”,然后看系统最终生成的答案是否引用了正确的片段ID。这样可以算出一个“正确引用率”,和召回率、生成延迟等指标一起自动跑。

另一轨是专家评审。找了三名有维修经验的技师,对输出结果按“信息准确”“维修顺序合理”“风险提示到位”三个维度打分,各1到5分。三个技师之间允许有分歧,最后取平均分。

这两轨的结果我们定期对齐。印象最深的一次是,客观指标显示引用正确率到了90%以上,但专家评审的分反而掉了,仔细一查,是切片把维修手册里“安全注意事项”的部分也当作答案片段引用了过来。模型引用的片段确实来自正确章节,但检索到的字块本身没有价值。这个案例说明,评估不能只看检索单元是否正确,还要看最终生成内容的可用性。

4. 二手车估值模型:一个回归问题的工程化过程

4.1 特征来源与构造:脱敏数据不干净是常态

估值模块和故障检测模块在数据层面有一个很大的不同:故障检测用的是“语义知识”,估值模型用的是“结构化标签”。数据来源是合作门店回传的脱敏交易记录,包括车辆基本信息、事故记录、保养记录和最终成交价。

拿到原始数据后的第一反应是:这数据比想象中脏得多。门店手工录入时车型名不规范,“卡罗拉”和“COROLLA”混着写,不同年份的同一款车还经常挂不一样的款型名。我们做了一轮比较细致的标准化:车型统一映射到标准车系编码,排量、变速箱类型这种离散字段统一枚举,缺失率超过30%的字段直接弃用。

特征构造的核心工作是两类。一类是“里程衰减”。行驶里程和车龄这两个变量对二手车价格的影响是非线性的,同样一年两万公里的车和一年五千公里的车,价格差不是简单的线性关系。我们构造了“年均里程”和“里程衰减指数”。另一类是“历史记录严重度”。“事故次数”不能只看次数,一次大事故和十次剐蹭完全不同。我们根据维修工单里的事故严重程度标签,给历史记录打了一个0到5的严重度分,这项特征后来在特征重要性里排进了前五。

最终固定的特征集一共42个字段,涵盖基本信息、车况记录、地区市场系数、季节因素和故障检测模块输出的维修难度等。

4.2 模型选型:从线性回归到LightGBM的对比

我习惯上先跑一个线性回归当基线,不是为了用,而是为了知道后面花里胡哨的模型到底比最蠢的方案强多少。第一版线性回归在验证集上的MAPE(平均绝对百分比误差)大约是8.3%左右,行业里对这类车龄3年内的准新车,要求大概在5%以内,所以线性回归肯定不够。

随后跑了随机森林和梯度提升树。随机森林提升到6.8%,XGBoost和LightGBM基本打到5.2%左右。当时还试过更深层的MLP结构,但数据量只有几千条有效样本,深层网络反而过拟合,验证集上比树模型差了将近一个点。所以最终选型是LightGBM,理由很简单:效果和XGBoost持平,但训练速度快一半,超参调起来更顺手。

模型训练环节有几个细节对效果影响很大:

  • 目标函数用的不是原始价格,而是价格的对数。二手车价格分布右偏严重,几辆豪车会拉大MAE,取对数后误差分布更接近正态,模型学起来更稳;
  • 价格区间不是简单的均值±方差,而是用分位数回归输出P10/P50/P90三条线,实际业务里收车员需要的是一个区间而不是一个点;
  • 交叉验证按车辆批次切,不随机切。同一辆车出现在训练集和验证集会造成严重的信息泄漏,交叉验证的分必须按车架号分组。

4.3 误差为什么一直压在5.5%下不去

项目做到中段,模型MAPE在5.2%到5.5%之间卡了一周多。说高不算高,但客户那边要求某些热销车型的误差得压到4%以下,一直不达标。我开始觉得是不是模型能力不够,后来排查数据才发现是“样本代表性问题”。

早期有效交易样本大多集中在低价车和准新车,中高价位、车龄6年以上的车型样本太少。LightGBM在样本量少的区间学出来的规律不稳定,个别车型的价格预测偏差很大。解决办法有两个:一是从合作方又导了一批历史成交数据,把中高价位段的样本补充上来;二是对样本量偏少的车型类别做类似SMOTE的过采样,让模型不至于被多数类带偏。

还有一个经常被忽略的因素是时间。二手车的价格随季节波动明显,春节前是交易旺季,价格普遍高一些,夏季是淡季,价格回落。如果不把“交易月份”这个特征做进去,模型学到的“同一款车为什么时贵时便宜”就全归因到错误特征上了。

4.4 估价与维修建议的联动:价格里要能看出“修”的钱

这一步是这个项目里最“拼业务理解”的地方。估价模型先出一版基础价,如果RAG故障检测模块给出了维修项目,我们就根据维修难度等级和维修项目耗时,计算一个“维修成本修正系数”,从基础价里扣减。

举个例子,一辆车估值模型给出的基础价是8万,但系统检出了变速箱P0700故障码,维修难度为高,历史类似维修工单的平均成本是1.2万元,那最终建议收购价就会在上浮一定风险系数后落在6.6万到6.8万附近。这个逻辑不复杂,但把“故障”和“价格”直接挂钩,门店收车员操作时就可以直接向卖方解释价格的构成。

这个联动在验证数据上也有效果:加入维修成本修正之后,实际成交价和系统建议价的偏差比单纯跑估值模型降低了约0.8个百分点。核心原因是,车辆交易中买方真正担心的是“后面还要出多少钱修”,这个信息比事故次数更贴近实际成交价。

5. 系统联调与上线那两周:琐碎问题才是时间黑洞

5.1 OBD终端数据接入与标准化的坑

故障检测模块在开发阶段用的是模拟数据,真正往生产上接的时候,第一个坑来自OBD终端。

不同品牌OBD设备的数据格式不统一,有的直接把DTC十六进制码吐出来,有的已经转成了字符串,有的还会把故障码和传感器数据混在一个报文里。我们做了一层终端的协议适配网关,通过设备型号识别走不同的解析器,再把结果统一成内部标准格式。这里最大的教训是:凡是硬的对接,都不要相信对方文档里的“标准输出”,必须以实际抓包数据为准。我们实测发现两家设备厂商同一种故障码,一个前缀带“C”,一个不带,校对这层数据就花了两天。

5.2 并发与超时:RAG链路太慢怎么处理

开发环境里所有人都在自己的电脑上连着测试,延迟看着还行。一旦上了生产环境,多个门店同时请求,RAG链路的问题就暴露了。

最开始的链路是:请求进来 → 向量检索 → BM25检索 → 融合 → 重排 → 拼装prompt → 调LLM → 返回Json。整个串行流程在低并发时平均耗时1.8秒,看着还行,但并发上来后,只是embedding和重排这两个计算环节就开始排队,接口耗时经常飘到4秒以上,严重拖垮体验。

我们做了三件事缓解:一是embedding结果做了缓存,同一车辆的同一组故障码短时间重复查询时直接走缓存不重新向量化;二是把BM25、向量检索和重排的调用改成了并行加超时保护,最慢的那个环节也不会拖死整个请求;三是LLM生成长度做了上限控制,输出超过一定长度直接截断,避免生成阶段无谓的等待。

5.3 权限与日志:上线前一天还在改

最后一天还在改的问题和算法无关,是权限与日志。

不同角色的门店用户看到的信息范围不一样,店长能看维修方案和估价区间,普通技师只能看故障诊断结论,如果权限没分对,直接就能绕过系统自己判断。我们在接口层加了角色字段的校验,然后根据用户角色动态裁剪返回字段,而不是单纯靠前端隐藏按钮,因为后者很容易被跳过。

日志也是临时抱佛脚补的。故障检测模块的每次查询都必须记录:用户问题、召回片段ID、模型生成的Json、置信度、耗时以及评估反馈入口。这些线上日志后来成了我们持续优化知识库和模型的最重要数据来源,但最初设计时几乎没花时间设计,是在联调阶段被运维同事提出来才补上。

6. 复盘:如果再做一次,我会在这些地方换路线

项目交付到现在,复盘时最大的感受是:RAG故障检测的难点不在“有没有大模型”,而在知识库的工程化程度;二手车估值的难点也不在“模型选得好不好”,而在样本的均衡与特征的业务含义。如果重做一次,有几件事我会在第一天就做。

第一,评估集要提前搭。我们评估集是在系统大体跑通后才开始认真构造的,导致前期很多优化都是靠主观感觉,走了弯路。正确的做法应该是第一天就先让业务专家梳理出30到50个高频故障场景,配上标准答案,从知识库整理阶段就拿着这些题目做验证,而不是把验证留到最后。

第二,知识库的版本管理要做成正式流程。维修手册和故障码表是会不断更新的,新增车型、修改维修规范都是常态。没有版本管理的知识库,上线后追问题堪比考古。这件事我们已经补起来了,但对项目初期的开发节奏影响不小。

第三,测量RAG“未知”的能力比测量“已知”的能力更重要。故障诊断系统最大的风险不是回答得不够准确,而是回答得过分自信。所有Prompt和知识库的迭代,我们都应该把“触发兜底回答的比例”作为一个固定观测指标,这个指标不下降,宁可不提升准确率。

最后再说一点个人体会。这类项目真正让我觉得有成就感的时刻,不是在测试集上看到指标提升,而是门店技师反馈说“这个系统居然知道我这台车之前换过什么”的那一刻。RAG和机器学习都只是工具,把资料、数据、业务语言串成一条线,让一线人员少一些猜和漏,才是这个项目真正的价值所在。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询