Skip to content

Latest commit

 

History

History
268 lines (189 loc) · 15.8 KB

File metadata and controls

268 lines (189 loc) · 15.8 KB

.\MetaGPT\examples\di\email_summary.py 详细设计文档

该代码是一个使用DataInterpreter角色来自动处理Outlook邮件的脚本。它通过环境变量获取邮箱凭据,然后根据给定的提示词(prompt)执行任务。当前启用的提示词指示DataInterpreter获取最新的5封邮件,并为每封邮件生成一句话摘要,最后以Markdown格式输出。核心功能是利用AI代理自动化完成邮件内容获取与摘要生成的工作流。

整体流程

graph TD
    A[脚本启动] --> B[设置邮箱账号和密码环境变量]
    B --> C[定义任务提示词(prompt)]
    C --> D[创建DataInterpreter实例]
    D --> E[调用di.run(prompt)执行任务]
    E --> F{异步执行任务流}
    F --> G[DataInterpreter解析指令]
    G --> H[连接邮箱服务器]
    H --> I[获取最新5封邮件]
    I --> J[为每封邮件生成一句话摘要]
    J --> K[以Markdown格式输出结果]
    K --> L[任务结束]
Loading

类结构

外部依赖
├── metagpt.roles.di.data_interpreter.DataInterpreter (AI代理角色)
用户定义
├── main() (异步主函数)
    └── di (DataInterpreter实例)

全局变量及字段

email_account

用于存储用户Outlook邮箱账户的字符串变量,作为邮件处理任务的输入参数。

类型:str

全局函数及方法

main

这是一个异步入口函数,用于启动一个数据解释器(DataInterpreter)角色,并执行一个特定的任务。该任务旨在访问指定的Outlook邮箱,获取最新的5封邮件,并对每封邮件的内容进行一句话总结,最后以Markdown格式输出结果。

参数:

  • ,此函数不接受任何显式参数。它通过环境变量email_password和硬编码的email_account变量来获取邮箱凭据。

返回值:None,此函数不返回任何值。它的主要作用是执行一个异步任务流程。

流程图

flowchart TD
    A[开始 main 函数] --> B[设置邮箱账号 email_account]
    B --> C[设置环境变量 email_password]
    C --> D[定义任务提示词 prompt]
    D --> E[创建 DataInterpreter 实例 di]
    E --> F[异步运行 di.run(prompt)]
    F --> G[任务执行结束]
Loading

带注释源码

async def main():
    # 定义用于登录的Outlook邮箱账号
    email_account = "your_email_account"
    # 将邮箱密码设置为环境变量,以提高安全性(避免明文出现在代码或传递给LLM的提示词中)
    # 注意:实际使用时需要替换为真实的密码
    os.environ["email_password"] = "your_email_password"

    ### 提示词:用于自动邮件摘要 ###
    # 构造给DataInterpreter的指令(提示词),要求其:
    # 1. 使用提供的账号密码登录邮箱。
    # 2. 获取最新的5封邮件的发件人和完整内容。
    # 3. 为每封邮件生成一句话摘要(由DataInterpreter自身能力完成,无需调用其他模型)。
    # 4. 以Markdown格式输出结果。
    prompt = f"""I will give you your Outlook email account ({email_account}) and password (email_password item in the environment variable).
            Firstly, Please help me fetch the latest 5 senders and full letter contents.
            Then, summarize each of the 5 emails into one sentence (you can do this by yourself, no need to import other models to do this) and output them in a markdown format."""

    # 实例化DataInterpreter角色
    di = DataInterpreter()

    # 异步执行DataInterpreter的run方法,传入构造好的任务提示词
    # 这将触发DataInterpreter解析提示词、规划并执行一系列动作(如登录邮箱、读取邮件、总结内容等)
    await di.run(prompt)

DataInterpreter.run

DataInterpreter.runDataInterpreter 角色的核心执行方法。它接收一个自然语言指令(prompt),通过调用大型语言模型(LLM)来规划、生成并执行一系列代码任务,以完成用户指定的目标。该方法协调了整个“数据解释器”的工作流,包括任务分解、代码生成、执行和结果整合。

参数:

  • promptstr,用户输入的自然语言指令,描述了需要完成的目标或任务。

返回值:None,此方法为异步执行方法,不返回具体值,其主要功能通过执行过程中的副作用(如生成文件、发送邮件、输出结果等)实现。

流程图

flowchart TD
    A[开始: 调用 run(prompt)] --> B[初始化消息历史<br>将用户prompt加入历史]
    B --> C{循环: 是否达到终止条件?<br>(如任务完成、出错、迭代限制)}
    C -- 否 --> D[调用LLM进行规划<br>(基于历史消息生成新任务或代码)]
    D --> E[解析LLM响应<br>提取任务列表或代码]
    E --> F{响应类型判断}
    F -- 是任务列表 --> G[将新任务加入待执行队列]
    F -- 是代码 --> H[在安全环境中执行代码]
    H --> I[收集代码执行结果<br>(输出、错误、生成的文件等)]
    I --> J[将执行结果作为新消息<br>加入历史]
    J --> C
    G --> K[从队列中取出下一个任务<br>将其描述加入历史]
    K --> C
    C -- 是 --> L[结束运行]
