16G 内存带不动 Milvus:用 opencode 重写私文简搜聊天版(上篇)
之前那版私文简搜 DocSolo 是「能搜也能聊」,靠的是 Milvus 存文本向量 + Spring AI Alibaba + Ollama。功能是有了,但 Milvus 这玩意儿是真的吃资源,我那台 16G 内存、2G 显存的机器根本扛不住。这篇记录我用 opencode 把它重写成轻量版的全过程:不碰 Milvus,向量改用本地方案,全文检索用 Lucene,模型还是本地 Ollama。上篇讲清楚为什么重做、怎么做计划、怎么编码、怎么把它第一次跑起来。全程真实记录,包括 opencode 反复犯同一个错误的那些事。
前言
先交代下背景,不熟的同学可以先看我之前的 DocSolo 系列。
私文简搜 DocSolo 是我一直在迭代的一个轻量化文档知识库:把 markdown 文档导进去,建索引,然后既能全文搜索,也能用聊天的方式问它问题。之前那版我结合 Spring AI Alibaba 和 Milvus 做过一次重构,让它「能导入、能聊天」。
问题就出在 Milvus 上。
我的主力机配置很一般:16G 内存、2G 显存,虚拟机最多只能分 8G 内存。Milvus 是分布式的向量数据库,功能强,但部署起来一套组件,内存占用居高不下。我就想做个自己用的小工具,实在没必要、也带不动这么重的东西。
所以这次的目标很明确------做一个简单版:
| 项 | 说明 |
|---|---|
| 向量库 | 不用 Milvus,改用本地轻量方案 |
| 全文检索 | Lucene + HanLP 中文分词 |
| 大模型 / 向量模型 | 本地 Ollama(qwen3.5:4b / nomic-embed-text),还是得用 |
| 基础框架 | Spring Boot(ivy-starter-cores),不引入 Spring Cloud |
| AI 接入 | Spring AI Alibaba |
| 运行要求 | 8G 内存内必须能跑 |
工具我选了 opencode。为什么是它?前面几篇我用过 Trae、Qoder,免费额度用完就得换,这次干脆试试 opencode。下面开始,全程真实记录。
一. 做计划
我没有上来就让它写代码,而是先让它做计划。我把背景、约束、参照的代码都讲清楚,尤其是「内存只有 8G」这条硬约束。
vbnet
我想编写一个上传md文档,然后结合文本向量和ollama,最终形成可以通过聊天的方式进行全文搜索,以前编写过类似代码实现方案是milvus和ollama,Lucene 全文检索,HanLP 中文分词,现在将问题是milvus太大,我想改一个小一点的版本, 因为我的脑只有16g内存2g显存,虚拟机最多只能给8g内存。
要求:
1、可以参照Double-AI-Agent-Wrapper中的ivy-service-knowledge-bootstrap代码,Spring ai alibaba访问ai
2、基础框架以ivy-simple-api中的ivy-starter-cores为准,不要求spring cloud
3、一定要在简单的8g内存可运行,ollama qwen3.5:4b nomic-embed-text
4、可以不要mysql保存上传md5检验
5、功能可以参照Double-AI-Agent-Wrapper中的ivy-service-knowledge-bootstrap
6、新建项目名 ivy-chat-docsolo-wrapper,最新项目ivy-chat-docsolo
先做计划
opencode 开始分析,先去看参照代码:


它一上来就想满世界找代码,我赶紧圈定范围------所有代码都在 opencode 目录里,别到处搜。
所有内容都在opencode目录,不要搜索其他地方的





计划过程中我还专门问了一个关键问题:如果只用 Lucene、不用向量,那是不是就把检索到的内容直接塞给 Ollama? 这其实就是「要不要做 RAG、向量还有没有必要」的问题。
有个问题,仅lucene不要向量,是直接将内容输入到ollama吗


opencode 给了两种方案,我都不太确定,就把我的最终诉求讲死了:先走文本向量,不够再回退 Lucene,最后把检索到的内容喂给 Ollama。我还特意反问了一句:要是把文本向量去掉,那跟我以前用 ES 有啥区别?顺便把文档、swagger 这些也交代了------swagger 不要,因为 knife4j 不支持高版本,都有缺陷。
你给的两种方案我不确定,但我的最终要求是:
我想先文本向量,不行再lucene,最终将内容喂给ollama,如果去掉文本向量和我以前使用es有什么区别,上传的md文件和图片放在一起,可以直接访问,当然md要替换一下images
计划形成文档放在ivy-chat-docsolo-wrapper中的doc中,按正规项目开发编写相关文档
这是全新项目ivy-chat-docsolo-wrapper没有创建
先做计划的
文档,swagger不要吧,因为knife4j不支持高版本,都有缺陷
你确定好方案我再看下。如果有选择一定要讲清优缺点,能否完全满足。



结果它又开始犯傻,跑到别的地方去找源码,我得再强调一遍:所有东西都在 opencode 目录里。
操,怎么又犯傻了,所有文档都在opencode中,不要在其他地方找源码。
补充
1、都要实例化,不能一重启就没了
2、ivy-starter-cores的源码复制过来,要修改可以在源码修改。

你看,它又想访问不在当前目录下的文件。这就是 AI 的幻觉,会一直犯同一个错误,真磨人的耐性。
总是犯同一个错误,怎么解??无解???






