当前位置: 首页 > news >正文

springboot+langchain4j 实战 Day15——打造一个“生产“级 Agent 服务:单个 Agent 同时持有多个 Tool,LLM 自主判断调用哪个

Day 15 — 单 Agent 多 Tool + MySQL 数据源 + Redis 缓存 + AOP 追踪 + SSE 流式输出

一、目标

融合 Day 1-14 全部能力,打造一个生产级 Agent 服务:单个 Agent 同时持有多个 Tool,LLM 自主判断调用哪个;数据落 MySQL、Tool 结果走 Redis 缓存、每次调用被 AOP 追踪。

二、架构

浏览器 (static/index.html) ├── GET /agent/chat?message=... → JSON 同步响应 └── GET /stream/chat?message=... → SSE 逐 token 流式推送 ↓ UnifiedAgentController ↓ UnifiedAgentService(单 Agent) ├── OrderTool ← MyBatis-Plus → MySQL t_order ├── RefundTool ← MyBatis-Plus → MySQL t_refund ├── KnowledgeBaseTool ← MyBatis-Plus → MySQL t_knowledge └── LLM (DeepSeek-V3 via 硅基流动) ↓ ToolTraceAspect(AOP 环绕通知,记录每次 @Tool 调用) ↓ Redis(@Cacheable,TTL 10min)

与 Day 13/14 的关键区别

