1. 从写业务代码到跑通第一个AI应用,Java人到底卡在哪
做了七八年Java后端,Spring Boot那套东西闭着眼睛都能写,CRUD、事务、缓存、消息队列,哪个不是手到擒来。可一提到AI,很多人的第一反应是"这玩意儿不是Python的天下吗"。我身边不少同行都是这个状态:业务代码写得飞起,但面对大模型、向量数据库、RAG这些词,总觉得隔着一层纱,不知道从哪里下手。
其实这个认知本身就是最大的障碍。Java在AI应用层的位置,跟Python完全不是一回事。Python强在模型训练、算法实验、数据处理,那是AI的"研发侧"。但AI要真正落地到企业系统里,要跟订单、库存、用户、权限这些业务逻辑长在一起,要保证高并发下的稳定性,要能接入现有的微服务架构——这恰恰是Java的主场。你不需要去训练模型,你需要的是把大模型的能力"嵌"进你已有的系统里,这件事Java不但能做,而且做得比Python更稳。
这篇内容就是写给那些有Java基础、想切入AI应用开发但不知道路怎么走的开发者。我会把整条路线拆开,从语言选型、框架对比、RAG落地、工具链搭建到实际踩过的坑,全部讲清楚。不是泛泛而谈的科普,而是我实际跑过一遍之后总结出来的路径。看完你至少能明确一件事:下一步该装什么、学什么、写什么。
关键词里提到的Spring AI、LangChain4j、RAG、Agentic RAG这些,我都会在对应的章节里展开。先把整体地图铺开,再逐个攻破。
2. 先搞清楚Java在AI技术栈里站哪个位置
2.1 训练侧和应用侧的分工,别搞混了
很多人一上来就想学PyTorch、学模型微调,方向就偏了。AI技术栈大致可以分成三层:最底层是模型训练和算法研究,中间层是模型服务和推理部署,最上层是AI应用开发。Java开发者的机会在第三层,偶尔涉及第二层。
训练侧需要的是数学功底、GPU集群、深度学习框架,这是算法工程师的活。应用侧需要的是工程能力、系统设计、业务理解,这是Java开发者的强项。你要做的事情是:调用大模型的API或者本地部署的模型服务,把模型输出跟业务数据结合起来,构建出对用户有价值的功能。
打个比方,模型就像一个刚毕业的博士生,知识渊博但不懂你公司的业务。Java开发者要做的是给这个博士生配一套办公系统——告诉他去哪里查资料(RAG)、怎么用工具(Function Calling)、按什么流程办事(Agent编排)。这套办公系统才是Java的主战场。
2.2 Java做AI应用的三个核心优势
第一个优势是工程化能力。Python写个Demo很快,但要做成7x24小时稳定运行的服务,要考虑连接池、线程安全、熔断降级、可观测性,这些Java生态有成熟到令人发指的解决方案。AI应用本质上还是一个应用,逃不开这些工程问题。
第二个优势是类型安全。大模型的输出是不确定的,但你的业务逻辑需要确定的输入。Java的强类型系统能在编译期就拦住很多问题,配合Spring AI或者LangChain4j的结构化输出功能,可以把模型的自由文本输出映射成强类型的Java对象,这在生产环境里太重要了。
第三个优势是生态整合。你现有的系统是Java写的,数据库连接、消息队列、缓存、权限体系全是Java生态。用Java做AI集成,不需要跨语言调用,不需要维护两套技术栈,运维成本直接砍半。
2.3 需要补的课其实没你想的那么多
从Java转AI应用开发,需要补的知识主要是这几块:大模型的基本概念(Token、上下文窗口、温度参数、Prompt Engineering)、向量和嵌入(Embedding是什么、向量检索怎么工作)、RAG的基本流程(文档切分、向量化、检索、增强生成)、以及至少一个Java AI框架的熟练使用。
这些概念听起来吓人,但本质上都是工程问题。Embedding就是把文本变成一串浮点数,向量检索就是算余弦相似度,RAG就是"先查资料再回答问题"。你不需要理解Transformer的注意力机制怎么算,就像你不需要理解MySQL的B+树怎么分裂也能写出高效的SQL一样。
3. 框架选型:Spring AI和LangChain4j到底怎么选
3.1 两个框架的定位差异
这是被问得最多的问题。我的结论是:如果你已经在用Spring Boot,优先选Spring AI;如果你需要更灵活的编排能力,或者不在Spring生态里,选LangChain4j。但实际情况往往更复杂,下面展开说。
Spring AI的定位是"Spring生态的AI抽象层"。它的设计哲学跟Spring Data、Spring Cache一脉相承——提供统一的抽象接口,底层可以切换不同的模型提供商。你写代码的时候面向ChatClient、EmbeddingClient这些接口编程,具体用OpenAI还是Ollama还是通义千问,改个配置就行。这种抽象带来的好处是供应商锁定风险低,坏处是某些模型特有的高级功能可能用不上。
LangChain4j的定位更偏向"AI应用编排框架"。它的灵感来自Python的LangChain,提供了Chain、Agent、Memory、Tool等更丰富的抽象。如果你要做复杂的多步骤推理、工具调用、对话记忆管理,LangChain4j的表达能力更强。但它的API变动相对频繁一些,版本升级时需要注意兼容性。
3.2 版本选择与依赖引入的实操细节
Spring AI在2.0版本之后API逐渐稳定,建议直接用最新的稳定版。Maven依赖的核心是spring-ai-bom和具体的starter。这里有个坑:Spring AI的版本跟Spring Boot的版本有对应关系,不是随便搭配都能跑。比如Spring AI 1.0.x对应Spring Boot 3.4.x,升级的时候要一起升。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>LangChain4j的依赖结构不太一样,它是按功能模块拆分的。核心是langchain4j,然后根据需要引入langchain4j-open-ai、langchain4j-ollama、langchain4j-spring-boot-starter等。这种模块化设计的好处是按需引入,不会把整个框架都拖进来。
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.35.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>0.35.0</version> </dependency>注意:LangChain4j的版本号在0.x阶段迭代很快,升级时务必看Release Notes,有些接口改名了但文档没跟上,容易踩坑。
3.3 一个决策表帮你快速定方向
| 维度 | Spring AI | LangChain4j |
|---|---|---|
| 生态绑定 | Spring Boot深度集成 | 框架无关,可独立使用 |
| 抽象层次 | 偏底层,贴近模型API | 偏上层,提供Chain/Agent抽象 |
| 学习曲线 | 对Spring开发者平缓 | 需要理解新概念 |
| 多模型支持 | 通过统一接口切换 | 通过不同模块支持 |
| RAG支持 | 内置Advisor机制 | 内置RetrievalAugmentor |
| 社区活跃度 | 背靠Spring官方,增长快 | 社区驱动,迭代快 |
| 适合场景 | 企业级Spring应用集成AI | 复杂AI编排、非Spring项目 |
我个人的做法是:新项目如果已经在Spring Boot体系里,直接用Spring AI,省心。如果是独立的AI服务,或者需要做很复杂的Agent编排,用LangChain4j。两个框架并不是互斥的,同一个项目里也可以混用,但没必要为了混用而混用。
4. RAG落地:从文档到可用的知识库
4.1 RAG到底解决了什么问题
大模型有两个硬伤:一是知识有截止日期,训练数据之后发生的事情它不知道;二是它不知道你企业的私有数据。RAG(Retrieval-Augmented Generation,检索增强生成)就是来解决这两个问题的。
RAG的核心思路很朴素:用户提问的时候,先去知识库里检索相关内容,把检索到的内容作为上下文一起发给大模型,让大模型基于这些上下文来回答。这样既解决了知识时效性问题,又解决了私有数据问题,还顺带降低了大模型胡编乱造的概率。
整个流程拆开就是四步:文档加载、文档切分、向量化存储、检索增强生成。每一步都有讲究,下面逐个说。
4.2 文档切分:最容易被低估的环节
很多人做RAG效果不好,第一反应是换模型、换向量库,其实问题往往出在文档切分上。切分粒度太粗,检索出来的内容包含大量无关信息,大模型被干扰;切分粒度太细,上下文不完整,大模型理解不了。
我的经验是:切分粒度控制在500到1000个Token之间,相邻块之间保留10%到20%的重叠。重叠的目的是防止一个完整的语义被切断。比如一段话正好在切分点被分成两半,没有重叠的话,检索到前半段就丢了后半段的信息。
Spring AI里用TokenTextSplitter来做这件事:
TokenTextSplitter splitter = new TokenTextSplitter(500, 100, 5, 10000, true); List<Document> chunks = splitter.apply(documents);参数含义分别是:目标块大小、最小块字符数、最小块行数、最大块大小、是否保留分隔符。实际调的时候,目标块大小和重叠量是最关键的两个参数。
LangChain4j里对应的是DocumentSplitters:
DocumentSplitter splitter = DocumentSplitters.recursive(500, 100); List<TextSegment> segments = splitter.split(document);实操心得:如果你的文档是结构化的(比如Markdown、HTML),优先按标题层级切分,而不是按固定长度切。按语义结构切分的效果远好于按字符数切分。Spring AI的MarkdownDocumentReader和LangChain4j的各个DocumentParser都支持这种模式。
4.3 向量化与向量库选型
文档切分完之后,需要把每个文本块通过Embedding模型转成向量,存到向量数据库里。Embedding模型的选择直接影响检索质量。常见的选项有OpenAI的text-embedding-3-small、通义千问的text-embedding-v2、以及本地可以跑的BGE系列模型。
如果数据敏感度不高,用云服务的Embedding API最省事。如果数据不能出内网,那就本地部署Ollama加BGE模型。Ollama的部署非常简单,装好之后拉个模型就能用:
ollama pull nomic-embed-text向量数据库的选择就更多了。简单场景用内存向量库(Spring AI的SimpleVectorStore、LangChain4j的InMemoryEmbeddingStore)就够,数据量大了再换Milvus、Qdrant、PgVector这些。我的建议是:除非你确定数据量会很大,否则先用内存向量库把流程跑通,后面再换。过早引入重型向量数据库只会增加调试成本。
PgVector是个被低估的选项。如果你已经在用PostgreSQL,直接装个pgvector扩展就能当向量库用,不需要额外维护一套数据库。对于中小规模的RAG应用,这个方案性价比极高。
4.4 检索策略:从朴素检索到混合检索
最基础的检索就是向量相似度检索:把用户问题也转成向量,然后找最相似的K个文本块。但纯向量检索有个问题:它对关键词匹配不敏感。比如用户搜一个特定的产品编号,向量检索可能找不准,但关键词检索一找一个准。
所以生产环境里更推荐混合检索:向量检索加关键词检索,两路结果合并后重排序。Spring AI提供了VectorStoreRetriever和各种Advisor来实现这个流程,LangChain4j有EmbeddingStoreContentRetriever和ReRankingContentAggregator。
检索数量(TopK)的设置也有讲究。设太小,可能漏掉关键信息;设太大,无关信息会干扰大模型。一般从5开始调,根据实际效果增减。如果发现大模型回答时经常"漏掉"某些信息,就加大TopK;如果回答里经常混入无关内容,就减小TopK或者加一个相似度阈值过滤。
4.5 一个最小可用的RAG示例
用Spring AI搭一个最简单的RAG,核心代码大概长这样:
@Configuration public class RagConfig { @Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { SimpleVectorStore store = SimpleVectorStore.builder(embeddingModel).build(); // 加载文档并写入 List<Document> docs = new TokenTextSplitter().apply( new TextReader(resource).get() ); store.add(docs); return store; } @Bean public ChatClient chatClient(ChatClient.Builder builder, VectorStore vectorStore) { return builder .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) .build(); } }这段代码做了三件事:把文档切分后存入向量库、配置ChatClient、挂上QuestionAnswerAdvisor。之后调用chatClient的时候,它会自动完成"检索-增强-生成"的流程。这就是Spring AI的Advisor机制的好处,把RAG的逻辑封装成了可插拔的组件。
LangChain4j的写法略有不同,需要显式构建RetrievalAugmentor:
RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder() .contentRetriever(EmbeddingStoreContentRetriever.builder() .embeddingStore(store) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.7) .build()) .build(); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .retrievalAugmentor(augmentor) .build();两种写法思路一致,只是API风格不同。Spring AI更"声明式",LangChain4j更"组装式"。
5. 工具链搭建:本地开发和调试环境怎么配
5.1 本地模型运行环境
开发阶段不建议直接调云API,一是费钱,二是网络延迟影响调试效率,三是有些实验性的Prompt不想发到外面。本地跑模型用Ollama是最省事的选择。
Ollama装好之后,拉一个7B左右的模型就够开发用了:
ollama pull qwen2.5:7b ollama pull nomic-embed-textqwen2.5:7b做对话,nomic-embed-text做Embedding。这两个组合在消费级显卡上就能跑,16G内存的机器也勉强能带动。如果机器配置更低,可以用qwen2.5:3b,效果差一些但开发调试够用。
Spring AI接入Ollama只需要加依赖和配置:
spring: ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen2.5:7b temperature: 0.7 embedding: options: model: nomic-embed-textLangChain4j接入Ollama:
OllamaChatModel model = OllamaChatModel.builder() .baseUrl("http://localhost:11434") .modelName("qwen2.5:7b") .build(); OllamaEmbeddingModel embeddingModel = OllamaEmbeddingModel.builder() .baseUrl("http://localhost:11434") .modelName("nomic-embed-text") .build();5.2 调试与可观测性
AI应用最难调试的地方在于:同样的输入,输出可能不一样。传统的断点调试基本失效,你需要的是完整的调用链日志。
Spring AI提供了ChatClient的日志功能,加上Advisor的日志,可以看到完整的Prompt、检索到的文档、模型的原始输出。LangChain4j有ChatModelListener接口,可以挂载各种监听器。
我习惯在开发阶段把每次调用的完整Prompt和响应都打到日志里,格式化成可读的文本。这样出问题的时候能快速定位是检索环节的问题还是生成环节的问题。生产环境再关掉详细日志,只保留关键指标。
关键指标包括:每次请求的Token消耗、检索耗时、生成耗时、检索到的文档数量和相似度分数。这些指标能帮你判断系统瓶颈在哪里。如果检索耗时占比高,考虑优化向量库索引;如果生成耗时高,考虑换更小的模型或者优化Prompt长度。
5.3 测试策略
AI应用的测试跟传统应用不一样。传统应用是确定性输入对应确定性输出,AI应用是概率性的。你不能断言"输入A必须输出B",但你可以断言"输出B必须包含某些关键信息"或者"输出B不能包含某些违规内容"。
我的做法是分两层:第一层是单元测试,Mock掉模型调用,只测试检索逻辑、Prompt组装逻辑这些确定性的部分。第二层是集成测试,用真实模型跑一批预设的问题,人工检查或者用另一个模型来评估输出质量。
LangChain4j提供了AiServices的测试支持,可以很方便地Mock掉模型。Spring AI也有对应的测试工具。关键是要把"模型调用"和"业务逻辑"隔离开,让业务逻辑可以独立测试。
6. 从RAG到Agent:能力进阶的路径
6.1 什么时候需要Agent
RAG解决的是"知识问答"问题,但很多场景不只是问答。比如用户说"帮我查一下上个月的销售数据,然后生成一份报告",这需要模型先调用数据库查询工具,拿到数据后再调用文档生成工具。这种多步骤、需要调用外部工具的场景,就需要Agent。
Agent的核心是让模型自己决定"下一步做什么"。你给它一组工具(Tool),它根据用户的问题决定调用哪个工具、传什么参数、拿到结果后怎么处理。这比RAG复杂得多,但能力也强得多。
6.2 Function Calling的实现方式
Spring AI里通过@Tool注解来定义工具:
@Component public class SalesTools { @Tool(description = "根据月份查询销售数据") public SalesData querySales(@ToolParam(description = "月份,格式yyyy-MM") String month) { // 实际查询逻辑 return salesService.queryByMonth(month); } }然后在ChatClient里注册这些工具:
ChatClient chatClient = builder .defaultTools(salesTools) .build();模型在对话过程中会自动判断是否需要调用工具,需要的话会生成工具调用请求,Spring AI负责执行工具并把结果返回给模型,模型再基于结果生成最终回答。
LangChain4j的工具定义类似,用@Tool注解:
public class SalesTools { @Tool("根据月份查询销售数据") public SalesData querySales(@P("月份,格式yyyy-MM") String month) { return salesService.queryByMonth(month); } }6.3 Agentic RAG:RAG和Agent的结合
Agentic RAG是最近很热的一个方向。传统RAG是"检索一次然后生成",Agentic RAG是"让模型自己决定检索什么、检索几次、什么时候检索够了"。
比如用户问一个复杂问题,模型可以先检索一次,发现信息不够,再换个关键词检索,或者调用其他工具补充信息,最后综合所有信息生成回答。这种模式对复杂查询的效果明显更好,但实现复杂度也更高,Token消耗也更大。
LangChain4j在这方面提供了比较丰富的抽象,可以通过AiServices配合RetrievalAugmentor和Tool来实现。Spring AI的Advisor机制也能支持,但需要自己写更多的编排逻辑。
我的建议是:先把基础RAG跑通,效果稳定了再考虑Agentic RAG。不要一上来就搞最复杂的架构,容易在调试上耗尽耐心。
7. 实际落地中踩过的坑和应对方案
7.1 中文分词的坑
做中文RAG的时候,TokenTextSplitter按Token切分,但中文的Token计算跟英文不一样。一个中文字可能对应1到2个Token,具体取决于分词器。如果按英文的经验设置块大小,中文文档切出来的块会偏小,语义不完整。
我的做法是:中文文档的块大小设置成英文的1.5到2倍。比如英文用500 Token,中文就用800到1000 Token。同时增加重叠量,中文用150到200 Token的重叠。
另外,如果用的是OpenAI的Tokenizer来算中文Token,结果会偏大,因为OpenAI的Tokenizer对中文的支持不是最优的。用国产模型的Tokenizer会更准,但Spring AI和LangChain4j默认用的都是OpenAI的Tokenizer。这个差异在调试的时候要注意。
7.2 向量维度不一致的问题
换Embedding模型的时候,向量维度会变。比如从OpenAI的1536维换成BGE的768维,之前存的向量就全部作废了。如果向量库里的数据没有清空重建,检索的时候会报维度不匹配的错误。
这个坑在开发阶段很容易踩,因为经常换模型做对比。我的建议是:向量库的集合名称里带上模型标识,比如"docs_openai_1536"和"docs_bge_768",换模型的时候用新的集合,不要复用。这样虽然占点存储空间,但省去了很多调试麻烦。
7.3 大模型输出格式不稳定的处理
让大模型输出JSON的时候,它有时候会加一些额外的解释文字,有时候JSON格式不对,有时候字段名大小写不一致。这些都会导致解析失败。
Spring AI提供了BeanOutputConverter,可以把模型输出直接映射成Java对象:
BeanOutputConverter<SalesReport> converter = new BeanOutputConverter<>(SalesReport.class); String format = converter.getFormat(); // 把format加到Prompt里,告诉模型按这个格式输出 SalesReport report = converter.convert(modelOutput);LangChain4j也有类似的功能,通过AiServices的返回值类型自动处理。但不管用哪个框架,都要做好解析失败的兜底处理。我的做法是:解析失败时重试一次,重试还失败就返回一个默认值或者错误提示,不要让整个请求挂掉。
7.4 检索命中率低的排查思路
RAG效果不好的时候,按这个顺序排查:先看检索到的文档是不是相关,如果不相关,问题在检索环节;如果相关但回答不好,问题在生成环节。
检索环节的问题通常是:Embedding模型不适合当前语言或领域、切分粒度不合适、TopK设置不合理、相似度阈值太高或太低。生成环节的问题通常是:Prompt模板不好、上下文太长导致模型"迷失在中间"、模型本身能力不够。
我一般会先把检索到的文档打印出来人工检查。如果检索结果明显不相关,就调Embedding模型和切分策略;如果检索结果相关但回答不好,就调Prompt和模型。这个排查顺序能省很多时间。
7.5 成本控制的几个实用技巧
Token是要花钱的,尤其是用云API的时候。几个实用的省钱技巧:一是缓存Embedding结果,同样的文本不要重复计算;二是控制上下文长度,检索到的文档不要一股脑全塞进去,按相似度排序后取TopK;三是用更小的模型做简单任务,复杂任务才用大模型;四是设置max_tokens限制输出长度,防止模型啰嗦。
Spring AI和LangChain4j都支持这些配置,关键是要有意识地去设置。我见过不少项目上线后才发现Token消耗远超预期,回头再优化就很被动。
8. 学习路线和资源推荐
8.1 分阶段的学习路径
第一阶段(1到2周):理解大模型的基本概念,跑通一个最简单的对话Demo。用Spring AI或者LangChain4j调通Ollama,能发消息收回复就行。这个阶段的目标是消除陌生感。
第二阶段(2到3周):理解RAG的完整流程,搭一个能用的知识库问答。重点搞懂文档切分、Embedding、向量检索这三个环节。这个阶段的目标是能独立完成一个RAG应用。
第三阶段(3到4周):学习Function Calling和Agent,让应用能调用外部工具。这个阶段的目标是能处理需要多步骤推理的复杂场景。
第四阶段(持续):深入Prompt Engineering、检索优化、性能调优。这些是需要在实践中不断积累的,没有速成的方法。
8.2 值得看的资源
Spring AI的官方文档写得不错,尤其是Advisor和RAG部分,有完整的示例代码。LangChain4j的文档相对零散一些,但GitHub上的examples目录很有参考价值。
Ollama的官方文档很简洁,基本看一遍就能上手。PgVector的README也写得很清楚,照着做就能跑起来。
至于大模型本身的知识,不建议一上来就看论文。先看一些工程实践类的博客和教程,把概念建立起来,有兴趣再深入原理。
8.3 一个建议:从解决实际问题开始
学AI应用开发最快的方式,是找一个你工作中真实存在的问题,用AI去解决它。比如你经常需要查文档,就做一个文档问答;你经常需要写周报,就做一个周报生成助手。有真实场景驱动,学习动力和效果完全不一样。
纯粹为了学而学,很容易在概念里打转。带着问题去学,每个知识点都能立刻验证,进步快得多。
9. 我个人的一些体会
从Java转AI应用开发这一年多,最大的感受是:这件事没有想象中那么难,但也没有想象中那么简单。难的不是技术本身,而是思维方式的转变。传统开发是确定性的,输入A必然输出B;AI应用是概率性的,同样的输入可能得到不同的输出。接受这种不确定性,学会跟不确定性共处,是每个Java开发者转AI要过的第一关。
另一个体会是:不要追求一步到位。我见过太多人一上来就想搭一个完美的RAG系统,结果在向量库选型、模型对比、参数调优上耗了几周,最后连一个能跑的Demo都没做出来。正确的做法是先跑通最小闭环,再逐步优化。先让系统能回答问题,再让它回答得更好。
最后,Java在AI应用层的优势会越来越明显。随着AI从"炫技"走向"落地",工程能力的重要性会超过算法能力。Java开发者在这个转变中占的位置,比很多人以为的要好得多。