ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LangChain4j实战:从@Tool到Agent编排与RAG的Java AI流水线

LangChain4j实战:从@Tool到Agent编排与RAG的Java AI流水线 1. 为什么我最终把整套 Agent 流水线压进了 LangChain4j1.1 从“能跑通”到“能上线”的那道坎我最早接触 LangChain4j 的时候心态其实很朴素Java 生态里终于有一个不用绕道 Python 就能把大模型接进业务系统的库了。最开始我只是拿它做最基础的事情——定义一个接口挂上几个方法用注解把提示词和模型绑定起来然后在一个 Spring Boot 项目里注入调用。那个阶段的感觉就像刚学会用 ORM觉得“原来这么简单”。但真正把东西往生产环境推的时候问题就一个接一个冒出来了。模型调用只是最表层的一环往下走你会发现工具怎么注册、工具之间怎么协作、多轮对话的上下文怎么维护、检索增强怎么接、检索回来的内容怎么重排、多个 Agent 之间怎么分工、并发上来之后会话状态会不会串、失败重试怎么做、可观测性怎么埋点。这些才是决定一个 AI 应用能不能真正落地的关键。LangChain4j 让我觉得值得深挖的地方就在于它没有停留在“封装一个 ChatClient”这个层面。它把Tool 工具调用、Agent 编排、RAG 检索增强、记忆管理这几块能力都做进了同一个库而且 API 设计保持了 Java 开发者熟悉的那种声明式风格。这意味着我不需要在一个项目里同时维护三四个不同来源的框架不用为了一个工具调用去写一堆胶水代码也不用为了接一个向量库再引入一套完全不同的抽象。这篇文章我想聊的就是怎么从最基础的Tool注解出发一步步搭出一条完整的 Agent 流水线。所谓“一个库打全套”不是说 LangChain4j 什么都能干而是说在 Java 技术栈里它能把工具调用、Agent 编排、RAG 检索这几件核心事情用一套统一的抽象串起来让你少踩很多“框架之间对不齐”的坑。1.2 这套东西到底适合谁如果你是有 Java 背景、想把大模型能力接进现有业务系统的后端开发这套内容基本是为你准备的。你不需要有很深的机器学习背景但需要对 Spring 生态、依赖注入、接口抽象这些概念比较熟。LangChain4j 的设计哲学就是“让 Java 开发者用自己熟悉的方式写 AI 应用”所以你会看到大量注解、接口、Builder 模式这些都是 Java 工程师的舒适区。如果你已经在用 LangChain4j 做简单的对话功能但卡在“怎么把多个工具串起来”“怎么让 Agent 自己决定调用哪个工具”“怎么把 RAG 接进 Agent 流程”这些地方那这篇文章里的实操细节应该能帮你把思路理顺。如果你是完全的新手我建议先把最基础的模型调用跑通再回来看工具调用和 Agent 编排的部分。因为 Agent 的本质是“让模型自己决定下一步做什么”如果连单次调用都还没跑顺直接上 Agent 会很容易迷失在调试里。1.3 整体架构的取舍逻辑在动手之前我想先说清楚这套流水线的整体设计思路因为后面所有的实操细节都是围绕这个思路展开的。核心链路是这样的用户输入进来先经过一层意图识别和路由判断这次请求是需要直接回答、需要检索知识库、还是需要调用外部工具。如果需要检索就走 RAG 流程把检索结果作为上下文注入提示词。如果需要调用工具就交给 Agent 去决策调用哪个Tool方法拿到结果后再组织成自然语言回复。整个过程中对话记忆由 LangChain4j 的ChatMemory管理保证多轮对话的连贯性。为什么这么设计因为如果把所有能力都塞给一个“万能 Agent”它会变得非常不可控。模型在每一步都要重新判断“我现在该检索还是该调工具”决策空间太大出错概率就高。把路由前置让不同类型的请求走不同的处理链路能显著提升稳定性和可观测性。这也是我在实际项目里踩过坑之后总结出来的Agent 不是越自主越好而是要在可控的范围内自主。2. Tool 注解工具调用的地基怎么打才稳2.1 Tool 的本质与最小可用示例Tool这个注解在 LangChain4j 里的定位是把一个普通的 Java 方法暴露给大模型让模型能够“看到”这个方法的名字、参数和描述并在需要的时候生成调用请求。它的工作方式和函数调用Function Calling机制是对应的模型不直接执行你的代码而是输出一个结构化的调用意图由框架负责解析并执行对应的方法再把结果回传给模型。一个最小的例子长这样public class WeatherTools { Tool(查询指定城市的当前天气) public String getWeather(P(城市名称) String city) { // 实际项目中这里会调用天气 API return city 今天晴气温 22 到 28 度; } }然后在构建模型时把工具注册进去ChatLanguageModel model OpenAiChatModel.builder() .apiKey(System.getenv(API_KEY)) .modelName(gpt-4o-mini) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(new WeatherTools()) .build();这里有几个细节值得展开。Tool里的描述文字非常关键它不是给人看的注释而是给模型看的“使用说明”。模型会根据这段描述判断什么时候该调用这个工具。描述写得含糊模型就可能在该调用的时候不调用或者在不该调用的时候乱调用。P注解用来描述参数含义同样会影响模型的参数填充准确率。2.2 工具描述怎么写才能让模型“看懂”我在这上面踩过的坑最多。最开始我写的工具描述是“获取天气”结果模型经常在用户问“今天适合穿什么”的时候不调用它因为它没把“天气”和“穿衣建议”关联起来。后来我把描述改成“查询指定城市的当前天气状况包括温度和天气现象适用于回答穿衣建议、出行安排、活动规划等需要天气信息的问题”调用命中率立刻上来了。这里的原则是描述里要包含“这个工具能回答什么问题”而不只是“这个工具是什么”。模型在做工具选择时本质上是在做语义匹配它拿用户的意图去和工具描述做相似度比较。你把使用场景写进去就等于给了模型更多的匹配线索。参数描述也是同理。一个参数叫city描述写“城市”模型可能传“北京”也可能传“北京市”还可能传“Beijing”。如果你在描述里写“城市名称使用中文例如北京、上海、广州”模型传参的规范性会好很多。如果参数有枚举值一定要在描述里列出来比如“查询类型可选值current当前天气、forecast未来预报、history历史天气”。2.3 工具方法的返回值设计工具方法的返回值会作为工具调用结果回传给模型所以返回内容的格式和长度都会影响后续的生成质量。我见过有人直接返回一个巨大的 JSON结果模型在组织回复时被大量无关字段干扰回复变得又长又乱。我的经验是工具返回值应该尽量精简只保留模型组织回复所需要的信息。如果底层 API 返回的数据结构很复杂在工具方法内部做一层裁剪和格式化。比如天气 API 返回了几十个字段你只需要温度和天气现象那就只返回这两个。另外返回值最好是自然语言友好的。模型拿到{temp: 22, condition: sunny}和拿到“当前温度 22 摄氏度天气晴朗”后者在生成回复时会更顺畅。当然这不是绝对的如果后续还有程序化处理结构化数据更合适。但在纯对话场景下自然语言化的返回值通常效果更好。2.4 工具注册的几种方式与选择LangChain4j 支持几种工具注册方式各有适用场景。第一种是直接传对象实例就像上面的tools(new WeatherTools())。这种方式最简单适合工具数量少、依赖简单的场景。缺点是工具类里的依赖需要自己管理如果工具方法需要访问数据库或调用其他服务你得自己把依赖注入进去。第二种是通过ToolProvider动态提供工具。这种方式适合工具数量多、需要按条件动态启用的场景。比如你可以根据用户权限决定哪些工具对当前用户可见或者根据会话状态动态增减工具。第三种是把工具定义在接口上配合AiServices使用。这种方式和声明式编程风格最契合工具方法和业务接口放在一起代码组织更清晰。我个人的选择是工具数量在十个以内、依赖关系简单时用第一种工具需要按权限或场景动态控制时用第二种工具和业务接口高度绑定时用第三种。不要一上来就追求最复杂的方案先从最简单的开始遇到瓶颈再换。3. Agent 编排让模型自己决定下一步3.1 Agent 和普通工具调用的区别很多人会把“带工具调用的对话”和“Agent”混为一谈其实两者有本质区别。普通工具调用是“一问一答一调用”用户问一个问题模型判断需要调工具调完拿到结果生成回复结束。整个过程只有一轮工具调用。Agent 的核心特征是多步推理和自主决策。用户提出一个复杂需求Agent 会把它拆解成多个步骤每一步可能调用不同的工具根据上一步的结果决定下一步做什么直到任务完成。比如用户说“帮我查一下北京和上海明天的天气然后告诉我哪个更适合户外活动”Agent 需要先调北京天气再调上海天气然后对比两个结果最后给出建议。这是一个典型的多步 Agent 流程。LangChain4j 里实现 Agent 的方式主要是通过AiServices配合工具和记忆让模型在多轮交互中自主决定调用哪些工具。框架本身不强制你用一个特定的“Agent 类”而是把 Agent 的能力拆解成工具调用、记忆管理、循环控制这几个部分你可以按需组合。3.2 多步工具调用的循环控制Agent 执行多步任务时最大的风险是“死循环”——模型反复调用同一个工具或者在一个步骤上卡住出不来。LangChain4j 提供了一些机制来控制这个循环。最直接的是设置最大工具调用轮数。你可以在构建AiServices时指定一个上限超过这个轮数就强制停止并返回当前结果。这个值设多少合适我的经验是简单任务设 3 到 5 轮复杂任务设 8 到 10 轮。设太小会导致任务没完成就中断设太大又会让异常情况下的等待时间过长。另一个控制手段是在工具方法内部做幂等性检查。如果同一个工具被连续调用多次且参数相同可以在方法内部直接返回缓存结果避免重复执行。这在调用外部 API 时尤其重要既能防止死循环又能节省调用成本。还有一个技巧是在系统提示词里明确告诉模型“如果已经获得了足够的信息就直接给出最终答案不要继续调用工具”。这句话看起来简单但实际效果很明显。模型有时候会“过度勤奋”明明信息已经够了还要再调一次工具确认加上这句话能减少很多不必要的调用。3.3 Agent 的记忆管理Agent 在多步执行过程中需要记住之前步骤的结果否则每一步都从零开始任务根本没法完成。LangChain4j 的ChatMemory就是干这个的。ChatMemory有几种实现最常用的是MessageWindowChatMemory它保留最近 N 条消息。N 设多少取决于你的任务复杂度和模型的上下文窗口大小。对于多步 Agent 任务我一般设 20 到 30 条确保整个任务执行过程中的关键信息不会被挤掉。但这里有个坑工具调用的中间结果也会占用记忆空间。如果工具返回的内容很长几轮下来记忆就被塞满了早期的关键信息反而被挤出去。解决办法是在工具方法内部对返回值做摘要只保留关键信息。或者在 Agent 流程中加一个“记忆压缩”步骤把已经处理完的中间结果压缩成一句话摘要再存入记忆。对于需要跨会话保持记忆的场景LangChain4j 支持把记忆持久化到外部存储。你可以实现自己的ChatMemoryStore把消息存到数据库或 Redis 里。这样即使用户关闭了页面再回来之前的对话上下文还在。实现的时候注意序列化和反序列化的问题消息对象里可能包含工具调用的结构化数据要确保这些数据能正确存取。3.4 多 Agent 协作的编排模式当任务复杂度进一步上升单个 Agent 可能就不够用了。比如一个客服系统既要处理订单查询又要处理退换货还要处理投诉建议把这些能力全塞给一个 Agent它的工具列表会非常长决策准确率会下降。这时候可以考虑多 Agent 协作。LangChain4j 本身没有提供现成的“多 Agent 编排器”但你可以基于它的工具调用能力自己搭。常见的模式有两种。第一种是路由模式一个主 Agent 负责判断用户意图然后把请求转发给对应的子 Agent。主 Agent 本身不执行业务逻辑只做分类和分发。子 Agent 各自有独立的工具集和提示词专注于自己的领域。这种模式实现简单适合意图边界清晰的场景。第二种是流水线模式多个 Agent 按顺序执行前一个的输出作为后一个的输入。比如一个内容生成流水线第一个 Agent 负责收集素材第二个负责组织大纲第三个负责撰写正文第四个负责润色。每个 Agent 只关注自己的环节通过共享的记忆或上下文传递信息。我实际项目里用得最多的是路由模式因为它最容易控制。流水线模式对 Agent 之间的接口约定要求比较高一旦某个环节的输出格式变了后面的环节就可能出错。如果要用流水线建议在 Agent 之间定义明确的数据结构而不是靠自然语言传递。4. RAG 检索增强把知识库接进 Agent 流水线4.1 RAG 在 Agent 流水线中的位置RAG 和 Agent 不是互斥的而是互补的。Agent 负责决策和编排RAG 负责提供准确的知识支撑。在一个完整的流水线里RAG 通常作为 Agent 的一个“工具”存在——Agent 判断需要查知识库时调用检索工具拿到相关文档片段再基于这些片段生成回答。LangChain4j 提供了完整的 RAG 组件文档加载器、文本分割器、嵌入模型、向量存储、检索器。你可以把这些组件组装成一个检索工具然后像注册普通工具一样注册给 Agent。这里的关键设计决策是检索应该作为工具让 Agent 自主调用还是作为前置步骤自动执行。两种方式我都试过各有适用场景。作为工具让 Agent 自主调用灵活性更高。Agent 可以根据用户问题判断是否需要查知识库不需要查的时候就不查节省时间和成本。缺点是模型有时候会“忘记”调用检索工具尤其是在问题看起来很简单但实际上需要专业知识的时候。作为前置步骤自动执行稳定性更高。每次请求都先检索一遍把结果作为上下文注入。缺点是即使问题不需要知识库也能回答也会走一遍检索增加延迟。而且检索结果如果和问题不相关反而会干扰模型生成。我的选择是对准确性要求高、问题类型相对固定的场景用前置自动检索对灵活性要求高、问题类型多样的场景用 Agent 自主调用。也可以两者结合前置检索做一个粗筛Agent 再根据情况决定是否做更精细的检索。4.2 文档切分策略对检索质量的影响RAG 效果好不好很大程度上取决于文档切分做得好不好。切分粒度太粗检索回来的内容包含大量无关信息模型容易被干扰。切分粒度太细单个片段信息不完整模型拼凑不出完整答案。LangChain4j 提供了几种文本分割器最常用的是按段落分割和按固定长度分割。我的经验是技术文档按段落分割效果最好因为段落本身就是语义完整的单元对话记录或会议纪要按固定长度分割更合适因为这类文本没有明显的段落结构。固定长度分割时重叠窗口的设置很关键。我一般设 10% 到 20% 的重叠比如每段 500 字重叠 50 到 100 字。这样能避免一个完整的句子被切断后前后两段都丢失关键信息。还有一个容易被忽略的点元数据保留。每个文档片段除了文本内容还应该保留来源、标题、章节等信息。检索的时候可以基于元数据做过滤比如只检索某个产品线的文档或者只检索最近更新的内容。LangChain4j 的Document对象支持附加元数据在切分时把原始文档的元数据传递下去就行。4.3 多路召回与重排的实操配置单一检索策略很难覆盖所有查询类型。向量检索擅长语义匹配但对精确的关键词匹配不够敏感关键词检索如 BM25擅长精确匹配但对同义词和语义变体无能为力。把两者结合起来就是多路召回。LangChain4j 本身没有内置多路召回的编排但你可以自己组合。基本思路是同时用向量检索器和关键词检索器各召回一批结果然后合并去重再用重排模型做精排。重排这一步很关键。召回阶段追求的是“不漏”所以会召回较多结果比如各召回 20 条合并后可能有三四十条。重排阶段追求的是“精准”用一个交叉编码器模型对每个候选片段和查询做相关性打分然后取 top 5 到 top 10 作为最终上下文。重排模型的选择上如果追求效果可以用专门的 rerank 模型如果追求轻量可以用嵌入模型算相似度做粗排。我实测下来加了重排之后最终回答的准确率比只用向量检索有明显提升尤其是在查询包含具体术语或编号的时候。4.4 RAG 常见瓶颈与应对思路RAG 最常见的瓶颈是“检索到了但没用对”。具体表现是检索结果明明包含正确答案但模型生成时没有采纳或者采纳了错误的部分。这通常有几个原因。一是检索结果太多太杂模型被干扰。解决办法是控制注入的片段数量一般 3 到 5 个片段就够了每个片段控制在 500 字以内。如果确实需要更多信息分多次检索而不是一次性塞进去。二是片段之间的顺序影响了模型的注意力。模型对上下文的开头和结尾部分关注度更高中间部分容易被忽略。所以把最相关的片段放在最前面或最后面能提升采纳率。三是提示词没有明确告诉模型“基于以下资料回答”。如果只是把检索结果拼在问题前面模型可能把它当成普通上下文而不是必须依据的信息。在提示词里明确写“请严格基于以下参考资料回答问题如果参考资料中没有相关信息请明确说明”能显著提升 RAG 的可靠性。5. 并发、安全与可观测性上线前必须补的课5.1 并发场景下的会话隔离单用户测试跑通不代表能上线并发一上来问题就暴露了。最常见的问题是会话串号A 用户的对话历史被 B 用户看到了。这通常是因为ChatMemory被错误地做成了单例所有用户共享同一个记忆实例。正确的做法是每个会话一个独立的ChatMemory实例用会话 ID 做区分。在 Web 应用里会话 ID 可以来自 HTTP Session、JWT Token 或者前端生成的唯一标识。LangChain4j 的ChatMemoryProvider就是干这个的你提供一个根据会话 ID 返回对应记忆实例的函数框架会在每次调用时自动取正确的记忆。ChatMemoryProvider memoryProvider memoryId - MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(memoryProvider) .tools(new WeatherTools()) .build();这里memoryId就是会话标识每个不同的 ID 会得到不同的记忆实例。注意MessageWindowChatMemory本身不是线程安全的如果同一个会话可能被并发访问需要在外面加锁或者用线程安全的实现。5.2 工具调用的安全边界工具调用本质上是让模型触发代码执行这天然带有安全风险。如果工具方法能访问数据库、能发邮件、能调用外部 API那模型的一次错误调用就可能造成实际影响。第一道防线是权限校验。工具方法内部必须校验当前用户是否有权限执行该操作。不要依赖模型来判断权限模型没有这个能力。比如一个“删除订单”的工具方法内部必须先检查当前用户是不是订单的所有者是不是有删除权限然后再执行。第二道防线是参数校验。模型生成的参数可能不符合预期比如传了一个不存在的订单号或者传了一个超出范围的数值。工具方法内部要做完整的参数校验不合法就返回错误信息让模型知道这次调用失败了。第三道防线是操作确认。对于高风险操作不要让模型直接执行而是先生成一个确认请求让用户确认后再执行。LangChain4j 本身不直接支持这种交互模式但你可以通过工具返回值来实现工具方法返回“需要用户确认”的状态Agent 把这个状态转成自然语言询问用户用户确认后再触发真正的执行。5.3 可观测性埋点Agent 流水线的调试比普通接口复杂得多因为中间经过了模型决策、工具调用、检索等多个环节出问题时很难定位是哪一步出了问题。所以可观测性必须提前埋好。最基本的埋点是记录每次模型调用的输入和输出。LangChain4j 提供了ChatModelListener接口你可以在模型调用前后插入自己的逻辑把请求和响应记录下来。这些日志在排查“模型为什么没调用工具”“模型为什么生成了错误答案”时非常有用。第二层埋点是工具调用的记录。每次工具被调用时记录工具名、参数、返回值、耗时。这能帮你发现哪些工具被频繁调用、哪些工具从来没被调用过、哪些工具调用耗时过长。第三层埋点是检索记录。记录每次检索的查询、召回结果、重排后的结果。这能帮你评估检索质量发现“检索到了但没被采纳”的情况。这些日志不需要一开始就做得很完善但至少要把原始数据留下来。等出了问题再去补日志往往就来不及了。5.4 失败重试与降级策略模型调用和外部 API 调用都可能失败必须有重试和降级机制。重试策略上我一般对模型调用设置 2 到 3 次重试每次间隔递增。但要注意工具调用不要盲目重试因为有些操作不是幂等的。比如“创建订单”这种操作重试可能导致重复创建。对于非幂等操作要么在工具内部做幂等性保证要么在重试前先查询状态确认是否已经成功。降级策略上如果模型调用持续失败可以降级到规则引擎或者返回预设的兜底回复。如果检索服务不可用可以降级到纯模型生成并在回复中说明“当前无法访问知识库以下回答基于通用知识”。降级的目标是保证系统可用而不是追求完美。6. 常见问题排查速查表6.1 工具调用相关问题问题现象可能原因排查方向解决思路模型不调用工具工具描述不清晰检查Tool描述是否包含使用场景补充“适用于回答什么问题”的描述模型调用错误的工具工具描述之间区分度不够对比多个工具的描述文本让每个工具的描述有明确的边界词工具参数传错参数描述不明确检查P描述是否说明了格式和示例补充参数格式、枚举值、示例工具调用死循环缺少循环控制检查是否设置了最大调用轮数设置上限并在提示词中加停止条件工具返回结果被忽略返回值太长或格式不友好检查工具返回内容精简返回值做自然语言化处理6.2 RAG 检索相关问题问题现象可能原因排查方向解决思路检索不到相关内容切分粒度或嵌入模型不匹配检查切分后的片段和查询的语义距离调整切分粒度换用更适合的嵌入模型检索到了但回答没用片段太多或顺序不对检查注入的片段数量和顺序控制片段数量把最相关的放首尾回答包含错误信息检索结果本身有误检查知识库内容是否准确清洗知识库增加元数据过滤多路召回效果差重排策略不合理检查重排模型的打分是否合理调整重排模型或增加关键词权重6.3 并发与性能相关问题问题现象可能原因排查方向解决思路会话串号记忆实例被共享检查ChatMemoryProvider实现确保每个会话 ID 对应独立实例响应越来越慢记忆窗口太大检查记忆中的消息数量限制窗口大小压缩中间结果并发时出错共享资源未加锁检查工具类和记忆的线程安全性加锁或改用线程安全实现超时频繁外部调用耗时过长检查工具方法和检索的耗时设置超时增加异步处理7. 我在实际项目里踩过的几个坑第一个坑是工具描述写得太“技术化”。我一开始按照 API 文档的风格写工具描述比如“调用天气服务接口返回 JSON 格式的天气数据”。结果模型经常不调用这个工具因为它不理解“JSON 格式的天气数据”对回答用户问题有什么帮助。后来改成“查询城市天气用于回答穿衣、出行、活动安排等问题”调用率立刻上来了。模型需要的是“这个工具能帮我回答什么”而不是“这个工具技术上是怎么实现的”。第二个坑是RAG 片段注入太多。我一开始觉得检索结果越多越好把 top 20 都塞进提示词。结果模型被大量无关信息干扰回答质量反而下降。后来改成 top 5并且加了重排效果明显提升。这让我意识到RAG 的关键不是“检索到更多”而是“检索到最相关的”。第三个坑是忽略了工具调用的幂等性。有一次一个“发送通知”的工具在重试时被调用了两次用户收到了两条重复通知。后来我在工具内部加了幂等性检查用业务 ID 做去重问题才解决。这个教训是任何有副作用的工具都必须考虑重试场景下的幂等性。第四个坑是没有做会话隔离。早期测试时只有一个用户没发现问题。上线后多个用户同时使用出现了对话历史串号。排查后发现是ChatMemory被做成了单例。改成ChatMemoryProvider按会话 ID 分配后解决。这个坑让我明白单用户测试通过不等于能上线并发场景必须单独验证。8. 后续可以继续深挖的方向这套流水线跑通之后还有几个方向可以继续优化。一是检索质量的持续调优。RAG 的效果不是一次配置就能达到最优的需要根据实际查询日志不断调整切分策略、嵌入模型、重排参数。可以建立一个评估集定期跑一遍检索准确率用数据驱动优化。二是Agent 决策的可解释性。目前 Agent 为什么选择某个工具、为什么跳过某个步骤对开发者来说是个黑盒。可以通过记录模型的推理过程如果模型支持或者在提示词中要求模型输出决策理由来提升可解释性。三是成本优化。模型调用和向量检索都是按量计费的并发上来之后成本会很明显。可以通过缓存高频查询的结果、对简单问题走轻量模型、对检索结果做缓存等方式来控制成本。四是多模态扩展。目前这套流水线主要处理文本如果业务需要处理图片、音频可以在工具层扩展多模态能力让 Agent 能够调用图像识别或语音转文字的工具。这套东西我前后调了大概两个月从最开始的单轮对话到现在的多 Agent 流水线中间踩的坑基本都写在上面的内容里了。LangChain4j 这个库给我的最大感受是它没有试图做一个“万能框架”而是把 AI 应用开发中最核心的几个抽象做好了剩下的交给你按需组合。这种设计哲学对 Java 开发者来说很友好因为你不需要改变自己的编程习惯用注解、接口、Builder 这些熟悉的东西就能把 AI 能力接进来。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进