维度Day 13/14Day 15
路由方式Router LLM 先分类,再分发到子 Agent无需 Router— 单 Agent 挂多个 Tool,LLM 自己判断
数据来源Mock 硬编码MySQL 真实数据库(Druid 连接池 + MyBatis-Plus)
缓存层Redis@Cacheable缓存 Tool 查询结果
调用追踪无(仅 log.info)AOP 切面记录入参 / 耗时 / 结果
前端SSE 打字机效果HTML 页面(static/index.html
流式对话Day 12 独立项目集成在统一项目中(双端点)

三、技术栈

组件版本用途
Spring Boot2.7.18应用框架
Tomcat9.0.83(内嵌)Web 容器
Java17运行语言
LangChain4j0.36.2Agent 框架(AiServices+@Tool
DeepSeek-V3via 硅基流动LLM 模型
MyBatis-Plus3.5.3.1ORM + Lambda 查询
Druid1.2.20数据库连接池(含监控页/druid
MySQL8.0(Docker 3307)业务数据库
Redis7(Docker 6379)Tool 结果缓存
Lombok1.18.30减少样板代码
Jackson2.13.x(Spring Boot 内置)JSON 序列化

四、项目结构

day15/ ├── pom.xml ├── README.md └── src/main/ ├── java/com/day15/demo/ │ ├── Day15Application.java # 启动类 │ ├── aop/ │ │ └── ToolTraceAspect.java # AOP 切面:环绕所有 @Tool 方法 │ ├── config/ │ │ ├── CacheConfig.java # Redis 缓存配置(TTL 10min) │ │ └── ChatModelConfig.java # LLM 模型 Bean(Chat + Streaming 双实例) │ ├── controller/ │ │ └── UnifiedAgentController.java # 双端点:/agent/chat + /stream/chat │ ├── dto/ │ │ └── Result.java # 统一响应体 {code, message, data} │ ├── entity/ │ │ ├── Order.java # t_order 映射(@TableId + @JsonIgnore + @JsonFormat) │ │ ├── Refund.java # t_refund 映射 │ │ └── Knowledge.java # t_knowledge 映射 │ ├── mapper/ │ │ ├── OrderMapper.java # MyBatis-Plus BaseMapper │ │ ├── RefundMapper.java │ │ └── KnowledgeMapper.java │ ├── service/ │ │ └── UnifiedAgentService.java # 单 Agent 注入 3 个 Tool │ └── tool/ │ ├── OrderTool.java # 订单查询 / 列表(@Cacheable + MySQL) │ ├── RefundTool.java # 退款创建 / 政策(写入 MySQL) │ ├── KnowledgeBaseTool.java # 知识库检索 / 目录(@Cacheable + MySQL) │ └── WeatherTool.java # 天气(备用,暂未挂载) └── resources/ ├── application.yml # MySQL + Druid + Redis 配置 ├── schema.sql # DDL 建表(LONGTEXT 兼容 MySQL) ├── data.sql # 种子数据(INSERT IGNORE,4 订单 + 1 退款 + 5 知识库) └── static/ └── index.html # 前端聊天页面(SSE 打字机效果)

五、核心代码

5.1 双模型注入(ChatModelConfig

@Bean("openAiChatModel")publicOpenAiChatModelopenAiChatModel(){// 普通对话用returnOpenAiChatModel.builder().apiKey(apiKey).baseUrl(baseUrl).modelName(modelName).temperature(0.3).timeout(Duration.ofSeconds(60)).maxRetries(2).build();}@Bean("openAiStreamingChatModel")publicOpenAiStreamingChatModelopenAiStreamingChatModel(){// SSE 流式用returnOpenAiStreamingChatModel.builder().apiKey(apiKey).baseUrl(baseUrl).modelName(modelName).temperature(0.3).timeout(Duration.ofSeconds(60)).build();}

两种模式用同一个AiServices.Builder构建,AiServices自动根据接口方法返回值分发:

  • 返回String→ 走chatLanguageModel
  • 返回TokenStream→ 走streamingChatLanguageModel

5.2 单 Agent 多 Tool(UnifiedAgentService

@PostConstructpublicvoidinit(){agent=AiServices.builder(UnifiedAgent.class).chatLanguageModel(chatModel).streamingChatLanguageModel(streamingChatModel).tools(orderTool,refundTool,knowledgeBaseTool)// 一次注入 3 个.chatMemory(MessageWindowChatMemory.withMaxMessages(10)).build();}

@SystemMessage引导 LLM 行为:

  • 订单/物流 → 优先用 Tool 查
  • 退款 → 主动创建工单
  • 技术问题 → 先搜知识库再回答
  • 不要凭自身知识猜测

5.3 MySQL 数据源(application.yml

spring:datasource:type:com.alibaba.druid.pool.DruidDataSourceurl:jdbc:mysql://localhost:3307/ai_logs?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername:rootpassword:root123driver-class-name:com.mysql.cj.jdbc.Driverdruid:initial-size:5min-idle:5max-active:20max-wait:60000filter:stat:enabled:trueslow-sql-millis:2000log-slow-sql:truewall:enabled:false# MyBatis-Plus Lambda 查询不支持 wall 拦截

为什么wall: false:MyBatis-Plus LambdaWrapper 生成的 SQL 会触发 Druid WallFilter 误判,关闭后不影响安全(SQL 由框架生成,无拼接注入风险)。

5.4 Entity 注解规范

@Data@TableName("t_order")publicclassOrder{@JsonIgnore// 不暴露给前端(内部主键)@TableId(type=IdType.AUTO)// 数据库自增privateLongid;privateStringorderId;// 业务编号privateStringproduct;privateStringstatus;privateStringlogistics;privateBigDecimalamount;@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreatedAt;// 格式化输出,避免序列化为数组 [2025,6,1,...]}

5.5 AOP 链路追踪(ToolTraceAspect

@Around("@annotation(dev.langchain4j.agent.tool.Tool)")publicObjecttrace(ProceedingJoinPointpjp)throwsThrowable{Stringmethod=pjp.getSignature().toShortString();Stringargs=/* 拼接参数 */;longstart=System.currentTimeMillis();log.info("[ToolTrace] ▶ {} | args=({})",method,args);Objectresult=pjp.proceed();longelapsed=System.currentTimeMillis()-start;log.info("[ToolTrace] ✔ {} | {}ms | result={}",method,elapsed,truncate(result,120));returnresult;}

输出示例:

[ToolTrace] ▶ OrderTool.queryOrder(..) | args=(20250615) [OrderTool] 查询订单(DB): 20250615 ==> Preparing: SELECT ... FROM t_order WHERE order_id = ? ==> Parameters: 20250615(String) <== Total: 1 [ToolTrace] ✔ OrderTool.queryOrder(..) | 45ms | result=订单 20250615 ...

5.6 Redis 缓存(CacheConfig+@Cacheable

@Tool("Query order by orderId")@Cacheable(value="order",key="#orderId")// 同一订单号 10 分钟内走缓存publicStringqueryOrder(StringorderId){...}
  • Key 序列化:StringRedisSerializer
  • Value 序列化:GenericJackson2JsonRedisSerializer
  • TTL:10 分钟(spring.cache.redis.time-to-live: 600000

5.7 SSE 流式推送

@GetMapping(value="/stream/chat",produces="text/event-stream;charset=UTF-8")publicSseEmitterstream(@RequestParamStringmessage){SseEmitteremitter=newSseEmitter(TimeUnit.MINUTES.toMillis(2));TokenStreamtokenStream=unifiedAgentService.stream(message);Executors.newSingleThreadExecutor().execute(()->{tokenStream.onNext(token->emitter.send(SseEmitter.event().data(token))).onComplete(resp->emitter.complete()).onError(emitter::completeWithError).start();});returnemitter;}

关键 API 匹配(LangChain4j 0.36.2):

  • onNext(Consumer<String>)— 每个 token 回调
  • onComplete(Consumer<Response<AiMessage>>)— 流结束
  • onError(Consumer<Throwable>)— 异常

六、双端点 API

GET /agent/chat— JSON 同步

curl"http://localhost:8088/agent/chat?message=查订单20250615"# → {"code":200,"message":"success","data":"订单 20250615\n商品: ..."}

GET /stream/chat— SSE 流式

curl-N"http://localhost:8088/stream/chat?message=hello"# → data:你好# → data:呀# → data:!

前端页面

浏览器打开http://localhost:8088/index.html

  • 左侧:Agent 信息 + 快捷提问
  • 右侧:对话区(SSE 打字机逐字渲染)
  • 支持:回车发送、Tool 调用标记、超时提示

七、Druid 监控

http://localhost:8088/druid/→ 用户名admin/ 密码admin123

八、启动方式

前置条件

# MySQL(已运行)dockerps|grepai-mysql# → 0.0.0.0:3307->3306/tcp# Redis(已运行)dockerps|grepai-redis# → 0.0.0.0:6379->6379/tcp

启动

cdday15 mvn clean compile spring-boot:run-DskipTests

输出关键日志:

Tomcat started on port(s): 8088 (http) with context path '' Day15 UnifiedAgent 初始化完成: OrderTool + RefundTool + KnowledgeBaseTool (MySQL数据源 + Redis缓存 + AOP追踪) Started Day15Application in 3.3 seconds

九、演进路线

Day 1-2 基础环境 + LangChain4j Demo Day 3 RAG (InMemoryEmbeddingStore) 英文全称:Retrieval-Augmented Generation(检索增强生成)。意思就是:让 AI 在回答之前,先去「查资料」,再基于查到的资料来回答。就像考试时允许你翻书,而不是只靠脑子记忆答题。 Day 4 PGVector 向量库 Day 5 Redis 聊天记忆 Day 6-7 单元测试 + 统一响应体 Day 8-9 AOP 日志 + 降级Sringboot 2.7.18 Day 10 工具类完善,并且 MySQL + PGVector + Redis 一键docker-compose部署依赖的开发环境 Day 11 Agent 联网搜索,调用天气api Day 12 网页 + SSE 流式对话 Day 13 多 Agent 协作 (Router) Day 14 子 Agent 工具注入 Day 15 ← 融合全部能力:单 Agent 多 Tool + MySQL + Redis + AOP + SSE

Day 15 是项目集大成的里程碑 —— 不再需要 Router 分流,LLM 自己看懂意图并选择 Tool,数据落库、结果缓存、调用可追踪,同时支持 JSON 和 SSE 两种输出模式。

http://www.jsqmd.com/news/1079062/

相关文章:

  • KMS智能激活脚本:5分钟彻底解决Windows和Office激活难题
  • Selenium自动化测试:从元素定位到健壮交互的完整指南
  • Mac NTFS读写终极解决方案:Free-NTFS-for-Mac免费完整指南
  • FPGA实战(32):多通道ADC数据打包模块设计
  • 云手机好用吗?直击三大痛点,普通人也能看懂的入坑指南CSDN
  • Web安全十大核心漏洞原理与防御实战指南
  • GAT注意力权重可视化实战:从公式到热力图
  • 低代码开发你会用吗?
  • 傅里叶级数收敛性反例:二进尖峰块与拉库纳序列构造解析
  • 035、LLVM Dialect:与LLVM IR的桥梁
  • 分享股票方面的API
  • 2026 电商客服外包公司哪家好?5 家头部服务商深度盘点,企业选型必备
  • 大气层整合包系统:终极Nintendo Switch定制固件完全指南
  • 微盟星启GEO竞争分析:洞察行业格局抢占AI搜索先机
  • [特殊字符] Spring MVC 四大参数注解笔记
  • 关于威尼斯系统检测注单尚未同步提不了怎么解决
  • 【Three.js 实战】结合 MediaPipe 实现 3D 粒子手势互动特效 (附原理解析)--手势控制粒子项目,附源码
  • 希迪迈向“重载具身智能”,AI改变物理世界有了新注解
  • OpenClaw+Kimi本地智能体工作流:多模态动作闭环实战指南
  • Claude 怎么用?网页端、API、第三方工具有什么区别
  • 数据库统计信息备份与还原技术实践
  • 063、Zephyr RTOS内核基础:内存管理之内存池
  • 2026年GEO优化系统源码怎么选?这份实操指南请收好
  • 从零开始打造你的《最终幻想14》专属外观:FFXIV TexTools完整使用指南
  • 2022年5月AI工程落地关键突破:LoRA、FlashAttention与QLoRA实战解析
  • COUNT(*)到底能不能走索引?覆盖索引的3个误区与4种优化方案
  • SAP-ABAP:SAP Process Orchestration 7.50 入门简介:PO核心概念、架构定位与版本演进
  • 2026年深圳AI定制服务商观察:案例复用能力为何越来越重要?
  • 深入拆解Agent核心:系统提示词与用户提示词的本质区别、工程落地与全场景避坑指南
  • 行业语言大模型体验榜2026:谁真正懂你的语音需求