第15篇-实战:构建一个多功能AI助手Agent
📝 作者说:前两篇,我们学会了给 AI 装工具(第13篇)和让 AI 自己决策(第14篇)。但都是小打小闹——查个天气、查个订单,一两行代码的事。这篇来个大的:构建一个真正的多功能私人助手,能管日程、能发邮件、能搜知识库,还能在工具出错时优雅降级。代码可以直接跑。
一、需求:一个真正有用的私人助手
1.1 为什么之前的实战都不够”真”?
用户:北京天气怎么样?AI:晴天,25°C。用户:帮我安排明天下午3点开会AI:已安排。
“帮我查一下明天下午有没有会议,如果有冲突就改到后天,然后给参会人发邮件通知变更,顺便帮我查一下上次季度汇报的PPT在哪个文件里。”
-
📅 日历查询 + 修改 -
📧 邮件发送 -
🔍 知识库检索(找文件)
🤔 思考 1:你觉得一个 Agent 能同时管日历、邮件、搜索吗?会不会”手忙脚乱”?
1.2 本篇目标
|
能力 |
工具 |
说明 |
|
📅 日程管理 |
CalendarTool |
查询、添加、删除日程 |
|
📧 邮件发送 |
EmailTool |
发送邮件通知 |
|
🔍 知识检索 |
KnowledgeTool |
从本地文档中搜索信息 |
|
🛡️ 异常回退 |
框架层 |
工具出错时优雅降级,不死掉 |
最终效果:
用户:帮我查明天下午有没有会议,如果有的话给张三发邮件确认AI: → 自动查日历 → 发现有"项目评审会"→ 自动调用邮件工具 → 给张三发确认邮件→ 回复:明天下午3点有项目评审会,已给张三发送确认邮件。用户:上次的销售数据报告在哪?AI: → 自动检索知识库 → 找到相关文档→ 回复:在文档库的 /reports/2026-Q1-销售报告.pdf,第三页有完整数据。
二、多个 Tool 的协调与注册
2.1 按领域分组:别把所有工具塞一个类
🤔 思考 2:如果把日历、邮件、搜索的工具全部写在一个类里,会怎样?
tools/├── CalendarTool.java ← 日程管理相关├── EmailTool.java ← 邮件相关└── KnowledgeTool.java ← 知识检索相关
2.2 日历工具组
public class CalendarTool {// 模拟日程存储(实际项目用数据库)private final Map<String, List<CalendarEvent>> events = new ConcurrentHashMap<>();@Tool("查询指定日期的所有日程安排,返回时间、标题和参与者")public String queryEvents(@P("日期,格式 yyyy-MM-dd,例如:2026-04-25") String date) {List<CalendarEvent> list = events.get(date);if (list == null || list.isEmpty()) {return date + " 没有日程安排";}StringBuilder sb = new StringBuilder();sb.append(date).append(" 共 ").append(list.size()).append(" 个日程:\n");for (CalendarEvent e : list) {sb.append(String.format(" %s %s(参与者:%s)\n",e.time, e.title, String.join("、", e.participants)));}return sb.toString();}@Tool("添加一条新的日程安排")public String addEvent(@P("日程标题,如'项目评审会'") String title,@P("日期,格式 yyyy-MM-dd") String date,@P("时间,格式 HH:mm") String time,@P("参与者列表,用逗号分隔,如'张三,李四'") String participants) {CalendarEvent event = new CalendarEvent(title, time,Arrays.asList(participants.split("[,,]")));events.computeIfAbsent(date, k -> new ArrayList<>()).add(event);return "日程已添加:" + date + " " + time + " " + title;}@Tool("删除指定日期的某个日程")public String removeEvent(@P("日期,格式 yyyy-MM-dd") String date,@P("要删除的日程标题") String title) {List<CalendarEvent> list = events.get(date);if (list == null) return date + " 没有日程";boolean removed = list.removeIf(e -> e.title.equals(title));return removed ? "已删除:" + title : "未找到日程:" + title;}// 初始化一些测试数据public CalendarTool() {events.put("2026-04-25", List.of(new CalendarEvent("项目评审会", "15:00", List.of("张三", "李四")),new CalendarEvent("1:1 周会", "10:00", List.of("王五"))));}record CalendarEvent(String title, String time, List<String> participants) {}}
2.3 邮件工具组
public class EmailTool {@Tool("发送一封邮件给指定收件人")public String sendEmail(@P("收件人姓名") String recipient,@P("邮件主题") String subject,@P("邮件正文内容") String body) {// 实际项目中:调用邮件服务 API(JavaMail / SendGrid / 企业微信)// 这里用控制台输出模拟System.out.println("📧 发送邮件 ====");System.out.println(" 收件人:" + recipient);System.out.println(" 主题:" + subject);System.out.println(" 正文:" + body);System.out.println("================");return "邮件已发送给 " + recipient + ",主题:" + subject;}@Tool("查询某个联系人的邮箱地址")public String lookupEmail(@P("联系人姓名") String name) {// 模拟通讯录Map<String, String> contacts = Map.of("张三", "zhangsan@company.com","李四", "lisi@company.com","王五", "wangwu@company.com");String email = contacts.get(name);return email != null ? email : "未找到 " + name + " 的邮箱";}}
2.4 知识检索工具组(RAG 集成)
public class KnowledgeTool {private final EmbeddingStore<TextSegment> embeddingStore;private final EmbeddingModel embeddingModel;public KnowledgeTool(EmbeddingStore<TextSegment> store, EmbeddingModel model) {this.embeddingStore = store;this.embeddingModel = model;}@Tool("从知识库中搜索与查询相关的文档片段,返回最匹配的内容")public String searchKnowledge(@P("搜索关键词或问题,如'销售报告'、'Q1数据'") String query) {try {// 生成查询向量Embedding queryEmbedding = embeddingModel.embed(query).content();// 搜索最相关的 3 个文档片段List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding).maxResults(3).minScore(0.5).build()).matches();if (matches.isEmpty()) {return "知识库中未找到与'" + query + "'相关的内容";}StringBuilder sb = new StringBuilder("找到以下相关内容:\n");for (int i = 0; i < matches.size(); i++) {EmbeddingMatch<TextSegment> match = matches.get(i);sb.append(String.format("\n[片段%d](相关度:%.0f%%)\n%s\n",i + 1, match.score() * 100, match.embedded().text()));}return sb.toString();} catch (Exception e) {return "知识检索出错:" + e.getMessage();}}}
2.5 注册到 AiService
🤔 思考 3:三个工具组,各自是一个对象。注册时怎么让 AI Service 知道所有工具?
// 构建 AI Service:把三个工具组的实例全部注册SuperAssistant assistant = AiServices.builder(SuperAssistant.class).chatModel(chatModel).chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(30)).tools(new CalendarTool(), // 日历工具组new EmailTool(), // 邮件工具组knowledgeTool // 知识检索工具组).build();
LLM 看到的工具列表:
工具1:queryEvents(日期) → 查询日程工具2:addEvent(标题, 日期, 时间, 参与者) → 添加日程工具3:removeEvent(日期, 标题) → 删除日程工具4:sendEmail(收件人, 主题, 正文) → 发邮件工具5:lookupEmail(姓名) → 查邮箱工具6:searchKnowledge(查询) → 搜索知识库
三、Agent 的 Planning 能力:让 AI 自己规划多步骤任务
3.1 单步 vs 多步:AI 能自己”想”吗?
“帮我查明天有没有会议,如果有的话给张三发邮件确认”
第1轮:用户提问→ LLM 思考:需要先查日历→ 调用 queryEvents("2026-04-25")→ 结果:"15:00 项目评审会(张三, 李四)"第2轮:LLM 分析结果→ LLM 思考:有会议,需要给张三发邮件→ 先查张三的邮箱:lookupEmail("张三")→ 结果:"zhangsan@company.com"第3轮:LLM 分析结果→ LLM 思考:有邮箱了,可以发邮件→ 调用 sendEmail("张三", "明天会议确认", "请确认...")→ 结果:"邮件已发送"第4轮:LLM 判断任务完成→ 输出最终回答
🤔 思考 4:AI 怎么知道”有会议”就要”发邮件”?是谁教它的?
3.2 设计高质量的 SystemMessage
@SystemMessage("""你是一个高效的私人助手,名叫"小助"。你同时管理日历、邮件和知识库。## 工具使用规则:1. 日程查询:用户问到"有没有会议"、"明天有什么安排"时,先查日历2. 邮件发送:- 发邮件前,先用 lookupEmail 查收件人的邮箱地址- 不要编造邮箱地址,只使用 lookupEmail 返回的地址- 邮件内容要简洁专业3. 知识检索:用户问到公司文档、报告、数据时,搜索知识库4. 组合操作:用户说"如果...就..."时,先查条件,再根据结果决定下一步## 终止条件:- 当你已经完成了用户要求的所有操作,输出总结性回复- 不要重复调用已经成功执行的工具## 出错处理:- 如果工具返回错误,向用户说明情况并建议替代方案- 不要因为一个工具失败就放弃整个任务""")interface SuperAssistant {String chat(@MemoryId String userId, @UserMessage String message);}
💡 经验之谈:SystemMessage 里最重要的是终止条件和出错处理。没有终止条件,AI 可能无限循环;没有出错处理,一个工具挂了整个 Agent 就瘫痪。
四、对话上下文的 Tool 调用追踪
4.1 为什么需要追踪?
❌ 用户问题 → AI 回答错误是工具选错了?参数传错了?还是 AI 理解错了用户意图?→ 没有追踪 = 盲人摸象
4.2 用 ChatModelListener 记录调用链
ChatModelListener listener = new ChatModelListener() {private final AtomicInteger round = new AtomicInteger(0);@Overridepublic void onRequest(ChatModelRequestContext context) {int r = round.incrementAndGet();int msgCount = context.chatRequest().messages().size();System.out.printf("\n🔷 [第%d轮] 发送给 LLM,消息数:%d%n", r, msgCount);}@Overridepublic void onResponse(ChatModelResponseContext context) {AiMessage ai = context.chatResponse().aiMessage();if (ai.hasToolExecutionRequests()) {for (ToolExecutionRequest req : ai.toolExecutionRequests()) {System.out.printf("🔧 调用工具:%s(%s)%n", req.name(), req.arguments());}} else {System.out.println("✅ 最终回答:" + ai.text());}}@Overridepublic void onError(ChatModelErrorContext context) {System.err.println("❌ 错误:" + context.error().getMessage());}};
4.3 追踪输出示例
用户:明天有没有会议?有的话给张三发邮件确认🔷 [第1轮] 发送给 LLM,消息数:2🔧 调用工具:queryEvents({"date":"2026-04-25"})🔷 [第2轮] 发送给 LLM,消息数:4🔧 调用工具:lookupEmail({"name":"张三"})🔷 [第3轮] 发送给 LLM,消息数:6🔧 调用工具:sendEmail({"recipient":"张三","subject":"明天会议确认","body":"..."})🔷 [第4轮] 发送给 LLM,消息数:8✅ 最终回答:明天下午3点有项目评审会,已给张三(zhangsan@company.com)发送确认邮件。
🤔 思考 5:通过追踪日志,你能看出 AI 的”思考链路”。如果第2轮调的不是 lookupEmail 而是直接调 sendEmail,说明 SystemMessage 里”先查邮箱再发邮件”的规则没生效,需要优化提示词。

五、异常处理与回退机制设计
5.1 工具会出错,Agent 不能死
🤔 思考 6:如果邮件服务宕机了,Agent 应该怎么办?
|
方案 |
体验 |
|
❌ 直接崩溃 |
用户看到500错误 |
|
❌ 返回 null |
AI 不知道出错了,继续编造结果 |
|
✅ 返回错误描述 + 建议 |
AI 告知用户邮件发不了,建议替代方案 |
5.2 工具内部异常处理
@Tool("发送一封邮件给指定收件人")public String sendEmail(@P("收件人姓名") String recipient,@P("邮件主题") String subject,@P("邮件正文内容") String body) {try {// 调用邮件服务emailService.send(recipient, subject, body);return "邮件已成功发送给 " + recipient;} catch (TimeoutException e) {// 超时 → 降级return "⚠️ 邮件服务响应超时,邮件未能发送。" +"建议:您可以稍后重试,或通过企业微信直接联系 " + recipient;} catch (Exception e) {// 其他异常return "⚠️ 邮件发送失败:" + e.getMessage() +"。建议:请检查收件人姓名是否正确,或联系管理员。";}}
5.3 AI 收到错误信息后的行为
第1轮:用户说"给张三发邮件"第2轮:AI 调用 lookupEmail("张三") → 成功第3轮:AI 调用 sendEmail("张三", ...) → 返回 "⚠️ 邮件服务超时"第4轮:AI 看到错误信息,自动判断:→ 输出:"抱歉,邮件服务目前响应超时,未能发送给张三。您可以通过企业微信直接联系张三,或者稍后让我重试。"
💡 核心原则:工具方法永远不要抛异常,永远返回 String 描述。让 AI 自己决定怎么告诉用户。
5.4 多级降级策略
工具正常 → 直接返回结果 → AI 使用结果回答工具超时 → 返回超时+建议 → AI 告知用户稍后重试工具彻底不可用 → 返回不可用信息 → AI 建议替代方案所有工具都不可用 → AI 只剩对话能力 → 降级为纯聊天模式
六、RAG + Tool 混合 Agent
6.1 为什么需要混合?
LangChain4j 还提供了一种更优雅的方式:RetrievalAugmentor,自动把检索结果注入到每次对话的消息中。
6.2 两种模式对比
模式 A:RAG 作为 Tool(本章做法)用户提问 → AI 判断是否需要检索 → 调用 searchKnowledge() → 获取结果 → 回答✅ AI 自主决定是否检索✅ 只在需要时消耗 Token❌ 多一轮工具调用,响应稍慢模式 B:RetrievalAugmentor 自动注入用户提问 → 框架自动检索 → 把相关内容塞到消息里 → AI 直接回答✅ 响应更快(少一轮调用)✅ AI 总是能看到相关上下文❌ 每次都检索,即使不需要❌ 消耗更多 Token
🤔 思考 7:哪种模式更适合”私人助手”场景?
6.3 混合集成代码
// 1. 创建 EmbeddingStore 和 EmbeddingModelEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();// 2. 索引一些测试文档TextSegment doc1 = TextSegment.from("2026年Q1销售报告:总销售额580万元,同比增长15%。" +"其中线上渠道占320万,线下渠道占260万。报告文件位于 /reports/2026-Q1-销售报告.pdf");TextSegment doc2 = TextSegment.from("项目评审流程:每次评审需提前2个工作日提交材料," +"评审委员会由3名技术专家和2名业务专家组成。");EmbeddingStoreIngestor.builder().embeddingModel(embeddingModel).embeddingStore(embeddingStore).build().ingest(List.of(doc1, doc2));// 3. 创建 RetrievalAugmentor(自动注入模式)ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel).maxResults(3).minScore(0.5).build();RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder().contentRetriever(contentRetriever).build();// 4. 同时保留 KnowledgeTool(按需调用模式)KnowledgeTool knowledgeTool = new KnowledgeTool(embeddingStore, embeddingModel);// 5. 构建 Agent:Tool + RetrievalAugmentor 双管齐下SuperAssistant assistant = AiServices.builder(SuperAssistant.class).chatModel(chatModel).chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(30)).tools(new CalendarTool(), new EmailTool(), knowledgeTool).contentRetriever(contentRetriever) // ← 自动注入 RAG.build();
用户:上次的销售数据怎么样?→ RetrievalAugmentor 自动检索到 Q1 销售报告片段→ 注入到消息上下文中→ AI 直接基于注入的内容回答(不需要额外调工具)用户:帮我找一下去年Q4的客户满意度调查报告→ 自动注入的内容可能不包含这个→ AI 判断需要更精确搜索 → 调用 searchKnowledge("Q4客户满意度")→ 返回更精准的结果
七、【完整实战】SuperAssistant 完整实现
7.1 完整主程序
import dev.langchain4j.data.embedding.Embedding;import dev.langchain4j.data.segment.TextSegment;import dev.langchain4j.memory.chat.MessageWindowChatMemory;import dev.langchain4j.model.chat.ChatModel;import dev.langchain4j.model.chat.listener.ChatModelErrorContext;import dev.langchain4j.model.chat.listener.ChatModelListener;import dev.langchain4j.model.chat.listener.ChatModelRequestContext;import dev.langchain4j.model.chat.listener.ChatModelResponseContext;import dev.langchain4j.model.embedding.EmbeddingModel;import dev.langchain4j.model.openai.OpenAiChatModel;import dev.langchain4j.model.openai.OpenAiEmbeddingModel;import dev.langchain4j.service.AiServices;import dev.langchain4j.service.MemoryId;import dev.langchain4j.service.SystemMessage;import dev.langchain4j.service.UserMessage;import dev.langchain4j.store.embedding.EmbeddingMatch;import dev.langchain4j.store.embedding.EmbeddingSearchRequest;import dev.langchain4j.store.embedding.EmbeddingStore;import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;public class SuperAssistantDemo {// ==================== Agent 接口定义 ====================@SystemMessage("""你是一个高效的私人助手,名叫"小助"。你同时管理日历、邮件和知识库。## 工具使用规则:1. 日程查询:用户问到"有没有会议"、"明天有什么安排"时,先查日历2. 邮件发送:- 发邮件前,先用 lookupEmail 查收件人的邮箱地址- 不要编造邮箱地址,只使用 lookupEmail 返回的地址- 邮件内容要简洁专业3. 知识检索:用户问到公司文档、报告、数据时,搜索知识库4. 组合操作:用户说"如果...就..."时,先查条件,再根据结果决定下一步## 终止条件:- 当你已经完成了用户要求的所有操作,输出总结性回复- 不要重复调用已经成功执行的工具## 出错处理:- 如果工具返回错误,向用户说明情况并建议替代方案- 不要因为一个工具失败就放弃整个任务""")interface SuperAssistant {String chat(@MemoryId String userId, @UserMessage String message);}// ==================== 启动入口 ====================public static void main(String[] args) {// 1. 创建 ChatModelChatModel chatModel = OpenAiChatModel.builder().apiKey(System.getenv("API_KEY")).baseUrl("https://api.minimax.chat/v1").modelName("MiniMax-M2.5").listeners(List.of(new TraceListener())).build();// 2. 创建 Embedding 模型和向量库(通过 API 调用,无需本地 ONNX)EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder().apiKey(System.getenv("API_KEY")).baseUrl("https://open.bigmodel.cn/api/paas/v4").modelName("embedding-3").build();EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();// 3. 索引测试文档indexSampleDocuments(embeddingModel, embeddingStore);// 4. 构建 AgentSuperAssistant assistant = AiServices.builder(SuperAssistant.class).chatModel(chatModel).chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(30)).tools(new CalendarTool(),new EmailTool(),new KnowledgeTool(embeddingStore, embeddingModel)).build();// 5. 测试对话System.out.println("===== 测试1:查日历 + 发邮件(组合任务)=====");String r1 = assistant.chat("user_001","帮我查一下2026-04-25有没有会议,如果有的话给张三发邮件确认");System.out.println("AI:" + r1);System.out.println("\n===== 测试2:知识检索 =====");String r2 = assistant.chat("user_001","上次Q1的销售数据怎么样?报告文件在哪?");System.out.println("AI:" + r2);System.out.println("\n===== 测试3:添加日程 + 通知 =====");String r3 = assistant.chat("user_001","帮我下周一上午10点安排一个产品评审会,参与者是张三和李四," +"然后发邮件通知他们");System.out.println("AI:" + r3);System.out.println("\n===== 测试4:综合查询 =====");String r4 = assistant.chat("user_001","帮我看下2026-04-25的安排,如果有空的话帮我安排下午2点技术分享");System.out.println("AI:" + r4);}private static void indexSampleDocuments(EmbeddingModel model,EmbeddingStore<TextSegment> store) {List<TextSegment> docs = List.of(TextSegment.from("2026年Q1销售报告:总销售额580万元,同比增长15%。" +"线上渠道320万,线下渠道260万。文件位于 /reports/2026-Q1-销售报告.pdf"),TextSegment.from("项目评审流程:每次评审需提前2个工作日提交材料," +"评审委员会由3名技术专家和2名业务专家组成。"));for (TextSegment doc : docs) {Embedding embedding = model.embed(doc).content();store.add(embedding, doc);}}// ==================== 调用追踪 Listener ====================static class TraceListener implements ChatModelListener {private final AtomicInteger round = new AtomicInteger(0);@Overridepublic void onRequest(ChatModelRequestContext context) {System.out.printf(" 🔷 [第%d轮] → LLM%n", round.incrementAndGet());}@Overridepublic void onResponse(ChatModelResponseContext context) {var ai = context.chatResponse().aiMessage();if (ai.hasToolExecutionRequests()) {for (var req : ai.toolExecutionRequests()) {System.out.printf(" 🔧 工具调用:%s(%s)%n",req.name(), req.arguments());}}}@Overridepublic void onError(ChatModelErrorContext context) {System.err.println(" ❌ " + context.error().getMessage());}}}
7.2 运行效果
===== 测试1:查日历 + 发邮件(组合任务)=====🔷 [第1轮] → LLM🔧 工具调用:queryEvents({"date":"2026-04-25"})🔷 [第2轮] → LLM🔧 工具调用:lookupEmail({"name":"张三"})🔷 [第3轮] → LLM🔧 工具调用:sendEmail({"recipient":"张三","subject":"会议确认","body":"..."})🔷 [第4轮] → LLMAI:2026-04-25 有一个项目评审会(15:00,参与者:张三、李四),已给张三发送确认邮件。===== 测试2:知识检索 =====🔷 [第1轮] → LLM🔧 工具调用:searchKnowledge({"query":"Q1销售数据报告"})🔷 [第2轮] → LLMAI:根据Q1销售报告,总销售额580万元,同比增长15%。其中线上渠道320万,线下渠道260万。报告文件在 /reports/2026-Q1-销售报告.pdf。===== 测试3:添加日程 + 通知 =====🔷 [第1轮] → LLM🔧 工具调用:addEvent({"title":"产品评审会","date":"2026-04-27","time":"10:00","participants":"张三,李四"})🔷 [第2轮] → LLM🔧 工具调用:lookupEmail({"name":"张三"})🔷 [第3轮] → LLM🔧 工具调用:lookupEmail({"name":"李四"})🔷 [第4轮] → LLM🔧 工具调用:sendEmail({"recipient":"张三","subject":"产品评审会通知","body":"..."})🔷 [第5轮] → LLM🔧 工具调用:sendEmail({"recipient":"李四","subject":"产品评审会通知","body":"..."})🔷 [第6轮] → LLMAI:已安排下周一上午10点的产品评审会,并分别给张三和李四发送了通知邮件。===== 测试4:综合查询 =====🔷 [第1轮] → LLM🔧 工具调用:queryEvents({"date":"2026-04-25"})🔷 [第2轮] → LLM🔧 工具调用:addEvent({"title":"技术分享","date":"2026-04-25","time":"14:00","participants":""})🔷 [第3轮] → LLMAI:2026-04-25 已有两个日程(10:00 1:1周会、15:00 项目评审会),下午2点空闲,已安排技术分享。
7.3 测试3 的 AI 思考链路分析
🤔 思考 8:测试3中,AI 为什么先查了张三和李四各自的邮箱,而不是一次发两封邮件?
如果想让 AI 更高效,可以增加一个 sendBulkEmail 工具支持批量发送,减少调用轮次。

八、工程实践:生产环境 Checklist
8.1 上线前的检查清单
|
检查项 |
说明 |
本章方案 |
|
工具描述准确 |
每个工具描述是否清晰、有示例? |
✅ 每个参数都有格式说明 |
|
SystemMessage 有终止条件 |
AI 知道什么时候停吗? |
✅ 明确写了终止规则 |
|
异常不外泄 |
工具方法都有 try-catch? |
✅ 每个工具都处理异常 |
|
Memory 有上限 |
对话不会无限膨胀? |
✅ maxMessages=30 |
|
调用可追踪 |
出问题能查到是哪一步? |
✅ ChatModelListener |
|
工具按领域分离 |
不是一个大杂烩类? |
✅ Calendar/Email/Knowledge 三组 |
8.2 性能优化建议
当前(演示级):用户提问 → LLM 调工具(1-2轮) → LLM 再调工具(1-2轮) → 回答总延迟:3-8秒优化方向:1. 工具合并:把 lookupEmail + sendEmail 合并为 sendEmailToContact→ 减少1轮调用,省2-3秒2. 并行工具调用:LangChain4j 支持 LLM 一次请求返回多个 ToolExecutionRequest→ 查张三邮箱 + 查李四邮箱同时进行3. 缓存工具结果:日历查询结果缓存5分钟,避免重复查询→ 相同日期不重复调API4. Token 预算:设置 maxTokens 限制,防止单次响应过长
8.3 Token 消耗估算
单次对话(3轮工具调用):消息上下文:~2000 tokens(SystemMessage + 历史 + 工具结果)每轮 LLM 调用:~500 tokens(输入+输出)总计:2000 + 3×500 = ~3500 tokens按 MiniMax-M2.5 定价(假设 ¥0.01/1K tokens):单次对话成本:¥0.0351000次/天:¥35/天
九、总结
核心要点
按领域分组工具 CalendarTool / EmailTool / KnowledgeTool多工具注册 AiServices.builder().tools(obj1, obj2, obj3)SystemMessage 规则 明确工具使用规则 + 终止条件 + 出错处理ReAct 多步规划 AI 自己决定先查日历→再发邮件→最后汇报异常降级 工具内 try-catch → 返回错误描述 → AI 给建议RAG + Tool 混合 RetrievalAugmentor 自动注入 + KnowledgeTool 按需搜索调用追踪 ChatModelListener 记录每轮工具调用
从第13篇到第15篇的完整演进
第13篇:给 AI 装上"手"(Tool)→ AI 能调工具了,但只能单步执行第14篇:给 AI 装上"脑"(Agent)→ AI 能自主规划多步任务,有了记忆第15篇:让 AI 成为真正的"助手"(实战整合)→ 多工具协作 + RAG + 异常降级 + 调用追踪→ 这就是一个能上线的 Agent 了
9.1 下一步
📌 下一篇预告:第16篇——Multi-Agent 编排:让一群 AI 协同作战
剧透:
·SequentialAgent / ParallelAgent / SupervisorAgent 三大编排模式
·把本篇的 SuperAssistant 拆成”日程 Agent”+”邮件 Agent”+”检索 Agent”
·Supervisor 模式:一个”经理 Agent” 调度多个”员工 Agent”
·AgenticScope:Agent 之间如何传递数据和共享上下文

一个专注Java AI应用开发的技术号
关注我,带你用Java玩转AI!
夜雨聆风

