本文介绍如何使用Spring Boot、DeepSeek大模型和LangGraph4j框架构建一个能实际解决问题的ReAct Agent。文章详细解析了六大工具的实现、增量Checkpoint机制、RAG混合检索、Text2SQL数据库查询以及SSE流式输出等技术要点,并通过实际案例对比了纯LLM ChatBot和ReAct Agent在O2O业务场景中的表现差异。该项目代码已开源,适合想要学习AI Agent开发的程序员和小白参考。

背景

你一定遇到过这种情况——

接了一个 AI 智能客服需求,老板说"要能查订单、能排队、能退款",你心想这不就是接个 LLM 的事吗。结果用户第一句话问"帮我查一下昨天在火锅店的消费",LLM 开始胡编订单号。

你发现事情没那么简单:LLM 不会查数据库、不知道你的业务规则、更没法真正帮用户"办事"。

于是你开始搜"Java AI Agent 实战",出来的要么是 Python 的 LangChain 教程,要么是抽象的 Agent 理论——思维链、多智能体协作、世界模型…看完只觉得更焦虑了。

我只是想写个 Java 项目,让 LLM 能调几个工具帮用户查个订单、排个队,有那么难吗?

这就是本文要解决的问题:用 Spring Boot + LangGraph4j 构建一个真正能"办事"的 ReAct Agent,打通从意图理解到工具执行到结果生成的完整闭环。

不需要啃 LangGraph 论文,不需要学 Python。你只需要三个东西:

  • Spring Boot — Java 人都熟
  • DeepSeek — 便宜好用的国产大模型
  • LangGraph4j — Java 版的状态机编排框架

项目展示

项目展示


什么是"能办事的 Agent"?

在聊代码之前,先对齐一个概念。

很多人以为接个 LLM 就算有 AI 了。但实际上,一个只会聊天的 ChatBot 和真正能办事的 Agent 之间,差了三层能力:

普通 ChatBot:  用户问 → LLM 答(全靠记忆,不知道你的数据库里有什么)
       ↓ 升级
带 RAG 的 ChatBot:用户问 → 检索文档 → LLM 引用文档回答(能"看书"了)
       ↓ 升级
ReAct Agent:   用户问 → 理解意图 → 制定计划 → 调用工具 → 观察结果 → 判断是否够了 → 回答
                (能"做事"了——查数据库、调接口、执行操作)

ReAct Agent

ReAct Agent

所谓的"能办事",就是 LLM 像一个实习生,你给它一本操作手册和几个工具,它能自己决定什么时候查手册、什么时候用哪个工具、看完结果后还需要什么信息。

下面我以 O2O 生活服务平台为例,一步步拆解怎么给 Java 项目装上这个能力。


架构总览

整个 AI 部分由三层组成,和业务代码松耦合:

┌──────────────────────────────────────────────────┐
│               前端 (Vue.js + Element UI)           │
│      右下角 AI 悬浮窗 / 知识库管理后台              │
└──────────────────────┬───────────────────────────┘
                       │ HTTP + SSE
┌──────────────────────▼───────────────────────────┐
│              Spring Boot 后端                      │
│  ┌─────────────────┐  ┌────────────────────────┐ │
│  │  ReAct 状态机    │  │  RAG 引擎              │ │
│  │  六节点编排      │  │  摄入 + 混合检索       │ │
│  │  Context→Planner │  │  切片→向量化→Qdrant   │ │
│  └─────────────────┘  └────────────────────────┘ │
│                       ┌────────────────────────┐ │
│                       │  工具集 (6 Tools)      │ │
│                       │  GeoSearch / Text2SQL   │ │
│                       │  订单查询 / 排队取号      │ │
│                       └────────────────────────┘ │
└──────┬───────────────────────┬───────────────────┘
       │                       │
┌──────▼──────┐  ┌─────────────▼──────────────┐
│   DeepSeek  │  │  Qdrant + Ollama/PostgreSQL│
│   LLM API   │  │  向量库 / Checkpoint       │
└─────────────┘  └────────────────────────────┘

