Spring AI 学习篇(十四)| 实战:开发一个智能办公Agent

Spring AI 学习篇(十四)| 实战:开发一个智能办公Agent

一、本章核心学习目标

学完本章,你将能够:

  1. 独立完成企业级Agent的需求分析与架构设计
  2. 构建智能办公场景的完整工具集(邮件、日历、文档、数据库)
  3. 设计专业的Agent系统提示词,精准控制Agent的行为
  4. 实现多任务上下文管理与用户个性化记忆
  5. 完成一个可直接上线的智能办公Agent完整代码
  6. 掌握Agent的测试方法与效果优化技巧
  7. 拥有一个可以放到简历上的完整AI Agent项目经验

二、前置知识准备

  • 已经完成前13篇的学习,熟练掌握ReAct Agent框架
  • 精通Spring AI工具调用与MCP协议
  • 了解Agent记忆系统的实现原理
  • 熟悉Spring Boot Web开发与数据库操作

三、为什么选择智能办公Agent作为实战项目?

智能办公是AI Agent落地最成熟的场景之一,需求非常旺盛。大量企业都有重复性的办公工作可通过AI Agent自动化,直接产生商业价值。

我们要实现的智能办公Agent核心能力

我们将开发一个个人智能办公助手,具备以下6大核心能力:

  1. 邮件管理:自动收发、分类、回复邮件,撰写邮件草稿
  2. 文档处理:生成、编辑、总结各类办公文档(周报、会议纪要、报告)
  3. 日程管理:安排会议、提醒待办事项、查询日程
  4. 数据查询:查询数据库、生成统计报表、分析数据
  5. 知识库问答:基于企业知识库回答内部问题
  6. 任务自动化:自动完成多步骤复杂办公任务

预告式提及:本章完成后,你将拥有一个可以直接在工作中使用的智能办公助手。下一章我们将学习如何为这个Agent添加可观测性,监控它的运行状态和性能。

四、智能办公Agent整体架构设计

我们采用分层架构设计,确保系统的可扩展性和可维护性:

复制代码
┌─────────────────────────────────────────────────────────┐
│ 接入层                                                  │
│  Web界面  企业微信集成  钉钉集成  Slack集成             │
├─────────────────────────────────────────────────────────┤
│ Agent核心层                                             │
│  ReAct Agent引擎  任务规划器  记忆系统  工具调度器      │
├─────────────────────────────────────────────────────────┤
│ 工具层                                                  │
│  邮件工具  日历工具  文档工具  数据库工具  RAG工具      │
├─────────────────────────────────────────────────────────┤
│ 基础设施层                                              │
│  大模型服务  向量数据库  关系型数据库  文件存储         │
└─────────────────────────────────────────────────────────┘

核心设计原则

  1. 模块化:每个工具独立封装,方便添加和修改
  2. 可配置:所有敏感信息和配置都通过配置文件管理
  3. 安全可控:所有危险操作都需要用户确认,有完整的审计日志
  4. 可扩展:方便后续添加新的工具和能力
  5. 个性化:能够记住用户的偏好和习惯,提供个性化服务

五、工具集设计与实现

工具集是智能办公Agent的核心(以下代码展示核心概念和设计思路,具体 API 以官方文档为准):

1. 邮件工具MCP服务器

实现邮件的收发、分类和回复功能:

java 复制代码
import org.springframework.ai.mcp.server.annotation.McpServer;
import org.springframework.ai.mcp.server.annotation.McpTool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.util.Properties;

@Component
@McpServer(name = "email-mcp-server", description = "提供邮件管理功能的MCP服务器")
public class EmailMcpServer {

    private final EmailConfig emailConfig;

    public EmailMcpServer(EmailConfig emailConfig) {
        this.emailConfig = emailConfig;
    }