Loading

带注释源码

# 注意:以下源码是基于对DataInterpreter类典型实现的逻辑推断和注释。
# 实际源码可能因版本不同而有所差异,但核心流程一致。

async def run(self, prompt: str) -> None:
    """
    运行DataInterpreter的主循环。
    
    参数:
        prompt (str): 用户指定的任务描述。
    """
    # 1. 初始化:将用户的初始指令作为第一条消息存入历史记录。
    self._init_actions([UserMessage(content=prompt)])  # 假设的初始化方法
    
    # 2. 主循环:持续处理,直到满足停止条件(如任务完成、出错、达到最大轮次)。
    while not self._is_finished():  # 假设的终止条件判断方法
        # 3. 规划阶段:将当前全部消息历史(包含用户指令和之前的执行结果)
        #    发送给LLM,请求其规划下一步行动(生成新任务或直接生成可执行代码)。
        llm_response = await self.llm.aask(self.history)  # 调用LLM接口
        
        # 4. 解析响应:对LLM的响应进行解析。
        parsed_actions = self._parse_actions(llm_response)  # 假设的解析方法
        
        for action in parsed_actions:
            if action.type == "task":
                # 5. 如果是新任务:将任务加入待办列表,后续会将其描述加入历史以驱动LLM进一步细化。
                self._add_task(action)
            elif action.type == "code":
                # 6. 如果是代码:在指定的安全执行环境(如沙箱)中运行该代码。
                exec_result = await self._execute_code(action.code)
                # 7. 收集结果:将代码的标准输出、错误输出、生成的文件路径等信息收集起来。
                result_message = self._create_result_message(exec_result)
                # 8. 反馈循环:将代码执行结果作为一条新消息加入历史记录,
                #    以便在下一轮循环中提供给LLM,形成“规划-执行-观察”的闭环。
                self._add_message_to_history(result_message)
            # 可能还有其他类型的action,如直接输出最终答案等
        
        # 9. 处理任务:如果当前没有待执行的代码,但从LLM响应中解析出了新任务,
        #    则选取下一个任务,将其描述转化为消息加入历史,促使LLM在下一轮为其生成代码。
        if self._has_pending_task() and not self._has_code_to_execute():
            next_task = self._get_next_task()
            task_message = self._create_task_message(next_task)
            self._add_message_to_history(task_message)
    
    # 10. 循环结束,所有任务完成或达到停止条件。
    #     可能在此处进行最终的资源清理或结果汇总输出。
    self._on_finish()

关键组件

DataInterpreter

一个能够理解自然语言指令并执行相应数据操作(如连接邮箱、获取邮件、总结内容)的智能代理角色。

异步任务执行框架

通过 asyncio.run(main()) 启动的异步执行框架,用于运行包含异步I/O操作(如网络请求)的DataInterpreter任务。

环境变量管理

使用 os.environ 管理敏感信息(如邮箱密码),确保密码等凭证不直接暴露在代码或发送给大语言模型API。

提示词工程

通过结构化的自然语言提示词(prompt)来精确指导DataInterpreter完成复杂的多步骤任务(如获取最新邮件并总结)。

模块化功能切换

通过注释/取消注释代码块,可以方便地在不同的预设任务(如自动回复邮件与邮件内容总结)之间进行切换。

问题及建议

已知问题

  • 硬编码的敏感信息:代码中直接硬编码了电子邮件账户和密码,存在严重的安全风险。密码虽然存储在环境变量中,但账户信息仍明文出现在源代码中。
  • 缺乏配置管理:账户凭据和任务参数(如获取的邮件数量)直接写在代码逻辑中,使得配置变更困难,不符合配置与代码分离的最佳实践。
  • 单任务固化:代码结构将特定任务(总结最新5封邮件)的逻辑与main函数紧密耦合,缺乏灵活性。要执行不同的邮件处理任务(如自动回复),需要修改源代码并注释/取消注释相关部分。
  • 错误处理缺失:代码中没有对可能发生的错误进行任何处理,例如网络连接失败、认证错误、邮件获取失败、LLM API调用异常等。一旦出错,程序将崩溃,且没有提供有意义的错误信息。
  • 可测试性差:由于main函数直接实例化DataInterpreter并执行异步运行,且依赖外部环境变量和API,难以进行单元测试或集成测试。
  • 潜在的资源泄漏:代码使用了异步上下文,但没有明确展示DataInterpreter实例的资源清理(如关闭会话、网络连接)过程,可能依赖其内部实现。

