面向 Java 团队的 LangChain4j:Spring 服务中的工具、智能体、技能及端到端功能
如何使用 LangChain4j 将工具调用、代理流水线、按需技能、暂停与恢复工作流以及远程端到端代理集成到现有的 Java 服务中。
来源: Java AI in 2026, LangChain4j Turns Agents into Ordinary Interfaces
想象一家使用 Java 运行的公司。多年来,订单处理、库存管理、风险控制以及客户服务功能都运行在 Spring Boot 环境中,现在他们需要一名能够查看订单、办理退款并将案件转交给相关人员的客服人员。网上找到的教程都是从 FastAPI 开始,接着介绍 LangChain,最后才是 Python 环境的搭建,而 Java 服务则一直闲置在后台。
LangChain4j 将这些功能整合到了 Java 项目中。它并非 Python 框架的移植版本,而是根据 Java 开发者的工作方式重新构建的。简而言之,使用它来构建智能体意味着将工具层、技能层和智能体层直接整合到你现有的服务中,无需先切换编程语言。
工具层:让模型调用业务方法
许多团队会在这里遇到难题,问题不在于模型本身,而在于模型如何确定要调用哪个Java方法。@Tool注解可以解决这个问题。只需用该注解标记某个方法,框架就会将其转换为模型能够识别的JSON Schema格式。
首先需要处理依赖关系。兼容OpenAI的模块只是众多选择之一。转向Azure、Ollama或Qwen时,通常只需更换聊天模型的实现方式,业务代码则基本保持不变。
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.19.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic</artifactId>
<version>1.19.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-skills</artifactId>
<version>1.19.0-beta29</version>
</dependency>
接下来是顺序工具类。并无特别之处:只是些参数和返回值都有明确说明的普通 Java 方法。
该代码需要 Java 17 或更高版本。在普通的 Maven 项目中,需启用 -parameters 编译器标志;否则像疑问符占位符这样的参数名可能会被编译为 arg0,从而对模型毫无意义。Spring 和 Quarkus 默认会处理这个问题。
class OrderTools {
@Tool("Look up the status of an order by ID")
String queryOrder(@P("Order ID") String orderId) {
return "Order " + orderId + " has shipped and should arrive tomorrow";
}
@Tool("Refund the specified order")
String refund(@P("Order ID") String orderId, @P("Refund reason") String reason) {
return "Order " + orderId + " refund initiated. Reason: " + reason;
}
}
然后将这些工具连接到接口上。客户只需提出请求,模型便会判断是查询订单还是开始退款流程。
interface CustomerServiceAssistant {
@UserMessage("You are an e-commerce customer service assistant. Answer questions about orders and refunds. The customer's question is {{question}}")
String answer(String question);
}
CustomerServiceAssistant assistant = AiServices.builder(CustomerServiceAssistant.class)
.chatModel(model)
.tools(new OrderTools())
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.build();
String reply = assistant.answer("Where is order 20260907001?");
这是Java开发者感到得心应手的领域,因为编写查询接口以及为模型暴露@Tool方法其实本质上是相似的操作。
代理:接口与共享作用域
编排机制归根结底基于两个理念:@Agent注解应用于方法而非类上,而AgenticScope则用于将一个代理的处理结果传递给下一个代理。
假设有三个小型代理,它们彼此之间从不直接调用。每个代理都会将自己的处理结果写入一个共享变量,然后由下一个代理读取该变量。
public interface DraftWriter {
@UserMessage("""
You are a technical editor. Write an opening of no more than 200 words on the topic {{topic}}.
Return only the opening, with no explanation.
""")
@Agent(outputKey = "draft", description = "Writes an opening based on a topic")
String write(@V("topic") String topic);
}
public interface Sharpener {
@UserMessage("""
You are a reviewer. Make the following opening more specific and remove filler.
The opening is {{draft}}.
Return only the revised text.
""")
@Agent(outputKey = "draft", description = "Makes the opening more specific")
String polish(@V("draft") String draft);
}
public interface TitlePicker {
@UserMessage("""
Write 3 headlines suitable for a technical blog for the following content, one per line.
The content is {{draft}}.
""")
@Agent(outputKey = "titles", description = "Writes headlines for the content")
String pickTitle(@V("draft") String draft);
}
每个智能体都会将其输出存储在由outputKey指定的变量中。后续的智能体通过@V读取该变量,这样它们便形成了一个处理流水线。
DraftWriter writer = AgenticServices
.agentBuilder(DraftWriter.class)
.chatModel(model)
.build();
Sharpener sharpener = AgenticServices
.agentBuilder(Sharpener.class)
.chatModel(model)
.build();
TitlePicker titlePicker = AgenticServices
.agentBuilder(TitlePicker.class)
.chatModel(model)
.build();
UntypedAgent editor = AgenticServices
.sequenceBuilder()
.subAgents(writer, sharpener, titlePicker)
.outputKey("titles")
.build();
Object result = editor.invoke(Map.of("topic", "Building agents with LangChain4j"));
其优势在于编排过程能够生成可重复使用的组件。草拟、润色和命名等步骤均可单独测试并重新排序。该框架还提供了循环、并行和条件模式,若需不断优化输出直至达到质量标准,可使用loopBuilder结合评分机制与退出条件来实现。
技能:按需加载的指令
LangChain4j中的技能更类似于Claude Code中的技能包,而非单纯的注释形式。一个技能对应一个包含SKILL.md文件的目录。默认情况下模型无法看到完整内容,只有当它判断某项任务与之匹配时,才会调用activate_skill来加载内容,这样既能保持初始上下文规模较小,也能降低每次调用的成本。
首先创建一个技能文件:
skills/refund/SKILL.md
---
name: refund
description: Handle refund requests. Look up the order, check the conditions, then either refund or escalate to a human.
---
When a customer requests a refund:
1. First call queryOrder to check the order status.
2. Only call refund when the order has shipped or is complete.
3. If the order is still awaiting payment, tell the customer to cancel it.
4. If the amount exceeds 500, escalate to a human instead of refunding directly.
然后将这些技能与服务关联起来。工具模式是官方推荐的途径,模型只能访问您已注册的工具,无法访问文件系统或命令行界面。
List<FileSystemSkill> loadedSkills = FileSystemSkillLoader.loadSkills(Path.of("skills"));
Skills skillSet = Skills.from(loadedSkills);
CustomerServiceAssistant assistant = AiServices.builder(CustomerServiceAssistant.class)
.chatModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.tools(new OrderTools())
.toolProvider(skillSet.toolProvider())
.systemMessage("You have access to the following skills. When a request relates to one of them, activate it first.\n"
+ skillSet.formatAvailableSkills())
.build();
另一种选择是shell模式,它为模型提供了run_shell_command工具。这种模式在开发原型时很方便,但在生产环境中使用时需谨慎:命令会直接在主机上执行,没有任何沙箱隔离。除非能够完全信任输入内容,否则建议不要使用该模式。
暂停与恢复长时间运行的工作流
在企业环境中,棘手的问题并非只有一个简单的答案,而是工作流在数十个步骤执行到一半时被中断。LangChain4j中的持久化机制与人工审批功能正是为解决这一问题而设计的。要让工作流具备可恢复性,需让其接口实现AgenticScopeAccess,并在相应方法上添加@MemoryId,这样就能启用持久化作用域。
public interface OrderWorkflow extends AgenticScopeAccess {
@Agent
String processOrder(@MemoryId String orderId, @V("order") String orderDetails);
}
接着依次进行三步操作:验证、人工审批和发货。审批环节会返回 SuspendedResponse,因此系统会保存当前状态并释放线程,而不会继续占用它。
AgenticScopeAction validateOrder = AgenticServices.agentAction(scope -> {
String order = scope.readState("order", "");
scope.writeState("validated_order", "VALIDATED: " + order);
});
HumanInTheLoop approvalGate = AgenticServices.humanInTheLoopBuilder()
.description("Large orders require manual approval")
.outputKey("approval")
.responseProvider(scope -> new SuspendedResponse<>("manager-approval"))
.build();
AgenticScopeAction shipOrder = AgenticServices.agentAction(scope -> {
String validated = scope.readState("validated_order", "");
String approval = scope.readState("approval", "");
scope.writeState("result", "Order processed " + approval);
});
OrderWorkflow workflow = AgenticServices.sequenceBuilder(OrderWorkflow.class)
.subAgents(validateOrder, approvalGate, shipOrder)
.outputKey("result")
.build();
调用方通过 ResultWithAgenticScope 来处理挂起状态。一旦管理者批准,工作流就会从之前的检查点继续执行。
ResultWithAgenticScope<String> result =
workflow.processOrder("order-10001", "1000 units");
if (result.suspended()) {
result = result.completePendingResponse("Manager approved");
}
仅靠内存中的状态无法在重启后保留,因此需要使用 Redis、数据库或文件系统作为后端来实现 AgenticScopeStore。框架会在每个智能体完成任务后进行检查点记录,从而保存规划器的当前状态,这样在恢复时就不必重复已完成的步骤。
AgenticScopePersister.setStore(new MyAgenticScopeStore());
它看起来并无特别之处,但正是这一点区分了演示用代理与实际生产环境中的代理。服务中断、重启以及审批延迟都是常见现象,而非异常情况。
A2A:将远程代理视为本地组件
在不同团队中,风险处理代理可能运行在某个服务中,而客户服务则属于另一个团队。A2A就是连接这些远程代理的纽带:只需引入langchain4j-agentic-a2a模块,编排代码就能像处理同一进程中的代理一样来操作远端的代理。
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic-a2a</artifactId>
<version>1.19.0</version>
</dependency>
声明一个指向远程 A2A 服务的本地接口。
public interface RemoteRiskAgent {
@Agent
String check(@V("transaction") String transaction);
}
UntypedAgent riskAgent = AgenticServices
.a2aBuilder("https://risk.internal/a2a", RemoteRiskAgent.class)
.outputKey("risk_result")
.build();
在多轮对话中,需将 contextId 和 taskId 写回共享作用域:服务器在第一轮对话时生成这些值,客户端则在第二轮对话时将其返回。
public interface ChatAgent {
@A2AClientAgent(a2aServerUrl = "http://localhost:8080", outputKey = "response")
ResultWithAgenticScope<String> chat(
@V("question") String question,
@A2AContextId @V("contextId") String contextId,
@A2ATaskId @V("taskId") String taskId);
}
ResultWithAgenticScope<String> first = chatAgent.chat("Hello", null, null);
String contextId = (String) first.agenticScope().readState("contextId");
String taskId = (String) first.agenticScope().readState("taskId");
ResultWithAgenticScope<String> second = chatAgent.chat("Continue", contextId, taskId);
多智能体协作不再要求所有内容都存储在同一个仓库中。各团队可以自行拥有并运行针对其智能体的A2A服务,而更高层则负责将这些组件整合在一起。
给Java团队的建议
从简单场景开始。不必立即开发完全自主的运营智能体,可以先构建一个用于核对订单并处理退款的辅助智能体,之后再逐步添加更多功能并引入人工审批环节。
将工具与流程分开。工具代表代码所能实现的功能,而流程则是呈现给模型的逐步操作步骤。保持工具的简洁稳定,同时让流程能随着业务规则的变化而频繁调整,二者互不干扰。
对于关键工作流,应尽早加入持久化机制。如果某个流程包含审批等待、远程调用或耗时数十秒以上的步骤,应提前配置 AgenticScopeStore,因为事后添加恢复功能通常成本更高。
LangChain4j 的优势在于它能让开发者回归常规的 Java 开发模式。它并非依靠某一项炫酷功能取胜,接口、依赖注入、测试、打包和监控等功能都保留了下来,人工智能只是其中的一项能力,而非迫使开发者彻底重构一切的理由。