springboot+langchain4j 实战 Day15——打造一个“生产“级 Agent 服务:单个 Agent 同时持有多个 Tool,LLM 自主判断调用哪个
Day 15 — 单 Agent 多 Tool MySQL 数据源 Redis 缓存 AOP 追踪 SSE 流式输出一、目标融合 Day 1-14 全部能力打造一个生产级 Agent 服务单个 Agent 同时持有多个 ToolLLM 自主判断调用哪个数据落 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 硅基流动) ↓ ToolTraceAspectAOP 环绕通知记录每次 Tool 调用 ↓ RedisCacheableTTL 10min与 Day 13/14 的关键区别维度Day 13/14Day 15路由方式Router LLM 先分类再分发到子 Agent无需 Router— 单 Agent 挂多个 ToolLLM 自己判断数据来源Mock 硬编码MySQL 真实数据库Druid 连接池 MyBatis-Plus缓存层无RedisCacheable缓存 Tool 查询结果调用追踪无仅 log.infoAOP 切面记录入参 / 耗时 / 结果前端无SSE 打字机效果HTML 页面static/index.html流式对话Day 12 独立项目集成在统一项目中双端点三、技术栈组件版本用途Spring Boot2.7.18应用框架Tomcat9.0.83内嵌Web 容器Java17运行语言LangChain4j0.36.2Agent 框架AiServicesToolDeepSeek-V3via 硅基流动LLM 模型MyBatis-Plus3.5.3.1ORM Lambda 查询Druid1.2.20数据库连接池含监控页/druidMySQL8.0Docker 3307业务数据库Redis7Docker 6379Tool 结果缓存Lombok1.18.30减少样板代码Jackson2.13.xSpring 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 模型 BeanChat 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 IGNORE4 订单 1 退款 5 知识库 └── static/ └── index.html # 前端聊天页面SSE 打字机效果五、核心代码5.1 双模型注入ChatModelConfigBean(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→ 走streamingChatLanguageModel5.2 单 Agent 多 ToolUnifiedAgentServicePostConstructpublicvoidinit(){agentAiServices.builder(UnifiedAgent.class).chatLanguageModel(chatModel).streamingChatLanguageModel(streamingChatModel).tools(orderTool,refundTool,knowledgeBaseTool)// 一次注入 3 个.chatMemory(MessageWindowChatMemory.withMaxMessages(10)).build();}SystemMessage引导 LLM 行为订单/物流 → 优先用 Tool 查退款 → 主动创建工单技术问题 → 先搜知识库再回答不要凭自身知识猜测5.3 MySQL 数据源application.ymlspring:datasource:type:com.alibaba.druid.pool.DruidDataSourceurl:jdbc:mysql://localhost:3307/ai_logs?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/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: falseMyBatis-Plus LambdaWrapper 生成的 SQL 会触发 Druid WallFilter 误判关闭后不影响安全SQL 由框架生成无拼接注入风险。5.4 Entity 注解规范DataTableName(t_order)publicclassOrder{JsonIgnore// 不暴露给前端内部主键TableId(typeIdType.AUTO)// 数据库自增privateLongid;privateStringorderId;// 业务编号privateStringproduct;privateStringstatus;privateStringlogistics;privateBigDecimalamount;JsonFormat(patternyyyy-MM-dd HH:mm:ss,timezoneAsia/Shanghai)privateLocalDateTimecreatedAt;// 格式化输出避免序列化为数组 [2025,6,1,...]}5.5 AOP 链路追踪ToolTraceAspectAround(annotation(dev.langchain4j.agent.tool.Tool))publicObjecttrace(ProceedingJoinPointpjp)throwsThrowable{Stringmethodpjp.getSignature().toShortString();Stringargs/* 拼接参数 */;longstartSystem.currentTimeMillis();log.info([ToolTrace] ▶ {} | args({}),method,args);Objectresultpjp.proceed();longelapsedSystem.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 缓存CacheConfigCacheableTool(Query order by orderId)Cacheable(valueorder,key#orderId)// 同一订单号 10 分钟内走缓存publicStringqueryOrder(StringorderId){...}Key 序列化StringRedisSerializerValue 序列化GenericJackson2JsonRedisSerializerTTL10 分钟spring.cache.redis.time-to-live: 6000005.7 SSE 流式推送GetMapping(value/stream/chat,producestext/event-stream;charsetUTF-8)publicSseEmitterstream(RequestParamStringmessage){SseEmitteremitternewSseEmitter(TimeUnit.MINUTES.toMillis(2));TokenStreamtokenStreamunifiedAgentService.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(ConsumerString)— 每个 token 回调onComplete(ConsumerResponseAiMessage)— 流结束onError(ConsumerThrowable)— 异常六、双端点 APIGET /agent/chat— JSON 同步curlhttp://localhost:8088/agent/chat?message查订单20250615# → {code:200,message:success,data:订单 20250615\n商品: ...}GET /stream/chat— SSE 流式curl-Nhttp://localhost:8088/stream/chat?messagehello# → 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 SSEDay 15 是项目集大成的里程碑 —— 不再需要 Router 分流LLM 自己看懂意图并选择 Tool数据落库、结果缓存、调用可追踪同时支持 JSON 和 SSE 两种输出模式。