别被"六节点状态机"吓到,核心代码其实很精简。下面我按 Agent 的五个关键能力来拆解——每个能力都是一块独立的拼图。


能力一:六大工具 — 让 Agent 能"动手"

这是 Agent 区别于 ChatBot 的核心:它不只是说话,它能做事。

工具怎么注册?

我们通过 LangGraph4j + LangChain4j 的桥接层,@Tool 注解的方法自动被提取为 JSON Schema,注册到 Agent 的工具箱里:

// ToolRegistry.java — 6 个工具,一行注册
LC4jToolMapBuilder<?> builder = new LC4jToolMapBuilder<>()
    .toolsFromObject(
        knowledgeRetrievalTool,  // RAG 知识检索
        queueTicketTool,         // 排队取号
        text2SqlTool,            // 动态 SQL 查询
        geoSearchTool,           // 地理位置搜索
        historySearchTool        // 对话历史回溯
    );

每个工具做什么?

工具 触发场景 背后做了什么
geoSearchTool “附近有什么火锅店” Redis GEO 查询 → 返回店铺列表和距离
queueTicketTool “帮我排个号” 查询排队状态 → 确认 → 取号
text2SqlTool “帮我看看xx店有没有优惠券” LLM 生成 SQL → 安全校验 → 执行
knowledgeRetrievalTool “怎么退款” RAG 检索帮助文档
historySearchTool “之前聊过的那家店” PostgreSQL ILIKE 关键词回溯历史对话

关键设计决策:LLM 自己决定何时调哪个工具。你不需要写 if-else 路由规则—— Agent 的 Planner 节点会根据用户意图自动制定计划并选择工具。这正是 ReAct 范式的价值:不是预设路径,而是推理出路径。


能力二:增量 Checkpoint — 给 Agent 装上"记忆"

问题

ReAct Agent 每轮对话要经过多个节点推理,如果每次都从头跑,一轮对话能跑十几秒。更致命的是,用户说了"帮我在海底捞排个号",Agent 需要确认"您确认在海底捞(朝阳店)排号吗?"——这时候 Agent 的工作状态(推理到哪了、已获取了哪些信息)必须被保存下来,等用户回复后继续。

传统的全量序列化方案每次存整个状态,图初始化耗时 14 秒,体感上是"点一下 → 喝口水 → 还没反应"。

解决方案:DeltaPostgresSaver

核心思路一句话:不存全量,只存变化。

完整状态(messages[1..100] + 所有标量字段)
        ↓ 对比上次快照
只存 delta:
  ├── messages 差集(新增的 5 条消息)
  ├── 变化的标量字段(如 currentPlan 更新)
  └── 每隔 N 轮存一个全量快照做"锚点"
// DeltaPostgresSaver.java — 增量序列化核心逻辑(简化)
public void put(Checkpoint checkpoint) {
// 1. 加载最近一次全量快照
Checkpoint baseline = loadLatestSnapshot(threadId);

// 2. 计算 delta:messages 差集 + 变化的标量字段
    Map<String, Object> delta = computeDelta(baseline, checkpoint);

// 3. 只序列化 delta 写入 PostgreSQL
//    周期性(如每 10 轮)存一份全量快照
if (shouldCreateSnapshot()) {
        saveFullSnapshot(checkpoint);
    } else {
        saveDelta(delta);
    }
}

效果对比:

指标 全量存储 DeltaPostgresSaver
图初始化耗时 ~14s < 1s
每次 checkpoint 写入量 全量序列化 仅变更部分
跨实例恢复 ✅ 支持 checkpoint resume

💡 这个思路和数据库的 WAL(Write-Ahead Log)如出一辙——找到最近的 checkpoint 锚点,回放之后的增量操作,重建完整状态。做后端的同学一看就懂。

