AI 只解决眼前那个坑【上篇】:来源、图片、鲁棒性,把能聊一处一处补齐
前一篇我把 16G 内存机器上的私文简搜重写成轻量版,后端能跑、前端能起,我以为快完事了。结果才发现「能跑」和「能聊」之间还隔着一片坑:答案不标来源、图片访问不了、deploy 目录不存在就报错、把检索到的全文整个塞给 Ollama、一次聊天要等半小时才出得来。这篇是补全实录——来源、图片、鲁棒性,一处一处填,又一次验证了那句话:AI 只解决眼前那个坑,你得把那些坑一个个夹逼回来。
前言
先说清楚写作背景,全文是我跟 opencode之间的拉扯的记录,满屏地情绪词,也真是难为我了,真的为真正项目使用AI的朋友感到默哀。
现在的AI啊,收费的也想尽办法将你的Token浪费掉,不收费的也总是想让你心情不好而弃用。我使用Claude整理一下文章,竟然将我的一周的量都用完了,觉得百炼有点水,这种包月的限量的,肯定是会水的,先让你用完,然后就可以休息五天了。这样想来OpenCode是真良心啊。
一、来源与图片:把「答得让人能信」立起来
开发时少了一项功能:检索结果要说明来源,聊天界面里注明「来自哪个 .md」,并且能打开查看;图片也必须是正确路径。于是第一件事,就是把需求发给它:
前面开发还少了一个大功能,就是如果有文档要指明来源,在聊天下面注明,如来自XXX.md,并能单击显示出来。图片也要是正确的路径。
先拿日常问题试一试:怎样保持电脑干净?
怎样保持电脑干净
结果被现实打脸:一句话没回,还在重复;就算显示点什么,也只是当前片段,图片没有替换。唉,真无语——docs 里明明有完整的 md 文件,为什么不用呢?
好了,聊天一句没有,还重复,不用会set?
先看下能不能显示,只显示当前片段,且图片没有替换,是错误的。唉。真无语了。 docs里面不是有完整的md文件吗
凭良心讲:把搜索到的原文档放出来、改成聊天式展示、图片用正确路径,不就结了?哦对,图片这种,前端拿到正确地址就会自动渲染。于是又试一次这个测试:
怎样保持电脑干净
二、鲁棒性:目录不存在,别动不动报错
显示是能显示了,但一测试就报错。习惯性地将deploy等目录直接删除,再重新发布。结果坑来了报错:deploy 目录不存在,不会先创建目录,却抛一长串错误信息,还明确说“部署目录不存在”。 这种目录,不存在不会自动创建吗?又不是什么核心错误。
部署目录不存在: E:\ivy\ai\ai-code-projects\opencode\ivy-chat-docsolo-wrapper\deploy
at vip.wayhua.ivy.chat.service.impl.ImportServiceImpl.deploy(ImportServiceImpl.java:89)
at vip.wayhua.ivy.chat.controller.ImportController.deploy(ImportController.java:34)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:991)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:896)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:165)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:88)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:492)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:113)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:83)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:72)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:1272)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1797)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:973)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:491)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:833) 目录不存在,要先创建
改了deploy目录创建错误,又变成「清空失败」,原因还是同一个:目录不存在。
非常生气,这都不算 bug 了,必须改成能适配我的习惯。我测试时会先删库重导。要它做到:目录不存在自己建,别动不动报错;真正碰到原则性问题才停下来。
[14:52:10.749] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalStateException: Lucene 清空失败: E:\ivy\ai\ai-code-projects\opencode\ivy-chat-docsolo-wrapper\data\index] with root cause
java.nio.file.NoSuchFileException: E:\ivy\ai\ai-code-projects\opencode\ivy-chat-docsolo-wrapper\data\index
at org.apache.lucene.util.IOUtils.fsync(IOUtils.java:460)
at org.apache.lucene.store.FSDirectory.syncMetaData(FSDirectory.java:279)
at org.apache.lucene.store.LockValidatingDirectoryWrapper.syncMetaData(LockValidatingDirectoryWrapper.java:62)
at org.apache.lucene.index.SegmentInfos.prepareCommit(SegmentInfos.java:911)
at org.apache.lucene.index.IndexWriter.startCommit(IndexWriter.java:5647)
at org.apache.lucene.index.IndexWriter.prepareCommitInternal(IndexWriter.java:3790)
at org.apache.lucene.index.IndexWriter.commitInternal(IndexWriter.java:4131)
at org.apache.lucene.index.IndexWriter.commit(IndexWriter.java:4093)
at vip.wayhua.ivy.chat.service.impl.LuceneFullTextIndexServiceImpl.clearAll(LuceneFullTextIndexServiceImpl.java:85)
at vip.wayhua.ivy.chat.service.impl.ImportServiceImpl.deploy(ImportServiceImpl.java:119)
at vip.wayhua.ivy.chat.controller.ImportController.deploy(ImportController.java:34)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:991)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:896)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:138)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:165)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:88)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:492)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:113)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:83)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:72)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:1272)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1797)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:973)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:491)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:833)
怎么老报这种错误,
我肯定会删除的,这种bug一定要修改
不要动不动就报错,要鲁棒一点。只要不是原则性错误,报毛错啊,这种不存在,创建一个就好了。要给客户方便。我测试,肯定会直接删除里面内容的。
前面我就说了,怎么查看deploy后的数据,也就是data目录,将里面全部删除就好了。deploy里面也一样。
如果是以前milvus,会安装客户端attu来查看数据是否导入,现在改成文件版本,就没法使用工具了。我要怎么查看数据呢?结果是只能看data目录里面是否有数据,当然有对应格式,不过我也不想细了解,只要有数据就行吧。删除的话就直接删除。 它问能不能手动删库,我给出的答案是带前提的:手动删没问题,但程序必须保证不报错。
不行,我还是要手动这样操作,你要保证这个不报错,我可以在先删除再运行
再来测试,终于不报错了。补一句真实体会:跟 AI 交流,命令尽量下成「命令式」,准确度立刻不同。
三、慢的元凶:把全文直接喂给模型
功能通畅了,进到对话测试。上来第一句:
spring ai alibaba 使用
结果还是老问题:聊天没内容、图片地址打不开。其中图片这条最有意思——前端 5173 打不开,但直接访问后端 8200 却能通。应当是前端的代理没配好。
现在还是两个问题:
1、聊天没内容,
2、图片地址无法访问。
1.
spring ai alibaba 使用 我说的
ai回答
来源
6.AI BI Helper 开发实录 02.md
6.AI BI Helper 开发实录 02:Graph 工作流编排——SQL 生成、执行与邮件推送(Spring AI Alibaba 1.1.2.0 + Milvus 3.0).md
7.AI BI Helper 开发实录 03.md
7.AI BI Helper 开发实录 03:评估节点与重试循环——SQL 自校验让生成更稳定.md
double-ai-agent实战手记01:系列规划、架构设计与技术选型(Spring AI Alibaba 1.1.2.0+tk-mybatis).md
2.http://127.0.0.1:5173/api/data/docs/859c480110904b61a0a08a718533ce45/%E9%A6%96%E4%B8%AASpring%20AI%20Alibaba%20Graph/images/image-20260729223603038.png 无法访问,是不是vue没有配置好
http://127.0.0.1:8200/api/data/docs/859c480110904b61a0a08a718533ce45/%E9%A6%96%E4%B8%AASpring%20AI%20Alibaba%20Graph/images/image-20260729223603038.png 可以访问
在修改的同时,查看了一下代码,不看不知道,原来是这样让Ollama变慢的,它将搜索到的所有文章(md)全部直接传入Ollama,这样一来,我问的问题那几个字,在海量的文本里面几乎可以忽略不计了。如果搜索到五个文档,几万字一次进 context。本来使用的虚拟机就只有8g,安装的也是qwen3.5:4b都是从7b降级下来的。
我以往用 Milvus 时,走的是「文本向量」链路,先把相关片段取出来,再交给模型,完全不是这种方式:
String userInput = state.value(Constant.KeyName.USER_INPUT, "");
// 创建重写查询转换器,借助LLM帮我们进行用户查询的重写,变成可以让LLM更好理解的Query
RewriteQueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(builder)
.build();
RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(queryTransformer)
//指定你使用哪一种文档检索器
.documentRetriever(VectorStoreDocumentRetriever
.builder()
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build()
)
.build();
Flux<String> content = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
Opencode还是我说的那个问题,我能帮你解决当前问题,至于后期多麻烦,你就说我有没有解决当前问题。
根本不管用户的需求,当然至少是解决了当前问题。至于是每次发送五篇大长文md文件,用户输入的内容几乎都忽略不计了。想想每次至少传入万字,qwen3.5 多少b都会死的,更何况只有8g内存。
你看他的解决方案
实测数据铁证:Ollama 纯 CPU 生成 100 token 花 200s(0.5 token/s)。你要的截断方向正确——prompt 越长,prompt-eval 阶段也越慢。但生成速度本身是硬约束,截断能把「首 token 时间」压下来,聊天的内容(最终答案)只要等,还是会慢慢生成完。
现在只改一处:buildContext 里每片截断到 300 字喂模型,来源弹窗全文不受。
是不是脑子有屎,这根本就不是解决问题的方法。
你确实可以说是用户提问不对,但我明确告诉了需求,还是不动脑的。
四、方向之争:AI 想「截断」,我要「向量」
我给出文本向量的代码,就想知道这样会不会快一些。 它给的方案却是「截断」——每段只留 300 字再喂。这分明是「把答案砍短来交差」。于是取消前面内容重新输入。
我要的是走文本向量的方式 和Ollama沟通,本来就慢,难怪前面一个聊天要11分钟。我要的是简洁,你不用管是否是ollama的问题,我要的是传入文本向量这种方式。再说一篇我要的是传入文本向量这种方式。你确实可以说是用户提问不对,但我明确告诉了需求,还是不动脑的。不要再说ollama的问题了,我已降为qwen3.5:2b了。根本问题就是没使用文本向量。一定要使用文本向量,而不是传入全文。一定要使用文本向量,而不是传入全文。一定要使用文本向量,而不是传入全文。
我已经要崩溃了,还是那么偷懒,如果一个真实上线项目,万一有哪一步没有夹逼好,后果想想真可怕。
之所以会出现整套方案全换掉的原因是,AI只解决当时提问题的而不管全局。除非我刚才那样一定要强烈要求非要使用文本向量,才更改方案。按用户的要求,看前面上传zip,添加个deploy,和这次这个坑,充分说明了AI只解决当前问题,而不管全局以及下一步的。后面的问题可以推翻前面的问题,如果后面强调,而前面问题没结束,就忘记了。这就是AI,也是当前形式,及时行乐哪管明天是死是活。
我只好强烈拍板:我要的是文本向量。模型慢,不是字节多,而是你喂了全文。Ollama 本来就是本地 CPU,4b、2b 已降到底,一个回答 11 分钟,「截断」根本不治本。命令式强调三遍——一定用向量,不要传全文:
我确定是使用文本向量传入ollama的?而不是其他取代方案。
不要搞折衷方案。确定吗? 不会传入全文到ollama吧
现在一点信任都没有了。
踩坑记录:AI 拿「每段 300 字截断」当方案,本质是在躲关键设计——它只想把这轮「跑完」,不管技术方案底子对不对。你不反复坚持,它就给你糊弄过去。
OpenCode不想认真解决问题,认真思索,而是一味的绕弯子,将就解决这一问题。哪管用户真实需求和想要的。
那qwen3.5:4b行不行,我是一定要使用文本向量传入的
// 1. 实现RAG的召回过程
String userInput = state.value(Constant.KeyName.USER_INPUT, "");
// 创建重写查询转换器,借助LLM帮我们进行用户查询的重写,变成可以让LLM更好理解的Query
RewriteQueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(builder)
.build();
RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(queryTransformer)
//指定你使用哪一种文档检索器
.documentRetriever(VectorStoreDocumentRetriever
.builder()
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build()
)
.build();
Flux<String> content = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
这种代码实现做不到吗
OpenCode还和我说完全等价,既然是等价为什么不用我的方案。它又复述了一遍确认:现在用的是我的方案对吧?真无脑。
操, 完全等价,那就用我的方案。
五、否定词它也分不清,连方案都要从头删
所有的AI基本上都有一个功能,有时会看不清否定词,所以尽量使用肯定语气,强列语气。 为了适配新需求,它差点把最早期定下的技术方案整个删掉——也包括 Spring AI Alibaba BOM 1.1.2.0(DashScope/Ollama 生态)。我立刻叫停:
不会连最早定的技术方案
AI-Ali 扩展spring-ai-alibaba BOM1.1.2.0提供 DashScope 与 ollama 生态(可选)
连这个也去掉了吧
它不但看不懂,还连「肯定否定」都拎不清:把我说的「不要改掉 Spring AI Alibaba,只要用 Ollama」给理解成了「删掉 Spring AI Alibaba」。一个「不」字没了,整个方向反了半天。
操 你看连肯定否定的话都看不懂,这可能是OpenCode免费的原因吧。
我要的是使用Spring AI Alibaba 1.1.2.0,只是使用Ollama。
不要改错了。
连肯定否定都看不懂了。真是唉。差点去掉了SpringAiAlibaba了,只能赶紧停止。
重新发。
操, 完全等价,那就用我的方案。前面说的这个方案现在是使用的是我的方案吗
根本就不是理解反了的表面意思,而是只提取了几个关键字,比如不好,分解为不、好、不好,全文搜索时,可以不要“不”,也可以不要“好”,结果,不就丢了。
要再确定一下,前面功能会不会又丢了。
以我的方案为准,要重新导入数据吗
六、又踩坑:批量与 append
重新部署总算能跑了,日志第一行就吓我一跳:[vector] 已清空全部数据。为什么重新部署,它反倒把库清得干干净净?deploy 词在语义上应该是 append(追加),不是 add(覆盖)。
[17:13:04.145] INFO v.w.i.c.s.i.LuceneFullTextIndexServiceImpl - [lucene] 已清空全部索引
[17:13:04.145] INFO v.w.i.c.s.i.ImportServiceImpl - [clear] data/docs 已清空: .\data\docs
[17:13:04.156] INFO v.w.i.c.s.i.ImportServiceImpl - [clear] 全部数据已清空
[17:13:28.932] INFO v.w.i.c.s.i.ImportServiceImpl - [import] 解压完成到部署目录, batchId=23dfb3a4bc954c70bb8af874400fbbb1, dir=E:\ivy\ai\ai-code-projects\opencode\ivy-chat-docsolo-wrapper\deploy
[17:13:32.731] INFO v.w.i.c.s.i.ImportServiceImpl - [deploy] file=从Ghost到Vagrant,搭建干净、可重建的本地开发环境.md, chunks=62
[17:13:32.765] INFO v.w.i.c.s.i.ImportServiceImpl - [deploy] file=代码心得.md, chunks=59
[17:13:32.766] INFO v.w.i.c.c.LocalTfidfVectorStore - [tfidf] 已保存到 E:\ivy\ai\ai-code-projects\opencode\ivy-chat-docsolo-wrapper\.\data\vector\tfidf.json
[17:13:32.766] INFO v.w.i.c.c.LocalTfidfVectorStore - [vector] 已清空全部数据
是不是代码写错了,怎么已清空全部数据,我测试了两次。并且不走了。
那你的日志要写清楚。那为什么不改成批量呢?以前都会设置一次多少条啊。是不是nomic-embed-text使用时,不用qwen3.5:4b
为什么[vector] 已清空全部数据,deploy是append,不是add,不能删除以前的全部。所有deploy都是append
为什么这个错误也会犯,脑子不好吧
不看代码就死了。 for 循环把所有文档的 chunk 全加进 all:
for (Path mdPath : mdFiles) {
List<Document> chunks = parseMd(mdPath, batchId, deploy);
all.addAll(chunks);
log.info("[deploy] file={}, chunks={}", mdPath.getFileName(), chunks.size());
}
这里肯定是不行的。要按maxChunk=5或10 每次maxCHunk来执行。一次全文,百炼都不行
七、收尾:让它直接写启动脚本
来点轻松的收尾——一个 Windows 下启动后台的脚本,把 jar 和 application.yml 一起拉起来:
编写一个ivy-chat-docsolo.jar application.yml的运行cmd windows的,直接给出代码
为什么会这么笨呢?
八、小结
我发现使用Opencode改使用Opencode操作记录的文章,会偷偷地将对Opencode不利的内容删除掉,真人性化啊。或者是直接删除。厉害,没想到还有这个功能。
踩坑记录
| 坑 | 现象 | 解法 |
|---|---|---|
| 来源可点开 | 聊天界面没来源 | 输出带来源文件 + 链接 |
| 图片打不开 | 5173 打不开图 | 验证后端直连 → 代理问题 |
| deploy 目录不存在 | 原生 Java 堆栈 | 先建目录再工作 |
| 索引目录不存在 | Lucene 清空失败 | 先建目录 |
| 全文喂模型 | 一次回答 11 分钟+ | 向量召回,只喂相关片段 |
| 「截断」糊弄 | 每段砍 300 字 | 明确要向量,命令式坚持 |
| 否定词反解 | 「不要删 A」成「删 A」 | 方案焊死「以我的方案为准」 |
| deploy 清了全库 | vector 已清空全部 | 改成 append |
| chunk 一次全推 | 推送极慢 | 按 maxChunk 分批 |
下期预告
上篇把「能聊」这一关勉强过了,而这一路踩的坑是真多。下一篇,让 opencode 把这些经验落在 skills 里、再造一个新环境试真章,看它是真的睁开眼认坑,还是又在同个位置摔下去。下篇见。