
1. 从Java工程到AI落地这个项目到底在解决什么问题很多写了三五年Java的朋友最近都有一个共同的焦虑业务代码写得再熟一聊到AI就插不上话。招聘JD里开始出现“熟悉Spring AI、LangChain4j、RAG”的要求面试官张口就问“你怎么保证知识库检索的命中率”而自己连一个能跑起来的AI Demo都没搭过。这个项目标题里的“星课IT-慕课网Java AI”本质上就是冲着这个断层来的——它要做的不是教你调个API就完事而是把Java工程师已有的工程能力平滑地迁移到AI应用开发这条线上。我先把话说透Java做AI和Python做AI走的是两条完全不同的路。Python那边生态成熟LangChain、LlamaIndex、各种实验性框架满天飞适合快速验证想法。但Java这边企业级应用才是主战场。你想想一个已经用Spring Boot跑了五年的业务系统数据库连接池、事务管理、权限体系、监控告警全都齐了现在要加一个智能问答或者文档检索的功能你不可能把整个系统推倒用Python重写。这时候Spring AI和LangChain4j的价值就出来了——它们让你在现有的Spring工程体系里用你熟悉的注解、依赖注入、配置管理方式把大模型能力接进来。这个项目覆盖的核心技术点非常明确Spring AI负责把大模型调用、向量存储、对话记忆这些能力做成Spring风格的BeanLangChain4j提供了更灵活的链式编排和Agent能力RAG则是整个落地场景里最刚需的一环——因为大部分企业要的不是一个通用聊天机器人而是一个能回答内部文档、产品手册、工单记录的专属助手。这三者串起来就是一条从Java工程到AI落地的完整路径。适合看这个内容的人我大致分三类。第一类是Java后端开发想转AI应用方向但不知道从哪下手第二类是技术负责人团队要接AI需求需要评估技术选型和落地成本第三类是在准备面试的求职者简历上想写AI项目但怕被问穿。不管你是哪一类接下来的内容都会把每个环节的“为什么”和“怎么做”讲清楚代码和配置都是可以直接抄作业的。2. 技术选型背后的真实考量为什么是Spring AI加LangChain4j2.1 Spring AI和LangChain4j到底怎么选这是被问得最多的问题没有之一。网上很多对比文章列一堆特性表格但看完还是不知道选哪个。我换个角度说看你团队的技术栈和项目阶段。如果你的项目本身就是Spring Boot体系团队对Spring的自动配置、Starter机制很熟那Spring AI是首选。它的核心优势是“无感集成”——你加一个spring-ai-openai-spring-boot-starter依赖配一下application.yml里的api-key就能直接注入ChatClient来用。对话记忆、向量存储、函数调用这些都有对应的Starter和Spring生态的契合度是其他框架比不了的。而且Spring AI Alibaba的出现让国内开发者对接通义千问等模型变得更顺滑。LangChain4j的优势在于编排灵活性和Agent支持。它提供了更细粒度的Chain组合方式比如你想做一个“先检索知识库、再判断是否需要调用外部工具、最后生成回答”的复杂流程LangChain4j的AiServices和Tool机制写起来更顺手。另外它的RAG模块封装得比较完整从文档加载、切分、向量化到检索有一套现成的抽象。我的实际建议是两者可以混用不冲突。基础的大模型调用和对话管理用Spring AI复杂的RAG流程和Agent编排用LangChain4j。很多生产项目就是这么干的Spring管Bean和配置LangChain4j管链式逻辑。别被“二选一”的思维框住。2.2 RAG为什么是Java AI落地的最短路径RAG这个词现在被说得有点烂但它的核心逻辑其实特别朴素大模型不知道你公司的内部信息你把相关资料找出来塞给它它就能回答了。就这么简单。为什么说RAG是Java工程师切入AI的最佳场景因为它本质上是一个检索系统加生成系统的组合而检索系统恰恰是Java工程师的舒适区。你想想RAG的流程是文档入库、文本切分、向量化、存向量数据库、用户提问时做相似度检索、把检索结果拼进Prompt、调用大模型生成回答。这里面除了向量化和大模型调用其他环节——数据管道、存储、并发处理、缓存——全是Java后端的日常。而且RAG的落地效果是肉眼可见的。你拿一份产品手册丢进去问它“这个功能支持哪些配置项”它能准确回答这个正反馈来得很快。相比之下训练模型、微调这些方向门槛高、周期长、效果还不一定好。所以对于想快速出成果的Java团队RAG是性价比最高的切入点。2.3 向量数据库的选型逻辑RAG绕不开向量数据库。市面上选择很多我按实际项目经验给个判断标准。开发验证阶段直接用内存向量存储或者Chroma就够了。Spring AI内置了SimpleVectorStoreLangChain4j也有InMemoryEmbeddingStore零依赖跑通流程最快。别一上来就搭Milvus集群那是给自己找麻烦。小规模生产PostgreSQL加pgvector插件是最稳的选择。你本来就有PG加个扩展就能存向量运维成本几乎为零。数据量在百万级以下性能完全够用。大规模场景再考虑Milvus、Qdrant或者Weaviate这些专业向量库。但说实话大部分企业的知识库文档量也就几千到几万条pgvector绰绰有余。这里有个容易踩的坑向量维度和Embedding模型必须匹配。比如你用OpenAI的text-embedding-3-small输出是1536维用通义千问的text-embedding-v2是1536维用BGE-M3是1024维。建表的时候维度写错了数据插进去就是错的检索结果会莫名其妙。这个错误我见过不止一次排查起来还挺费时间。3. 核心细节拆解RAG知识库的每个环节怎么做对3.1 文档切分最容易被忽视但最影响效果的一步很多人RAG效果不好第一反应是“模型不行”或者“检索算法不行”但实际问题出在文档切分上。你把一份50页的PDF整段丢进去向量化检索出来的是一大坨无关内容大模型拿着这堆噪音当然答不好。切分的核心原则是每个chunk要语义完整同时不能太长。太短了信息不完整太长了噪音多。我的经验值是300到800个token一个chunk相邻chunk之间保留10%到20%的重叠防止关键信息被切断。具体怎么切按文档类型来。Markdown和Word这种有明确标题层级的按标题切最自然一级标题一个大chunk二级标题一个小chunk。PDF如果没有清晰的段落结构就用递归字符切分优先按段落切段落太长再按句子切。代码文件按函数或类切。Spring AI的TokenTextSplitter和LangChain4j的DocumentSplitters.recursive()都能做这件事。但我要提醒一句别完全依赖自动切分。对于核心文档手动检查一下切分结果调整一下chunk大小和重叠参数效果提升非常明显。这个时间花得值。3.2 Embedding模型的选择与本地化方案Embedding模型负责把文本转成向量它的质量直接决定检索的准确度。选型的时候看三个指标语义理解能力、中文支持、推理成本。如果走API方案OpenAI的text-embedding-3-small性价比很高通义千问的text-embedding-v2中文效果不错。但很多企业场景不允许数据出内网这时候就得用本地模型。Ollama加BGE-M3是目前最省事的本地方案一条ollama pull bge-m3就能跑起来1024维输出中文语义理解在开源模型里属于第一梯队。本地部署的硬件门槛也不高BGE-M3在CPU上就能推理只是速度慢一些。如果文档量大建议用一张消费级显卡批量向量化的速度会快很多。我实测下来一张RTX 3060处理一万条文档大概十几分钟完全可以接受。这里有个细节Embedding模型和检索时的Query必须用同一个模型。你不能入库用BGE-M3检索用OpenAI的模型向量空间都不一样检索结果全是乱的。这个坑很隐蔽因为代码不会报错只是效果差新手很难发现。3.3 检索策略从朴素相似度到混合检索最基础的检索就是余弦相似度Top-K用户问题向量化之后在向量库里找最相似的K个chunk。K一般设3到5太少了信息不够太多了噪音多。但纯向量检索有个问题它对关键词匹配不敏感。比如用户问“错误码E5021怎么解决”向量检索可能找出一堆关于错误处理的通用文档但就是找不到那个精确提到E5021的chunk。这时候就需要混合检索——向量检索加关键词检索两路结果合并排序。LangChain4j的EmbeddingStoreContentRetriever支持配置动态过滤和最低相似度阈值。Spring AI的VectorStore也提供了similarityThreshold参数。我的建议是设一个0.7左右的阈值低于这个分数的chunk直接丢掉宁缺毋滥。再进阶一点是重排序。检索出Top-20之后用一个重排序模型比如BGE-Reranker对这20个结果重新打分选出最相关的3到5个。这一步能显著提升精度代价是多一次模型推理。如果对回答质量要求高这个环节值得加。3.4 Prompt模板的设计要点检索到的内容怎么塞给大模型也是有讲究的。一个典型的RAG Prompt模板长这样你是一个专业的技术支持助手。请根据以下参考资料回答用户问题。 如果参考资料中没有相关信息请如实告知不要编造。 参考资料 {context} 用户问题{question} 回答要求 1. 只基于参考资料回答 2. 引用具体的信息来源 3. 如果资料不足明确说明关键点是明确约束大模型的行为。“只基于参考资料回答”这句话能大幅减少幻觉“资料不足时如实告知”能避免它硬编答案。另外把{context}放在{question}前面效果通常比反过来好因为大模型对靠近末尾的内容注意力更强问题放最后能让它更聚焦。4. 完整实操流程从零搭一个可用的RAG知识库4.1 环境准备与依赖配置先列一下我用的技术栈和版本都是实测稳定的组合组件选型版本说明JDKOpenJDK17Spring AI要求17框架Spring Boot3.2.x与Spring AI兼容AI框架Spring AI1.0.0-M5核心调用AI框架LangChain4j0.35.0RAG编排向量库PostgreSQL pgvectorPG15 pgvector 0.7生产可用EmbeddingOllama BGE-M3latest本地推理大模型通义千问 qwen-plusAPI生成回答Maven依赖核心是这几个dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-pgvector/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-ollama/artifactId version0.35.0/version /dependency配置文件里把模型连接信息配好spring: ai: openai: base-url: https://dashscope.aliyuncs.com/compatible-mode api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.3 langchain4j: ollama: base-url: http://localhost:11434 embedding-model: bge-m3temperature设0.3是因为RAG场景要的是准确而不是创意温度低一点回答更稳定。4.2 文档入库的完整代码实现入库流程分四步加载文档、切分、向量化、存储。我用LangChain4j的API来写因为它的文档处理抽象更顺手。Service public class DocumentIngestService { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public DocumentIngestService(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } public void ingest(Path filePath) { // 1. 加载文档 Document document FileSystemDocumentLoader.loadDocument( filePath, new ApachePdfBoxDocumentParser()); // 2. 切分chunk大小500 token重叠50 DocumentSplitter splitter DocumentSplitters.recursive(500, 50); ListTextSegment segments splitter.split(document); // 3. 向量化 ListEmbedding embeddings embeddingModel.embedAll(segments).content(); // 4. 存储 embeddingStore.addAll(embeddings, segments); log.info(文档入库完成共 {} 个片段, segments.size()); } }这段代码看着简单但有几个实操细节值得说。DocumentSplitters.recursive(500, 50)里的500是最大token数50是重叠token数。如果你的文档技术术语多可以适当调大到800如果是FAQ这种短文本300就够了。ApachePdfBoxDocumentParser处理PDF但扫描件PDF它提取不出文字需要先做OCR这是另一个话题了。批量入库的时候注意分批处理。一次丢几千个chunk进去Embedding模型可能扛不住。我一般每批100个chunk批间加个小延迟稳得很。4.3 检索与问答的串联实现检索问答的核心是把用户问题向量化找到最相关的chunk拼进Prompt调大模型。Service public class RagQueryService { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; private final ChatClient chatClient; public String query(String question) { // 1. 问题向量化 Embedding queryEmbedding embeddingModel.embed(question).content(); // 2. 检索Top-5相关片段 ListEmbeddingMatchTextSegment matches embeddingStore.findRelevant( queryEmbedding, 5, 0.7); if (matches.isEmpty()) { return 抱歉知识库中没有找到相关信息。; } // 3. 拼接上下文 String context matches.stream() .map(m - m.embedded().text()) .collect(Collectors.joining(\n\n---\n\n)); // 4. 构造Prompt并调用大模型 String prompt String.format( 你是一个专业的技术支持助手。请根据以下参考资料回答用户问题。 如果参考资料中没有相关信息请如实告知不要编造。 参考资料 %s 用户问题%s , context, question); return chatClient.prompt(prompt).call().content(); } }findRelevant(queryEmbedding, 5, 0.7)里的5是返回条数0.7是最低相似度阈值。这两个参数需要根据实际数据调。如果发现回答经常说“没有找到相关信息”说明阈值太高了降到0.6试试如果回答里经常混入无关内容说明阈值太低或者返回条数太多。4.4 对话记忆的处理单轮问答够用但用户往往希望多轮对话。比如先问“这个功能怎么配置”再问“那它的默认值是多少”第二问依赖第一问的上下文。这时候需要加对话记忆。Spring AI提供了ChatMemory接口InMemoryChatMemory是最简单的实现。把它注入ChatClient框架会自动管理对话历史。但要注意记忆窗口大小默认是最近10轮太多会撑爆上下文窗口太少会丢失关键信息。对于技术支持场景10轮通常够用。还有一个细节RAG检索时要不要把历史对话也纳入检索我的做法是不纳入。检索只用当前问题但生成回答时把历史对话一起给大模型。这样检索精准生成又有上下文。5. 常见问题与排查技巧实录5.1 检索命中率低的排查思路这是RAG最常遇到的问题用户问了一个明明文档里有答案的问题系统却说找不到。排查按这个顺序来第一步确认文档确实入库了。直接查向量库的条数看看是不是入库失败了。有时候PDF解析出问题提取出来是空文本向量化了也是空向量检索当然找不到。第二步检查Embedding模型是否一致。入库和检索用的必须是同一个模型。我见过一个项目入库用BGE-M3检索用OpenAI排查了一天才发现。第三步看检索返回的原始结果。把Top-5的chunk内容打出来看看相似度分数是多少。如果分数普遍在0.5以下说明要么切分有问题要么Embedding模型不适合这个领域。第四步调整切分策略。如果chunk太大关键信息被稀释了相似度就上不去。试着把chunk调小或者换一种切分方式。5.2 大模型回答不准确或编造内容RAG的幻觉问题比纯对话好很多但也不是完全没有。常见原因有三个检索结果本身不相关。大模型拿着无关资料硬答当然会编。这时候要回到检索环节优化。Prompt约束不够强。把“只基于参考资料回答”改成“必须严格依据参考资料不得添加任何未在资料中出现的信息”约束力会更强。参考资料之间有矛盾。如果检索出的几个chunk说法不一致大模型会困惑。这时候需要在Prompt里加一句“如果资料之间存在矛盾请指出并说明”。5.3 性能瓶颈的定位与优化RAG系统的延迟主要来自三个环节Embedding推理、向量检索、大模型生成。我实测下来Embedding推理通常占30%向量检索占10%大模型生成占60%。所以优化重点在大模型调用上。流式输出是最有效的体验优化。用户不用等完整回答生成完首字延迟能降到1秒以内。Spring AI的stream()方法和LangChain4j的StreamingChatLanguageModel都支持。缓存是另一个手段。高频问题的检索结果可以缓存相同问题的回答也可以缓存。用Caffeine或者Redis都行命中率在技术支持场景下通常不低。批量Embedding能提升入库效率。embedAll()比循环调embed()快很多因为底层做了批处理。5.4 常见问题速查表问题现象可能原因排查方法解决方案检索不到相关内容文档未入库或入库为空查向量库条数检查文档解析是否正常检索结果不相关Embedding模型不一致对比入库和检索的模型统一使用同一模型回答编造内容Prompt约束不足检查Prompt模板加强“只基于资料回答”约束回答延迟高大模型生成慢分段计时启用流式输出多轮对话丢失上下文记忆窗口太小检查ChatMemory配置调大记忆轮数相似度分数普遍偏低切分粒度过大查看chunk内容调小chunk大小入库速度慢单条Embedding检查是否批量调用改用embedAll批量处理5.5 几个我踩过的坑坑一pgvector的索引没建。数据量小的时候没感觉上万条之后检索明显变慢。建一个IVFFlat或者HNSW索引速度能提升一个数量级。建索引的SQL是CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops);别忘了。坑二文本里的特殊字符没处理。有些PDF提取出来带一堆乱码和特殊符号向量化之后噪音很大。入库前做一次清洗去掉连续空白、特殊符号、页眉页脚效果提升明显。坑三中文分词没做。如果用关键词检索做混合检索中文需要分词。LangChain4j默认的分词器对中文支持一般建议换成HanLP或者jieba的分词器。坑四API Key硬编码。这个不用多说用环境变量或者配置中心别提交到代码仓库。6. 从Demo到生产还需要补哪些能力跑通一个RAG Demo可能只需要半天但要从Demo走到生产可用还有几个关键能力要补。权限控制。企业知识库不是所有人都能看所有文档的。需要在检索时加过滤条件比如按部门、按角色过滤。pgvector支持在查询时加WHERE条件LangChain4j的EmbeddingStoreContentRetriever也支持动态过滤。可观测性。生产系统必须能监控。记录每次检索的query、返回的chunk、相似度分数、大模型响应时间这些数据既能用来排查问题也能用来持续优化检索策略。降级方案。大模型API挂了怎么办向量库连不上怎么办要有兜底逻辑比如返回缓存结果或者提示用户稍后重试而不是直接报错。数据更新机制。文档会更新知识库也要跟着更新。最简单的做法是定期全量重建但文档量大时成本高。更好的做法是增量更新文档变了就重新向量化对应的chunk删掉旧的插入新的。效果评估。怎么知道RAG系统好不好建一个测试集准备一批问题和标准答案定期跑一遍看命中率和回答准确率。这个测试集不用很大50到100条就能反映问题。这些能力不需要一开始就全上但心里要有数。先跑通核心流程再逐步补齐生产级能力这个节奏比较稳。我个人在实际项目中的体会是Java做AI落地最大的优势不是技术本身而是工程化能力。Python那边可能三天出Demo但要从Demo到稳定运行Java这套东西反而更快。Spring的依赖管理、配置体系、监控生态都是现成的。把大模型当成一个外部服务来集成用你熟悉的方式去治理它这条路走得通而且走得稳。