顺带解决了"人机协同"

基于 Checkpoint 机制,Agent 确认中断变得很自然:

Agent 需要用户确认 → 序列化当前 ReAct 上下文 → 挂起
用户回复 "确认"   → updateState 注入决策数据 → 恢复状态机执行
                    (绕过 Planner 重跑,避免上下文丢失)

能力三:RAG 混合检索 — 让 Agent 能"看书"

很多 RAG 系统的通病是搜不准。用户搜"怎么退款",返回的是"退款政策的历史沿革"——因为只做了关键词匹配。

这个项目的检索链路是双路召回 + 重排序:

Step 1:结构化切分文档

普通的 RecursiveCharacterTextSplitter 不认识中文——它会把"第一章"、"一、"、"1.1"这些中文章节标记当作普通文本一刀切。

我们实现了一个 AdaptiveSplitter,多层级降级切分:

// AdaptiveSplitter.java — 中文文档感知的切片策略(简化)
public List<Chunk> split(String markdown) {
// 1. 保护特殊块:代码块 + 表格(占位符替换)
    Map<String, String> protectedBlocks = protectCodeAndTable(markdown);

// 2. 多层级切分(逐级降级)
//    H2 标题 → H3 标题 → 中文标记("**一、" "第一章") → 段落 → 句子 → 字符**

    List<Section> sections = splitByHeading(text, "##");       // Level 1: H2
if (sections.size() <= 1) 
        sections = splitByChineseMarker(text);                  // Level 2: **一、第一章**

if (sections.size() <= 1)
        sections = splitByParagraph(text);                      // Level 3: 段落

// 3. 还原被保护的特殊块
return restoreBlocks(chunks, protectedBlocks);
}

💡 为什么不用现成的 LangChain 切分器?因为 RecursiveCharacterTextSplitter 不认识文档结构。一篇 FAQ 被切在代码块中间,检索出来的片段就废了。结构化文档必须用结构感知切分。

Step 2:双路召回 + RRF 融合

单独的语义检索容易漏掉精确关键词匹配,单独的 BM25 又不理解语义。所以两路一起跑,用 RRF 融合排序:

// RetrievalService.java — 混合检索核心(简化)
public List<SearchResult> hybridSearch(String query, int topK) {
// 1. LLM 改写查询(扩展同义词、纠正口语化)
String rewrittenQuery = llmQueryRewriter.rewrite(query);

// 2. 双路并行召回
    List<SearchResult> semanticResults = qdrant.search(embed(rewrittenQuery), topK * 2);
    List<SearchResult> keywordResults = bm25Index.search(rewrittenQuery, topK * 2);

// 3. RRF 融合排序 → LLM 精排
    List<SearchResult> fused = rrfFusion(semanticResults, keywordResults);
return llmReranker.rerank(query, fused);  // 返回 Top-K
}

写入管线串联

一条 API 调用,文档就变成了知识库:

# POST /kb/ingest
curl -X POST http://localhost:8080/kb/ingest /
  -H "Content-Type: application/json" /
  -d '{"source": "faq", "title": "帮助中心", "content": "# 退款政策/n/n## 申请条件/n..."}'

背后发生了什么:

Markdown 文档 → AdaptiveSplitter 切片 → bge-m3 向量化 → Qdrant 存储

能力四:Text2SQL — 让 Agent 能"查库"

这是让 Agent 从"客服"升级为"管家"的关键。用户问"帮我看看xx店有没有优惠券"——知识库没有答案,必须查数据库。

但让 LLM 直接写 SQL 执行是危险的。我们做了多层防护:

