PHP在线客服接入AI知识库:从工单清洗到智能问答的落地实践

发布时间:2026/10/25 20:40:06

PHP在线客服接入AI知识库:从工单清洗到智能问答的落地实践 简介这份资源是一套基于PHP与ThinkPHP框架的运营级在线客服系统源码重点解决客服系统接入AI知识库的问题适合有一定PHP基础、希望搭建企业级智能客服的开发者与运维人员。压缩包共2010个文件约68.72MB以613个js、368个html、258个css等前端资源为主配合131个php业务代码、3个sql数据库脚本及大量xml、json、md配置与说明文档整体结构完整便于二次开发与部署。资源附带安装教程涵盖fileinfo、redis插件安装以及pcntl_signal、pcntl_fork等禁用函数的配置说明帮助读者快速完成环境搭建。目前已有137人学习下载。通过该源码读者可掌握AI知识库与在线客服的对接思路理解NLP问答匹配、缓存加速与ThinkPHP模块化开发并获得一套可直接运行、便于扩展的客服系统实践参考。1. 从工单到知识库PHP 在线客服接入 AI 的落地路径很多团队做在线客服系统第一版都是「人工坐席 常见问题静态页」用户问一句、客服查半天响应慢、口径还不统一。这套基于 PHP 的在线客服接入 AI 知识库方案解决的就是这个断层把历史工单、产品文档、FAQ 沉淀成可检索的知识库用户提问时先由 AI 召回并生成回答答不上来再转人工。它适合用 ThinkPHP 做后台、已经有一套客服工单表结构、想低成本加一层智能问答的团队。整套逻辑不依赖重型框架核心是「知识入库 → 向量或关键词召回 → 拼 Prompt → 调模型 → 回写会话」这条链路PHP 侧负责调度和兜底模型侧只做生成。下面按落地顺序拆开讲从环境到代码到踩坑能直接照着改。2. 知识库接入前的环境与数据准备ThinkPHP 侧要先把地基打平2.1 为什么选 ThinkPHP 做调度层而不是纯脚本在线客服系统本身有会话、坐席、工单、消息几张核心表ThinkPHP 的模型和查询构造器能直接复用现有连接不用为了接 AI 再搭一套服务。常见做法是把 AI 问答封装成一个 Service 类在消息入库的钩子里调用而不是单独起一个进程。这样会话上下文、用户身份、历史消息都能顺手拿到省掉一次跨服务查询。选型上不建议一上来就上向量数据库中小体量用 MySQL 全文索引或关键词匹配先跑通等知识条目过万再考虑 embedding 方案否则运维成本会吃掉收益。环境上PHP 建议 8.0 以上ThinkPHP 6.x扩展需要curl、mbstring、json、pdo_mysql。如果本地用 phpstudy 或小皮面板注意 PHP 版本和 CLI 版本要一致否则命令行跑迁移脚本时会报扩展缺失。下面先建知识库表。-- 知识条目表存 FAQ、文档片段、历史优质回答 CREATE TABLE kb_article ( id int unsigned NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL DEFAULT COMMENT 条目标题, content text NOT NULL COMMENT 正文用于召回和拼 Prompt, keywords varchar(500) NOT NULL DEFAULT COMMENT 逗号分隔关键词提升召回, category_id int unsigned NOT NULL DEFAULT 0 COMMENT 分类对应产品线, status tinyint NOT NULL DEFAULT 1 COMMENT 1启用 0停用, updated_at int unsigned NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_category (category_id), FULLTEXT KEY ft_content (title,content) WITH PARSER ngram ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTAI知识库条目;逻辑说明content是召回主体keywords用来补全文索引对短词不敏感的问题category_id让不同产品线互不串味。WITH PARSER ngram是 MySQL 5.7 对中文全文检索的支持没加这个中文分词基本失效。参数上ngram_token_size默认 2意味着至少两个汉字才能命中单字查询会漏这是后面避坑章要展开的点。2.2 把历史工单清洗成知识条目直接拿原始工单喂给模型效果很差因为工单里有大量「你好」「在吗」「转人工」这类噪声。清洗思路是只保留客服最终回复且用户未再追问的会话把用户首问作为title客服回复作为content再从首问里抽关键词。下面是一个批量导入脚本。?php // 命令行执行php think kb:import namespace app\command; use think\console\Command; use think\console\Input; use think\console\Output; use think\facade\Db; class KbImport extends Command { protected function configure() { $this-setName(kb:import)-setDescription(清洗工单并导入知识库); } protected function execute(Input $input, Output $output) { // 只取已关闭且客服有最终回复的会话 $rows Db::name(chat_session) -alias(s) -join(chat_message m, m.session_id s.id) -where(s.status, 2) -where(m.role, agent) -field(s.id, s.first_question, m.content, m.create_time) -order(m.create_time desc) -limit(2000) -select(); $batch []; foreach ($rows as $r) { $q trim($r[first_question]); $a trim(strip_tags($r[content])); // 过滤过短和纯寒暄内容 if (mb_strlen($q) 4 || mb_strlen($a) 10) { continue; } if (preg_match(/^(你好|在吗|转人工|有人吗)/u, $q)) { continue; } $batch[] [ title mb_substr($q, 0, 100), content $a, keywords $this-extractKeywords($q), category_id 1, status 1, updated_at time(), ]; } if ($batch) { Db::name(kb_article)-insertAll($batch); } $output-writeln(导入条数 . count($batch)); } // 简易关键词抽取按标点和停用词切分 private function extractKeywords(string $q): string { $stop [的,了,吗,呢,怎么,如何,请问]; $q str_replace($stop, , $q); $parts preg_split(/[\s,。?!]/u, $q, -1, PREG_SPLIT_NO_EMPTY); return implode(,, array_slice(array_unique($parts), 0, 5)); } }逻辑说明status2表示会话已结束roleagent取客服消息避免把用户追问也当成答案。mb_strlen过滤掉过短内容正则过滤寒暄。extractKeywords是兜底方案停用词表按自己业务补比如电商要加「发货」「退款」。参数上limit(2000)是防止一次性拉爆内存实际可以分批跑。跑完用SELECT COUNT(*) FROM kb_article确认条数再抽查几条看content有没有混入 HTML 标签。提示导入前先备份kb_article清洗规则调一次可能要重跑有备份就不用后悔药。3. 召回与拼 Prompt让 AI 答得像你们自己的客服3.1 关键词召回与全文索引的取舍召回质量直接决定回答质量。中小体量下MySQL 全文索引够用但中文场景有两个坑一是ngram_token_size最小 2单字词查不到二是全文索引对keywords字段不生效因为上面只给title和content建了索引。常见做法是先用keywords做一次 LIKE 匹配再用全文索引兜底两路结果合并去重。下面是对外暴露的召回方法。?php namespace app\service; use think\facade\Db; class KbRecall { /** * param string $question 用户问题 * param int $categoryId 产品线 * param int $topN 返回条数 */ public function recall(string $question, int $categoryId 0, int $topN 3): array { $question trim($question); if ($question ) { return []; } // 第一路关键词 LIKE命中快适合短查询 $like Db::name(kb_article) -where(status, 1) -where(function ($q) use ($question) { $q-whereLike(keywords, % . $question . %) -whereOr(title, like, % . $question . %); }) -when($categoryId 0, function ($q) use ($categoryId) { $q-where(category_id, $categoryId); }) -limit($topN) -select()-toArray(); // 第二路全文索引适合长句 $fulltext []; if (mb_strlen($question) 2) { $fulltext Db::name(kb_article) -where(status, 1) -whereRaw( MATCH(title, content) AGAINST(:q IN NATURAL LANGUAGE MODE), [q $question] ) -when($categoryId 0, function ($q) use ($categoryId) { $q-where(category_id, $categoryId); }) -limit($topN) -select()-toArray(); } // 合并去重保留顺序 $merged []; foreach (array_merge($like, $fulltext) as $item) { $merged[$item[id]] $item; } return array_slice(array_values($merged), 0, $topN); } }逻辑说明whereLike那一路对keywords和title同时匹配解决全文索引对短词不敏感的问题MATCH ... AGAINST那一路处理长句。when是 ThinkPHP 的条件构造categoryId为 0 时不加分类过滤。参数topN建议 3 到 5太多会撑大 Prompt 导致模型跑偏太少召回不全。注意whereRaw里用了绑定参数别直接拼字符串否则有注入风险。3.2 拼 Prompt 的模板与上下文控制召回结果不能直接丢给模型要拼成结构化 Prompt明确告诉模型「只根据以下资料回答资料没有就说不知道」。这样能大幅降低胡编。下面是一个可复用的 Prompt 构造方法。?php namespace app\service; class PromptBuilder { public function build(string $question, array $articles): array { $context ; foreach ($articles as $i $a) { $no $i 1; $context . 【资料{$no}】{$a[title]}\n{$a[content]}\n\n; } $system 你是一名在线客服助手。只能依据下面提供的资料回答用户问题。 . 资料中没有的内容直接回复「这个问题我需要转人工帮您确认」。 . 回答要简洁不超过 150 字不要编造资料外的信息。; $user 资料如下\n{$context}用户问题{$question}; return [ [role system, content $system], [role user, content $user], ]; } }逻辑说明system里三条约束是关键——限定来源、限定兜底话术、限定长度。context拼接时给每条资料编号方便模型引用。参数上如果content很长要做截断单条建议不超过 800 字总上下文控制在模型窗口的三分之一以内留出余量给用户问题和模型输出。常见做法是在build前先按updated_at排序优先用新资料。注意Prompt 里的兜底话术要和你们转人工的关键词对齐否则用户看到「转人工」但系统没触发转接体验会断。4. 调用模型与回写会话把 AI 回答接进现有消息流4.1 封装模型调用与超时重试模型调用是整条链路最容易翻车的地方网络抖动、限流、返回格式异常都会遇到。封装时要设超时、重试和降级。下面用 curl 封装一个通用调用方法兼容常见的对话补全接口。?php namespace app\service; class AiClient { private string $endpoint; private string $apiKey; private int $timeout 8; public function __construct(string $endpoint, string $apiKey) { $this-endpoint $endpoint; $this-apiKey $apiKey; } /** * param array $messages PromptBuilder 产出的消息数组 * return string 模型回答失败返回空串 */ public function chat(array $messages): string { $payload json_encode([ model your-model-name, messages $messages, temperature 0.3, max_tokens 300, ], JSON_UNESCAPED_UNICODE); for ($i 0; $i 2; $i) { // 最多重试一次 $ch curl_init($this-endpoint); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER true, CURLOPT_POST true, CURLOPT_POSTFIELDS $payload, CURLOPT_TIMEOUT $this-timeout, CURLOPT_HTTPHEADER [ Content-Type: application/json, Authorization: Bearer . $this-apiKey, ], ]); $resp curl_exec($ch); $code curl_getinfo($ch, CURLINFO_HTTP_CODE); $err curl_error($ch); curl_close($ch); if ($code 200 $resp) { $data json_decode($resp, true); $text $data[choices][0][message][content] ?? ; if ($text ! ) { return trim($text); } } // 记录日志便于排查别把 key 写进去 trace(ai_call_fail code{$code} err{$err}, error); usleep(300000); // 等 300ms 再试 } return ; } }逻辑说明temperature设 0.3 是为了让客服回答稳定不要发挥max_tokens300 对应 150 字左右。重试只做一次避免雪崩。trace是 ThinkPHP 的日志助手记录时不要带apiKey。参数上timeout8 秒是经验值超过这个时间用户已经等不及了不如直接走兜底。4.2 在消息入库钩子里接入并回写AI 回答生成后要写回chat_message表并标记来源方便后续统计命中率。下面是在消息服务里的接入片段。?php namespace app\service; use think\facade\Db; class ChatService { public function handleUserMessage(int $sessionId, string $question): array { // 1. 先入库用户消息 Db::name(chat_message)-insert([ session_id $sessionId, role user, content $question, create_time time(), ]); // 2. 召回 拼 Prompt $recall new KbRecall(); $articles $recall-recall($question, 1, 3); // 3. 没召回到直接转人工不浪费一次模型调用 if (empty($articles)) { return [type transfer, msg 这个问题我需要转人工帮您确认]; } $builder new PromptBuilder(); $messages $builder-build($question, $articles); // 4. 调模型 $client new AiClient(config(ai.endpoint), config(ai.key)); $answer $client-chat($messages); // 5. 模型失败也转人工 if ($answer ) { return [type transfer, msg 这个问题我需要转人工帮您确认]; } // 6. 回写 AI 消息source 标记来源 Db::name(chat_message)-insert([ session_id $sessionId, role ai, content $answer, source kb, create_time time(), ]); return [type ai, msg $answer]; } }逻辑说明先入库用户消息保证会话完整召回为空直接转人工省调用成本模型失败也转人工保证不冷场。sourcekb方便后面统计 AI 命中率。参数上recall的categoryId传 1 是示例实际按用户当前咨询的产品线传。注意config(ai.key)要放在.env或独立配置文件里别硬编码进代码。提示上线前先用几条真实问题跑一遍看召回条数和回答是否对得上别直接放全量流量。5. 避坑与排查接入 AI 知识库最常见的五个翻车点5.1 中文全文索引查不到短词现象用户问「退款」召回结果为空但知识库明明有退款条目。原因是ngram_token_size默认 2单字和部分双字词在全文索引里匹配不到。解决短查询走keywords的 LIKE 那一路或者把ngram_token_size调到 1 并重建索引。调小会增加索引体积权衡后我一般保留 2靠 LIKE 兜底。5.2 模型回答里出现资料外的内容现象知识库没有的内容AI 也编了一段。原因是 Prompt 约束不够强或者temperature太高。解决system里明确「只能依据资料」temperature压到 0.3 以下并在代码里做一次校验——如果回答里出现资料中没有的关键实体直接替换成兜底话术。5.3 curl 调用偶发超时导致用户干等现象高峰期部分用户消息发出后十几秒没反应。原因是模型接口超时没设或设太长。解决CURLOPT_TIMEOUT设 8 秒超时后走转人工别让用户等。同时加日志统计超时率超过 5% 要考虑换接口或加缓存。5.4 知识库条目重复导致召回冗余现象同一个问题召回三条内容几乎一样的资料Prompt 被撑大。原因是导入时没去重。解决导入前按title做一次GROUP BY去重或者用keywords做相似度过滤。我一般会在导入脚本里加一步SELECT title, COUNT(*) FROM kb_article GROUP BY title HAVING COUNT(*) 1检查。5.5 会话上下文丢失导致多轮对话答非所问现象用户追问「那这个要多久」AI 答的是上一个不相关的问题。原因是每次只拿当前问题去召回没带历史。解决召回时把最近两轮用户消息拼进去一起查或者在 Prompt 里带上最近一轮问答。注意别把整个会话历史都塞进去会超上下文。6. 进阶用缓存和命中率统计把成本压下来跑通之后最该做的是两件事缓存高频问题和统计命中率。高频问题比如「怎么退款」「发货时间」每天被问几百次每次都调模型是浪费。常见做法是用 Redis 缓存「问题 → 回答」key 用问题归一化后的哈希过期时间设 1 小时。归一化就是去掉标点、转小写、去空格这样「怎么退款」和「怎么退款」能命中同一条。?php // 在 ChatService 调模型前加一层缓存 $cacheKey kb_ans: . md5(mb_strtolower(preg_replace(/[\s,。?!]/u, , $question))); $cached cache($cacheKey); if ($cached) { return [type ai, msg $cached, from cache]; } // ... 调模型拿到 $answer 后 cache($cacheKey, $answer, 3600);逻辑说明preg_replace做归一化md5生成定长 keycache是 ThinkPHP 的缓存助手底层配 Redis。过期时间 3600 秒是平衡新鲜度和命中率的值知识库更新频繁就调短。注意缓存要区分产品线否则 A 产品的答案会串到 B 产品可以在 key 里拼categoryId。命中率统计则是给chat_message加一个source字段前面已经加了定期跑 SQL 看 AI 回答占比和转人工占比。如果转人工率超过 40%说明知识库覆盖不够该补条目了如果 AI 回答里用户紧接着又追问同一问题说明回答质量不行该优化 Prompt 或换模型。我一般每周跑一次这个统计把转人工率高的问题捞出来人工补进知识库形成闭环。从那以后我每次接新的知识库都强制先跑一周的召回日志再调 Prompt不然根本不知道模型在哪些问题上翻车。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/25 20:35:06

混合语言单据的跨行表格:先恢复行身份,再让 AI 提取字段

一份海运单据里,品名可能换行,数量在下一页,表头又重复了一次。如果只让模型“提取成数组”,输出常常十分整齐,行却已经错配。错误最难发现的地方,是每个字段单独看都像真的。 我会先建立行与页面证据&…

2026/10/25 20:35:06

AI 识别票据之后:金额、币种和原文证据要分开校验

票据图片变成JSON,只完成录入的一部分。界面显示total为1200时,用户还需要知道它是哪一种币种,模型有没有把“1.200,00”按另一种格式解释。比“全自动记账”更值得做好的是,让人快速发现不该自动入账的字段。 2026年10月19日&am…

2026/10/25 20:35:06

把一张穿孔卡做成可导出的 SVG

复古界面最容易做出“像真的”,也最容易顺手写下“仿真”。一张浅黄色卡片,配上等距孔位,就有了老机器的气息;但它并没有因此成为历史设备的编码器。 这个小工具只做一件事:把一段短 ASCII 文本转换为八位二进制&…

2026/10/25 21:55:38

Linux实时调度延迟实战:从cyclictest测量到CPU隔离调优

1. 什么是“调度延迟初体验”:一个嵌入式与实时系统工程师的真实入门现场“调度延迟初体验”这六个字,乍看像极了某本Linux内核书的课后习题,或者新同事在晨会上支吾着汇报的模糊感受。但在我带过的二十多个嵌入式项目里,它从来不…

2026/10/25 21:55:38

SpringBoot资产管理系统功能介绍

商品编号:000042【一、项目简介】 围绕企事业单位固定资产管理构建业务流程,涵盖资产登记、分类、领用审批、盘点、维保及统计分析。提供账号登录、角色信息和资产状态、部门分布等概览。维护资产编码、名称、规格型号、品牌、所属部门及采购金额&#x…

2026/10/25 21:55:38

用Wind Python接口从零构建Fama-French三因子五因子模型

简介:这是一份围绕Fama-French三因子与五因子模型的Python实现资料包,面向金融量化初学者、金融专业学生以及需要做因子分析的从业者。压缩包共121个文件、约11.98MB,以101个py脚本为主,辅以csv因子数据表、txt说明、pyc编译文件、…

2026/10/25 21:55:38

SpringBoot微信小程序校园闲置书籍交换系统功能介绍

商品编号:000041【一、项目简介】 面向校园闲置书籍流通的微信小程序系统,配套网页管理后台。学生可浏览推荐书籍、分类搜索、发布书籍、查看详情并管理个人书籍、交换和订单;管理员可开展图书、审核、公告与统计管理。提供学号密码登录、注册…

2026/10/25 21:50:37

无Tool Calling的结构化Agent:用Schema约束LLM输出

1. 这个“无 Tool Calling”的Agent,到底在对抗什么?你有没有试过这样写一个Agent:给它一段用户问题,让它调用天气API、查数据库、发邮件、读文件……然后发现代码越写越厚,错误日志越堆越多,调试时要同时盯…

2026/10/25 0:02:22

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/25 0:02:23

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/25 0:02:23

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/25 0:02:22

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/25 0:02:23

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/25 0:02:23

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