    @McpTool(description = "发送邮件")
    public String sendEmail(
            @ToolParam("收件人邮箱地址") String to,
            @ToolParam("邮件主题") String subject,
            @ToolParam("邮件内容") String content,
            @ToolParam("抄送人邮箱地址,多个用逗号分隔") String cc) {
        try {
            Properties props = new Properties();
            props.put("mail.smtp.host", emailConfig.getSmtpHost());
            props.put("mail.smtp.port", emailConfig.getSmtpPort());
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.starttls.enable", "true");

            Session session = Session.getInstance(props, new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(emailConfig.getUsername(), emailConfig.getPassword());
                }
            });

            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(emailConfig.getUsername()));
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
            if (cc != null && !cc.isEmpty()) {
                message.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc));
            }
            message.setSubject(subject);
            message.setText(content);

            Transport.send(message);
            return "邮件发送成功";
        } catch (Exception e) {
            return "邮件发送失败:" + e.getMessage();
        }
    }

    @McpTool(description = "读取最新的邮件")
    public String readLatestEmails(
            @ToolParam("读取的邮件数量,默认10封") int count) {
        // 实现读取邮件的逻辑
        return "已读取最新" + count + "封邮件";
    }
}

2. 文档工具MCP服务器

实现文档的生成、编辑和总结功能:

java 复制代码
@Component
@McpServer(name = "document-mcp-server", description = "提供文档处理功能的MCP服务器")
public class DocumentMcpServer {

    private final ChatClient chatClient;

    public DocumentMcpServer(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @McpTool(description = "生成周报")
    public String generateWeeklyReport(
            @ToolParam("本周完成的工作") String completedWork,
            @ToolParam("下周计划的工作") String plannedWork,
            @ToolParam("遇到的问题和风险") String problems) {
        String prompt = """
                请根据以下信息生成一份专业的项目周报:
                本周完成工作:{completedWork}
                下周计划工作:{plannedWork}
                遇到的问题和风险:{problems}
                
                要求:
                1. 结构清晰,分为本周总结、下周计划、问题与风险三个部分
                2. 语言简洁专业,符合企业规范
                3. 突出重点工作和成果
                """.replace("{completedWork}", completedWork)
                .replace("{plannedWork}", plannedWork)
                .replace("{problems}", problems);

        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }

    @McpTool(description = "总结文档内容")
    public String summarizeDocument(
            @ToolParam("文档内容") String content,
            @ToolParam("总结的长度,可选:short/medium/long") String length) {
        String prompt = """
                请总结以下文档内容,长度要求:{length}
                文档内容:{content}
                """.replace("{length}", length).replace("{content}", content);

        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }
}

3. 数据库工具MCP服务器

实现安全的数据库查询功能:

java 复制代码
@Component
@McpServer(name = "database-mcp-server", description = "提供数据库查询功能的MCP服务器")
public class DatabaseMcpServer {

    private final JdbcTemplate jdbcTemplate;

    public DatabaseMcpServer(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @McpTool(description = "执行SQL查询,只能执行SELECT语句")
    public String executeQuery(
            @ToolParam("SQL查询语句,只能是SELECT语句") String sql) {
        // 安全检查:只允许执行SELECT语句
        if (!sql.trim().toLowerCase().startsWith("select")) {
            return "错误:只允许执行SELECT查询语句";
        }

        try {
            List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
            if (result.isEmpty()) {
                return "查询结果为空";
            }
            return result.toString();
        } catch (Exception e) {
            return "查询失败:" + e.getMessage();
        }
    }

    @McpTool(description = "获取数据库表结构")
    public String getTableSchema(
            @ToolParam("表名") String tableName) {
        try {
            Map<String, Object> result = jdbcTemplate.queryForMap(
                    "DESCRIBE " + tableName);
            return result.toString();
        } catch (Exception e) {
            return "获取表结构失败:" + e.getMessage();
        }
    }
}

4. RAG知识库工具MCP服务器

集成我们之前实现的RAG系统:

java 复制代码
@Component
@McpServer(name = "knowledge-base-mcp-server", description = "提供企业知识库查询功能的MCP服务器")
public class KnowledgeBaseMcpServer {

    private final RagService ragService;

    public KnowledgeBaseMcpServer(RagService ragService) {
        this.ragService = ragService;
    }

