在本地把开源模型跑起来之后,很多人都会遇到同一个问题:模型会答,但不会“查”。它不知道今天天气如何,不知道最新新闻,更不知道你问的那款产品现在卖多少钱。原因很简单——模型的知识有截止日期,而搜索是实时的。把两者接起来,让AI在回答问题之前先去网上查一圈,这就是联网搜索增强,也是今天要聊的这套组合拳的核心:MoltBot 负责把大模型包装成服务,Bocha 负责把搜索结果喂给模型,两者拼在一起,就是一个自带实时信息检索能力的开源 AI 助手方案。
这套方案适合谁?适合那些已经在本地或云端部署了开源模型、想要快速给模型加上联网搜索能力、又不愿意碰复杂编排框架的开发者。它不需要你从头搭一套 RAG 流水线,也不需要你写一堆调度代码。核心思路就一句话:让模型在需要的时候,主动调用一个搜索函数,把返回结果拼进上下文,再生成答案。听起来简单,真做起来还是有不少细节值得抠一抠。这篇文章就把我实际搭建这套方案的完整过程、踩过的坑、以及调参心得一次讲清楚。
1. 整体设计与思路拆解
1.1 为什么是 MoltBot + Bocha 这个组合
先聊选型。MoltBot 是一个开源的 AI 助手框架,底层接的是各类开源大模型,支持 API 方式对外提供服务。它最大的特点是轻——不依赖重型运行时,配置好模型路径和 API Key 就能跑起来,适合快速把模型能力暴露成 HTTP 接口。相比那些动辄要起数据库、要装向量引擎、要配消息队列的重框架,MoltBot 更像是一个“尽职的传话筒”:接收请求、组织上下文、调用模型、返回结果。
Bocha 这边就是一个专门为 AI 场景设计的联网搜索服务。它跟我之前用过的一些通用搜索 API 不太一样,返回的不是一长串网页链接,而是经过提炼的搜索结果块,每条都带着标题、摘要、原文链接和发布时间。这种结构对模型非常友好——模型不需要自己去解析一堆 HTML,直接拿结构化文本就能用。
这两者放在一起,正好互补。MoltBot 解决了“模型怎么被调用”的问题,Bocha 解决了“模型怎么获取实时信息”的问题。前者是骨架,后者是血肉。
1.2 核心流程:从用户提问到带搜索结果的回答
整体链路是这样的:用户向 MoltBot 发起请求,MoltBot 先把问题交给大模型。模型在 MoltBot 里注册了一个搜索工具,当它判断自己不知道答案、或者问题需要实时信息时,会生成一个搜索调用请求。MoltBot 拦截这个请求,转发给 Bocha,Bocha 返回结构化搜索结果。MoltBot 把结果追加到对话上下文中,再让模型基于这些新信息生成最终回答。
这个流程里有三个关键点:
第一,模型必须支持函数调用(Function Calling),或者至少支持工具调用格式。如果模型本身不支持,这条路走不通。好在现在主流开源模型基本都支持了,包括比较新的那几个量级不大的模型也都带这个能力。
第二,搜索结果的插入位置有讲究。不能把一堆搜索结果直接堆在历史对话后面,那会让模型分不清哪些是它自己说的、哪些是外部信息。我通常的做法是在系统提示词里预留一个明确的区域,比如“以下是搜索到的参考资料,请基于这些内容回答”,然后把搜索结果放在这里,同时把用户的原始问题放在最后,这样模型才知道该干什么。
第三,超时处理。搜索是个外部调用,慢的时候可能要好几秒。MoltBot 默认的推理超时可能不够用,需要单独调大工具调用阶段的超时时间,否则经常会出现“模型还没等到搜索结果就自己瞎编一个答案”的情况。
1.3 我为什么放弃更重的 RAG 方案
说实话,我也试过用向量数据库 + 离线知识库的方式给模型做增强。效果在某些场景下确实好,但问题也很明显:知识库的更新太折磨人。今天新增了一条新闻,你就要重新切片、重新向量化、重新灌库,不然模型永远答的是旧信息。而搜索增强的方案,天然就没有这个负担——每次提问都实时去网上查,查到的永远是最新的。
另外,RAG 方案对硬件的要求也更高。向量检索虽然不重,但切片和 embedding 的过程很耗 CPU,如果想让检索质量好一点,还得上重排模型。对于个人开发者或者小团队来说,这整套东西的维护成本已经超过收益了。搜索增强的方案则简单得多——一个搜索 API 搞定全部检索逻辑,模型该干嘛干嘛。
2. 核心细节解析与实操要点
2.1 MoltBot 的部署方式与配置关键
MoltBot 的部署不算复杂,但对配置文件的细节要求比较高。我第一次部署的时候就是吃了配置不够细的亏,模型虽然加载成功了,但一调用就报错,折腾了半天才发现是一个参数单位填错了。
主要的配置文件围绕着模型加载和 API 服务两部分展开。模型加载部分要关注的是模型路径、上下文长度、显存分配这几个指标。API 服务部分则要注意监听地址、端口、以及和搜索服务对接时的超时设置。
我用的配置大致是这样的思路:模型路径指向已经转换好的模型文件,上下文长度设置成模型训练的默认长度,采样参数保持默认。关键是推理超时那一项,我专门为了搜索场景调大了一些。因为启用搜索后,一次回答的时间 = 模型思考时间 + 搜索耗时 + 模型生成时间,比纯对话多了一个外部调用环节。如果不把这个时间留够,进程会被频繁中断。
2.2 Bocha 搜索结果的结构化处理
Bocha 返回的数据结构是我比较满意的地方。它不像传统搜索接口那样丢给你一堆 JSON 嵌套的原始链接,而是在每个搜索结果里都保留了“标题、摘要、链接、发布时间”这样清晰的字段。我需要做的只是把它们拼成一段文本。
但这里有个细节很多人会忽略:拼文本的时候,要按相关度排序,并且把发布时间靠前的排在前面。模型对顺序很敏感,如果你把旧闻排在前面,它就容易优先引用旧信息。另外,摘要如果太长,要截断。我试过把整段摘要全塞进去,结果模型回答的时候反而抓不住重点,开始大段复述搜索摘要。后来我把每条摘要限制在一个可控的长度内,回答的精准度立刻就上来了。
还有一个关键点:要过滤掉广告或推广性质的结果。Bocha 的搜索结果里偶尔会有一些带有明显推广倾向的链接,这些内容混入上下文后,模型可能会一本正经地引用,这是很尴尬的事情。我在接入时通过关键词过滤和域名白名单的方式做了简单的清洗。
2.3 模型选择对搜索效果的直接影响
不是所有模型都适合做搜索增强。我踩过的坑包括:有些模型虽然支持工具调用,但理解力不够,经常把搜索参数填错;有些模型即使拿到了搜索结果,也分不清哪些是事实、哪些是搜索摘要里的观点,回答还是会跑偏。
我给这套方案的实际模型建议是:优先选择在工具调用评测集上分数较高的模型,同时参数量不能太小。参数量太小的模型即使支持工具调用,指令遵循能力也很弱,经常出现“调了搜索,但无视搜索结果继续瞎编”的情况。
另外一个重点是系统提示词。很多人在用开源模型时完全不写系统提示词,这是不对的。在搜索增强的场景下,系统提示词的作用是告诉模型:你有一个搜索工具,当知识不够或信息可能过时时,必须使用搜索工具;当搜索结果返回后,必须基于搜索结果回答,不能编造。这两句话看着简单,但直接决定了模型会不会用搜索、以及敢不敢编答案。
3. 实操过程与核心环节实现
3.1 环境准备:Python 环境与基础服务
我这次实操的环境比较朴素:一台带 GPU 的 Linux 服务器,Python 3.10 环境,模型用的是公开渠道可以下载的开源权重。整个过程不依赖额外的第三方库,主要就是 MoltBot 自身的依赖以及一个 HTTP 请求库。
需要说明的是,我没有用任何需要申请审核或填写复杂表单的平台,所有依赖都是公开的 Python 包,直接从软件源安装即可。这一步大概耗时十几分钟,主要是网络传输的时间。
3.2 MoltBot 配置编写与模型加载
安装好 MoltBot 之后,下一步就是改配置。我的配置内容大概长这样,去掉了敏感信息后仍然可以跑通整个流程:
# MoltBot 服务配置示例 host: 0.0.0.0 port: 8000 model: path: /data/models/your-model-path context_length: 8192 max_new_tokens: 2048 temperature: 0.7 tool_call: enabled: true timeout: 30这里特别说一下tool_call.timeout。默认值是 10 秒左右,但对搜索场景来说太短了。Bocha 的正常响应时间在 2 到 5 秒之间,但网络波动时可能到 8 秒以上,加上模型生成搜索调用参数的时间,总耗时很容易突破 10 秒。我把它调到 30 秒之后,就没再遇到过搜索超时的问题。
模型加载这块没什么好说的,MoltBot 会自动根据显存分配模型到 GPU。我唯一要提醒的是,如果模型文件是散开的多个分片,一定要让它们放在同一个目录下,并且配置路径指向目录而不是某个具体文件。
3.3 Bocha API 接入与搜索结果格式拼接
Bocha 接入不算难,核心就是发一个 POST 请求,带上问题和一个 API Key。返回的 JSON 里直接就有结构化的搜索结果。我封装成了一个函数,方便 MoltBot 调用。
import requests def bocha_search(query: str, count: int = 6) -> str: url = "https://api.bochaai.com/v1/web-search" headers = { "Authorization": f"Bearer {BOCHA_API_KEY}", "Content-Type": "application/json" } payload = { "query": query, "count": count, "freshness": "noLimit" } resp = requests.post(url, json=payload, headers=headers, timeout=10) data = resp.json() results = data["data"]["webPages"]["value"] formatted = [] for item in results[:count]: title = item.get("name", "") snippet = item.get("summary", "")[:200] link = item.get("url", "") publish = item.get("dateLastCrawled", "")[:10] formatted.append(f"[{publish}] {title}: {snippet} 源: {link}") return "\n".join(formatted)这个函数会把搜索结果拼成一个多行字符串。每条结果里包含发布时间、标题、摘要和来源链接。摘要截断到 200 字以内,避免太长导致上下文膨胀。freshness字段可以根据场景改成oneDay、oneWeek,比如做新闻类问答时,限定一天内的结果会准很多。
3.4 在 MoltBot 中注册搜索工具
接下来就是把这个函数注册成 MoltBot 能调用的工具。这一步要看 MoltBot 当前版本的工具定义格式,但核心思路都是一样的:给工具起一个名字,描述它的功能,列出参数结构,然后绑定到具体的 Python 函数上。
我习惯把工具描述写清楚一点,不要写“搜索工具”,而要写“联网搜索工具,用于获取实时信息、新闻、天气、价格、最新动态等,当用户询问实时信息或模型知识过期时使用”。描述越详细,模型越知道什么时候该调用它。
注册完成后,我简单测试了一下。我问模型“今天某地天气怎么样”,模型立刻生成了一个搜索调用请求,参数是“某地 今天 天气”,MoltBot 把请求转发给 Bocha,Bocha 返回搜索结果,模型基于结果给出了回答。整个流程跑通的那一刻,还是有点小成就感的。
4. 常见问题与排查技巧实录
4.1 模型不调用搜索工具,总是直接回答
这是最常见的坑。模型明明支持工具调用,也正确注册了搜索函数,但它就是不用,所有问题都凭记忆硬答。
排查思路分两步。第一步,确认模型在 MoltBot 的配置里已经启用了工具调用能力。有些模型的服务端默认关闭工具调用,需要在配置里显式打开。我之前就遇到过配置文档里默认值是非启用的情况。
第二步,检查系统提示词。很多模型在没有明确指示时会倾向于直接回答,因为训练数据里大部分指令都不涉及外部搜索。你需要把“必须使用搜索工具”的指令写得非常明确,甚至可以带上触发规则。比如“当用户询问实时信息或你不确定答案时,必须先调用搜索工具,再基于搜索结果回答”。
4.2 搜索结果返回了,但模型答非所问
这个问题的典型表现是:搜索功能正常,Bocha 也返回了不错的摘要,但模型的回答完全没有引用搜索结果,还在自顾自地发挥。
原因往往是上下文拼接的格式不够规范。模型需要非常清楚地知道哪些内容是“参考资料”,哪些内容是“用户问题”。我推荐的做法是在发送给模型的 prompt 里,用分隔线把搜索结果和问题隔开。
格式大概这样:
请阅读以下搜索结果,并基于这些内容回答用户问题。 <搜索结果> [发布日期] 标题: 摘要 源: 链接 </搜索结果> 用户问题: 今天天气怎么样?这样模型就很容易理解:它不是在做开放问答,而是在做基于给定材料的阅读理解和摘要。加上这个结构之后,答非所问的频率明显下降了。
4.3 Bocha 搜索超时或返回错误码
搜索 API 偶尔会不稳定,尤其是在高峰期。我自己遇到过比较多的是 429(请求过于频繁)和 5xx(服务端错误)。针对这类问题,我的处理方式是在搜索函数外包一层简单的重试机制:第一次请求失败后等待两秒重试,最多重试三次;如果还是失败,就返回一个“搜索暂时不可用”的占位文本,并让模型直接基于自身知识回答。
有人可能会问,搜索不可用时让模型硬答,不是又会胡说八道吗?我的想法是:两害相权取其轻。如果搜索挂了,至少用户还能得到一个交互式的回答,而不是干等超时。同时,可以在占位文本里注明“搜索结果暂无”,这样模型会倾向于用更谨慎的语气回答,比如加上“根据我的了解”“可能”这类措辞。
4.4 上下文窗口被搜索结果撑爆
搜索结果如果太多,或者摘要太长,很容易把上下文窗口塞满。尤其是当历史对话已经很长的时候,再加一段搜索结果,模型可能直接报错。
我的经验是给搜索结果设置一个上限。Bocha 返回的结果数量可以调,我默认设置 6 条,每条摘要截断 200 字,这样算下来搜索结果的文本量基本能控制在 1500 字以内。如果要追求更精准的答案,可以把数量减少到 4 条,但摘要截断放宽到 300 字。
另外还要注意历史对话的裁剪。MoltBot 内部有历史消息管理,在开启搜索后,建议把历史消息的最大轮数减少一些,比如从 20 轮降到 10 轮。因为搜索占用的 token 是额外开销,你不给历史对话留出余量,上下文迟早会溢出。
5. 优化思路与实际体验
5.1 让搜索触发更智能一点
目前的触发机制依赖模型自己判断是否调用搜索,这有个天然缺陷——模型偶尔会过度调用,明明能答的问题也要搜一下;偶尔又过于自信,该搜的不搜。针对这个问题,可以在系统提示词里做更细粒度的规则约束。
我自己试过比较有效的一个办法是给模型举例子。在系统提示词里写上几条用户问题和对应的工具调用行为,比如:
- 用户问“帮我写一首关于秋天的诗” -> 不调用搜索
- 用户问“今天股票行情怎么样” -> 调用搜索
- 用户问“介绍一下某个新发布的产品” -> 调用搜索
这种 few-shot 示例的方式比单一规则约束要有效得多。模型通过类比来判断什么时候该搜、什么时候不该搜,比理解抽象的“实时性”和“确定性”概念容易得多。
5.2 搜索范围预筛选:限定地区、语言、时间
Bocha 的搜索参数里还有一些可以调整的项,比如结果的语言偏好、搜索地区、以及前面提到的时间范围。做中文场景时,把语言参数设定为中文,返回的结果相关性会明显好于默认设置。做新闻追踪场景时,把时间范围限定在一天内,可以过滤掉大量旧内容。
这一层筛选如果放到代码层面,就是在调用搜索之前,先判断一下用户问题的语义。比如问题里含“天气”,就给搜索结果加上当天的限制;含“最新”“发布”,就加上一周或一天的限制。这样每一类问题都用最合适的时间窗口去搜索,检索质量会提升不少。
5.3 回答质量的实测对比
我拿一些常见问题做了对比测试。在同一组问题上,关闭搜索和开启搜索的回答质量差异还是挺明显的。涉及具体数字、最新发布、热点事件的问题,开启搜索后的准确率大幅提升。而一些常识类问题,比如“某个经典编程概念是什么”,两者差别不大,模型本身的储备已经足够。
这里想给一个比较中肯的建议:搜索增强不是所有场景的银弹。如果你的用户群体问的都是稳定的知识类问题,模型本身就能答得不错,加搜索反而增加了延迟。但如果你的场景涉及咨询、比价、资讯获取、政策更新这类实时性强的需求,搜索增强带来的收益是立竿见影的。
5.4 延迟与成本的权衡
加了搜索之后,回答延迟不可避免会增加。纯对话可能一两秒就出结果了,搜索增强后至少要等搜索请求往返的时间,整体耗时大概在 4 到 8 秒之间。这在异步消息场景下完全没问题,但如果你的前端是流式输出,用户会明显感觉到首字变慢。
成本方面,搜索 API 是按调用次数计费的,每次搜索的调用成本通常很低,但架不住调用量大。我这里做了一个简单的限制策略:同一个用户在五分钟内的搜索次数最多 10 次。防止高频调用把每个月的搜索配额烧光。这个限制对正常使用来说完全够用。
6. 实际部署中的注意事项
6.1 安全与隐私:你的搜索会留下痕迹
任何联网搜索服务都会记录你的搜索词。如果你做的事情涉及敏感数据,建议不要把原始问题直接拿去搜索,而是让模型先对问题做一次脱敏改写,再提交给搜索服务。比如用户问“我的项目某配置该怎么调”,模型先提取出关键的技术关键词再搜索,避免原样把完整问题发出去。
MoltBot 本身支持在工具调用前对参数做处理,可以在搜索函数内部实现这个脱敏逻辑。多写几行代码,换来的是隐私层面的安心,这笔账还是划得来的。
6.2 请求体大小限制与响应处理
无论服务跑在本地还是容器里,建议检查一下反向代理或网关对请求体的限制。搜索结果拼接后的字符串虽然不大,但如果并发量高,还是会占一些带宽。
另外,搜索函数的异常处理要做得健壮一些。我和 MoltBot 联调时发现,如果搜索函数抛异常,MoltBot 会把它当作工具调用的执行失败,有时候会把错误信息直接回传给模型,导致模型回答时前言不搭后语。正确的做法是在函数内部捕获所有异常,返回一个标准化的失败占位文本,这样模型能理解“搜索没成功”而不是看到一堆堆栈信息。
6.3 日志与调试的实用技巧
调试搜索功能时,强烈建议把工具调用的完整参数和搜索结果记录到日志里。MoltBot 本身带了日志模块,但默认的日志级别可能不够详细。可以把工具调用相关的日志级别调到调试模式,这样能看到模型生成的搜索参数以及函数返回的结果。
我实际排查问题的时候,最常用的一招就是把搜索函数返回的文本原样保存到文件里,然后用命令行工具查看拼接后的 prompt 实际长什么样。很多时候你觉得模型答得不好,看了 prompt 才发现是拼接格式的问题,模型其实是无辜的。掌握这个技巧之后,很多玄学问题都能一眼定位。
我在实际使用中还有一个习惯:在开发环境里单独写一个测试脚本,不经过 MoltBot,直接调搜索函数,验证返回的文本里有没有广告、有没有乱码、时间格式对不对。等这一层验证通过了,再去 MoltBot 里集成。把复杂链路拆成独立环节来调试,效率会高很多,这也是我踩过多次坑之后总结出来的经验。
这套 MoltBot + Bocha 的方案,本质上是用最轻量的方式,给开源模型补上了实时信息这块短板。它不算完美,偶尔模型还是会出现“搜了也不看”的情况,但它已经能覆盖大部分需要联网知识的真实场景。如果你也正在为模型的时效性发愁,不妨照着这个思路自己搭一套试试,成本不高,收获却非常直接。