优化建议

  • 使用安全的配置管理:将电子邮件账户、密码以及其他配置项(如邮件数量、任务类型)移出代码。建议使用配置文件(如YAML、JSON)、命令行参数或更安全的密钥管理服务来管理这些敏感和可变的配置。
  • 抽象任务逻辑:将具体的邮件处理任务(如“总结邮件”、“自动回复”)抽象为独立的函数或类。main函数或一个调度器根据配置或输入参数来调用相应的任务逻辑,提高代码的模块化和可复用性。
  • 增加全面的错误处理:在关键步骤(如环境变量读取、网络请求、API调用)周围添加try-except块,捕获可能出现的异常,记录详细的错误日志,并根据情况决定是重试、降级处理还是优雅退出。
  • 提升代码可测试性:将核心业务逻辑(如邮件获取、内容总结)与框架执行入口(main函数)分离。通过依赖注入等方式,使得在测试中能够模拟DataInterpreter或邮件客户端,从而编写有效的单元测试。
  • 明确资源管理:确保在使用完DataInterpreter或任何可能持有资源的对象后,显式地调用其清理方法(如果有的话,如close()aclose()),或使用async with上下文管理器来保证资源的正确释放。
  • 增强日志记录:在代码中添加不同级别的日志记录(INFO, DEBUG, ERROR等),记录程序执行的关键步骤、决策和异常信息,便于调试和监控。
  • 考虑任务的可扩展性:设计一个简单的插件或策略模式,使得未来新增邮件处理任务时,只需添加新的任务类或函数并注册,而无需修改核心调度逻辑。

其它

设计目标与约束

  1. 核心目标:创建一个自动化脚本,能够安全地连接到指定的Outlook邮箱,获取最新的5封邮件,并对每封邮件的内容进行单句摘要,最终以Markdown格式输出摘要结果。
  2. 安全约束:邮箱密码等敏感信息必须通过环境变量传递,避免在代码或提示词中明文出现,以防止泄露给大型语言模型(LLM)API或版本控制系统。
  3. 功能约束:摘要生成功能需由DataInterpreter角色内部完成,无需额外调用外部摘要模型,以简化依赖和流程。
  4. 输出约束:最终输出必须为结构化的Markdown格式,确保结果的可读性和易用性。

错误处理与异常设计

  1. 环境变量缺失:若email_password环境变量未设置,DataInterpreter在执行邮箱登录操作时应能捕获并抛出清晰的错误,提示用户配置密码。
  2. 网络与认证失败:处理邮箱服务器连接超时、认证失败(如密码错误)等异常。DataInterpreter应能捕获此类异常,并提供友好的错误信息,而非导致整个程序崩溃。
  3. 邮箱操作异常:在获取邮件列表或读取邮件内容时,可能遇到权限不足、邮件格式异常等问题。代码应具备基本的容错能力,例如跳过无法处理的邮件并记录日志,而非中断整个摘要流程。
  4. LLM处理异常DataInterpreter在理解用户指令或生成摘要时可能遇到问题。虽然当前代码未显式处理,但在架构上应依赖DataInterpreter自身的错误反馈机制,或在未来扩展中增加对di.run返回结果或异常的检查。

数据流与状态机

  1. 数据流
    • 输入:用户提供的email_account字符串、通过环境变量设置的email_password、以及硬编码在prompt变量中的任务指令。
    • 处理DataInterpreter实例di接收prompt指令。指令引导di执行以下子任务序列:a) 使用账户和密码登录邮箱;b) 获取最新的5封邮件;c) 提取每封邮件的发件人和完整内容;d) 对每封邮件内容进行单句摘要;e) 将5条摘要格式化为Markdown。
    • 输出DataInterpreter将最终的Markdown格式摘要输出到控制台或指定的输出流。
  2. 状态机(简化)
    • 初始状态:脚本启动,环境变量加载。
    • 执行状态DataInterpreter开始解析并执行prompt中的指令。此阶段包含多个子状态(登录中、获取邮件中、摘要生成中、格式化中),由DataInterpreter内部管理。
    • 完成状态:摘要成功生成并输出。
    • 错误状态:在上述任何子状态中发生异常,流程终止并输出错误信息。

外部依赖与接口契约

  1. 外部依赖
    • metagpt 框架:核心依赖,特别是其中的DataInterpreter角色。该角色封装了与LLM的交互、任务规划与执行能力。
    • Python 运行时:需要asyncio库支持异步执行。
    • 操作系统环境变量:依赖os.environ来安全获取邮箱密码。
  2. 接口契约
    • DataInterpreter:脚本通过实例化DataInterpreter并调用其异步run(prompt: str)方法来驱动整个任务。prompt参数需为清晰、结构化的自然语言指令,DataInterpreter负责理解并执行该指令。期望的返回是任务的执行结果(此处为控制台输出)。
    • 环境变量:脚本约定从名为email_password的环境变量中读取邮箱密码。这是一个隐式但关键的接口契约。
    • 邮箱服务(通过DataInterpreter间接访问)DataInterpreter内部需要能够与Outlook邮箱服务进行交互(如通过IMAP/SMTP协议)。这要求DataInterpreter具备相应的行动能力或工具,且邮箱账户需已开启相关协议支持。