
1. Java 开发者切入 AI 的真实路径拆解1.1 为什么 Java 开发者不需要从零学 Python我做了十多年 Java 后端这两年身边问得最多的一句话就是“搞 AI 是不是得先把 Python 学一遍”我的答案一直很明确不需要从零开始但需要理解 AI 应用的运行范式。原因很简单。绝大多数企业级 AI 落地场景不是训练大模型而是把大模型能力集成进已有的业务系统。你手里那套 Spring Boot 微服务、MyBatis 数据访问层、Redis 缓存、MQ 异步链路才是 AI 真正要嵌入的地方。训练模型是算法团队的事而推理调用、上下文编排、知识库检索、结果后处理这些恰恰是 Java 工程师的主场。我见过太多 Java 同行一上来就扎进 PyTorch 教程学了两周矩阵运算回头发现公司要的只是“把客服知识库接进大模型做个问答”。这就是方向跑偏了。正确的切入姿势是先跑通一条 Java 调用大模型的链路再逐步往 RAG、Agent 方向延伸。具体来说Java 开发者入门 AI 的路线图可以拆成四个阶段第一阶段模型调用打通。用 HTTP 客户端或官方 SDK 调通一个大模型接口理解 token、temperature、system prompt 这些基础概念。这一步一两天就能完成。第二阶段框架化封装。引入 Spring AI 或 LangChain4j把裸调用升级成可维护的工程结构学会用 ChatClient、PromptTemplate、Advisor 这些抽象。第三阶段RAG 知识库。把企业私有文档向量化接入向量数据库实现“基于自有知识的问答”。这是目前企业需求最集中的方向。第四阶段Agent 与工作流。让模型能调用工具、编排多步任务也就是常说的 Agentic 方向。这四个阶段不是必须严格串行但顺序乱了会很难受。我试过直接上手 RAG结果连 embedding 和 completion 的区别都没搞清调了一整天以为向量库有问题其实是 prompt 模板写错了。1.2 工具链选型的核心考量Spring AI 还是 LangChain4j这是被问得最多的问题没有之一。我的判断逻辑是这样的Spring AI 的优势在于和 Spring 生态的无缝融合。如果你的项目本身就是 Spring Boot那引入 Spring AI 几乎是零摩擦——自动配置、依赖注入、Actuator 监控全都对得上。它的 ChatClient API 设计得很 Spring 味写起来就像在用 RestTemplate。缺点是生态相对年轻一些高级的 Agent 编排能力还在演进中。LangChain4j 的优势在于抽象层次更丰富。它的 AiServices 可以用接口加注解的方式定义 AI 服务声明式写法很优雅。RAG 相关的组件也更完整文档加载器、分割器、嵌入存储、检索器一应俱全。缺点是它自成一套体系和 Spring 的整合需要额外配置。我的实际选择是业务系统集成用 Spring AI独立 AI 应用或复杂 RAG 用 LangChain4j。两者并不冲突甚至可以在同一个项目里共存——Spring AI 负责对话链路LangChain4j 负责知识库检索。对比维度Spring AILangChain4j生态融合与 Spring Boot 无缝独立体系需适配API 风格命令式Spring 味浓声明式注解驱动RAG 组件基础完备更丰富细致Agent 能力演进中相对成熟学习曲线低Spring 开发者友好中等概念较多适合场景业务系统集成独立 AI 应用提示不要纠结“哪个更好”先看你的项目底座是什么。Spring Boot 项目硬上 LangChain4j光是配置整合就能耗掉你半天热情。1.3 环境准备从 JDK 版本到依赖管理动手之前环境得先理顺。这块看似简单但踩坑的人不少。JDK 版本建议 17 或 21。Spring AI 和 LangChain4j 的新版本都要求 JDK 17 起步21 的虚拟线程在处理大量并发模型调用时优势明显。我实测过同样的 RAG 检索并发场景JDK 21 虚拟线程比传统线程池吞吐量高出约 40%。构建工具用 Maven 或 Gradle 都行但要注意依赖版本对齐。Spring AI 有 BOM 管理直接引入spring-ai-bom就能统一版本。LangChain4j 也有类似的 BOM。我见过有人手动指定每个模块版本结果 core 和 openai 模块版本不一致启动就报 NoSuchMethodError。模型服务的选择上本地开发和测试建议用 Ollama 跑一个小参数模型比如 qwen2.5:7b 或 llama3.1:8b。好处是免费、离线、数据不出本机。生产环境再切换到云端 API。这样做的理由是开发阶段频繁调试用云端 API 会产生大量无效调用成本而本地模型虽然效果差一些但足够验证链路是否通畅。依赖清单大致如下dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama-spring-boot-starter/artifactId /dependency /dependencies配置文件里把模型地址、密钥、模型名配好Spring Boot 启动时就会自动装配 ChatClient。这一步跑通你就已经迈过 AI 应用的门槛了。2. 核心概念与实操要点深度解析2.1 Prompt、Token 与上下文窗口三个必须搞懂的基础概念很多人调模型调不明白根子在这三个概念上没吃透。Prompt 就是你给模型的输入但它不只是“一句话”。一个完整的 prompt 通常包含 system 角色设定、user 用户输入、assistant 历史回复三部分。system prompt 决定了模型的“人设”和行为边界这部分写得好不好直接决定输出质量。我一般会把业务规则、输出格式要求、禁止事项全塞进 system prompt实测下来比在 user 消息里反复强调有效得多。Token 是模型的计费和处理单位。中文大致一个字对应 1 到 2 个 token英文一个单词约 1.3 个 token。为什么要关心这个因为上下文窗口是有上限的。比如 8k 窗口的模型你的 system prompt 加历史对话加检索到的文档总共不能超过 8k token。超了怎么办要么截断要么换更大窗口的模型要么做上下文压缩。我踩过的一个坑RAG 场景下检索回来 10 个文档片段每个 500 字加起来就 5000 多字再叠加历史对话直接把窗口撑爆。模型要么报错要么悄悄截断前面的内容导致回答驴唇不对马嘴。后来我改成检索 Top 3 片段每段限制 300 字并做去重和相关性排序问题才解决。上下文窗口这个概念你可以理解成模型的“短期记忆容量”。它记不住窗口之外的东西。所以多轮对话时要么把历史消息一起传进去要么用外部存储做长期记忆。这也是为什么 RAG 这么重要——它相当于给模型外挂了一个“长期记忆库”。概念通俗理解实操影响Prompt给模型的指令包决定输出质量和格式Token计费和容量单位影响成本和窗口是否溢出上下文窗口模型的短期记忆决定能塞多少历史与文档2.2 结构化输出让模型返回可解析的 JSON模型默认返回的是自然语言但业务系统需要的是结构化数据。比如你让模型分析一段文本的情感返回“这段话是积极的”没法直接用你需要的是{sentiment: positive, confidence: 0.92}。Spring AI 提供了BeanOutputConverter可以把模型输出直接映射成 Java 对象。用法是先定义一个 record 或 POJO然后用转换器生成格式指令附加到 prompt 里。模型看到格式要求后会按 JSON 输出转换器再反序列化成对象。record SentimentResult(String sentiment, double confidence) {} BeanOutputConverterSentimentResult converter new BeanOutputConverter(SentimentResult.class); String prompt 分析以下文本的情感倾向并按指定格式返回。 {format} 文本这家餐厅的服务态度太差了等了一个小时才上菜。 ; PromptTemplate template new PromptTemplate(prompt); template.add(format, converter.getFormat()); String response chatClient.call(template.render()); SentimentResult result converter.convert(response);这里有个关键经验不是所有模型都能稳定输出合法 JSON。小参数本地模型经常多输出一句“好的以下是分析结果”导致解析失败。解决办法有两个一是用支持 JSON mode 的模型二是在 prompt 里明确写“只输出 JSON不要任何额外文字”并在解析前做一次清洗把 JSON 之外的内容剥掉。我一般会写一个容错解析方法先用正则提取第一个{到最后一个}之间的内容再尝试反序列化。这样即使模型多说了废话也能兜住。2.3 向量化与 EmbeddingRAG 的地基RAG 的核心思路是把文档变成向量存起来用户提问时也转成向量然后找最相似的文档片段喂给模型。这里的“变成向量”就是 embedding。Embedding 模型和对话模型是两回事。对话模型负责生成回答embedding 模型负责把文本转成高维浮点数组。常见的中文 embedding 模型有 bge、m3e、text-embedding 系列。选型时主要看维度、中文效果、推理速度三个指标。维度越高表达能力越强但存储和计算成本也越高。768 维和 1536 维是常见选择。我实测下来中文场景下 bge-large-zh 的检索命中率明显优于一些通用多语言模型尤其是在专业术语较多的领域。向量存到哪里开发阶段可以用内存向量库比如 LangChain4j 自带的InMemoryEmbeddingStore重启数据就没了但调试方便。生产环境一般用 Milvus、Qdrant、PgVector 这些。如果团队已经有 PostgreSQLPgVector 是最省事的选择不用额外维护一套向量数据库。EmbeddingModel embeddingModel new BgeSmallZhEmbeddingModel(); EmbeddingStoreTextSegment store new InMemoryEmbeddingStore(); Document doc Document.from(Java 的垃圾回收机制分为新生代和老年代...); DocumentSplitter splitter DocumentSplitters.recursive(300, 50); ListTextSegment segments splitter.split(doc); for (TextSegment segment : segments) { Embedding embedding embeddingModel.embed(segment).content(); store.add(embedding, segment); }这段代码里recursive(300, 50)表示每段目标 300 字重叠 50 字。重叠是为了避免语义被切断——如果一句话正好卡在分割点上两段各拿一半检索时可能两边都匹配不上。重叠区能让关键语义至少完整出现在一段里。2.4 文档分割策略RAG 效果的分水岭RAG 做得好不好七成看分割三成看检索。这话不夸张。分割太粗一个片段里混了好几个主题检索时相关性被稀释分割太细语义不完整模型拿到手也不知道在说什么。我的经验是按文档结构分割优先按固定长度分割兜底。具体来说Markdown 文档按标题层级分PDF 按段落分代码按函数分。LangChain4j 提供了DocumentByParagraphSplitter、DocumentByLineSplitter等多种分割器。如果文档结构混乱再用递归字符分割器兜底。还有一个容易被忽略的点元数据保留。每个片段除了文本内容还应该带上来源文件名、页码、章节标题等信息。这样检索回来时你可以告诉用户“这个答案来自《运维手册》第 3 章”可信度立刻提升。而且元数据还能用于过滤比如只检索某个产品线的文档。Document doc Document.from(text, Metadata.from(source, 运维手册.pdf) .add(chapter, 第三章 故障排查));注意分割长度没有万能值。技术文档 300 到 500 字比较合适法律合同可能要 800 字以上聊天记录则适合按对话轮次分割。一定要拿真实文档试别照搬网上的参数。3. 完整实操流程从零搭一个本地 RAG 知识库3.1 项目初始化与依赖配置我以 LangChain4j 加 Ollama 加内存向量库为例走一遍完整流程。这套组合零成本、离线可用、适合学习和原型验证。第一步建一个 Maven 项目引入依赖dependencies dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-ollama/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-easy-rag/artifactId version0.35.0/version /dependency /dependencieslangchain4j-easy-rag这个模块值得单独说一句。它把文档加载、分割、嵌入、存储、检索这一整套流程封装成了几行代码非常适合快速验证。但生产环境不建议直接用因为它的默认参数不一定适合你的文档而且可定制性差。学习阶段用它跑通链路理解每个环节在干什么然后再拆开自己实现。第二步确认 Ollama 已经跑起来并且拉好了模型ollama pull qwen2.5:7b ollama pull nomic-embed-textqwen2.5:7b 负责对话生成nomic-embed-text 负责向量化。两个模型各司其职别想着用一个模型干两件事。3.2 文档加载与向量入库假设你有一批 Markdown 格式的内部文档放在docs/目录下。加载和入库的代码如下EmbeddingModel embeddingModel OllamaEmbeddingModel.builder() .baseUrl(http://localhost:11434) .modelName(nomic-embed-text) .build(); EmbeddingStoreTextSegment embeddingStore new InMemoryEmbeddingStore(); EmbeddingStoreIngestor ingestor EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(300, 50)) .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); ListDocument documents FileSystemDocumentLoader.loadDocuments(docs/); ingestor.ingest(documents);EmbeddingStoreIngestor把分割、嵌入、存储三步串起来了。loadDocuments会自动识别文件类型Markdown、txt、PDF 都支持。如果你的文档是 Word 格式需要额外引入langchain4j-document-parser-apache-poi模块。入库完成后可以做个简单验证拿一个已知问题去检索看返回的片段是否相关。Embedding queryEmbedding embeddingModel.embed(如何排查内存泄漏).content(); ListEmbeddingMatchTextSegment matches embeddingStore.findRelevant(queryEmbedding, 3); matches.forEach(m - System.out.println(m.embedded().text()));如果返回的片段跟内存泄漏无关说明分割或嵌入环节有问题先别急着往下走。3.3 检索增强的对话链路搭建检索通了接下来把检索和对话串起来。LangChain4j 的RetrievalAugmentor就是干这个的RetrievalAugmentor augmentor DefaultRetrievalAugmentor.builder() .contentRetriever(EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(3) .minScore(0.7) .build()) .build(); ChatLanguageModel chatModel OllamaChatModel.builder() .baseUrl(http://localhost:11434) .modelName(qwen2.5:7b) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .retrievalAugmentor(augmentor) .build(); String answer assistant.chat(系统频繁 Full GC 该怎么排查);Assistant是一个接口用SystemMessage注解定义人设interface Assistant { SystemMessage(你是一个运维助手只根据提供的参考资料回答问题。 如果资料中没有相关内容直接说不知道不要编造。) String chat(String userMessage); }这里有两个参数值得细说。maxResults(3)表示检索最相关的 3 个片段。为什么不取更多因为片段越多噪声越大而且会挤占上下文窗口。实测 3 到 5 个是比较平衡的值。minScore(0.7)是相似度阈值低于这个分数的片段直接丢弃。这个值设太低会引入无关内容设太高可能什么都检索不到。建议先用 0.6 起步根据实际效果调整。3.4 效果验证与参数调优链路跑通只是开始调优才是真正花时间的地方。我一般从三个维度验证检索命中率。准备 20 个典型问题人工标注每个问题的正确答案在哪个文档片段里然后看检索器 Top 3 里有没有包含这个片段。命中率低于 80% 就要优化分割策略或换 embedding 模型。回答准确率。看模型回答是否忠实于检索到的资料有没有编造。这个只能人工评估但可以抽样做。响应延迟。本地 7b 模型在普通笔记本上一次完整问答大概 3 到 8 秒。如果超过 15 秒检查是不是检索片段太多或者模型参数太大。调优的常见手段包括调整分割长度和重叠、更换 embedding 模型、调整 maxResults 和 minScore、优化 system prompt。每次只改一个变量否则你分不清是哪个改动起了作用。调优维度常见问题调整方向检索不准返回片段与问题无关换 embedding 模型调分割粒度回答编造模型不看资料瞎说强化 system prompt降低 temperature响应慢等待时间过长减少检索片段换小模型答非所问理解偏差优化 prompt 模板增加示例4. 常见问题与排查技巧实录4.1 模型调用报错排查速查表实际开发中报错是家常便饭。我把踩过的坑整理成一张表方便对照排查报错现象可能原因解决方向401 UnauthorizedAPI Key 错误或过期检查配置确认密钥有效429 Too Many Requests调用频率超限加退避重试降低并发400 Context Length Exceeded上下文超窗口截断历史减少检索片段连接超时网络或服务地址错误检查 baseUrl确认服务可达JSON 解析失败模型输出非纯 JSON加清洗逻辑用 JSON mode中文乱码编码不一致统一 UTF-8其中429 和上下文超限是最常见的两个。429 的解决办法是加指数退避重试Spring AI 和 LangChain4j 都支持配置重试策略。上下文超限则需要在业务层做控制比如限制历史对话轮数、对检索片段做长度截断。4.2 RAG 效果差的五个隐藏原因RAG 搭起来容易效果好难。我总结了五个最容易被忽略的原因第一文档质量差。如果原始文档本身就有大量错别字、格式混乱、内容重复检索效果不可能好。先清洗文档再谈 RAG。我一般会做一轮预处理去页眉页脚、合并断行、去除乱码。第二分割破坏了语义。前面提过分割点卡在句子中间是灾难。解决办法是优先按段落、标题分割固定长度分割作为兜底并且一定要加重叠。第三embedding 模型不匹配。用英文模型处理中文文档效果必然打折。中文场景一定要选中文优化的 embedding 模型。第四检索策略单一。纯向量检索对关键词不敏感。比如用户搜“AQS 原理”向量检索可能返回一堆并发相关但没提 AQS 的片段。这时候需要混合检索——向量检索加关键词检索两路结果合并去重。LangChain4j 支持这种组合。第五prompt 没有约束。如果 system prompt 不明确要求“只根据资料回答”模型会自由发挥把训练时的知识混进来。这在专业领域是致命的。提示RAG 调优是个迭代过程别指望一次到位。我的习惯是建一个测试问题集每次改动后跑一遍用数据说话。4.3 本地模型与云端 API 的取舍经验开发阶段用本地模型生产环境用云端 API这是我一直推荐的组合。但具体怎么切有几个经验点本地模型选 7b 到 14b 参数。再小效果太差再大普通机器跑不动。qwen2.5:7b 在中文场景下表现均衡是我常用的选择。云端 API 选支持流式输出的。流式输出能让用户看到逐字生成的效果体验好很多。Spring AI 的StreamingChatClient和 LangChain4j 的StreamingChatLanguageModel都支持。切换时注意 prompt 兼容性。不同模型对 prompt 的敏感度不一样。本地模型能理解的指令云端模型可能理解得更好也可能理解偏。切换后一定要重新验证。成本控制。云端 API 按 token 计费RAG 场景下每次调用都带着检索片段token 消耗不小。我的做法是简单问题走本地模型复杂问题才走云端同时对检索片段做长度限制避免无谓消耗。4.4 从 RAG 到 Agent 的进阶思路RAG 跑顺了下一步自然是 Agent。Agent 的核心是让模型能调用工具。比如用户问“帮我查一下订单 12345 的状态”模型识别出需要调用订单查询接口传参、拿结果、组织回答。LangChain4j 用Tool注解定义工具class OrderService { Tool(根据订单号查询订单状态) String queryOrderStatus(P(订单号) String orderId) { return orderRepository.findById(orderId).getStatus(); } } Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .tools(new OrderService()) .build();模型会自动判断什么时候调用这个工具。但工具描述一定要写清楚否则模型不知道该在什么场景下用。我见过工具描述写“查询订单”的模型在用户问“今天天气”时也去调就是因为描述太模糊。Agentic RAG 是更进阶的形态让 Agent 自己决定检索什么、检索几次、要不要换个关键词再检。这比固定流程的 RAG 灵活但也更难控制。我的建议是先把基础 RAG 做扎实再考虑 Agent 化否则问题排查会非常痛苦。5. 学习资源与进阶方向建议5.1 官方文档与源码阅读顺序学 Spring AI 和 LangChain4j最靠谱的资料就是官方文档加源码。我的阅读顺序建议是先看 Spring AI 的 ChatClient 章节理解最基础的调用模型。然后看 Advisor 机制这是 Spring AI 的拦截器体系理解它才能做日志、限流、RAG 这些横切功能。最后看 RAG 章节把检索增强的完整链路走一遍。LangChain4j 则建议先看 AiServices理解声明式定义 AI 服务的方式。然后看 RAG 包下的各个组件从 DocumentLoader 到 EmbeddingStore 到 ContentRetriever逐个搞明白职责。最后看 Agent 和 Tool 相关的内容。源码阅读不要贪多。挑一个核心类比如DefaultRetrievalAugmentor跟一遍它的 augment 方法看它怎么把检索结果拼进 prompt。这一遍下来比看十篇博客都管用。5.2 面试与实战中的高频考点如果你是为了面试准备这几个点几乎必问RAG 的完整流程。从文档加载、分割、嵌入、存储、检索到生成每一步都要能说清楚。面试官常追问“分割长度怎么定”“检索 Top K 怎么选”这些要结合具体场景回答别背固定值。向量检索的原理。余弦相似度、内积、欧氏距离的区别什么时候用哪个。HNSW 索引的大致思路。这些不要求推导公式但要能说清概念。Prompt 工程的实际经验。system prompt 怎么写、few-shot 怎么加、输出格式怎么约束。最好能举一个你实际调优的例子。模型调用的工程问题。超时重试、限流降级、成本控制、流式输出。这些是 Java 工程师的强项一定要结合自己的后端经验来答。Agent 与工具调用。工具怎么定义、模型怎么决策、多步任务怎么编排。这块是加分项能聊清楚说明你真的动手做过。5.3 后续可以深入的方向基础 RAG 跑通后有几个方向值得深入混合检索与重排序。向量检索加关键词检索再用一个重排序模型对结果精排。这套组合能把检索命中率提升一大截。重排序模型可以用 bge-reranker 系列。GraphRAG。把文档里的实体和关系抽出来构建知识图谱检索时同时利用图结构和向量。适合实体关系复杂的领域比如法律、医疗。实现复杂度高但效果上限也高。多模态 RAG。文档里不只有文字还有表格、图片。把表格转成结构化数据图片做 OCR 或视觉嵌入一起纳入检索范围。这块目前还在快速演进。Agentic 工作流。让模型编排多步任务比如“先查订单再查物流最后生成一封道歉邮件”。这需要模型有较强的规划和工具调用能力目前用大参数模型效果更稳。评测体系建设。RAG 效果好不好不能靠感觉。建一套自动化评测流程用固定问题集跑分每次改动后对比。这是从“能跑”到“可靠”的关键一步。我个人在实际操作中的体会是AI 应用开发工程能力比算法能力更重要。模型是现成的但怎么把它稳定、高效、低成本地嵌进业务系统这才是 Java 工程师的价值所在。别被那些花哨的算法名词吓住你手里的 Spring Boot 和微服务经验才是真正的护城河。