    @McpTool(description = "查询企业知识库")
    public String queryKnowledgeBase(
            @ToolParam("查询问题") String query,
            @ToolParam("知识库ID") Long kbId) {
        return ragService.chat(kbId, query).getAnswer();
    }
}

六、Agent定制化与系统提示词设计

系统提示词是Agent的灵魂,一个好的系统提示词可以让Agent的表现提升一个档次。

智能办公Agent专业系统提示词

java 复制代码
@Bean
public Agent officeAgent(ChatClient.Builder chatClientBuilder,
                        List<McpClient> mcpClients,
                        ChatMemory chatMemory) {
    return ReActAgent.builder()
            .chatClient(chatClientBuilder.build())
            .tools(mcpClients)
            .maxIterations(15)
            .chatMemory(chatMemory)
            .systemPrompt("""
                    # 智能办公助手系统提示词
                    
                    ## 身份定位
                    你是一个专业、高效、可靠的个人智能办公助手,拥有5年以上的企业办公经验。
                    你的目标是帮助用户自动化处理各种办公任务,提高工作效率。
                    
                    ## 核心能力
                    你可以使用以下工具来完成任务:
                    1. 邮件工具:发送、读取和管理邮件
                    2. 文档工具:生成、编辑和总结各类办公文档
                    3. 数据库工具:查询业务数据和生成报表
                    4. 知识库工具:查询企业内部知识库
                    5. 文件系统工具:读取和写入本地文件
                    
                    ## 行为准则
                    1. **用户至上**:始终以用户的需求为中心,提供最优质的服务
                    2. **专业严谨**:所有回答和输出都要专业、准确、符合企业规范
                    3. **安全第一**:对于删除文件、发送邮件等危险操作,必须先向用户确认
                    4. **透明沟通**:清晰地告诉用户你正在做什么,以及为什么这么做
                    5. **主动学习**:记住用户的偏好和习惯,提供个性化的服务
                    6. **诚实可信**:如果你不知道答案或者无法完成任务,如实告诉用户,不要编造内容
                    
                    ## 任务处理流程
                    当用户提出一个任务时,请按照以下步骤处理:
                    1. **理解需求**:仔细分析用户的需求,明确任务目标
                    2. **制定计划**:将复杂任务分解为多个清晰的步骤
                    3. **执行计划**:调用合适的工具逐步执行计划
                    4. **检查结果**:检查每个步骤的执行结果是否正确
                    5. **总结反馈**:任务完成后,给用户一个简洁清晰的总结
                    
                    ## 输出格式
                    - 使用简洁明了的语言,避免使用技术术语
                    - 重要信息使用加粗标记
                    - 列表内容使用有序列表或无序列表
                    - 代码和数据使用代码块包裹
                    
                    ## 注意事项
                    - 不要讨论政治、宗教、色情等敏感话题
                    - 不要泄露企业的机密信息
                    - 不要执行任何可能危害系统安全的操作
                    - 如果用户的要求违反公司规定,礼貌地拒绝
                    
                    现在,你已经准备好开始工作了。请以专业、友好的态度回应用户的每一个请求。
                    """)
            .build();
}

七、个性化记忆系统优化

我们将短期记忆和长期记忆结合起来,为用户提供个性化的服务。

1. 用户偏好记忆

让Agent记住用户的常用设置和偏好:

java 复制代码
@Component
public class UserPreferenceService {

    private final LongTermMemory longTermMemory;

    public UserPreferenceService(LongTermMemory longTermMemory) {
        this.longTermMemory = longTermMemory;
    }

    // 保存用户偏好
    public void savePreference(String userId, String key, String value) {
        longTermMemory.saveMemory(userId, key + "=" + value, "preference");
    }

    // 获取用户偏好
    public String getPreference(String userId, String key) {
        List<Document> memories = longTermMemory.retrieveMemory(userId, key);
        if (memories.isEmpty()) {
            return null;
        }
        return memories.get(0).getContent().split("=")[1];
    }
}

2. 上下文管理

实现多任务上下文切换,让Agent能够同时处理多个任务:

java 复制代码
@Component
public class ContextManager {

    private final Map<String, String> currentTaskContext = new ConcurrentHashMap<>();

    // 设置当前任务上下文
    public void setCurrentTask(String userId, String task) {
        currentTaskContext.put(userId, task);
    }

    // 获取当前任务上下文
    public String getCurrentTask(String userId) {
        return currentTaskContext.get(userId);
    }

    // 清除当前任务上下文
    public void clearCurrentTask(String userId) {
        currentTaskContext.remove(userId);
    }
}

八、完整Agent服务实现

1. Agent服务层

java 复制代码
@Service
public class OfficeAgentService {

