系列第 17 篇我们刻意不用 LangChain4j AiServices 做主编排。本篇开始 LangGraph4j 演进:先在 Java 侧引入 状态图,但不破坏现有 trace 与 SPI 边界。


写在前面

当客服场景从「单轮问答」走向「查订单 → 判断 → 建工单 → 人工审核」时,线性七步管道 的表达力不够。LangGraph4j 提供了第三条路:图编排 + 显式节点 + checkpoint/interrupt,且与 Spring、LangChain4j 同栈。

你将学到什么

  • 为什么选 LangGraph4j 而不是 Python LangGraph sidecar
  • ai-graph 模块结构与 ChatGraphState 设计
  • CustomerServiceGraph 如何把 SPI 包成节点
  • OrchestrationProperties.engine 双引擎开关
  • GraphOrchestrationEquivalenceTest 怎么做回归

1. 背景:七步管道遇到了什么天花板

原编排入口 AiChatService是固定顺序:

load memory → route → rag? → tool? → build prompt → LLM → save memory

优点:trace 清晰、每步可测、与前端 Agent 面板一一对应。

局限:

局限 业务影响
单次工具 无法「查订单再建工单」
路由与主 LLM 分离 固定 2 次模型调用
无条件分支 售前/售后/投诉无法走不同子流程
无 interrupt 敏感操作无法等人点头

LangGraph4j 补的是 编排表达力;LangChain4j 仍只负责 Model LayerLlmClient / StreamingLlmClient)。

2. 为什么用 LangGraph4j(Java 版)

方案 优点 缺点
继续手写 if-else 管道 简单 复杂流程不可维护
LangChain4j AiServices 开发快 trace 黑盒
Python LangGraph 微服务 官方生态全 双运行时、契约维护成本高
LangGraph4j 与 Spring 同栈、支持 interrupt/checkpoint 需学习 StateGraph API

本项目为 Java 17 + Spring Boot 3.3 多模块骨架,选用 LangGraph4j 1.8.x(BOM 锁定于 ai-parent)。

3. Phase 1 架构:只换编排层

engine=linear

engine=graph

CustomerChatFacade

AiChatService

LinearChatPipeline

CustomerServiceGraph

Memory / RAG / Tools / Prompt / LLM

不变的部分

  • HTTP:ChatReactiveController(8081)
  • 门面:CustomerChatFacade(限流)
  • SPI:ChatMemoryKnowledgeRetrieverToolExecutorPromptComposerLlmClient
  • 对外契约:ChatTurnTraceResultChatTraceResponse → 前端 Agent 面板

新增模块ai-graph(图定义、状态、节点、配置)

4. ChatGraphState:图状态与 trace 对齐

ChatGraphState 继承 LangGraph4j AgentState,用 Channel 描述字段合并策略:

  • 普通字段:Channels.base((old, neu) -> neu) 覆盖写
  • executedNodesChannels.appender 追加(用于图轨迹)

核心字段与 trace 映射:

状态键 trace 字段
routerDecision agentDecision
ragUsed / ragContext RAG 面板
toolResult / toolCalls Tool 面板
prompt Prompt 面板
answer 助手回复
executedNodes Graph 轨迹(Phase 4 前端展示)

节点原则:薄包装——只读写 state,调用 SPI Bean,不写业务 if-else 泥潭。逻辑集中在 GraphNodes

5. CustomerServiceGraph:Phase 1 等价图

CustomerServiceGraph 在 Phase 1 等价于线性管道(react-enabled: false 时走 tool_execute_linear):

load_memory

route

rag_retrieve

tool_execute_linear

build_prompt

llm_generate

save_memory

构建方式(节选):

var graph = new StateGraph<>(ChatGraphState.SCHEMA, ChatGraphState::new)
    .addNode("load_memory", node_async(nodes::loadMemory))
    .addNode("route", node_async(nodes::route))
    // ...
    .addEdge(START, "load_memory")
    .addEdge("save_memory", END);
CompiledGraph<ChatGraphState> compiled = graph.compile(compileConfig());

invoke(sessionId, message, context) 返回最终 ChatGraphState,由 AiChatService 映射为 ChatTurnTraceResult

6. 双引擎开关:linear 与 graph 并存

OrchestrationProperties 新增:

aics:
  orchestration:
    engine: graph   # linear | graph

LinearChatPipeline 保留原七步实现,用于:

  1. 回滚:生产事故时一行配置切回 linear
  2. 对比测试:同一 fixture 下断言 graph 与 linear 等价

AiChatService 委托逻辑:

if (orchestrationProperties.getEngine() == OrchestrationEngine.GRAPH) {
    return fromGraphState(customerServiceGraph.invoke(...));
}
return linearChatPipeline.chatWithTrace(...);

7. 动手验证

7.1 编译与等价性测试

cd ai-customer-service
mvn -pl ai-service -am test -DskipTests=false -Dmaven.test.skip=false

关注:

GraphOrchestrationEquivalenceTest ... OK
AiServiceEvolutionTest ............ OK

GraphOrchestrationEquivalenceTest 断言:answerragUsedtoolsUsedtoolResultprompt 在 linear/graph 下一致。

7.2 运行时切换引擎

ai-reactive-chat/src/main/resources/application.yml

aics:
  orchestration:
    engine: graph   # 改为 linear 可即时回退

启动聊天服务:

mvn -pl ai-reactive-chat spring-boot:run
curl -s -X POST http://localhost:8081/api/chat \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"lg-phase1","message":"我的订单123为什么还没发货?"}' | jq .

开发环境 expose-prompt-trace: true 时,响应仍含 agentDecisionragContexttoolResultprompt——前端四个 Agent 面板无需改动。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