news 2026/10/5 4:38:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain4j实战:从@Tool到Agent编排与RAG的Java AI流水线

1. 为什么我最终把整套 Agent 流水线压进了 LangChain4j

1.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 能力接进来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 4:37:54

雷达原理习题精讲:从雷达方程到模糊函数与脉冲压缩

确实,雷达原理这门课,在西电的电子工程类专业的培养方案里,分量一直很重。我当年学的时候,也是被那些公式推导和系统框图折磨得够呛。这两天整理移动硬盘,翻出了以前做的习题笔记,想了想,与其让…

作者头像 李华
网站建设 2026/10/5 4:37:49

RAG进阶实战:架构设计、向量库选型与MVP快速验证指南

1. 为什么我要做这个RAG进阶实战专栏过去大半年,我几乎把市面上能跑通的RAG方案都折腾了一遍。从最朴素的“文档切块塞进向量库”到带重排序、带知识图谱、带智能体路由的复合架构,踩过的坑比写过的代码还多。最直观的感受是:RAG入门容易&…

作者头像 李华
网站建设 2026/10/5 4:37:39

算法工程师面试:梯度下降与反向传播的工程化思维

1. 这不是题库,是算法工程师面试的“压力测试现场”“深度学习-算法工程师岗位面试常见问题及解答”——看到这个标题,很多人第一反应是翻出收藏夹里那几份PDF,划重点、背答案、默写公式。但我在一线带过37位校招新人、参与过152场技术终面、…

作者头像 李华
网站建设 2026/10/5 4:37:20

3A游戏引擎架构深度解析:图形、物理与脚本引擎的协同工作原理

1. 从“能跑就行”到“电影级画面”:3A游戏到底难在哪很多人第一次接触游戏引擎,是从“我想做个游戏”这个念头开始的。下载一个引擎,拖几个模型进去,点一下运行,角色能跑能跳,于是觉得“好像也不难”。但当…

作者头像 李华
网站建设 2026/10/5 4:36:54

DataX MySQLReader 插件实战:核心参数、部署与性能调优

1. 项目概述:DataX 与 MySQLReader 到底能干什么做数据同步的,应该都听说过 DataX。阿里开源的这款异构数据源离线同步工具,在我接触过的数据迁移方案里,算是团队用得最多、也最让人省心的一个。这几天在做本地部署的时候&#xf…

作者头像 李华
网站建设 2026/10/5 4:36:52

CT肋骨骨折AI检测:从DICOM预处理到临床部署全链路

简介:本资源是一篇聚焦医学影像AI落地的高质量学术论文,面向医学影像技术、人工智能辅助诊断及放射科临床科研人员,解决肋骨骨折CT图像自动识别与分类的临床痛点。研究基于卷积神经网络构建多中心验证模型,覆盖新鲜、愈合期与陈旧…

作者头像 李华