    private final Agent officeAgent;
    private final UserPreferenceService preferenceService;
    private final ContextManager contextManager;
    private final AuditLogService auditLogService;

    public OfficeAgentService(Agent officeAgent,
                              UserPreferenceService preferenceService,
                              ContextManager contextManager,
                              AuditLogService auditLogService) {
        this.officeAgent = officeAgent;
        this.preferenceService = preferenceService;
        this.contextManager = contextManager;
        this.auditLogService = auditLogService;
    }

    public AgentResponse chat(String userId, String message) {
        // 记录审计日志
        auditLogService.log(userId, "user_message", message);

        // 获取用户偏好
        String emailSignature = preferenceService.getPreference(userId, "email_signature");
        String reportFormat = preferenceService.getPreference(userId, "report_format");

        // 构建增强的用户消息
        String enhancedMessage = message;
        if (emailSignature != null) {
            enhancedMessage += "\n\n用户邮件签名:" + emailSignature;
        }
        if (reportFormat != null) {
            enhancedMessage += "\n\n用户偏好的报告格式:" + reportFormat;
        }

        // 调用Agent
        AgentResponse response = officeAgent.call(enhancedMessage);

        // 记录审计日志
        auditLogService.log(userId, "agent_response", response.getContent());

        // 更新上下文
        contextManager.setCurrentTask(userId, message);

        return response;
    }

    // 危险操作确认
    public String confirmAction(String userId, String actionId, boolean confirmed) {
        if (confirmed) {
            // 执行确认的操作
            auditLogService.log(userId, "action_confirmed", actionId);
            return "操作已确认,正在执行...";
        } else {
            // 取消操作
            auditLogService.log(userId, "action_cancelled", actionId);
            return "操作已取消";
        }
    }
}

2. Agent控制器

java 复制代码
@RestController
@RequestMapping("/office-agent")
public class OfficeAgentController {

    private final OfficeAgentService agentService;

    public OfficeAgentController(OfficeAgentService agentService) {
        this.agentService = agentService;
    }

    @PostMapping("/chat")
    public Result<AgentResponseVO> chat(@RequestParam String userId,
                                        @RequestParam String message) {
        AgentResponse response = agentService.chat(userId, message);
        AgentResponseVO vo = new AgentResponseVO();
        vo.setContent(response.getContent());
        vo.setSteps(response.getSteps().stream()
                .map(step -> new StepVO(step.getThought(), step.getAction(), step.getObservation()))
                .toList());
        return Result.success(vo);
    }

    @PostMapping("/confirm")
    public Result<String> confirmAction(@RequestParam String userId,
                                        @RequestParam String actionId,
                                        @RequestParam boolean confirmed) {
        String result = agentService.confirmAction(userId, actionId, confirmed);
        return Result.success(result);
    }
}

九、测试与效果演示

现在我们来测试这个智能办公Agent的能力。

测试用例1:生成并发送周报

用户输入:帮我写一份本周的项目周报,然后发送给team@example.com,抄送给manager@example.com。本周我完成了用户管理模块的开发和测试,下周计划开发订单管理模块,遇到的问题是数据库性能有点慢。

Agent执行过程

  1. 思考:我需要先生成周报内容,然后发送邮件。首先调用文档工具生成周报。
  2. 行动:调用generateWeeklyReport工具,传入完成的工作、计划的工作和遇到的问题。
  3. 观察:周报生成成功。
  4. 思考:现在我需要发送邮件。但发送邮件是危险操作,我需要先向用户确认。
  5. 行动:向用户发送确认请求:"我已经生成了周报内容,是否确认发送给team@example.com并抄送给manager@example.com?"
  6. 观察:用户确认发送。
  7. 行动:调用sendEmail工具发送邮件。
  8. 观察:邮件发送成功。
  9. 思考:所有步骤都已完成,任务结束。

最终回答:已为你生成并发送了本周的项目周报。邮件已发送至team@example.com,抄送给manager@example.com。

测试用例2:查询数据并生成报表

用户输入:帮我查询上个月的销售数据,然后生成一个销售报表。

Agent执行过程

