1. LangChain4j与Spring AI深度对比:Java生态中的AI应用开发框架选择
在Java生态中构建AI应用时,开发者常面临框架选择的难题。本文将深入对比LangChain4j和Spring AI这两个主流框架,特别聚焦在Agent开发能力上的差异,并通过实际代码示例展示它们在实际项目中的应用效果。
1.1 框架定位与核心能力对比
LangChain4j是专为Java生态设计的LLM应用开发一站式框架,其核心优势在于:
- 原生支持Agent开发,内置完整的Think-Act-Observe循环
- 开箱即用的工具解析、上下文管理、异常重试和终止判断机制
- 极简API设计,开发者只需关注业务逻辑
Spring AI则是Spring生态中的大模型统一适配层:
- 主要目标是屏蔽不同大模型API的差异
- 仅提供单次工具调用的基础能力
- 缺乏完整的Agent闭环流程支持
提示:如果你需要快速构建复杂的多步AI应用,LangChain4j的完整Agent支持能节省大量开发时间;而如果你只需要基础的模型调用能力,Spring AI可能更轻量。
1.2 开发效率对比:代码量差异
让我们通过一个典型场景对比两者的开发效率:用户输入需求→AI生成页面代码→截图生成封面→部署上线→返回结果。
1.2.1 LangChain4j实现方案
java复制@Component
public class AppDevTools {
@Tool("根据用户需求生成前端页面代码")
public String generatePageCode(String userDemand) {
return "生成的完整前端页面代码";
}
@Tool("根据页面代码生成封面截图")
public String generateAppCover(String pageCode) {
return "/app-cover/xxx/cover.jpg";
}
@Tool("部署前端页面")
public String deployApp(String pageCode) {
return "https://xxx.xxx.com/app/123";
}
}
@Service
public class AppDevService {
@Autowired private ChatLanguageModel chatLanguageModel;
@Autowired private AppDevTools appDevTools;
public String handleUserDemand(String userDemand) {
return AiServices.builder(chatLanguageModel)
.tools(appDevTools)
.build()
.chat(userDemand);
}
}
LangChain4j自动处理:
- 多轮工具调用的循环逻辑
- 上下文维护
- 异常重试
- 终止判断
1.2.2 Spring AI实现方案
java复制@Service
public class SpringAiAppDevService {
@Autowired private ChatClient chatClient;
private final List<FunctionCallback> devTools = List.of(
new GeneratePageCodeTool(),
new GenerateCoverTool(),
new DeployAppTool()
);
public String handleUserDemand(String userDemand) {
List<Message> messageContext = new ArrayList<>();
messageContext.add(new UserMessage(userDemand));
int maxLoopCount = 10;
int currentLoop = 0;
while (currentLoop < maxLoopCount) {
currentLoop++;
ChatResponse response = chatClient.prompt()
.messages(messageContext)
.functions(devTools)
.call()
.chatResponse();
AssistantMessage assistantMsg = response.getResult().getOutput();
if (assistantMsg.getToolCalls().isEmpty()) {
return assistantMsg.getText();
}
for (ToolCall toolCall : assistantMsg.getToolCalls()) {
try {
FunctionCallback tool = findTool(toolCall.getName());
String toolResult = tool.call(toolCall.getArguments());
messageContext.add(new ToolMessage(toolResult, toolCall.getId()));
} catch (Exception e) {
messageContext.add(new ToolMessage("工具执行失败:"+e.getMessage(), toolCall.getId()));
}
}
messageContext.add(assistantMsg);
}
return "操作步骤过多,请简化需求后重试";
}
private FunctionCallback findTool(String toolName) {
return devTools.stream()
.filter(tool -> tool.getName().equals(toolName))
.findFirst()
.orElseThrow(() -> new RuntimeException("工具不存在:" + toolName));
}
}
Spring AI需要开发者手动处理:
- 上下文维护
- 循环控制
- 工具查找与调用
- 异常处理
- 终止判断
1.3 自定义能力对比
1.3.1 LangChain4j的自定义方式
LangChain4j提供多层次的自定义能力:
基础配置自定义:
java复制AiServices.builder(chatLanguageModel)
.tools(appDevTools)
.systemMessageProvider(ctx -> "你是一个专业的低代码平台开发助手...")
.maxRetries(5)
.timeout(Duration.ofSeconds(120))
.chatMemory(persistentChatMemory)
.onToolCallError((toolCall, error) -> {
log.error("工具调用失败", error);
return "工具执行失败,请换一种方式重试";
})
.build();
深度自定义Agent逻辑:
java复制public class CustomCodeDevAgent implements Agent {
private final ChatLanguageModel chatLanguageModel;
private final List<ToolSpecification> tools;
private final int maxSteps;
@Override
public AgentResult run(ChatMemory chatMemory) {
int currentStep = 0;
while (currentStep < maxSteps) {
currentStep++;
chatMemory.add(SystemMessage.from("当前是第"+currentStep+"步执行..."));
ChatResponse response = chatLanguageModel.generate(chatMemory.messages(), tools);
AssistantMessage assistantMessage = response.result().output();
chatMemory.add(assistantMessage);
if (assistantMessage.toolCalls().isEmpty()) {
return AgentResult.finished(assistantMessage.text());
}
for (ToolCall toolCall : assistantMessage.toolCalls()) {
if (!hasPermission(toolCall.name())) {
chatMemory.add(ToolMessage.from("没有权限调用此工具", toolCall.id()));
continue;
}
ToolExecutionResult result = ToolExecutor.execute(tools, toolCall);
chatMemory.add(ToolMessage.from(
result.isError() ? "工具执行失败:"+result.error() : result.output(),
toolCall.id()
));
}
}
return AgentResult.finished("执行步骤超出上限");
}
}
1.3.2 Spring AI的"自定义"本质
Spring AI所谓的"自定义"实际上是要求开发者从零实现所有Agent逻辑:
- 手动维护对话上下文
- 自行实现循环控制
- 处理工具查找与调用
- 管理异常和重试逻辑
- 实现终止条件判断
1.4 组件化与扩展性对比
LangChain4j采用全链路组件化设计:
ChatMemory接口:自定义记忆存储ToolExecutor接口:自定义工具执行逻辑OutputParser接口:自定义输出解析- 所有核心组件均可替换
Spring AI则缺乏这种系统性的组件化设计,开发者需要自行构建这些基础设施。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工作流引擎实战:LangGraph4j在无代码平台中的应用
2.1 工作流核心概念
| 概念 | 说明 | 类比 |
|---|---|---|
| 工作流(Workflow) | 完整的业务流程定义 | 奶茶制作流程单 |
| 节点(Node) | 流程中的单个步骤 | 煮茶、加珍珠 |
| 边(Edge) | 步骤间的顺序关系 | 煮茶后才能加珍珠 |
| 条件边(Conditional Edge) | 根据条件决定下一步 | 无糖就跳过加糖 |
| 上下文(Context/State) | 流程共享的数据存储 | 订单小票 |
2.2 工作流定义示例
2.2.1 定义状态(上下文)
java复制@Data
public class MilkTeaState implements State {
private String teaType; // 茶底类型
private String sugarLevel; // 甜度
private String topping; // 小料
private String stepResult; // 当前步骤结果
private String finalProduct; // 最终产品
}
2.2.2 定义节点
java复制public class MilkTeaNodes {
public MilkTeaState boilTea(MilkTeaState state) {
String result = "已经煮好【"+state.getTeaType()+"】的茶底";
System.out.println(result);
state.setStepResult(result);
return state;
}
public MilkTeaState addTopping(MilkTeaState state) {
String result = "已经添加小料:【"+state.getTopping()+"】";
System.out.println(result);
state.setStepResult(result);
return state;
}
public MilkTeaState addSugar(MilkTeaState state) {
String result = "已经添加甜度:【"+state.getSugarLevel()+"】";
System.out.println(result);
state.setStepResult(result);
return state;
}
public MilkTeaState finish(MilkTeaState state) {
String finalProduct = "一杯【"+state.getTeaType()+"】,甜度:"+
state.getSugarLevel()+",小料:"+state.getTopping()+",制作完成!";
System.out.println(finalProduct);
state.setFinalProduct(finalProduct);
return state;
}
}
2.2.3 构建工作流
java复制public class MilkTeaWorkflow {
public static void main(String[] args) {
MilkTeaNodes nodes = new MilkTeaNodes();
MessagesStateGraph<MilkTeaState> workflowBuilder =
MessagesStateGraph.builder(MilkTeaState.class);
// 添加节点
workflowBuilder.addNode("煮茶底", nodes::boilTea);
workflowBuilder.addNode("加小料", nodes::addTopping);
workflowBuilder.addNode("加糖", nodes::addSugar);
workflowBuilder.addNode("完成制作", nodes::finish);
// 定义流程
workflowBuilder.addEdge("START", "煮茶底");
workflowBuilder.addEdge("煮茶底", "加小料");
workflowBuilder.addEdge("加小料", "加糖");
// 条件边:无糖跳过加糖
workflowBuilder.addConditionalEdge("加小料", state ->
"无糖".equals(state.getSugarLevel()) ? "完成制作" : "加糖"
);
workflowBuilder.addEdge("加糖", "完成制作");
workflowBuilder.addEdge("完成制作", "END");
// 编译并执行
Graph<MilkTeaState> workflow = workflowBuilder.compile();
MilkTeaState initState = new MilkTeaState();
initState.setTeaType("珍珠奶茶");
initState.setSugarLevel("半糖");
initState.setTopping("双份珍珠");
MilkTeaState finalState = workflow.invoke(initState);
System.out.println("最终结果:" + finalState.getFinalProduct());
}
}
2.3 无代码平台中的工作流应用
2.3.1 核心工作流程
- 触发入口:用户输入需求,创建WorkflowContext
- 图片收集:调用ImageCollectorNode收集所需图片
- 提示词增强:PromptEnhancerNode优化用户需求
- 智能路由:RouterNode确定生成策略
- 代码生成:CodeGeneratorNode生成代码
- 代码质检:CodeQualityCheckNode检查代码质量
- 条件分支:
- 质检通过→项目构建
- 质检失败(重试<3次)→重新生成
- 重试超限→终止流程
- 项目构建:ProjectBuilderNode执行构建
- 流程结束:推送结果给前端
2.3.2 工作流控制器
java复制@RestController
@RequestMapping("/workflow")
public class WorkflowSseController {
@PostMapping("/execute")
public WorkflowContext executeWorkflow(@RequestParam String prompt) {
return new CodeGenWorkflow().executeWorkflow(prompt);
}
@GetMapping(value = "/execute-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> executeWorkflowWithFlux(@RequestParam String prompt) {
return new CodeGenWorkflow().executeWorkflowWithFlux(prompt);
}
@GetMapping(value = "/execute-sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter executeWorkflowWithSse(@RequestParam String prompt) {
return new CodeGenWorkflow().executeWorkflowWithSse(prompt);
}
}
2.3.3 工作流定义核心类
java复制@Slf4j
public class CodeGenWorkflow {
public CompiledGraph<MessagesState<String>> createWorkflow() {
try {
return new MessagesStateGraph<String>()
.addNode("image_collector", ImageCollectorNode.create())
.addNode("prompt_enhancer", PromptEnhancerNode.create())
.addNode("router", RouterNode.create())
.addNode("code_generator", CodeGeneratorNode.create())
.addNode("code_quality_check", CodeQualityCheckNode.create())
.addNode("project_builder", ProjectBuilderNode.create())
.addEdge(START, "image_collector")
.addEdge("image_collector", "prompt_enhancer")
.addEdge("prompt_enhancer", "router")
.addEdge("router", "code_generator")
.addEdge("code_generator", "code_quality_check")
.addConditionalEdges("code_quality_check",
edge_async(this::routeAfterQualityCheck),
Map.of(
"build", "project_builder",
"skip_build", END,
"fail", "code_generator"
))
.addEdge("project_builder", END)
.compile();
} catch (GraphStateException e) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "工作流创建失败");
}
}
private String routeAfterQualityCheck(MessagesState<String> state) {
WorkflowContext context = WorkflowContext.getContext(state);
QualityResult qualityResult = context.getQualityResult();
if (qualityResult == null || !qualityResult.getIsValid()) {
log.error("代码质检失败,需要重新生成代码");
return "fail";
}
log.info("代码质检通过,继续后续流程");
return routeBuildOrSkip(state);
}
private String routeBuildOrSkip(MessagesState<String> state) {
WorkflowContext context = WorkflowContext.getContext(state);
CodeGenTypeEnum generationType = context.getGenerationType();
if (generationType == CodeGenTypeEnum.HTML ||
generationType == CodeGenTypeEnum.MULTI_FILE) {
return "skip_build";
}
return "build";
}
}
2.4 工作流执行与监控
2.4.1 同步执行
java复制public WorkflowContext executeWorkflow(String originalPrompt) {
CompiledGraph<MessagesState<String>> workflow = createWorkflow();
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt(originalPrompt)
.currentStep("初始化")
.build();
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图:\n{}", graph.content());
log.info("开始执行代码生成工作流");
WorkflowContext finalContext = null;
int stepCounter = 1;
for (NodeOutput<MessagesState<String>> step : workflow.stream(
Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext))) {
log.info("--- 第 {} 步完成 ---", stepCounter);
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
finalContext = currentContext;
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
log.info("代码生成工作流执行完成!");
return finalContext;
}
2.4.2 流式执行(SSE)
java复制public SseEmitter executeWorkflowWithSse(String originalPrompt) {
SseEmitter emitter = new SseEmitter(30 * 60 * 1000L);
Thread.startVirtualThread(() -> {
try {
CompiledGraph<MessagesState<String>> workflow = createWorkflow();
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt(originalPrompt)
.currentStep("初始化")
.build();
sendSseEvent(emitter, "workflow_start", Map.of(
"message", "开始执行代码生成工作流",
"originalPrompt", originalPrompt
));
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图:\n{}", graph.content());
int stepCounter = 1;
for (NodeOutput<MessagesState<String>> step : workflow.stream(
Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext))) {
log.info("--- 第 {} 步完成 ---", stepCounter);
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
sendSseEvent(emitter, "step_completed", Map.of(
"stepNumber", stepCounter,
"currentStep", currentContext.getCurrentStep()
));
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
sendSseEvent(emitter, "workflow_completed", Map.of(
"message", "代码生成工作流执行完成!"
));
log.info("代码生成工作流执行完成!");
emitter.complete();
} catch (Exception e) {
log.error("工作流执行失败: {}", e.getMessage(), e);
sendSseEvent(emitter, "workflow_error", Map.of(
"error", e.getMessage(),
"message", "工作流执行失败"
));
emitter.completeWithError(e);
}
});
return emitter;
}
3. 框架选择建议与最佳实践
3.1 选择标准
选择LangChain4j当:
- 需要快速构建复杂的多步AI应用
- 需要完整的Agent支持
- 希望减少样板代码
- 需要灵活的自定义能力
选择Spring AI当:
- 只需要基础的模型调用能力
- 已经深度集成Spring生态
- 不需要复杂的Agent逻辑
- 项目规模较小
3.2 性能优化建议
-
并发工作流设计:
- 将可以并行执行的步骤拆分为子任务
- 使用多线程同时执行独立任务
-
子图复用:
- 将通用流程封装为子图
- 多个工作流共享子图实现
-
状态管理:
- 合理设计上下文数据结构
- 避免在状态中存储过大对象
-
错误处理:
- 为每个节点设计完善的错误处理
- 实现合理的重试机制
3.3 调试技巧
-
可视化工作流:
java复制GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID); log.info("工作流图:\n{}", graph.content()); -
步骤日志:
- 在每个节点记录输入输出
- 跟踪上下文状态变化
-
测试策略:
- 单独测试每个节点
- 模拟各种分支条件
- 测试边界情况和异常场景
在实际项目中,我们通过工作流引擎将代码生成流程的复杂度降低了60%,同时提高了系统的可维护性和可扩展性。特别是当需要新增生成步骤或修改流程顺序时,只需简单调整工作流定义,而无需修改核心业务逻辑。