// Text2SqlTool.java — SQL 生成 + 安全校验链(简化)
public String executeQuery(String question, String userId) {
// 1. LLM 选表:从 Redis 缓存的 Schema 中匹配最相关的表
String tableName = selectTable(question, getCachedSchemas());

// 2. LLM 生成 SQL
String sql = generateSql(question, getTableSchema(tableName));

// 3. 安全校验链
    validate(sql);  // 仅允许 SELECT / 禁止 DROP ALTER TRUNCATE / 强制 LIMIT

// 4. COUNT(*) 预检 → 行数太多时提示缩小范围
long totalRows = countCheck(sql);
if (totalRows > MAX_RESULT_ROWS) {
return "查询结果约" + totalRows + "条,建议缩小查询范围(如指定日期区间)";
    }

// 5. 执行 + 格式化
return formatResult(execute(sql));
}

还有一个容易被忽视的细节:表名缓存。用户说"xx店",数据库里是 tb_shop_type;用户说"优惠券",数据库里是 tb_voucher。我们在 Redis 里维护了表名和表的comment,能够在用户查询时快速加载到所需要的表。采用渐进式披露的方式,当用户具体查询某张表时,才暴露表的全部字段名称。


能力五:SSE 流式 + 上下文管理 — 用户体验兜底

打字机效果

没人愿意盯着空白页面等 10 秒。LLM 每生成几个 token 就推到前端,用户看到的是逐字出现的"打字机效果":