  1. 思考:我需要先查询销售数据表的结构,然后编写SQL查询数据,最后生成报表。
  2. 行动:调用getTableSchema工具获取sales表的结构。
  3. 观察:获取到表结构。
  4. 思考:现在我可以编写SQL查询上个月的销售数据。
  5. 行动:调用executeQuery工具执行SQL查询。
  6. 观察:获取到销售数据。
  7. 思考:现在我需要根据这些数据生成销售报表。
  8. 行动:调用文档工具生成销售报表。
  9. 观察:报表生成成功。
  10. 思考:任务完成。

最终回答:已为你查询到上个月的销售数据并生成了销售报表。报表显示上个月总销售额为120万元,同比增长15%。

十、企业级优化

1. 安全控制增强

  • 操作分级:将操作分为普通操作、敏感操作和危险操作,不同级别需要不同的确认机制
  • 权限控制:根据用户的角色限制可以调用的工具和可以访问的数据
  • 内容审核:对所有输入和输出进行内容审核,防止敏感信息泄露
  • 沙箱执行:在沙箱环境中执行所有外部操作,隔离风险

2. 可观测性

  • 步骤追踪:记录Agent的每一步思考、行动和观察
  • 性能监控:监控Agent的响应时间、工具调用次数和成功率
  • 错误告警:当Agent失败次数超过阈值时,发送告警通知
  • 用户反馈:收集用户对Agent回答的反馈,持续优化

3. 性能优化

  • 工具调用缓存:缓存相同参数的工具调用结果
  • 并行执行:对于独立的子任务,并行调用多个工具
  • 模型缓存:缓存大模型的响应结果
  • 异步处理:对于耗时较长的任务,异步执行并通知用户结果

十一、常见坑与解决方案

1. ❌ Agent过度依赖工具

问题 :Agent遇到任何问题都调用工具,即使可以直接回答

解决方案:在系统提示词中明确告诉Agent"如果可以直接回答的问题,不要调用工具"

2. ❌ Agent忘记用户的要求

问题 :Agent在执行过程中忘记了用户的部分要求

解决方案

  • 在系统提示词中强调"严格按照用户的要求执行任务"
  • 在每个步骤开始前,回顾用户的原始需求
  • 定期向用户确认任务进展和要求

3. ❌ 工具调用参数错误

问题 :Agent调用工具时参数格式不正确

解决方案

  • 为工具添加详细的参数描述和示例
  • 在工具内部添加严格的参数验证
  • 当参数错误时,返回清晰的错误信息,让Agent可以调整参数重试

4. ❌ 复杂任务规划失败

问题 :对于非常复杂的任务,Agent无法制定正确的执行计划

解决方案

  • 将复杂任务分解为多个简单任务,由用户逐个确认
  • 使用更强大的推理模型(如DeepSeek-R1-14B)
  • 提供任务分解的示例,引导Agent正确分解任务

十二、本章总结与下章预告

本章总结

  1. 智能办公是AI Agent落地最成熟的场景,能够直接产生商业价值
  2. 我们设计并实现了一个具备邮件管理、文档处理、数据查询等6大核心能力的智能办公Agent
  3. 使用MCP协议实现了标准化的工具集,确保可复用和可扩展
  4. 设计了专业的系统提示词,精准控制Agent的行为
  5. 实现了个性化记忆系统和上下文管理,提供个性化服务
  6. 添加了安全控制、可观测性和性能优化,确保系统可以在生产环境运行

预告式提及:我们现在已经完成了一个功能完善的智能办公Agent。但要将它部署到生产环境,我们还需要解决可观测性、安全合规和性能优化等问题。下一章我们将学习Spring AI应用的可观测性,如何监控和调试AI应用。

下章预告

下一章我们将学习Spring AI应用的可观测性。你将学会:

  • 日志配置与链路追踪
  • 模型调用监控:耗时、token消耗、成功率
  • 向量数据库监控与性能调优
  • 用户行为分析与反馈收集
  • 告警机制与故障排查

十三、课后练习

  1. 为智能办公Agent添加日历管理工具,支持安排会议和提醒待办事项
  2. 实现会议纪要生成功能,让Agent能够根据会议录音生成会议纪要
  3. 添加用户偏好设置功能,让用户可以自定义邮件签名、报告格式等
  4. 实现操作确认机制,对于发送邮件、删除文件等危险操作,要求用户确认
  5. 部署你的智能办公Agent,让你的同事也能使用它来提高工作效率