做物业信息化的朋友这两年问得最多的一句话是我们能不能也搞一个“物业行业大模型”。问得多了我发现大家真正卡住的不是买不到模型而是**检索增强生成RAG**这套东西怎么和自己手里那一堆业主公约、服务标准、设备台账、报修工单、验收记录对接起来。物业这行的知识有个特点它不像通用问答那样“差不多就行”收费口径差一块钱、责任边界差一句话第二天就会有业主拿着截图来对质。所以我在做这类项目时第一反应从来不是选哪个模型而是先把“物业行业大模型”这五个字拆开看它得能查得到、说得准、指得出出处最后才轮到“说得好听”。这篇内容我按自己实际推进的顺序来写从数据怎么盘、切片怎么切、检索怎么调、生成怎么兜底一直写到上线后怎么评估和排障适合正在做数智化转型的技术负责人、产品经理也适合刚开始接触 RAG 的后端和算法同学。文中涉及的代码和参数都是可复现的模板拿去改成自己项目里的命名就能跑。1. 先把问题想清楚物业场景为什么绕不开 RAG1.1 物业知识的三类“硬骨头”我在动手之前会先做一次“知识体检”把要喂给模型的东西分成三类因为它们的处理方式完全不一样。第一类是强约束文本比如服务标准、收费依据、装修管理规定、应急预案流程。这类文本的特点是“一句话一个判定”模型不能发挥不能概括只能原样引用。你让它“总结一下装修押金的退还条件”它只要漏掉“验收合格且无违规施工”这个前置条件答复就是错的而且错得有杀伤力。第二类是半结构化台账比如设备清单、维保记录、巡检计划、公共能耗分摊表。它们大多躺在 Excel 里字段名不统一同一个“电梯编号”在不同表里可能叫“设备号”“资产编码”“梯号”。这类数据的价值在于精确匹配和聚合用大段自然语言去“理解”它反而是浪费。第三类是口语化沉淀比如客服工单的处置记录、管家和业主的沟通纪要、老员工脑子里的“这个楼栋的水压晚上会低”。这类内容是物业行业最有含金量的部分也是最难结构化的部分但恰恰是 RAG 最擅长捞出来的东西。为什么强调这个分类因为很多项目一上来就把所有文档一股脑塞进向量库结果召回出来一堆“看起来相关”的废话。分类的意义在于第一类要保证逐条可追溯第二类要走结构化查询而不是向量检索第三类才适合放开做语义召回。三种路子混着用才是一个真实可用的系统。1.2 微调和 RAG 到底该怎么分工这是我被问得第二多的问题也是踩坑最多的地方。我的经验判断很简单知识用 RAG风格和能力用微调两者别互相兼职。物业行业的规章制度、收费标准、项目专属信息是高频变化的——一个新项目进场可能一次带来上百份文件一个地方性收费标准调整几十个问答的标准答案就得跟着改。这些东西如果靠微调灌进模型权重每次更新都要重训、重测、重发版成本和风险都不划算。而 RAG 的更新只需要重新解析、重新入库模型本身不动这是决定性的优势。反过来微调适合解决什么“说话的语气”。比如要求客服助手统一使用“您”而不是“你”要求答复先给结论再给依据要求遇到情绪激动的业主先共情再解释——这些是行为模式是风格用少量高质量样本做指令微调效果确实比纯 prompt 稳。但如果指望微调让模型记住“3 号楼 2 单元电梯的年检到期日”那就是用错了工具。维度微调RAG知识更新成本高需重训重测低重新入库即可事实准确性无法保证容易张冠李戴有原文支撑可追溯风格与格式控制强行为模式稳定依赖 prompt长对话易漂移权限隔离难权重里分不清谁能看易检索阶段就能过滤冷启动速度慢需要标注样本快有文档就能跑适合的内容表达习惯、输出结构、话术制度条款、项目数据、历史工单我一般的做法是先纯 RAG 跑通闭环把召回和引用做扎实再考虑用微调去统一话术。很多团队顺序搞反了先花两个月微调一个“物业专用模型”上线发现它照样编造收费条目因为微调解决不了“不知道”的问题。1.3 给项目定一个能验收的目标“构建物业行业大模型”这个说法太大落到项目上必须切成可验收的小目标。我习惯定三个层级L1 查得到用户问“地下车库月租车位的收费标准是什么”系统能在 3 秒内返回答案并附带原文位置哪份文件、第几条。这一层只考核召回率和响应时间。L2 说得准答案的事实性错误率控制在可接受范围遇到知识库里没有的内容明确说“没查到”而不是硬编。这一层要建人工评测集。L3 用得上答案能直接进入业务流程比如自动生成工单处置建议、自动填充答复模板、自动关联历史相似案例并带上处理时长。这一层考核的是业务采纳率。我在项目启动会上一定会把这三层写进文档因为不同角色关注点完全不同老板看 L3业务部门看 L2技术团队天天卡在 L1。目标不写清楚后期一定扯皮。2. 数据层把散落的物业文档变成可检索的资产2.1 文档盘点和优先级排序数据这活儿没什么技术含量但决定成败。我的做法是先做一次全量盘点产出一张表字段至少包含文档名称、所属项目、文档类型、责任人、更新频率、是否涉密、当前存放位置。盘完之后你会发现两个典型现象。第一同一份制度存在多个版本2021 版、2023 修订版、某个项目自己改过的版本同时存在谁也不知道哪个是现行有效的。第二大量关键知识根本不在文档里在老员工的微信收藏、在某个班组的交接本上、在已经离职的人脑子里。针对第一个问题我要求入库前必须做版本裁决每类文档指定唯一责任人确认现行版本过期文档不删除但打上deprecated标签并在检索时降权或过滤。这一步看起来笨但不做的话模型召回一条三年前的旧标准比召回不到还危险。针对第二个问题用“工单反哺”的方式补。把过去一年处理时长超过 24 小时、或者被反复催办的工单抽出来让一线主管口述处置要点整理成 200 到 500 字的短文入库。这批“经验片段”往往是后续问答命中率提升最明显的部分因为业主问的问题八成也是当年工单里问过的。优先级上我一般按这个顺序推服务标准与收费依据 → 应急预案 → 高频工单处置记录 → 设备台账 → 项目基础信息。前两类是刚需后三类是加分项先把刚需跑通业务方才有耐心陪你做后面的。2.2 解析与清洗表格、扫描件和多栏排版这一环节是整个链路里最脏最累的也是最容易被低估的。我给团队的预期是数据处理时间占项目总工期的四到五成低于这个比例通常是清洗不彻底。物业文档的坑主要有这么几个扫描件。大量历史制度是纸质扫描的 PDF直接抽取文本得到的是一堆乱码或者空白。必须走 OCR而且 OCR 之后要做版面还原。我的做法是 OCR 后保留坐标信息按纵坐标排序再拼接避免一段话被拆成两半。表格。收费价目表、维保计划几乎全是表格。普通文本抽取会把表格压成一行字段和数值完全对不上。这类必须用能识别表格结构的解析器转成 Markdown 表格或者结构化 JSON 再入库。我实测下来表格转成“表头 每行一段自然语言”的形式检索效果明显好于原样保留 Markdown比如把“| 车位类型 | 月租 |”转成“地下产权车位月租金 300 元另收管理费 50 元”。多栏排版。有些项目手册是双栏排版抽取时左右栏会交错。解决办法是按坐标做分栏切分再按栏内顺序拼接。页眉页脚和目录。这些不清理会污染检索尤其是页眉里带项目名的时候会让所有切片都带上项目名导致跨项目检索时误命中。我的处理是逐页检测重复行出现频次超过 30% 的行直接删。清洗完之后还要做一轮规范化。比如统一“㎡ / 平方米 / 平米”的写法统一日期格式把“电梯”“升降梯”“客梯”做同义词归并。这些在检索阶段能省掉大量麻烦。# 一个最小可用的清洗管线示意实际项目里按需扩展 import re def normalize_text(raw: str) - str: text raw # 1. 统一单位与常见异体写法 text text.replace(㎡, 平方米).replace(m2, 平方米).replace(m², 平方米) text text.replace(升降梯, 电梯).replace(客梯, 电梯).replace(货梯, 货运电梯) # 2. 去除多余空白与软换行造成的断句 text re.sub(r[ \t], , text) text re.sub(r(?[\u4e00-\u9fa5])\n(?[\u4e00-\u9fa5]), , text) # 3. 统一编号符号方便后续按条款切分 text re.sub(r第\s*([一二三四五六七八九十百零\d])\s*条, r第\1条, text) return text.strip() def drop_repeated_header(pages: list[str], ratio: float 0.3) - list[str]: 删除在多页中反复出现的页眉页脚行 from collections import Counter lines Counter() for p in pages: for ln in set(p.splitlines()): lines[ln.strip()] 1 threshold max(2, int(len(pages) * ratio)) bad {ln for ln, c in lines.items() if c threshold and len(ln) 40} return [\n.join(l for l in p.splitlines() if l.strip() not in bad) for p in pages]提示清洗阶段务必保留原始文本的备份和“原文偏移量”。后面做引用溯源时要靠它定位到原文位置一旦被覆盖就得重跑一遍解析。2.3 分块策略按条款切不按字数切切片这件事是我认为对效果影响最大、但讨论最少的环节。网上教程大多告诉你“chunk size 设 512overlap 设 50”这在自己写的测试文档上没问题放到物业制度文本上会出大问题。原因很简单物业制度的语义单元是条款不是字数。你把“第五条 装修押金的收取与退还”从中间切开前半段进了 A 块后半段进了 B 块用户问退还条件时检索到 A 块模型看到的是半句话很可能自己补全成一个错误的结论。我的切分策略分三层第一层按文档结构切。识别“第 X 章”“第 X 条”“一、二、三”这类标题层级优先在这里断开。切出来的块就是一条完整的规则天然自洽。第二层对超长条款做语义内切。有些条款一整段四五百字包含多个并列情形比如三种情况分别怎么处理这时候按并列项“一”“二”“1.”“2.”再切一层但要在每块头部拼上父级标题保证脱离上下文也能读懂。第三层是块头注入。每块入库前在正文前面拼一段“来源指纹”比如“文档住宅小区装修管理规定项目某园区条款第五条”。这段指纹会一起被向量化实测下来对“跨项目串味”和“张冠李戴”的抑制效果非常明显。块大小上我的经验值是这样制度类 300 到 600 字工单记录 200 到 400 字设备台账转成的自然语言片段 100 到 200 字。overlap 我只在语义内切时加加 15% 左右因为条款级切分本身已经有天然边界加太多 overlap 反而会引入重复召回。内容类型建议块大小切分依据是否加 overlap制度条款300-600 字按条 / 按并列项内切时加 15%应急预案200-400 字按处置步骤加 10%工单处置记录200-400 字单条工单为一块不加设备台账100-200 字单台设备为一块不加收费标准表100-250 字单条收费项为一块不加2.4 元数据设计让检索会“挑食”元数据是 RAG 里最容易被敷衍过去的地方。很多人只存一个source字段就完事等到要做权限隔离、要做版本过滤、要做项目范围限定时发现根本没法下手。我在物业项目里必存的元数据有这么几项project_id项目/园区标识。物业是典型的“一项目一套规则”跨项目检索是错误的主要来源之一。doc_type文档类型制度、标准、台账、工单、预案、合同。doc_version和effective_from/effective_to版本与生效区间。检索时默认只召回当前生效的。section_path章节路径用于展示“依据来自第几章第几条”。security_level密级。涉及业主个人信息、合同金额的内容必须标记检索阶段就要拦掉。owner_dept责任部门。有些问答只对内部开放。last_verified_at最近一次人工确认时间。超过一年的制度类内容在答复里加一句“该条信息建议向项目确认”。这里有个实际考量元数据不是越多越好。字段太多会让入库和更新的维护成本飙升尤其是工单这种高增量数据。我的做法是过滤强相关的用硬过滤project_id、security_level、生效状态排序用的软权重last_verified_at、doc_type 优先级放在重排阶段做。硬过滤放检索前避免先召回再剔除造成的召回数量损失。3. 检索层召回质量决定天花板3.1 向量模型怎么选别只看榜单选 embedding 模型这件事我踩过一次很典型的坑在一个公开评测榜单上排名很靠前的通用中文模型放到物业文本上表现平平反而是某个体量不大的领域微调模型效果更好。原因是物业文本里有大量专业缩写和内部叫法“公维金”“专项维修资金”“大修基金”指的是同一个东西通用模型未必能把它们拉到相近的向量位置。我的选型流程是这样先自己造评测集。从真实工单和客服记录里抽 200 条问题人工标注每条问题应该命中哪些文档块可能是一个也可能是多个。这是后面所有调优的基准没有它所有调优都是盲调。准备三到五个候选模型本地部署和 API 调用的都放进来比。只看两个指标Recall5前五条里有没有正确答案和 MRR正确答案排在第几位。前者看覆盖后者看排序。看长文本表现。有些模型对超过 512 token 的输入会截断而物业条款动辄六七百字必须实测。我一般要求 Recall5 至少到 0.85 才继续往下走。低于这个值先怀疑数据处理和切分而不是急着换模型——十次里有七八次问题出在切分上。3.2 混合检索为什么必须双路走纯向量检索有个绕不过去的短板对精确匹配不敏感。用户问“3 号楼 2 单元”向量检索可能给你召回“5 号楼 2 单元”的内容因为在语义空间里它们太像了。而物业场景里楼栋号、单元号、车位编号、设备编码这些恰恰是最关键的信息。所以我的标配是混合检索稠密向量召回 稀疏检索BM25 之类召回两路结果融合。# 混合检索 倒数排名融合RRF的最小实现 from collections import defaultdict def rrf_fuse(dense_ids: list[str], sparse_ids: list[str], k: int 60) - list[tuple[str, float]]: 两路召回结果用 RRF 融合k 为平滑常数常用 60 scores defaultdict(float) for rank, doc_id in enumerate(dense_ids, start1): scores[doc_id] 1.0 / (k rank) for rank, doc_id in enumerate(sparse_ids, start1): scores[doc_id] 1.0 / (k rank) return sorted(scores.items(), keylambda x: x[1], reverseTrue)RRF 的好处是不需要关心两路分数量纲不一致的问题——向量相似度是 0 到 1 的余弦值BM25 分数可能是几十直接加权相加会失衡。用排名倒数融合简单粗暴但效果稳定。两路的召回数量我通常设成这样稠密召回 top 30稀疏召回 top 30融合后取 top 20 送进重排。这个数量不是拍脑袋是权衡出来的——召太少容易漏召太多重排压力大、延迟上去。做物业场景20 条进重排基本能覆盖绝大多数问题。3.3 重排和阈值把“看起来像”变成“确实对”重排rerank这一步很多小项目为了省成本会跳过我建议别省。向量检索本质是“粗筛”它算的是整体语义相似度对“哪一句才是真正回答问题的”这件事判断力有限。交叉编码器类的重排模型会把 query 和每个候选块拼在一起做精细匹配效果提升很明显我实测在物业问答上把 MRR 从 0.62 提到了 0.81。重排之后还有一步很关键阈值判定。低于某个分数就认为没找到直接触发拒答而不是硬着头皮送进生成。这个阈值不能凭感觉定我的做法是拿评测集跑一遍看正确样本和错误样本的重排分数分布找一个能切开的点。通常正确样本的分数集中在高位错误样本散在低位中间有一段重叠区阈值就设在重叠区偏下的位置宁可多拒几个也别乱答。这里还要补一个特殊兜底如果查询里包含明确的楼栋号、单元号、设备编码这类实体走一次精确匹配的前置检索。命中就直接用不走向量那套。这条规则解决了我项目里大约三成的“答非所问”问题。3.4 多租户与权限过滤检索阶段就得拦住物业公司通常管着多个项目这些项目之间的数据必须隔离。而且隔离不是“展示层隐藏”那么简单是检索阶段就不能召回。技术上实现不难关键在设计上别偷懒。我在入库时给每个块打project_id检索时把当前用户的可见项目列表作为过滤条件传进去。主流向量库基本都支持带过滤条件的近似检索注意要用预过滤而不是后过滤——后过滤是先召回 top N 再剔除一旦过滤掉一大半返回的结果可能只剩两三条召回数量直接塌了。# 带预过滤的检索调用示意伪代码按你用的向量库改接口 def search(query: str, user: dict, top_k: int 30): filters { project_id: {$in: user[accessible_projects]}, security_level: {$lte: user[clearance]}, status: {$ne: deprecated}, } return vector_store.search( vectorembed(query), filterfilters, top_ktop_k, params{ef_search: 128}, # HNSW 参数召回与延迟的权衡 )注意涉及业主姓名、手机号、房号的工单记录入库前必须做脱敏。我的做法是把这类字段替换成占位符如“某业主”“尾号 1234”同时在元数据里保留一个指向原始记录的 ID需要查看详情时走受控接口。这样做的好处是模型永远接触不到原始隐私数据合规风险大幅降低。4. 生成层让回答像老师傅而不是像说明书4.1 Prompt 骨架角色、证据、边界生成层的 prompt 我改过十几版最后稳定下来的结构是四段式角色设定 证据块 回答规则 输出格式。这个顺序不是随意的角色在前是为了定调证据在中是为了让模型先“读材料”规则在后是为了约束输出格式放最后是因为它最具体、最不容易被前面干扰。你是某物业项目的服务顾问回答对象是业主和一线客服。 你只能依据下面【参考资料】中的内容作答不得使用常识补充或推测。 【参考资料】 [1] {chunk_1} [2] {chunk_2} [3] {chunk_3} 【回答规则】 1. 先给结论再给依据。结论一句话说清依据标明引用编号。 2. 参考资料中没有的信息直接回答“该问题暂未在现行资料中查到建议联系项目服务中心确认”禁止编造。 3. 涉及金额、时限、责任划分的内容必须原文引用关键表述不得改写数字。 4. 涉及业主个人信息的提问一律不回答引导其通过正式渠道查询。 5. 语气平实、不使用夸张表述不承诺资料中未写明的事项。 【输出格式】 结论xxx 依据[编号] 原文关键句 补充说明可选仅在需要提示时效性或适用范围时填写几个细节值得说。第一条“先结论后依据”是为了适配客服场景客服需要的是能立刻回给业主的话不是一篇分析。第二条拒答规则必须写得非常直白模型对“禁止编造”这种措辞的服从度比“请注意准确性”高得多。第三条要求原文引用数字是因为我自己踩过坑——模型把“押金 2000 元”改写成“约两千元”业主拿着截图来质疑非常麻烦。4.2 引用和可追溯这是物业场景的刚需通用问答里引用只是个加分项。物业场景里引用是必要条件。原因很实际客服拿着模型给的答案回复业主业主一句“你凭什么这么说”客服必须能立刻翻出依据。如果翻不出来这个工具就会被弃用。所以我要求每一条回答都必须带可点击的原文定位点开能看到原文档、原章节、高亮的那一句话。实现上有两个要点。一是入库时保存原文偏移量或页面坐标二是生成时强制模型输出引用编号并在后处理里把编号映射回具体的块 ID。映射关系不能在生成后靠字符串匹配去猜那太脆弱我的做法是给每个块编一个短 ID直接写进 prompt 的证据块头部模型引用它就行。还有个小技巧证据块不要塞太多。我试过塞 10 条进 prompt结果模型反而抓不住重点引用还经常张冠李戴。后来压到 3 到 5 条并且按重排分数从高到低排列引用准确率明显上升。这跟人看材料是一个道理给五份文件能看清给二十份就懵了。4.3 幻觉抑制的三道闸幻觉这事没法根治只能层层设防。我在项目里用三道闸第一道在检索。阈值不够就拒答宁可不答也别乱答。这一道能拦掉大部分“知识库里没有”的问题。第二道在生成。prompt 里明确写“不得使用常识补充”并且给一个标准拒答句式。这里有个细节拒答句式一定要给得具体不能只说“无法回答”要说清楚“没查到 建议去哪里问”这样用户体验才不会断崖式下跌。第三道在后校验。生成完之后跑一遍规则检查包括回答里的数字是否都能在证据块里找到对应、回答里提到的文档名是否在检索结果里、是否出现了“大概”“可能”“一般来说”这类模糊词在物业场景里这些词是危险信号。命中规则就降级处理改成一个保守的回复或者转人工。import re def post_check(answer: str, evidence_texts: list[str]) - tuple[bool, str]: 生成后校验数字一致性与模糊表述检查 joined \n.join(evidence_texts) # 1. 抽取回答中的所有数字检查是否在证据中出现 nums re.findall(r\d(?:\.\d)?, answer) missing [n for n in nums if n not in joined] if missing: return False, f数字未在依据中找到{missing} # 2. 模糊表述检查 fuzzy [w for w in (大概, 可能, 一般来说, 通常情况下) if w in answer] if fuzzy: return False, f存在模糊表述{fuzzy} return True, ok第三道闸看着简单实际非常有用。我做过统计上线初期大约有 6% 的回答被它拦下来转人工而这 6% 里绝大多数确实是证据不足或者表述含糊的。业务方对这套机制的接受度很高因为他们看到的不是“AI 乱说”而是“AI 拿不准时会找人”。4.4 结构化输出能对接系统才算真落地只输出一段自然语言的问答价值是有限的。真正让业务方觉得“这东西有用”的是它能直接进入工单流程。我做过一个改造让模型在回答问题之外额外输出一个 JSON 结构包含问题分类、涉及的知识条目、建议的处置动作、预计处理时长区间。客服在工单系统里点一下就能带着这些信息建单省掉了手动填写的环节。{ category: 公共设施-电梯, matched_kb: [KB-0231, KB-0455], suggestion: 先行确认电梯是否在维保期内若在期内联系维保单位同时排查是否为电源或门锁故障, estimated_duration_hours: [2, 8], need_onsite: true, confidence: 0.78 }这里有个经验结构化字段不要太多。我第一版设计了十几个字段结果模型输出经常缺字段或者格式错乱解析失败率超过 15%。后来压到六个字段并且用 few-shot 给了两个完整示例失败率降到 2% 以内。字段越少模型越专注这个规律在小参数模型上尤其明显。5. 工程落地从 Demo 到能用的系统5.1 技术栈和部署形态的选择技术选型我一般遵循“能不引入新组件就不引入”的原则因为物业公司的 IT 团队规模通常不大运维能力有限搞一套需要专人盯的复杂架构最后大概率变成没人管的黑盒。我的常规组合是这样文档解析用 Python 生态里的成熟库处理 PDF 和表格向量库优先选支持元数据过滤和混合检索的轻量方案数据量在一千万块以内完全够用embedding 和生成模型可以走私有化部署也可以走 API取决于数据敏感度。这里有个真实的取舍。有些项目的数据涉及业主信息必须私有化那就得接受单卡推理的并发限制响应时间会明显变长。有些项目只涉及制度文件完全可以走 API成本低、效果还好。我建议的做法是分级处理制度类、公开标准走 API涉及业主数据和合同金额的走私有化模型两套并行但共用同一套检索和编排逻辑。这个设计在多个项目里验证过是比较务实的方案。部署形态上小规模项目我倾向于单机部署加容器编排把解析、向量化、检索、生成拆成独立服务方便单独扩缩容。别一上来就搞微服务全家桶检索这个环节的瓶颈通常不在服务拆分上。5.2 增量更新和版本管理物业文档是持续变化的一套只支持全量重建的管线上线三个月就会变成没人愿意碰的负担。所以增量更新必须在第一版就设计进去。我的做法是给每个文档算一个内容指纹解析前先比对没变化的直接跳过。变化了的走“新版本入库 旧版本标记失效”的流程而不是直接删旧的。为什么要留旧版本因为会有人问“上个月的标准不是这样的吗”这时候能翻出历史版本说明变更时间点比一句“我们改了”有说服力得多。import hashlib def doc_fingerprint(content: bytes, version: str) - str: 内容指纹 版本号用于判断是否需要重新入库 h hashlib.sha256(content).hexdigest()[:16] return f{version}:{h}还有个容易忽略的点删除。制度废止、项目退场对应的块必须能被清理掉。很多向量库支持按元数据批量删除用project_id或者doc_id做条件就行。我见过因为没有删除机制导致退场项目的旧标准还在被召回答复里给出的服务电话是空号的案例这种错误对信任度的伤害极大。5.3 成本和延迟的账要提前算上线前一定要把这两个账算清楚不然容易被业务方一句“怎么这么慢”打回来。延迟方面我做过的典型拆解是这样查询改写 100 到 200 毫秒如果需要多轮改写会更久查询向量化 20 到 50 毫秒向量检索 30 到 80 毫秒稀疏检索 20 到 50 毫秒重排 150 到 300 毫秒取决于候选数量和模型大小生成首字 500 毫秒到 1.5 秒完整生成 2 到 5 秒。加起来端到端三到六秒是常态。要压到两秒以内就得在重排和生成上做取舍比如重排只放前 10 条进去或者用流式输出让用户先看到结论。成本方面主要吃在 embedding 和生成两块。一次问答如果召 20 条、重排后取 4 条输入大概在 1500 到 2500 token输出 200 到 400 token。按这个量级估算日均一千次问答的规模成本是可控的。真正吃成本的是批量入库尤其是首次全量导入几万份文档跑一遍 embedding费用和时间都不小。我的建议是首次导入分批跑跑完抽样验证质量别一次性跑完发现切分策略有问题全部推倒重来。提示入库任务一定要支持断点续跑和失败重试。我遇过一批扫描件 OCR 跑到一半超时如果没有断点机制就得从头再来。5.4 灰度上线和反馈闭环我不建议一次性全量放开。稳妥的节奏是先给一个项目的客服团队用两周收集问题再扩到两三个项目最后全量。灰度期间最重要的不是看准确率是看用户怎么用。我观察到一个有意思的现象客服并不会完全照抄答案他们会把答案当成“草稿”改一改再用。所以真正有价值的反馈不是“答案对不对”而是“哪句话被改掉了”。基于这个我在界面上加了一个轻量的“修改记录”客服改完会自动留存这批数据后来成了优化 prompt 和补充知识库的主要来源。反馈闭环上我设了三个入口一是回答下方的“有帮助/没帮助”按钮二是“转人工”的自动埋点三是每周一次的人工抽检。抽检不用多一百条就够重点看被标为“没帮助”的那批往往能发现系统性的问题比如某类问题总是召回不到或者某个项目的文档解析有系统性偏差。6. 效果评估与常见问题排查6.1 一套能落地的评估方法评估这件事我见过两种极端一种是不评估上线凭感觉另一种是搞一套复杂的自动化评估框架跑出来的分数很好看但跟实际体验对不上。我的做法比较简单粗暴但管用。第一步建一套黄金测试集。从真实工单和客服记录里抽 300 条问题覆盖各个类型人工标注标准答案、应该引用的文档块、是否属于“应该拒答”的情况。这套集子是整个项目的标尺每次改动都要跑一遍。第二步分环节评估别只看端到端。环节指标我的参考线检索Recall5≥ 0.85检索Recall10≥ 0.92重排MRR≥ 0.75生成事实准确率人工评分≥ 0.90生成引用准确率≥ 0.85整体拒答正确率≥ 0.80整体端到端响应时间 P95≤ 6 秒分环节评估的价值在于定位问题。端到端准确率掉了是检索没召回到还是召回到了但生成没用上还是生成用了但改写错了三种情况的修法完全不同。我一般先看检索因为检索是天花板检索不到后面怎么优化都没用。第三步做对比评测。任何改动包括换模型、改 prompt、调分块大小都要做 A/B 对比不能凭感觉。我吃过亏有一次觉得新的分块策略“明显更合理”改完上线整体准确率反而掉了两个点原因是大块导致引用定位变模糊。这种问题不做对比根本发现不了。6.2 典型问题速查表下面这张表是我这些年积累的问题清单基本覆盖了物业 RAG 项目里八成以上的故障。现象可能原因排查动作解决方向问 A 项目答 B 项目元数据缺失或过滤失效查检索日志里的 project_id 条件加预过滤块头注入项目名条款答案缺关键限定条件切分把条款切断了抽查召回块的边界改按条款切分超长条款加父标题数字答错/改写模型自由发挥检查 prompt 是否要求原文引用加数字校验后处理楼栋号召回不准纯向量检索对实体不敏感看召回结果排序引入 BM25 混合检索 实体精确匹配表格内容答不出解析把表格压平了检查入库前的文本形态改表格解析转成自然语言片段扫描件完全检索不到OCR 失败或未做 OCR查该文档的入库记录补 OCR 流程做版面还原答案引用对不上原文引用编号映射错误查块 ID 与编号的映射逻辑用短 ID 直接写入 prompt不靠字符串猜测响应太慢重排候选太多或生成过长打点看各环节耗时减少重排数量限制输出长度上流式旧版本内容仍在被召回缺少失效标记查该文档的版本状态加版本字段检索时过滤模型答“不知道”太多阈值设太严看被拒问题的重排分数分布调低阈值补充知识库有几个问题我想单独说说。**“问 A 项目答 B 项目”**这个问题的破坏力最大因为一旦出错业主会立刻发现而且会觉得整个系统不可信。根因通常是元数据没做或者过滤没生效。我建议在测试阶段专门造一批跨项目的相似问题比如两个项目都有“地面临时停车收费标准”强制测试隔离是否生效。**“数字答错”**这个也很棘手。物业场景里数字太重要了收费、时限、面积、数量错一个就会被投诉。除了 prompt 里明确要求原文引用我还会在后处理里加一道校验回答中的每个数字都要在证据块里出现过否则降级为“建议向项目确认”。这道校验拦下过不少问题。**“模型答不知道太多”**看着是保守其实也伤体验。如果用户连问三个问题都被拒他就会放弃这个工具。我的调法是先看被拒的那批问题里有多少其实知识库里有答案但召回没召到如果有相当比例就该去优化召回而不是调阈值。真正该拒答的是知识库里确实没有的问题这两者要分开统计。6.3 几条踩坑之后的实操心得最后分享几条我认为最有价值的经验都是踩过坑才明白的。第一条先做窄再做全。我见过太多项目一上来就想“覆盖所有物业场景”结果每个场景都做得半吊子。真正跑通的路径是选一个高频、边界清晰的场景先做透比如“电梯困人应急处置”或者“装修管理咨询”把召回率做到 0.9 以上让业务方看到效果再横向扩展。一个场景的成功比十个场景的平庸有说服力得多。第二条数据处理投入的时间永远比你预期的多。我在项目排期时会给数据处理留 40% 到 50% 的工时即使这样还是经常超。超的原因通常是历史文档的质量比预想的差扫描件多、版本乱、格式杂。这里有个务实建议先把质量最好的那批文档跑通别一上来就啃最脏的数据否则很容易在项目前期就耗尽耐心。第三条把“不知道”当成一个正式功能来做。很多团队把拒答当成兜底随手写一句话就完事。实际上拒答的体验设计很重要要告诉用户“为什么不知道”和“该去哪里问”。我在项目里给拒答配了三条不同的引导话术知识库没有的去问项目服务中心超出权限的去走审批表述不清的引导用户补充信息。这三条让“不知道”从一个失败变成了一个有价值的指引。第四条让一线员工参与知识生产。物业行业最懂业务的是班组长和资深管家不是写文档的人。我在项目里搞过一个“知识认领”机制每个知识条目指定一个责任人每季度确认一次是否还有效。这个机制看起来增加工作量实际上大幅降低了过期知识的召回率。而且认领的过程本身就是一次知识梳理很多原本只在口头流传的经验被写下来了。第五条别把评估集当成一次性工作。上线后每周从真实问答里抽一批新样本补进评估集尤其是被标为“没帮助”的。三个月下来评估集会从 300 条长到 600 条覆盖面完全不同。我见过评估集一直不更新的项目跑了半年分数还在 0.9但业务方的满意度一路下滑因为评估集测的都是老问题新出现的问题根本不在里面。第六条日志要打全。每次问答至少记下原始问题、改写后的问题、召回的块 ID 和分数、重排后的块 ID 和分数、最终 prompt、模型输出、校验结果、用户反馈、端到端耗时。这些日志在排障时的价值无可替代。我印象最深的一次线上问题用户说“系统瞎答”看日志发现是那次查询的改写环节把“地库”改写成了“地库租赁合同”检索自然跑偏了。没有日志这种问题根本查不出来。再补一个关于模型选择的小体会。我一开始执着于找“最强的模型”后来发现在 RAG 这套架构里检索质量对最终效果的影响远大于生成模型的选择。同一套检索换个中档模型和旗舰模型人工评分差距通常在 3 到 5 个百分点而检索策略从纯向量换成混合检索加强阈值评分能提升十几个点。所以资源该往哪投答案很清楚。我在后期项目里宁可把预算花在数据清洗和重排模型上也不追求生成端用最大的模型性价比完全不在一个量级。