PHP在线客服接入AI知识库:RAG混合检索与转人工实践

发布时间:2026/11/6 3:56:55
PHP在线客服接入AI知识库:RAG混合检索与转人工实践 简介面向PHP开发者与客服系统搭建者的在线客服AI知识库接入源码包基于ThinkPHP框架实现可用于构建具备智能问答与人工客服协同能力的运营级客服平台帮助解决传统客服响应慢、知识检索分散等问题。压缩包共2010个文件、约68.72MB包含131个PHP核心程序、613个JavaScript脚本、368个HTML页面与258个CSS样式文件并配有XML、JSON、Markdown、TXT等配置说明与文档便于按模块查阅前端界面、后端逻辑及部署资料。包内附有安装配置说明涉及fileinfo、redis插件及PHP运行环境调整适合需要二次开发或学习客服系统架构的中高级PHP开发者。目前已有138人学习可作为客服系统搭建、AI知识库接入与ThinkPHP项目实践的参考样本。1. 需求与边界一套 PHP 在线客服为什么要接 AI 知识库很多团队做在线客服的 AI 改造第一反应是「接个大模型接口就完事」结果上线三天就被业务打回答得挺流畅就是不对。问题从来不在模型而在知识从哪来、答案怎么控、什么时候必须转人工。这套方案面向已经在用 PHP ThinkPHP 维护在线客服系统的团队——人工坐席工作台、会话记录、渠道接入都已经有了现在要加一层 AI 前置应答常见问题由机器人先接答不准就干脆转人工知识库运营人员能自己更新文档而不用改代码每条回答都能追溯到具体知识片段。它适合做课程设计、毕业设计也适合已经上班、要交一份能落地的客服智能化方案的人。下面从架构一路写到验收中间给的是可直接抄改的建表语句、切片逻辑和转人工判定。2. 总体架构设计消息接入、会话、检索、生成、坐席五层职责划分一条用户消息从网页挂件发出到收到回答中间要经过鉴权、去重、落库、意图判定、知识检索、模型生成、敏感词过滤、超时兜底这一串动作。全塞进一个控制器里也能跑但两周后加一个渠道、换一个模型整段代码就得重写。合理的做法是按职责切层层与层之间只用约定的数据结构通信任何一层替换都不影响其他层。这一章先把这个骨架立起来后面几章往骨架里填肉。2.1 从一条用户消息说起链路上的六个处理节点第一个节点是接入层网页、H5、小程序、APP 的 SDK 都往同一个入口打入口只做三件事校验渠道签名、按用户维度限流、把原始报文规整成统一结构。第二个节点是消息网关负责幂等——同一个 client_msg_id 重复上报只落一条客服系统里重复消息引发的串话是最常见的低级事故。第三个节点是会话服务判断这条消息该落到哪个会话、当前会话是机器人接待还是人工接待、人工接待时坐席是否在线。第四个节点是意图与路由这是整条链路的分水岭先用规则和 FAQ 精确匹配拦掉一部分剩下的进知识检索检索有高分片段才交给模型生成分不够就直接转人工。第五个节点是知识库服务负责切片、向量化、召回和重排。第六个节点是运营后台客服主管在这里看未命中的问题、补知识、调阈值。六个节点里只有第四个会调外部模型其余全在 PHP 进程内或本地存储里完成这样成本和排障范围都可控。提示把模型调用收敛到单一节点是后期换模型、做灰度、限流的前提。散落在多个控制器里的模型调用改一次要动十几处。2.2 技术选型为什么让 ThinkPHP 做编排层而不是把逻辑塞给模型ThinkPHP 在这套方案里的定位很明确HTTP 接口、中间件鉴权、数据库访问、缓存、队列、命令行任务这些它都成熟。模型能力则通过 HTTP 接口调用不把 PHP 变成算法工程。有人会问为什么不干脆用 Python 写一整套因为客服系统的主体——坐席管理、工单、权限、报表——已经在 PHP 里了为了 AI 部分重写一遍后台投入产出比不划算。知识检索这块要给两种落地路径。数据量在几万条片段以内向量直接存进 MySQL 的 JSON 列或二进制列召回时在 PHP 里算余弦相似度配合关键词倒排就够用部署简单、无额外组件。数据量再往上或者要做多路召回、按租户隔离就接一个独立的向量检索服务PHP 侧只负责拼查询和拿结果。两条路的上层代码结构完全一致只是检索驱动换实现用一个接口类隔离掉。组件承担职责选型理由ThinkPHP接口编排、鉴权、任务调度已有业务基础团队熟悉MySQL会话、消息、知识元数据事务可靠运营后台直接查缓存热点答案、会话状态抗住重复提问的瞬时并发队列文档切片、向量化、日志落盘把重活从请求链路里挪走模型接口答案生成、向量化走 HTTP可替换可灰度注意向量化和答案生成是两类不同接口别混在同一个 SDK 里封装前者要批量、要重试后者要流式、要超时控制调用特征完全不一样。2.3 数据表设计会话、消息、知识、召回日志四组核心表表结构决定后面能做什么。会话表管状态消息表管过程知识表和片段表管内容召回日志表管效果。少了召回日志线上出现答非所问时你只能靠猜这是最难受的黑匣子。-- 会话一条会话从机器人接待到转人工状态在这张表上流转 CREATE TABLE cc_session ( id bigint unsigned NOT NULL AUTO_INCREMENT, session_no varchar(32) NOT NULL COMMENT 对外会话号, channel varchar(20) NOT NULL COMMENT web/h5/app/mini, user_key varchar(64) NOT NULL COMMENT 渠道内用户标识, agent_id int unsigned NOT NULL DEFAULT 0 COMMENT 人工坐席ID0为未分配, status tinyint NOT NULL DEFAULT 1 COMMENT 1机器人 2排队 3人工 4已结束, last_msg_at datetime DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_no (session_no), KEY idx_status (status, last_msg_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客服会话; -- 消息每条回答都带上引用的知识片段与置信分用于回溯 CREATE TABLE cc_message ( id bigint unsigned NOT NULL AUTO_INCREMENT, session_id bigint unsigned NOT NULL, role varchar(10) NOT NULL COMMENT user/assistant/agent, content text NOT NULL, client_msg_id varchar(64) DEFAULT NULL COMMENT 幂等键, ref_chunks varchar(255) DEFAULT NULL COMMENT 命中的片段ID,逗号分隔, confidence decimal(5,4) DEFAULT NULL, latency_ms int unsigned DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_client (session_id, client_msg_id), KEY idx_session (session_id, id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT会话消息;知识侧拆成文档表和片段表两级。文档表记录来源、分类、版本、状态片段表存切片正文、关键词、向量。两级设计的好处是重新切片时只动片段文档的运营属性分类、生效范围、负责人不受影响。-- 知识片段embedding 以 JSON 数组存储规模小的时候直接在 PHP 侧算相似度 CREATE TABLE kb_chunk ( id bigint unsigned NOT NULL AUTO_INCREMENT, doc_id int unsigned NOT NULL, chunk_no int unsigned NOT NULL DEFAULT 0, content text NOT NULL, keywords varchar(255) DEFAULT NULL COMMENT 抽取的关键词用于倒排召回, embedding json DEFAULT NULL COMMENT 向量数组, token_len int unsigned DEFAULT 0, status tinyint NOT NULL DEFAULT 1 COMMENT 1生效 0失效, PRIMARY KEY (id), KEY idx_doc (doc_id, chunk_no), FULLTEXT KEY ft_content (content) WITH PARSER ngram ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT知识片段;client_msg_id上的唯一索引是幂等的基础前端重发、网络抖动重试都会被数据库挡掉。ref_chunks用逗号分隔存片段 ID虽然不符合范式但查起来快运营后台点开一条回答就能看到依据。FULLTEXT ... WITH PARSER ngram让中文能走 MySQL 原生全文索引作为关键词召回的第一层省掉一次外部调用。2.4 接口契约前端、PHP 与 AI 服务之间的三个约定前后端之间走 SSEPHP 与模型服务之间走流式 HTTP。约定三条请求体统一带 session_no 和 client_msg_id响应统一是「事件类型 数据」两段式任何异常都必须在 200 的响应体里以 error 事件表达不要让前端去解析 500。{ session_no: S20240613001, client_msg_id: c-8f2a91, channel: web, content: 订单还没发货能改地址吗, context: {last_intent: logistics, turns: 2} }请求体里带context.last_intent是为了让路由层知道用户上一轮在聊什么避免多轮里话题一跳就重新检索。turns用来控制上下文长度超过预设轮次就丢弃最早的历史。{event: delta, data: {text: 可以}} {event: delta, data: {text: 未发货订单支持修改收货地址。}} {event: done, data: {msg_id: 90211, confidence: 0.83, refs: [1201, 1207]}} {event: handoff, data: {reason: low_confidence, queue: after_sale}}delta负责吐字done携带最终置信分和引用片段handoff表示转人工并给出排队队列。前端收到handoff就把聊天窗切到排队态不需要自己判断该不该转。这套契约定死之后后面换模型、加渠道、做灰度前端一行不用改。3. 知识库侧设计文档切片、向量化与混合检索的落地细节AI 客服答得准不准八成取决于知识库这一侧模型只占两成。我见过太多项目把精力全花在调提示词上知识库里躺着三年前的产品说明、格式混乱的工单记录、一整篇五万字的制度文件切片切得七零八落再怎么调提示词也救不回来。这一章讲怎么把知识整理成模型能用的形状。3.1 知识从哪来FAQ、文档、历史工单三种来源的清洗规则FAQ 是最优质的知识一问一答直接就是天然的片段几乎不用处理只要把同义问法合并、把答案里的过时信息剔掉。文档类知识产品手册、售后政策质量参差需要先做结构化去掉页眉页脚、目录、修订记录保留标题层级因为标题层级决定了后面切片时的语义边界。历史工单是最容易被忽略的一块里面藏着大量真实用户问法但噪声也最大只保留「坐席明确给出结论且用户未再追问」的对话对其余的丢掉。清洗要有统一的入口不能让运营直接往库里贴。我一般会做一个导入流程上传文件 → 解析成纯文本 → 按标题切成章节 → 人工确认章节标题 → 进入切片队列。人工确认这一步看着笨但能挡掉大量格式灾难。导入时给每篇文档打上分类、生效渠道、生效时间、负责人四个运营属性后面检索时可以按渠道过滤避免把 A 渠道的政策答给 B 渠道的用户。3.2 切片策略按语义切还是按字数切纯按字数切是最省事的也是最容易翻车的一句话被从中间劈开前半句在片段一、后半句在片段二两片召回都不完整模型拼出来的答案就飘。可行的做法是三级切分先按标题层级切大块再按段落切中块最后对超长段落按句子边界合并到目标长度。目标长度控制在 300 到 500 字之间太短丢上下文太长稀释向量语义。/** * 按段落与句子边界切片避免把一句话劈成两半 * param string $text 已清洗的纯文本 * param int $maxLen 单片段目标长度字符 * param int $overlap 相邻片段重叠字符数防止边界信息丢失 */ public function split(string $text, int $maxLen 400, int $overlap 60): array { // 先按空行切成段落保留原有语义块 $paras preg_split(/\n{2,}/u, trim($text)); $chunks []; $buf ; foreach ($paras as $p) { $p trim($p); if ($p ) { continue; } // 单段超长时按中文句末标点再切 $sentences preg_split(/(?[。])/u, $p); foreach ($sentences as $s) { if (mb_strlen($buf . $s) $maxLen $buf ! ) { $chunks[] $buf; // 重叠保留尾部若干字符接上下一段 $buf mb_substr($buf, -$overlap) . $s; } else { $buf . $s; } } $buf . \n; } if (trim($buf) ! ) { $chunks[] trim($buf); } return array_values(array_filter($chunks, fn($c) mb_strlen($c) 20)); }maxLen不是拍脑袋定的它和模型的上下文窗口、召回精度共同决定。片段太长一次召回五条就撑爆提示词太短单条片段信息不完整。400 字左右是中文客服知识的常见甜点区。overlap的作用是给边界上保险比如「退货期限为 7 天」和「超过期限不予受理」被切到两片重叠 60 字能让两片都带上关键限定。切完必须落一个片段序号chunk_no召回时按序号可以把相邻片段一起取出来补全上下文。3.3 向量化与存储MySQL 存元数据向量单独管理向量化的时机放在文档审核通过之后走队列异步做不要卡在导入请求里。一批文档几十个片段逐条调接口慢且贵按批提交每批 16 到 32 条失败重试三次后标记为失败运营后台可以手动重跑。向量维度按所选向量模型定不要自己在代码里写死数字配置化换模型时只改配置。存储上有两条路。片段量在几万以内把向量以 JSON 数组存进kb_chunk.embedding召回时用 SQL 拉出候选集先按分类、状态、关键词过滤把候选压到几百条再在 PHP 里算余弦相似度响应时间完全够用。片段量上去了就把向量同步到独立的检索服务MySQL 只留元数据召回时先去检索服务拿 ID 列表再回 MySQL 补正文。两条路用一个Retriever接口隔离方法签名固定为「查询文本 过滤条件 topK → 片段列表」。提示向量入库一定要带版本号。换了向量模型旧向量和新查询不在同一个空间混着用结果会很难看版本号能让新旧两批向量各自独立切换时整体重建。3.4 混合检索关键词召回加向量召回再做一个轻量重排纯向量召回对同义表达友好但对产品型号、订单号、专有名词这类精确匹配很弱纯关键词召回反过来。客服场景里两类查询都大量存在所以必须混合。做法是两路各取 topK比如各 20 条按片段 ID 去重合并再用加权分排序。/** * 混合召回打分向量相似度 关键词命中 业务权重 * 权重可按业务调整客服场景通常向量略高 */ public function score(array $candidates, array $queryVec): array { $w [vec 0.6, kw 0.3, biz 0.1]; foreach ($candidates as $c) { $vecSim $this-cosine($queryVec, json_decode($c[embedding], true)); // 关键词得分命中词数 / 查询词数简单但有效 $kwHit 0; foreach ($c[query_terms] as $t) { if (mb_strpos($c[content], $t) ! false) { $kwHit; } } $kwScore $c[query_terms] ? $kwHit / count($c[query_terms]) : 0; // 业务权重置顶知识、近期更新的文档给一点加成 $biz ($c[is_top] ? 1 : 0) * 0.5 $c[fresh_score]; $c[score] $w[vec] * $vecSim $w[kw] * $kwScore $w[biz] * $biz; } usort($candidates, fn($a, $b) $b[score] $a[score]); return $candidates; }三个权重里vec决定语义泛化能力kw保证型号和编号不被漏掉biz给运营留了一个手动干预的口子——某条政策刚更新运营可以在后台置顶is_top直接体现在排序上。fresh_score用文档更新时间的衰减函数算越新越高上限 1。这套打分算完之后取前 3 到 5 条作为模型输入分数低于阈值的直接丢弃不要抱着「多给几条总有一条对的」的心态噪声片段是幻觉的主要来源之一。3.5 知识更新与失效增量重建和版本切换知识库不是建完就不动的。产品改价、政策调整、活动上下线都需要知识跟着变。做法是文档级版本文档每次编辑生成一个新版本旧版本的片段状态置为失效但不删除新版本重新切片、重新向量化全部完成后原子切换文档的当前版本号。这样任何一次更新都可回滚出问题能退回上一版。失效还有个隐含需求定时失效。活动类知识应该带生效起止时间到期自动下线别指望运营记得手动删。检索时把时间条件加进过滤过期片段根本不进候选集比事后清理干净得多。4. AI 应答链路意图判定、上下文拼装与转人工阈值知识库准备好了接下来是把用户的一句话变成一条靠谱的回答。这条链路上最容易被低估的是「什么时候不该答」——一个高质量客服机器人的标志不是答得多而是知道什么时候闭嘴转人工。这一章把链路的每一段拆开讲包括提示词怎么拼、流式怎么输出、置信度怎么算。4.1 意图分层先用规则和缓存把成本压下来用户消息进来不要一上来就检索加调模型。先做三层过滤。第一层是精确匹配把用户问题做归一化去标点、转小写、繁简统一、去语气词和 FAQ 的标准问做哈希比对命中就直接返回缓存答案这条路径耗时在毫秒级。第二层是高频问缓存把最近一段时间内出现频次高且反馈良好的问题答案缓存起来key 用归一化后的问题文本重复提问直接命中。第三层才进检索和生成。经过前两层通常能拦掉四成到六成的请求模型调用量直接减半成本和时间都省下来了。public function route(string $question, string $channel): array { $norm $this-normalize($question); // 去标点、统一大小写、繁转简 // 第一层FAQ 精确命中 if ($faq $this-faqHit($norm, $channel)) { return [type faq, answer $faq[answer], confidence 0.99]; } // 第二层热点答案缓存带渠道前缀防止串渠道 $cacheKey cc:ans:{$channel}: . md5($norm); if ($cached Cache::get($cacheKey)) { return [type cache, answer $cached, confidence 0.9]; } // 第三层检索 生成 return [type rag, chunks $this-retrieve($question, $channel)]; }normalize里最容易漏的是繁简和全半角用户从不同端复制粘贴过来的文本差异很大。缓存 key 必须带渠道前缀A 渠道的促销政策答给 B 渠道用户是客服系统里典型的翻车点。confidence在这里是预估值最终值在生成后再算。4.2 提示词拼装系统约束、知识片段、历史对话三段式提示词的结构要固定不要让每次请求的模板都不一样否则出问题没法复盘。三段式系统段写死角色和红线只依据给定资料回答、不猜测、资料不足时明确说不知道并引导转人工、不透露内部文档编号知识段按打分顺序编号贴入片段对话段只保留最近若干轮且做长度截断。public function buildPrompt(string $question, array $chunks, array $history): array { $sys 你是某产品的在线客服助手。规则\n . 1. 只能依据【参考资料】回答资料未覆盖时回答“这个问题我需要请人工同事帮您确认”。\n . 2. 不编造价格、时效、政策条款不引用资料以外的编号。\n . 3. 回答控制在 150 字内分点不超过 3 条语气简洁礼貌。\n . 4. 涉及退款金额、账号安全、投诉时直接建议转人工。; $ref ; foreach ($chunks as $i $c) { $ref . [ . ($i 1) . ] . $c[content] . \n; } // 历史对话截断只保留最近 6 条且总字符不超过 800 $hist array_slice($history, -6); $messages [[role system, content $sys]]; foreach ($hist as $h) { $messages[] [role $h[role], content mb_substr($h[content], 0, 200)]; } $messages[] [role user, content 【参考资料】\n{$ref}\n【用户问题】\n{$question}]; return $messages; }系统段里的第 4 条是硬性兜底把高风险意图直接排除在模型决策之外不让模型去判断「这笔退款该不该批」。知识段编号是为了让模型能引用同时方便日志里对齐是哪条片段支撑了答案。历史截断看着粗暴但比按 token 精确计算好维护中文客服对话很少有超过 200 字的单条消息。4.3 流式输出与超时控制PHP 侧怎么不卡死用户等不了五秒才看到第一个字。流式输出的关键是关掉所有缓冲让每个数据块立刻出去。PHP 里做 SSE要设text/event-stream、关掉输出缓冲、关掉压缩、每条消息后 flush同时要把执行时间拉长否则默认 30 秒就断了。public function stream(string $sessionNo, array $messages): void { header(Content-Type: text/event-stream); header(Cache-Control: no-cache); header(X-Accel-Buffering: no); // 关掉反向代理缓冲否则会攒着一起发 while (ob_get_level() 0) { ob_end_flush(); } set_time_limit(120); ini_set(zlib.output_compression, 0); $start microtime(true); $full ; foreach ($this-callModelStream($messages) as $piece) { // 首字超时保护3 秒没吐字就中止走兜底话术 if ($full microtime(true) - $start 3) { $this-emit(handoff, [reason first_token_timeout]); return; } $full . $piece; $this-emit(delta, [text $piece]); } $this-emit(done, [ msg_id $this-saveMessage($sessionNo, $full, $start), confidence $this-confidence($full, $messages), ]); } private function emit(string $event, array $data): void { echo event: {$event}\ndata: . json_encode($data, JSON_UNESCAPED_UNICODE) . \n\n; flush(); }X-Accel-Buffering: no这行是血泪经验不加的话反向代理会把整个响应攒完再发前端看起来和一次性返回没区别白折腾。首字超时保护设在 3 秒超时就发handoff比让用户对着转圈强。set_time_limit要和网关的超时对齐网关 60 秒、PHP 120 秒让 PHP 后死才能把错误事件发出去。4.4 置信度与转人工三个信号决定交不交给人转人工不是「模型说不知道就转」要有量化信号。第一个信号是检索分最高片段得分低于阈值说明知识库压根没覆盖这个问题生成出来的必然是幻觉。第二个信号是模型自评在提示词末尾要求模型输出一个 0 到 1 的把握程度或者用一个轻量的判定接口对答案做一次「是否被资料支持」的二分类。第三个信号是行为信号用户连续两轮追问同一个问题、出现负面情绪词、消息里带订单号但检索没命中。public function shouldHandoff(array $ctx): bool { if (($ctx[top_score] ?? 0) 0.35) { return true; // 知识未覆盖 } if (($ctx[self_score] ?? 1) 0.5) { return true; // 模型自评不自信 } if (($ctx[repeat_count] ?? 0) 2) { return true; // 连续追问视为未解决 } // 高风险意图一律转人工不交给模型判断 return in_array($ctx[intent] ?? , [refund, account, complaint], true); }三个阈值都要用线上数据校准0.35只是起点。上线前两周把每次转人工的记录连同当时的分数存下来看分布把阈值调到「转人工率可接受且投诉率不上升」的位置。4.5 上下文与成本历史裁剪、答案缓存与配额会话状态放在缓存里key 用 session_novalue 存最近若干轮对话加当前意图设置合理的过期时间。不要每次请求都从数据库全量拉历史消息会话长了数据库压力顶不住。缓存失效时从数据库回填回填只取最近二十条。答案缓存要分两类一类是精确命中问题的缓存key 是归一化问题文本命中率高但覆盖面窄另一类是「问题簇」缓存把语义相近的问题归到同一个簇簇内共享答案覆盖面广但要定期清理。两类都设过期时间政策类知识一改缓存必须主动清掉否则用户拿到的是旧答案这种事故排查起来极其费劲。模型调用加配额按租户或渠道设日限额超了自动降级到 FAQ 匹配保证系统不整体挂掉。5. 避坑与常见问题排查上线后最容易翻车的五个点方案跑通和系统能用是两回事。下面五条是这类项目里出现频率最高的问题每条都按「现象、原因、解决」写照着排查能省不少时间。5.1 答非所问召回片段是对的答案还是偏现象是运营抽查时发现明明库里有一条完全对应的知识机器人答的却是另一回事。原因通常有两个一是召回返回了多条片段按分数排序后把真正相关的那条排到了第四第五模型在拼装时被前几条带偏二是片段本身内容太杂一条片段里塞了退款、换货、发票三件事模型抓到哪句算哪句。解决办法是提高召回精度而不是加大召回数量。把 topK 从 10 降到 5加一个分数差判断第一条和第二条分差过大时只给第一条避免干扰。切片那一侧回头检查一条片段只讲一件事一件事超过 400 字就拆成两片。再加一步「答案归因」校验——生成完成后拿答案和引用片段做一次相似度比对相似度太低就判定为偏离直接走转人工。5.2 首字延迟高不是模型慢是链路在等现象是用户发完消息转了两三秒才看到第一个字但模型侧的耗时日志显示首字只要 800 毫秒。原因往往在 PHP 侧输出缓冲没关干净、反向代理开了压缩缓冲、或者检索那一步串行调了三次接口。排查顺序是从外往里。先看网关日志里的 TTFB再在 PHP 里打点把「收到请求」「检索完成」「模型首字」「输出完成」四个时间戳记下来哪一段占大头一目了然。检索接口能并行的并行向量召回和关键词召回同时发别串着等。缓冲区那部分ob_end_flush之外还要确认框架自身的中间件没有接管输出有些框架的响应对象会把内容攒到最后统一发送流式接口要绕开它。注意本地开发环境常常看不出缓冲问题因为本地没有反向代理。流式接口必须在有代理的环境里测否则上线才发现。5.3 重复回答与串话会话状态存错了地方现象有两个同一个用户连着发两条消息机器人把第一条的答案又答了一遍或者两个用户的对话串到一起。原因是会话状态用了错误的 key比如用用户 IP 或者渠道 ID 做 key同一网络下多个用户就串了也可能是幂等没做前端重发导致重复处理。解决办法是把 session_no 作为唯一状态键由服务端在会话创建时生成并下发前端每次带回来。消息表上(session_id, client_msg_id)的唯一索引必须存在插入冲突时直接返回上一次的结果不要重新走一遍链路。会话状态的缓存 key 加前缀区分环境测试环境的数据不要污染生产。5.4 知识更新不生效缓存和索引没对齐现象是运营在后台改了知识前端提问还是旧答案重启服务才好。原因是三层缓存各活各的应用层缓存了答案、检索层缓存了向量、模型侧可能还有提示词缓存。改知识的时候只清了数据库。解决思路是把「知识变更」做成一个事件文档版本切换成功后发一条消息订阅方各自清各自的缓存。应用层按文档 ID 打标签清缓存时按标签批量清不要用全量 flush那会把无关的热点答案一起干掉。检索层重建时用新版本号写入旧版本保留一段时间再删切换瞬间不会出现查不到的情况。5.5 敏感内容与越权内部文档被答出去了现象是用户问了一个刁钻问题机器人把内部定价规则、成本说明、内部工单编号答了出来。原因是知识入库时没有做可见范围标记检索时也没按用户身份过滤。解决要两道闸。入库时给每条片段打上可见范围公开、仅内部、指定渠道内部文档默认不进 AI 检索池要用也得走单独的库。检索时把用户身份和渠道作为过滤条件传下去SQL 层面就过滤掉不要靠提示词里写「不要透露」来约束模型那是靠不住的。输出侧再加一道敏感词和编号格式的检测命中就替换成兜底话术并记一条告警。6. 验收与答辩用一套 60 条评测集把效果讲清楚这套东西做完最难回答的问题是「效果到底怎么样」。说「感觉挺准的」过不了答辩也过不了业务评审。可行的做法是建一套自己的评测集从历史工单和真实提问里抽 60 条覆盖常见问题、长尾问题、多轮追问、超纲问题四类每条标注标准答案要点和期望动作直接回答 / 转人工。每次改动检索策略或提示词跑一遍看四个数。指标计算方式参考目标首条命中率标准答案要点被回答覆盖的比例75% 以上误答率答了但要点错误的比例5% 以下转人工准确率应转人工的问题中实际转了的比例85% 以上首字延迟从请求到第一个字输出的 P901.5 秒以内评测脚本不用搞复杂一个命令行任务读 CSV逐条调内部接口把回答和期望动作一起写回结果表人工复核一遍。跑完输出一份按类别分组的报告答辩时直接展示这张表比讲十分钟架构有说服力。# 逐条跑评测集输出每条的回答与判定人工复核后统计 php think eval:run --fileeval_set.csv --outresult_$(date %m%d).csv答辩演示控制在五分钟先演示一次标准问题直接命中再演示一次知识库没覆盖时的转人工最后打开召回日志指着某条回答说清楚它引用了哪几个片段、分数多少、为什么没转人工。这条链路讲得通说明你真做过而不是拼了个界面。上线节奏上先灰度一个渠道、一个知识分类观察一周的转人工率和投诉量再逐步放开。转人工率突然升高通常是知识更新导致的检索失效先查最近改过的文档别急着调模型。从那以后我每次交付这类系统都会在灰度前强制走一遍评测集哪怕只改了一个阈值——阈值这种东西改一个数字看着无害实际影响的是整条召回链路的分布不跑一遍心里没底。希望这套结构能帮到你少走几步弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询