我把 AI 助手的响应从 30 秒优化到了 8 秒
我的 AI 助手,最近是越来越没法用了。
它本身是个挺成熟的方案:OpenClaw 打底,接了自托管的 Outline 当知识库,平时问个问题、查个资料、让它帮忙写文档,全靠它。可不知道从哪天起,每回一句话都慢得让人抓狂——单纯问一句,光标能转 30 秒;一旦让它连着调几个工具干活,两分钟都打不住。
我一开始想当然地以为是模型不行,琢磨着换个更强的。结果换过来一试,该慢还是慢,一点没见好。折腾到后面我实在受不了,干脆静下心,把"慢"这个模糊的感受一层一层拆开看,才发现问题压根不在模型,而是我自己埋下的三个隐患。这篇就把我排查和解决的全过程记下来,给同样被"慢"折磨的人做个参考。
先说结论:慢,不一定就是模型慢。我这次的排查顺序是——先看链路和网络,再看参数有没有真正生效,最后才动任务分工。按这个顺序走下来,响应从 30 秒压到了 8 秒。
第一个问题:数据链路绕了地球一圈
最先发现的问题,纯属意外,但也是最直观的一个。
我的知识库是通过一个叫 MCP 的协议暴露给 AI 助手的。MCP 说白了就是让模型调用外部工具的一套标准接口,我这边跑着对应的服务,模型要查知识库的时候,就跟这个服务建立连接、发请求。问题,就出在这个连接地址上。
当时配置图省事,地址我直接填了公网域名。结果这个域名解析出去,指向的是一台海外的服务器,而真正跑着知识库服务的机器,其实就安静地躺在我自己的内网里。于是每次模型想查知识库,数据的实际路径就变成了这样:
本地机器 → 公网域名 → 海外服务器 → 再绕回内网那台机器
绕了地球整整一圈。更要命的是,MCP 每次请求前都要先握手建立连接,而握手不是一次就够的——一个回合下来要握五轮手。每一轮,数据都要在国内和海外之间来回跑一趟,延迟高的时候一轮就近一秒。这么一算,光握手这个过程,每个回合就白烧掉差不多五秒。这跟模型半毛钱关系都没有,纯粹是我自己搭出来的路由灾难。
我是怎么发现的?在 AI 助手的执行日志里,我看到一个叫 bundle-tools 的环节,耗时赫然写着五千多毫秒,而其余所有环节加起来才一百毫秒上下。一眼就锁定了:九成的时间都耗在跟工具建立连接上,跟模型推理压根无关。
修复思路也很直接——服务本来就在内网,那就别绕公网了,直接内网连。我把 MCP 的地址改成了内网地址,指向那台机器对应的端口。
可这里又冒出个麻烦,我折腾了好一会儿:改完内网直连,服务端直接给我回了 405。查了半天才弄明白,这个知识库应用对 MCP 接口做了严格的域名校验,要求请求里必须带一个特定的 Host 请求头,指名道姓地说"我是来找这个虚拟主机的",否则一律拒绝。这个头在走公网域名的时候是自动带上的,一改成内网直连就没了,于是被挡在门外。
解决的办法,就是手动把 Host 头补回去,再补一个转发协议的头,让服务端知道外部请求是走 HTTPS 进来的。改完一测,最简回合从 12-15 秒直接掉到 8-10 秒。这五秒,白捡的,一分钱没花。
第二个问题:模型的思考参数,从来就没发出去过
网络这关过了,还是觉得慢。这回我不得不开始认真怀疑模型本身。
我把模型从 A 换到 B,又从 B 换回 A,来回折腾了几轮,发现一个挺诡异的现象:不管用哪个模型,只要是复杂一点的编码任务,开头几秒总是"憋着不出字",像是后台在疯狂运转。我试着去调低它的思考强度,按理说应该立竿见影,结果纹丝不动。
我干脆去翻了 AI 框架的源码。这一翻,翻出一个特别隐蔽的问题。
这个框架在把请求发给模型服务商之前,会先检查当前模型在配置里有没有声明一个叫 supportsReasoningEffort 的能力。只有声明了,它才会在请求里带上那个控制思考强度的参数(reasoning_effort)。要是没声明,这个参数压根不会被发送——框架直接把它吞掉了。
坏就坏在我给模型服务商配置自定义模型的时候,完全没声明这个能力。于是结果就是:我在界面里怎么调思考强度都没用,因为参数从头到尾就没发出去过。而模型服务商那边,收不到这个参数,就默认走最重的"全量深度思考"模式——所有问题不分轻重,全部火力全开地"想"一遍。
这就解释了为什么连简单任务都慢。我拿一个很普通的编码任务实测:默认状态下,光"思考"阶段就要跑 30 秒,思考内容输出了一万多字符;而整个请求从头到尾完整耗时 38 秒。你想想,一个普通问题,它"想"了一万多字才肯开口,能不慢吗?
修复就是给这个模型的配置补上能力声明,再把思考档位做个映射——因为框架里的档位名,跟服务商那边认的档位名不完全一样,不映射的话,传过去可能直接报错(400)。
改完之后同一个任务重新测:首字延迟从 30 秒砸到 1.5 秒,总耗时从 38 秒降到 12.6 秒,思考量从一万多字直接归零,代码产出还一点没少。这一下,才是真正的大头。回头想想,之前我花在纠结换模型上的时间全白费了——问题从来不在模型,在于参数根本没传出去。
第三个问题:知识库索引,天天都在做无用功
前两个问题解决完,响应速度算是正常了,但用着用着,我又发现一处隐性的浪费——知识库的索引。
我的 AI 助手查资料依赖一份本地索引,有了它才能快速定位到相关内容。以前这个索引是每次会话都要重建一遍的,也就是说,你每问一个问题,系统都得把整个知识库重新扫描、重新建一遍索引。知识库小的时候无所谓,可文档一多,这就是纯纯的重复劳动,每次都在做一模一样的事。
我的方案,是把它从"每次会话重建"改成"每天晚上定时自动重建一次"。白天随便你怎么用,索引都是现成的,查起来秒出;到了半夜,脚本自己爬起来,把索引更新到最新,静默地跑,不打扰任何人,只有失败了才发告警通知我。实测下来,五个分类、十七篇文档,全量重建也就一两秒的事,代价可以忽略不计。
而且这个脚本是个纯脚本任务,全程不调用任何 AI 模型,零模型消耗,就是纯粹的后台体力活。白天的每一次查询,都省下了重建的功夫。
顺手做的一件事:给任务分分工
前面三个都是"把该省的全省掉",属于做减法。最后我又做了一个加法——把不同性质的任务,拆开走不同的通道。
思路其实很简单:日常的简单问答、查个资料、写个普通文件,这些都是高频但轻量的活,没必要动用最强、最贵的模型——用个快而便宜的模型就够了,响应快还省钱。真正吃力的活,比如复杂的多步推理、要看图理解内容,才切到更强的模型上去。
于是我在系统里做了一套路由规则:默认走快模型,遇到重活再由它自己判断要不要升级,升级前还会问我一嘴,让我确认。这么一分,日常绝大多数对话都走在了快车道上,响应基本稳定在 8 秒左右;重活才偶尔付一次"高级模型"的代价,完全不心疼。
写定时脚本时遇到的几个坎
最后那个定时重建脚本,写的时候也遇到了几个挺典型的问题,记下来,说不定能帮到别人。
一是无头环境(没有界面、没有终端)里跑脚本,很多你以为理所当然的东西根本不存在。比如 console 这个打印日志的对象,直接就是未定义,一调就炸;而且脚本的返回值必须是对象,你随便返回个字符串,它会直接报错,根本不给你含糊。
二是执行命令这回事,它是异步的。你以为命令跑完了、结果拿到了,其实它还挂在后台没返回。你得像轮询一样主动去查它到底跑没跑完,才能拿到最终结果,不能干等着。
三是命令跑失败了,它不会主动抛异常给你看,退出码非零也就是个不起眼的数字,很容易被忽略。我的做法是让命令自己写一个状态文件,跑完了我再回头去读这个文件,看里面写的是成功还是失败,才能判断这活儿到底干没干成。
写在最后
回头看看这几个问题,每一个单拎出来都不难,改起来也就几行配置的事。难的是什么呢?是肯花时间把"慢"这个抽象的感受,一层一层拆开,看看到底是哪个环节在拖后腿。
模型往往是最容易背锅的那个——反正大家都说 AI 慢,那就怪模型呗。但很多时候,问题就藏在你以为"没问题"的配置里:可能是一个绕远路的地址,可能是一个没被发送的参数,可能是一段重复了几百遍的无用功。
把每一层都拆开看一遍,该省的省,该补的补,30 秒变 8 秒,也就一晚上的事。