来回拉扯了几轮,计划总算成型了。整体架构是这样:上传 md → 解析分块 → 一路做向量化存本地,一路写 Lucene 全文索引;提问时先向量检索,不够再回退 Lucene,最后把命中的内容和图片组装成 Prompt 喂给 Ollama,流式返回。
bash
┌───────────────────────────── 浏览器 / HTTP 客户端 ─────────────────────────────┐
│ 上传 md │ 聊天提问(SSE) │ 访问 /api/data/** 图片与文档 │
└─────┬────────┴────────┬───────┴───────────────▲───────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ ivy-chat-docsolo 应用(单进程 Spring Boot,内嵌 Tomcat) │
│ │
│ Controller 层: Import / Chat / File / ModelSwitch │
│ ├─ ImportService: 接收上传 → Tika 解析 → 按标题分块 │
│ ├─ 双写步骤: ① 向量化(nomic-embed-text) 写入 SimpleVectorStore(JSON 文件) │
│ │ ② 写入 Lucene 全文索引(HanLP + BM25, 磁盘目录) │
│ ├─ ChatService: 先向量检索 → 不足回退 Lucene → 提取来源+图片 → 组装 Prompt │
│ │ ├─ VectorRetriever (SimpleVectorStore) │
│ │ ├─ LuceneSearch (Lucene BM25 BooleanQuery OR) │
│ │ └─ ChatClientProvider (Ollama / DashScope 切换) → 流式回答 │
│ └─ FileService.netty 静态映射数据目录 │
└──────────────────────────────────┬───────────────────────────────────────────┘
│ HTTP (ollama /localhost:11434)
▼
┌─────────────────────────────┐
│ Ollama 本机服务 │
│ 对话: qwen3.5:4b │
│ 向量: nomic-embed-text │
└─────────────────────────────┘
二. 编码
计划确认完,我让它开始执行。
好吧,开始执行计划



它闷头写了一阵,我去看代码,发现目录结构不对。

我的项目目录是有固定规矩的:外层用复数,里层用单数,比如 cores → core、services → service、models → model。ivy-simple-api 和 Double-AI-Agent-Wrapper 都是这个结构。这个必须写进 skills,不然后面还会乱。
rust
你这个目录结构是不对的,看ivy-simple-api以及Double-AI-Agent-Wrapper,都是s,再放单数的。
如 cores-> core services->service models->model
必须按这种目录结构,写进skills



所以说,AI 编码还真不能完全不懂的人来做。当然它是可以完成任务的,这个前面我也说过------就是能完成当前任务,至于以后的任务,以后再说。
AI 编码一定要做非常多的限制,也就是 skills。我管这叫夹逼定律:用规范把代码夹成我们想要的样子。这点很重要。你讲了,它是能写出来的;但你不说,它就瞎搞。

这不就对了吗,结构多清晰。

你看,其实它是可以做得很好的,只是懒(省算力)。这时候就要求我们自己的实力了------你得知道什么是对的,才能把它掰回来。
编码过程中我确认了下进度:
什么情况,前面任务完成了吗

Ollama 我也在另一台机器上部署好了,告诉它地址,让它对着这个来:
192.168.55.130 ollama部署成功,已启动
目录结构没问题


后面就是等它慢慢生成,反正是要很长时间。

前端页面它也给搞出来了:

我来看源码的时候,又发现一个问题:Maven 的 module 配置不对。
arduino
<module>ivy-starter-cores/ivy-starter-core</module>
<module>ivy-services/ivy-chat-docsolo</module>
这样配置不对,要一级一级配置,这样会乱的。
ivy-starter-cores、ivy-services 这些得一级一级配,不能图省事一把梭,不然会乱。

这种基础错误它也会犯,这个也是铁律,你怎么会这么傻呢。
这个也是铁律,你怎么会这么傻呢

三. 初步跑起来
代码写得差不多了,接下来就是验证能不能跑。
3.1 后端代码
先启动 Ollama,把模型拉起来。

后端代码能直接运行,这点还是值得肯定的,没有一上来就报错。

3.2 前端
前端就是常规操作,装依赖、起服务。
arduino
npm i
npm run dev


到这一步,后端能跑、前端能跑,看起来一切顺利------好像已经成功了。
但别高兴太早。真正一测试,问题就开始一个接一个地冒出来了。这些问题,以及后面为了「到底几个功能」跟 opencode 反复拉扯的过程,都放下篇讲。
小结
上篇先把背景和搭建过程交代清楚:
关于为什么重做
- 之前那版 DocSolo 用 Milvus 存向量,功能是全的,但 Milvus 太重,16G 内存、2G 显存的机器带不动
- 这次目标就是做轻量版:不碰 Milvus,向量用本地方案,全文检索用 Lucene + HanLP,模型还是本地 Ollama,8G 内存内必须能跑
关于做计划和编码
- 先做计划再动手,把「内存 8G」这条硬约束讲死,把「先向量后 Lucene 再喂 Ollama」的检索链路讲死
- AI 会反复犯同一个错误(到处找源码、目录结构不规范、Maven 配置乱),得靠 skills 和人工纠偏把它夹回来------这就是「夹逼定律」
- 好消息是后端能直接跑起来,前端也能起来,第一步算是成了
下篇《说好 3 个功能做成 2 个:用 opencode 调试私文简搜聊天版(下篇)》进入正题:一开始测试,deploy 理解偏差、界面太丑、Ollama 内存不足退出、说好三个功能只做两个......一堆问题排队上场。想看 opencode 怎么被反复「教育」的,下篇见。
end
这篇是「私文简搜 DocSolo」轻量化重写的上篇:因为 Milvus 太重、机器带不动,改用 opencode 重做一个本地轻量聊天知识库。上篇把计划、编码、首次跑通讲完了,真正的考验在下篇。感谢阅读。