// ReactStreamController.java — SSE 流式输出(简化)
@PostMapping(value = "/chat/react/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)


public Flux<ServerSentEvent<String>> chat(@RequestBody ChatRequest request) {
return Flux.create(sink -> {
        graph.compile().stream(request.getThreadId(), userMessage)
            .forEachRemaining(event -> {
                sink.next(ServerSentEvent.<String>builder()
                    .event(event.type())
                    .data(event.content())
                    .build());
            });
        sink.complete();
    });
}

⚠️ Nginx 代理 SSE 必须关 buffering:proxy_buffering off; proxy_cache off;——这是最常见的坑,忘了改就看不到打字机效果。

滑动窗口

对话超过 3 轮后全塞进 Prompt 会爆 Token。我们用了"近全量 + 远摘要"的策略:


最近 3 轮:完整保留(用户刚说的内容最重要)
超出部分:
  ├── LLM 摘要压缩(提取关键信息,丢掉废话)
  └── 硬上限裁剪(Token 数超阈值时强制截断)

用户提到之前聊过的内容时,通过 PostgreSQL ILIKE 关键词检索回捞历史片段——不是全量回放,是按需检索。


两个关键坑,帮你填了

坑一:Spring Boot 2.7 + LangGraph4j 的版本联调

LangGraph4j 的版本更新节奏很快,和 LangChain4j 的版本之间有耦合。项目目前稳定在:

组件 版本 选型理由
LangGraph4j 1.8.19 稳定的图编排 API,StateGraph 语法成熟
LangChain4j 1.16.2 @Tool + OpenAiStreamingChatModel 完美支持 DeepSeek
Spring Boot 2.7.18 生态最成熟,大部分公司仍在使用

版本组合建议:不要追新,这三个版本已在大批量对话中验证过稳定性。

坑二:Checkpoint 旧数据导致加载变慢

如果从旧版本升级,全量存储的旧数据会让 DeltaPostgresSaver 的优化失效。

症状:升级后图初始化反而更慢。

解决方案:


-- 清空旧的全量 checkpoint 数据,让 Delta 机制重新工作
DELETE FROM lg4jcheckpoint;

应用重启后,DeltaPostgresSaver 会重新建立增量存储——初始化耗时立刻降到 1 秒以内。


效果实测

用同一个 O2O 业务场景,对比三种模式的实际表现:

对比维度 纯 LLM ChatBot + RAG ReAct Agent(本项目)
“帮我找附近的火锅店” 编造店铺名 引用文档里的店铺列表(可能过时) 调用 GeoSearchTool 查 Redis,返回实时数据
“我昨天消费多少” 编造金额 无法回答(文档里没有) 调用 Text2SqlTool 查数据库,返回真实金额
“帮我排个号” 假装排了 回复"请联系人工" 调用 QueueTicketTool 实际取号,返回排号单
推理过程可见 ✅ 输出每个节点信息(规划→执行→观察→判断→回答)

总结

核心要点:

  • ReAct Agent ≠ ChatBot 加 Prompt。关键区别是:Agent 有自己的推理循环(规划→执行→观察→判断),能自主决定调用哪个工具、什么时候需要更多信息
  • 六个节点里,Observer 和 Judge 是两个最重要的角色——前者负责纠偏(空结果重试、错误分类),后者负责控制循环(信息够了就答、不够就重规划)。没有这两个节点,Agent 要么停不下来,要么停下来时说的是错的
  • 增量 Checkpoint 不是性能优化,是必备基础设施——没有它,人机协同中断恢复、跨实例状态迁移都无从谈起
  • RAG 检索的效果 80% 取决于切片和召回策略——结构化文档用结构感知切分,代码块和表格用占位符保护
  • Text2SQL 的安全防护比 SQL 生成本身重要得多——强制 LIMIT、COUNT 预检、仅允许 SELECT 缺一不可

适用场景:

  • O2O / 电商智能客服(查店、排队、下单、售后)

  • 企业内部数据查询助手(订单、库存、报表——Text2SQL 直接查库)

  • 任何"既有文档库需要 RAG,又有业务系统需要 Tool Calling"的场景

    如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线科技企业深耕十二载,见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事,早已在效率与薪资上形成代际优势,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我们整理出这套 AI 大模型突围资料包

  • ✅ 从零到一的 AI 学习路径图
  • ✅ 大模型调优实战手册(附医疗/金融等大厂真实案例)
  • ✅ 百度/阿里专家闭门录播课
  • ✅ 大模型当下最新行业报告
  • ✅ 真实大厂面试真题
  • ✅ 2026 最新岗位需求图谱

所有资料 ⚡️ ,朋友们如果有需要 《AI大模型入门+进阶学习资源包》下方扫码获取~
在这里插入图片描述

① 全套AI大模型应用开发视频教程

(包含提示工程、RAG、LangChain、Agent、模型微调与部署、DeepSeek等技术点)
在这里插入图片描述

② 大模型系统化学习路线

作为学习AI大模型技术的新手,方向至关重要。 正确的学习路线可以为你节省时间,少走弯路;方向不对,努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划,带你从零基础入门到精通!
在这里插入图片描述

③ 大模型学习书籍&文档

学习AI大模型离不开书籍文档,我精选了一系列大模型技术的书籍和学习文档(电子版),它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。
在这里插入图片描述

④ AI大模型最新行业报告

2025最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。
在这里插入图片描述

⑤ 大模型项目实战&配套源码

学以致用,在项目实战中检验和巩固你所学到的知识,同时为你找工作就业和职业发展打下坚实的基础。
在这里插入图片描述

⑥ 大模型大厂面试真题

面试不仅是技术的较量,更需要充分的准备。在你已经掌握了大模型技术之后,就需要开始准备面试,我精心整理了一份大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余

图片

以上资料如何领取?

在这里插入图片描述

为什么大家都在学大模型?

最近科技巨头英特尔宣布裁员2万人,传统岗位不断缩减,但AI相关技术岗疯狂扩招,有3-5年经验,大厂薪资就能给到50K*20薪!

图片

不出1年,“有AI项目经验”将成为投递简历的门槛。

风口之下,与其像“温水煮青蛙”一样坐等被行业淘汰,不如先人一步,掌握AI大模型原理+应用技术+项目实操经验,“顺风”翻盘!
在这里插入图片描述
在这里插入图片描述

这些资料真的有用吗?

这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理,现任上海殷泊信息科技CEO,其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证,服务航天科工、国家电网等1000+企业,以第一作者在IEEE Transactions发表论文50+篇,获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。

资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的技术人员,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。
在这里插入图片描述
在这里插入图片描述

以上全套大模型资料如何领取?

在这里插入图片描述



Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