1. 引言
随着大模型应用的深入,单一的 Prompt 调用已经难以满足复杂业务场景的需求。无论是多步骤推理、工具调用编排,还是状态持久化与人工审批,都需要一套成熟的工作流引擎来支撑。LangGraph 作为 LangChain 生态中强大的图编排框架,能够以图的方式定义智能体的状态流转;而 Spring AI 则是 Java 生态对接大模型的事实标准。本文将详细介绍如何将 Spring AI 与 LangGraph 对接,构建企业级智能体工作流。
2. 为什么需要 LangGraph
2.1 从单次调用到工作流
传统的大模型应用通常是「请求-响应」模式:用户输入,模型输出。但在真实业务中,往往需要:
- 多步骤推理:先分析问题,再检索资料,最后生成答案;
- 工具调用:模型需要决定是否调用外部 API、数据库或搜索服务;
- 状态管理:跨多轮对话保持上下文与中间结果;
- 人工介入:在关键节点插入人工审批或确认。
LangGraph 正是为解决这些问题而生的图编排框架。
2.2 LangGraph 的核心概念
LangGraph 的核心思想是把智能体流程抽象为一张有向图:
- 节点(Node):一个处理步骤,可以是调用 LLM、执行工具、或运行任意函数;
- 边(Edge):节点之间的流转关系,支持条件分支;
- 状态(State):贯穿整个图执行的共享数据,节点之间通过状态传递信息;
- 检查点(Checkpoint):保存执行快照,支持断点续跑与人工介入。
3. Spring AI 与 LangGraph 的对接思路
3.1 技术选型考量
Spring AI 是 Java 生态的 AI 框架,而 LangGraph 官方提供 Python 与 JavaScript 版本。在 Java 项目中对接 LangGraph,通常有以下几种方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 独立部署 LangGraph 服务(Python) | 功能完整、生态成熟 | 需要维护两套技术栈 |
| 使用 LangGraph 的 REST API | 解耦清晰、易于扩展 | 需要自行封装接口 |
| 纯 Java 实现图编排 | 技术栈统一 | 需要自行实现状态管理与持久化 |
本文推荐第一种方案:将 LangGraph 工作流封装为独立的 Python 微服务,Spring AI 通过 HTTP 接口调用,实现技术栈解耦与能力复用。
3.2 整体架构
#mermaid-svg-V5eo27utrtpmr7qh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V5eo27utrtpmr7qh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V5eo27utrtpmr7qh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V5eo27utrtpmr7qh .error-icon{fill:#552222;}#mermaid-svg-V5eo27utrtpmr7qh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V5eo27utrtpmr7qh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V5eo27utrtpmr7qh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V5eo27utrtpmr7qh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V5eo27utrtpmr7qh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V5eo27utrtpmr7qh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V5eo27utrtpmr7qh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V5eo27utrtpmr7qh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V5eo27utrtpmr7qh .marker.cross{stroke:#333333;}#mermaid-svg-V5eo27utrtpmr7qh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V5eo27utrtpmr7qh p{margin:0;}#mermaid-svg-V5eo27utrtpmr7qh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V5eo27utrtpmr7qh .cluster-label text{fill:#333;}#mermaid-svg-V5eo27utrtpmr7qh .cluster-label span{color:#333;}#mermaid-svg-V5eo27utrtpmr7qh .cluster-label span p{background-color:transparent;}#mermaid-svg-V5eo27utrtpmr7qh .label text,#mermaid-svg-V5eo27utrtpmr7qh span{fill:#333;color:#333;}#mermaid-svg-V5eo27utrtpmr7qh .node rect,#mermaid-svg-V5eo27utrtpmr7qh .node circle,#mermaid-svg-V5eo27utrtpmr7qh .node ellipse,#mermaid-svg-V5eo27utrtpmr7qh .node polygon,#mermaid-svg-V5eo27utrtpmr7qh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V5eo27utrtpmr7qh .rough-node .label text,#mermaid-svg-V5eo27utrtpmr7qh .node .label text,#mermaid-svg-V5eo27utrtpmr7qh .image-shape .label,#mermaid-svg-V5eo27utrtpmr7qh .icon-shape .label{text-anchor:middle;}#mermaid-svg-V5eo27utrtpmr7qh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V5eo27utrtpmr7qh .rough-node .label,#mermaid-svg-V5eo27utrtpmr7qh .node .label,#mermaid-svg-V5eo27utrtpmr7qh .image-shape .label,#mermaid-svg-V5eo27utrtpmr7qh .icon-shape .label{text-align:center;}#mermaid-svg-V5eo27utrtpmr7qh .node.clickable{cursor:pointer;}#mermaid-svg-V5eo27utrtpmr7qh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V5eo27utrtpmr7qh .arrowheadPath{fill:#333333;}#mermaid-svg-V5eo27utrtpmr7qh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V5eo27utrtpmr7qh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V5eo27utrtpmr7qh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V5eo27utrtpmr7qh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V5eo27utrtpmr7qh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V5eo27utrtpmr7qh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V5eo27utrtpmr7qh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V5eo27utrtpmr7qh .cluster text{fill:#333;}#mermaid-svg-V5eo27utrtpmr7qh .cluster span{color:#333;}#mermaid-svg-V5eo27utrtpmr7qh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V5eo27utrtpmr7qh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V5eo27utrtpmr7qh rect.text{fill:none;stroke-width:0;}#mermaid-svg-V5eo27utrtpmr7qh .icon-shape,#mermaid-svg-V5eo27utrtpmr7qh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V5eo27utrtpmr7qh .icon-shape p,#mermaid-svg-V5eo27utrtpmr7qh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V5eo27utrtpmr7qh .icon-shape .label rect,#mermaid-svg-V5eo27utrtpmr7qh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V5eo27utrtpmr7qh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V5eo27utrtpmr7qh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V5eo27utrtpmr7qh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 调用
Spring AI 应用
LangGraph 服务
LangGraph 图执行引擎
LLM 调用
工具执行
状态持久化
OpenAI / 通义 / 本地模型
4. 实战:构建一个带工具调用的智能体
4.1 定义 LangGraph 工作流(Python 侧)
首先,在 Python 侧定义一个包含「意图识别 → 工具调用 → 结果生成」的图工作流:
python
from langgraph.graph import StateGraph, END
from typing import TypedDict, List
class AgentState(TypedDict):
messages: List[dict]
tool_result: str
def analyze_intent(state: AgentState):
# 调用 LLM 分析用户意图
return {"messages": state["messages"]}
def call_tool(state: AgentState):
# 根据意图执行工具
return {"tool_result": "工具执行结果"}
def generate_answer(state: AgentState):
# 结合工具结果生成最终答案
return {"messages": state["messages"]}
# 构建图
graph = StateGraph(AgentState)
graph.add_node("analyze", analyze_intent)
graph.add_node("tool", call_tool)
graph.add_node("generate", generate_answer)
graph.set_entry_point("analyze")
graph.add_edge("analyze", "tool")
graph.add_edge("tool", "generate")
graph.add_edge("generate", END)
app = graph.compile()
4.2 暴露 REST 接口
使用 FastAPI 将编译后的图封装为 HTTP 服务:
python
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class InvokeRequest(BaseModel):
messages: List[dict]
thread_id: str
@app.post("/invoke")
def invoke(req: InvokeRequest):
config = {"configurable": {"thread_id": req.thread_id}}
result = app.invoke({"messages": req.messages}, config=config)
return {"result": result["messages"]}
4.3 Spring AI 侧对接
在 Spring Boot 项目中,通过 RestClient 调用 LangGraph 服务:
java
@Service
public class LangGraphClient {
private final RestClient restClient;
public LangGraphClient(RestClient.Builder builder) {
this.restClient = builder.baseUrl("http://localhost:8000").build();
}
public String invoke(List<Map<String, String>> messages, String threadId) {
Map<String, Object> request = Map.of(
"messages", messages,
"thread_id", threadId
);
Map<String, Object> response = restClient.post()
.uri("/invoke")
.body(request)
.retrieve()
.body(new ParameterizedTypeReference<Map<String, Object>>() {});
return (String) response.get("result");
}
}
4.4 在 Controller 中使用
java
@RestController
@RequestMapping("/agent")
public class AgentController {
private final LangGraphClient langGraphClient;
public AgentController(LangGraphClient langGraphClient) {
this.langGraphClient = langGraphClient;
}
@PostMapping("/chat")
public String chat(@RequestBody ChatRequest request) {
List<Map<String, String>> messages = List.of(
Map.of("role", "user", "content", request.message())
);
return langGraphClient.invoke(messages, request.threadId());
}
}
5. 状态管理与持久化
5.1 为什么需要持久化
在多轮对话场景中,用户可能随时中断对话,稍后再继续。LangGraph 的检查点机制允许我们保存每次执行的完整状态,实现:
- 断点续跑:从上次中断的节点继续执行;
- 人工审批:在关键节点暂停,等待人工确认后继续;
- 多租户隔离:不同用户/会话使用独立的线程 ID。
5.2 配置持久化存储
LangGraph 支持多种持久化后端,生产环境推荐使用 PostgreSQL:
python
from langgraph.checkpoint.postgres import PostgresSaver
# 初始化 PostgreSQL 检查点
checkpointer = PostgresSaver.from_conn_string(
"postgresql://user:password@localhost:5432/langgraph"
)
app = graph.compile(checkpointer=checkpointer)
5.3 Spring AI 侧管理会话
Spring AI 侧只需维护 thread_id 与用户/会话的映射关系:
java
@Service
public class SessionService {
private final Map<String, String> sessionThreadMap = new ConcurrentHashMap<>();
public String getOrCreateThreadId(String sessionId) {
return sessionThreadMap.computeIfAbsent(sessionId,
k -> UUID.randomUUID().toString());
}
}
6. 人工审批与中断机制
6.1 在图中插入人工节点
python
def human_review(state: AgentState):
# 返回 interrupt 信号,等待人工确认
return {"messages": state["messages"]}
graph.add_node("human_review", human_review)
graph.add_edge("tool", "human_review")
graph.add_edge("human_review", "generate")
6.2 从 Spring AI 侧恢复执行
java
// 第一次调用,触发人工审批
Map<String, Object> response = langGraphClient.invoke(messages, threadId);
// 响应中包含 interrupt 状态,等待人工确认
// 人工确认后,携带确认结果继续执行
Map<String, Object> resumeRequest = Map.of(
"messages", messages,
"thread_id", threadId,
"resume", Map.of("approved", true)
);
7. 生产环境最佳实践
7.1 超时与重试
java
@Bean
public RestClient langGraphRestClient(RestClient.Builder builder) {
return builder
.baseUrl("http://langgraph-service:8000")
.requestFactory(new JdkClientHttpRequestFactory(
HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build()
))
.build();
}
7.2 可观测性
建议在 LangGraph 服务侧接入 OpenTelemetry,将图执行的每个节点耗时、状态变化上报到监控系统;Spring AI 侧则记录每次调用的 thread_id 与耗时,便于问题追踪。
7.3 安全与鉴权
- LangGraph 服务应部署在内网,仅允许 Spring AI 应用访问;
- 通过 API Key 或 mTLS 进行服务间认证;
- 对
thread_id做权限校验,防止越权访问他人会话。
8. 总结
本文介绍了 Spring AI 对接 LangGraph 的完整方案:通过将 LangGraph 工作流封装为独立的 Python 微服务,Spring AI 应用通过 HTTP 接口调用,实现了技术栈解耦与能力复用。同时涵盖了状态持久化、人工审批、生产环境最佳实践等关键内容。
这种架构既保留了 LangGraph 强大的图编排能力,又让 Java 团队能够无缝接入,是构建企业级智能体工作流的推荐方案。后续可以进一步探索流式输出、多智能体协作、与 Spring AI 的 ChatClient 深度集成等进阶话题。