Переглянути джерело

feat: 添加 EmailDigestAgent - 邮件智能摘要助手

Johnx-w 3 тижнів тому
батько
коміт
28181eb425

+ 20 - 0
Co-creation-projects/Johnx-w-EmailDigestAgent/.env.example

@@ -0,0 +1,20 @@
+# LLM配置
+# 模型名称
+LLM_MODEL_ID=your_model_id_here
+# API密钥
+LLM_API_KEY=your_api_key_here
+# 服务地址
+LLM_BASE_URL=https://api-inference.modelscope.cn/v1/
+
+# ========================================
+# 邮箱 IMAP 配置(可选,演示模式不需要)
+# ========================================
+# 国内邮箱推荐方案:
+#   QQ邮箱:     imap.qq.com:993     去QQ邮箱设置 -> 开启IMAP -> 获取授权码
+#   网易163:    imap.163.com:993    去网易邮箱设置 -> 开启IMAP -> 获取授权码
+#   网易126:    imap.126.com:993    同上
+
+IMAP_SERVER=imap.qq.com
+IMAP_PORT=993
+IMAP_USERNAME=your_email@qq.com
+IMAP_PASSWORD=你的IMAP授权码

+ 42 - 0
Co-creation-projects/Johnx-w-EmailDigestAgent/.gitignore

@@ -0,0 +1,42 @@
+@"
+# --- 环境变量与敏感信息 ---
+.env
+
+# --- Python ---
+__pycache__/
+*.py[cod]
+*.pyo
+*.egg-info/
+dist/
+build/
+*.egg
+.eggs/
+
+# --- Jupyter Notebook ---
+.ipynb_checkpoints/
+*.ipynb_checkpoints/
+
+# --- 虚拟环境 ---
+venv/
+.venv/
+env/
+envs/
+.conda/
+virtualenv/
+
+# --- IDE/编辑器 ---
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+.DS_Store
+Thumbs.db
+
+# --- 日志与临时文件 ---
+*.log
+*.tmp
+
+# --- 运行时输出 ---
+output/
+"@ | Set-Content -Path "Co-creation-projects\Johnx-w-EmailDigestAgent\.gitignore" -Encoding UTF8

+ 171 - 0
Co-creation-projects/Johnx-w-EmailDigestAgent/README.md

@@ -0,0 +1,171 @@
+# 邮件智能摘要助手(EmailDigestAgent)
+
+> 基于 HelloAgents 框架的智能邮件处理系统,自动获取收件箱邮件、AI 分类摘要、生成每日邮件日报,告别收件箱过载
+
+## 📝 项目简介
+
+每天早上打开邮箱,面对几十封未读邮件——哪些需要立即处理?哪些可以稍后阅读?哪些是垃圾信息?
+
+**EmailDigestAgent** 解决的就是这个真实痛点。它自动获取你的收件箱邮件,通过大模型对每封邮件进行智能分类和摘要,最终生成一份结构化的「邮件日报」,让你在 5 分钟内掌握收件箱全貌。
+
+**解决的问题:**
+- 📧 收件箱邮件堆积,逐封阅读筛选耗时费力
+- 🔍 重要邮件淹没在通知、促销和垃圾邮件中
+- ⏰ 每天早上需要花 30-60 分钟处理邮件
+- 📊 缺少对收件箱的整体视图和优先级排序
+
+**特色功能:**
+- 🤖 基于大模型的智能邮件摘要,而非简单截取前几行
+- 🏷️ 自动多维度分类(工作/客户/个人/通知/垃圾)
+- 📊 生成结构化的 Markdown 邮件日报
+- 🔗 支持 IMAP 真实接入和模拟数据演示两种模式
+- 🪶 轻量 Pipeline 设计:获取 → 分类 → 摘要 → 日报
+
+## ✨ 核心功能
+
+- [x] **邮件获取**:支持 IMAP 真实接入和模拟数据演示两种模式
+- [x] **智能分类**:按类型和优先级自动分类
+- [x] **AI 摘要**:用大模型对每封邮件生成一句话摘要,提取关键信息和待办事项
+- [x] **日报生成**:结构化 Markdown 日报输出,含统计概览、分类详情、优先级排序
+- [x] **模拟演示**:内置 10 封不同场景的模拟邮件,无需配置邮箱即可体验
+
+## 🛠️ 技术栈
+
+- **核心框架**:HelloAgents 框架(Tool 系统 + 自定义 Pipeline)
+- **智能体范式**:Pipeline(获取 → 分类 → 摘要 → 日报)
+- **LLM 接口**:兼容 OpenAI 格式的 API(DeepSeek / ModelScope / OpenAI 等)
+- **邮件接入**:通用 IMAP 协议(QQ/163/126/Gmail/Outlook)
+- **依赖库**:hello-agents, python-dotenv, rich
+
+## 🚀 快速开始
+
+### 环境要求
+
+- Python 3.10+
+- Jupyter Notebook / JupyterLab
+- 一个兼容 OpenAI 格式的 API Key(DeepSeek / ModelScope / OpenAI 等均可)
+
+### 安装依赖
+
+```bash
+pip install -r requirements.txt
+```
+
+> **注意:** 如果报 `externally-managed-environment` 错误,加 `--break-system-packages` 参数,或者先创建虚拟环境(`python -m venv venv`)。
+
+### Windows 用户额外配置
+
+运行前在终端执行以下命令,避免中文输出乱码:
+
+```bash
+set PYTHONIOENCODING=utf-8
+```
+
+### 配置 API 密钥
+
+```bash
+# 创建 .env 文件并配置
+cp .env.example .env
+# 编辑 .env,填入你的 API Key
+```
+
+### 运行项目
+
+```bash
+jupyter lab
+# 打开 main.ipynb,按顺序运行所有单元格
+```
+
+## 📖 使用示例
+
+### 演示模式(无需配置邮箱)
+
+```python
+pipeline = EmailDigestPipeline(llm=llm, use_demo=True)
+report = pipeline.run()
+print(report)
+```
+
+运行后输出如下日报:
+
+```markdown
+# 📬 邮件日报 - 2026-07-02
+
+## 📊 概览
+- 总邮件数: 10
+- 高优先级: 3  |  中优先级: 4  |  低优先级: 3
+
+## 🔴 高优先级
+| # | 发件人 | 主题 | 类型 | 一句话摘要 |
+|---|--------|------|------|-----------|
+| 1 | boss@corp.com | 紧急:Q3预算确认 | 工作 | 老板要求今天内确认Q3预算方案 |
+...
+
+## 🟡 中优先级 | 🟢 低优先级 | 🗑️ 垃圾/促销
+(按优先级分区展示)
+```
+
+### 真实 IMAP 接入
+
+```python
+pipeline = EmailDigestPipeline(llm=llm, use_demo=False)
+report = pipeline.run(hours=24, max_emails=50)
+```
+
+## 🔧 真实邮箱配置(IMAP 通用协议)
+
+支持 QQ邮箱 / 163 / 126 / Gmail / Outlook 等所有支持 IMAP 的邮箱。
+
+### 启用 IMAP 并获取授权码
+
+| 邮箱 | IMAP 服务器 | 端口 | 操作说明 |
+|------|------------|------|---------|
+| QQ邮箱 | imap.qq.com | 993 | 邮箱设置 → 账户 → 开启 IMAP/SMTP → 获取授权码 |
+| 163邮箱 | imap.163.com | 993 | 邮箱设置 → POP3/SMTP/IMAP → 开启 IMAP → 获取授权码 |
+| 126邮箱 | imap.126.com | 993 | 同 163 邮箱 |
+| Gmail | imap.gmail.com | 993 | Google 账户 → 安全性 → 两步验证 → 应用专用密码 |
+| Outlook | outlook.office365.com | 993 | Microsoft 账户 → 安全性 → 应用密码 |
+
+### 配置方式(二选一)
+
+**方式一(推荐):** 编辑 `.env` 文件,填入 `IMAP_SERVER` / `IMAP_PORT` / `IMAP_USERNAME` / `IMAP_PASSWORD`。
+
+**方式二:** 编辑 `config/email_config.json`,修改 `imap` 段中的对应字段。
+
+配置完成后,`use_demo=False` 即可切换为真实模式。连接失败时会直接报错并展示原因,方便排查。
+
+## 🎯 项目亮点
+
+- **真实痛点驱动**:收件箱过载是每个职场人的日常困扰
+- **LLM 原生摘要**:真正理解邮件内容后生成摘要,而非关键词匹配
+- **Pipeline 清晰**:获取→分类→摘要→日报,四步完成
+- **双重运行模式**:模拟数据演示 + 真实邮箱接入
+
+## 🏗️ 项目结构
+
+```
+WHS-EmailDigestAgent/
+├── main.ipynb              # 主 Jupyter Notebook
+├── requirements.txt        # Python 依赖列表
+├── README.md              # 项目说明文档
+├── .env.example           # 环境变量示例
+├── config/                # 配置文件目录
+│   └── email_config.json  # 邮箱配置
+└── output/                # 日报输出目录
+```
+
+## 🔮 未来计划
+
+- [ ] 支持 Outlook API
+- [ ] 定时任务:每日自动生成日报并推送
+- [ ] 邮件趋势分析:周报/月报统计
+- [ ] Web 界面展示
+
+## 👤 作者
+
+- GitHub: [Johnx-w (VyNox)](https://github.com/Johnx-w)
+- 日期:2026-07-02
+
+## 🙏 致谢
+
+感谢 Datawhale 社区和 Hello-Agents 项目!

+ 49 - 0
Co-creation-projects/Johnx-w-EmailDigestAgent/config/email_config.json

@@ -0,0 +1,49 @@
+{
+  "imap": {
+    "server": "imap.qq.com",
+    "port": 993,
+    "use_ssl": true,
+    "username": "your_email@qq.com",
+    "password": "你的IMAP授权码",
+    "max_emails_per_fetch": 50
+  },
+  "presets": {
+    "qq": {
+      "server": "imap.qq.com",
+      "port": 993,
+      "note": "需要在QQ邮箱设置中开启IMAP/SMTP服务,使用授权码而非登录密码"
+    },
+    "163": {
+      "server": "imap.163.com",
+      "port": 993,
+      "note": "需要在网易邮箱设置中开启IMAP/SMTP服务,使用授权码"
+    },
+    "126": {
+      "server": "imap.126.com",
+      "port": 993,
+      "note": "同163邮箱配置方式"
+    },
+    "gmail": {
+      "server": "imap.gmail.com",
+      "port": 993,
+      "note": "需要开启两步验证后生成应用专用密码,或使用OAuth"
+    },
+    "outlook": {
+      "server": "outlook.office365.com",
+      "port": 993,
+      "note": "微软邮箱,使用登录密码或应用专用密码"
+    }
+  },
+  "classification_categories": {
+    "work": "工作相关(会议、项目、任务、汇报)",
+    "client": "客户/外部合作(咨询、需求、合同)",
+    "personal": "个人(同事闲聊、团建、活动)",
+    "notification": "系统通知(Jira、GitHub、CI/CD、日历)",
+    "promotion": "促销/营销(广告、推广、优惠)",
+    "spam": "垃圾邮件"
+  },
+  "priority_rules": {
+    "high_keywords": ["紧急", "urgent", "asap", "deadline", "截止", "重要", "important", "合同", "审批"],
+    "low_keywords": ["newsletter", "周报", "日报", "通知", "订阅", "promotion", "offer"]
+  }
+}

+ 832 - 0
Co-creation-projects/Johnx-w-EmailDigestAgent/main.ipynb

@@ -0,0 +1,832 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# 📬 邮件智能摘要助手(EmailDigestAgent)\n",
+    "## 基于 HelloAgents 框架的邮件智能分类、摘要与日报生成系统\n",
+    "\n",
+    "**核心流程:** 获取邮件(IMAP) → 智能分类 → AI 摘要 → 日报生成\n",
+    "\n",
+    "**支持邮箱:** QQ邮箱 / 163 / 126 / Gmail / Outlook — 任何支持 IMAP 的邮箱\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第1部分:环境配置\n",
+    "\n",
+    "### 1.1 安装依赖\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# 安装依赖(如已安装可跳过)\n",
+    "# hello-agents 框架 + 基础工具,imaplib 是 Python 标准库无需安装\n",
+    "# !pip install -q hello-agents python-dotenv rich\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 导入库并配置 LLM\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "import json\n",
+    "import re\n",
+    "import email\n",
+    "import imaplib\n",
+    "from email.header import decode_header\n",
+    "from datetime import datetime, timedelta, timezone\n",
+    "from typing import Dict, Any, List, Optional\n",
+    "from dataclasses import dataclass, field\n",
+    "from collections import Counter\n",
+    "\n",
+    "# HelloAgents\n",
+    "from hello_agents import SimpleAgent, HelloAgentsLLM\n",
+    "from hello_agents.tools import Tool\n",
+    "\n",
+    "# Utils\n",
+    "from dotenv import load_dotenv\n",
+    "from rich.console import Console\n",
+    "from rich.table import Table\n",
+    "from rich.panel import Panel\n",
+    "from rich.markdown import Markdown\n",
+    "\n",
+    "console = Console()\n",
+    "load_dotenv(override=True)\n",
+    "\n",
+    "# Init LLM\n",
+    "llm = HelloAgentsLLM()\n",
+    "model_id = os.getenv(\"LLM_MODEL_ID\", \"gpt-4o-mini\")\n",
+    "print(\"✅ 环境配置完成\")\n",
+    "print(f\"   LLM 模型: {model_id}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第2部分:模拟邮件数据\n",
+    "\n",
+    "内置 10 封覆盖不同场景的模拟邮件,用于演示模式。\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ========================================\n",
+    "# 模拟邮件数据集(10封覆盖不同场景)\n",
+    "# ========================================\n",
+    "\n",
+    "DEMO_EMAILS = [\n",
+    "    {\n",
+    "        \"id\": \"1\",\n",
+    "        \"from\": \"boss@company.com\",\n",
+    "        \"subject\": \"紧急:请今天下班前确认Q3预算方案\",\n",
+    "        \"date\": \"2026-07-02 08:30:00\",\n",
+    "        \"body\": \"各位部门负责人,附件是Q3预算方案初稿。请各位于今天下班前完成审核并回复确认。如有修改意见,请在明天上午的预算会议上提出。未及时确认的部门将按现有方案执行。\\n\\n附件:Q3_Budget_v2.xlsx\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"2\",\n",
+    "        \"from\": \"client@vip-corp.com\",\n",
+    "        \"subject\": \"Re: 合同条款第5条和第8条的修改意见\",\n",
+    "        \"date\": \"2026-07-02 09:15:00\",\n",
+    "        \"body\": \"你好,我们法务团队审核了合同草案,对第5条(付款条款)和第8条(违约责任)有较大异议。特别是违约金的设定比例需要调整。请尽快安排一次线上沟通,我们的法务希望本周内解决这个问题。\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"3\",\n",
+    "        \"from\": \"hr@company.com\",\n",
+    "        \"subject\": \"【通知】年度绩效评估系统本周五关闭\",\n",
+    "        \"date\": \"2026-07-02 10:00:00\",\n",
+    "        \"body\": \"各位同事,本年度绩效自评和互评系统将于本周五(7月4日)17:00正式关闭。目前系统显示你还有2位同事的互评未完成。请尽快登录系统完成评估,逾期将影响年度绩效结果。\\n\\n评估系统链接:http://hr.company.com/performance\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"4\",\n",
+    "        \"from\": \"github-notifications@github.com\",\n",
+    "        \"subject\": \"[hello-agents] New PR #156: Add EmailDigestAgent project\",\n",
+    "        \"date\": \"2026-07-02 10:30:00\",\n",
+    "        \"body\": \"Pull Request #156 opened by WHS.\\n\\nTitle: Add EmailDigestAgent project\\nBranch: feature/email-digest to main\\nFiles changed: 5\\n\\nView on GitHub: https://github.com/datawhalechina/hello-agents/pull/156\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"5\",\n",
+    "        \"from\": \"newsletter@techweekly.com\",\n",
+    "        \"subject\": \"Tech Weekly #234: AI Agent 最新趋势与工具盘点\",\n",
+    "        \"date\": \"2026-07-02 11:00:00\",\n",
+    "        \"body\": \"本周精选:\\n1. OpenAI 发布新一代 Agent 框架\\n2. LangChain v0.3 重大更新\\n3. 深度解析:多智能体协作的最佳实践\\n4. 开源推荐:5个值得关注的 Agent 项目\\n\\n点击阅读全文:https://techweekly.com/234\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"6\",\n",
+    "        \"from\": \"li.team@company.com\",\n",
+    "        \"subject\": \"下午3点的项目例会议程更新\",\n",
+    "        \"date\": \"2026-07-02 12:00:00\",\n",
+    "        \"body\": \"Hi team, 今天下午3点的项目例会议程有更新:\\n\\n1. Sprint 回顾(15min)\\n2. Bug 修复进展讨论(20min)\\n3. 新需求评审——用户权限模块(25min)\\n\\n请提前查阅 Jira board 准备更新。\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"7\",\n",
+    "        \"from\": \"marketing@online-shop.com\",\n",
+    "        \"subject\": \"年中大促最后一天!全场5折起\",\n",
+    "        \"date\": \"2026-07-02 13:00:00\",\n",
+    "        \"body\": \"年中狂欢倒计时!最后24小时!\\n\\n全场商品5折起\\n满299包邮\\n满599送精美礼品\\n\\n立即抢购:https://shop.example.com/sale\\n退订请回复TD\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"8\",\n",
+    "        \"from\": \"noreply@calendar.google.com\",\n",
+    "        \"subject\": \"提醒:明天上午10:00 季度评审会议\",\n",
+    "        \"date\": \"2026-07-02 14:00:00\",\n",
+    "        \"body\": \"日历提醒:\\n\\n事件:季度评审会议\\n时间:2026年7月3日 10:00-12:00\\n地点:3楼会议室A / Zoom\\n参与者:全体部门负责人\\n\\n请提前准备汇报材料。\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"9\",\n",
+    "        \"from\": \"spam-bot@random-mail.xyz\",\n",
+    "        \"subject\": \"Congratulations! You've won a FREE iPhone!\",\n",
+    "        \"date\": \"2026-07-02 15:00:00\",\n",
+    "        \"body\": \"Dear Winner,\\n\\nYour email address has been selected in our annual lottery! You have won a brand new iPhone 16 Pro Max!\\n\\nTo claim your prize, click: http://suspicious-link.xyz/claim\\n\\nHurry! This offer expires in 24 hours!\"\n",
+    "    },\n",
+    "    {\n",
+    "        \"id\": \"10\",\n",
+    "        \"from\": \"team-lead@company.com\",\n",
+    "        \"subject\": \"关于新同事入职培训和 mentor 安排\",\n",
+    "        \"date\": \"2026-07-02 16:30:00\",\n",
+    "        \"body\": \"大家下午好,下周一我们团队有新同事加入(前端开发,2年经验)。需要安排以下事项:\\n\\n1. 入职培训计划(HR 已发邮件)\\n2. 指定一位 mentor(建议资深前端同事)\\n3. 准备开发环境和新手引导文档\\n\\n请今天下班前在群里确认谁能担任 mentor!\"\n",
+    "    }\n",
+    "]\n",
+    "\n",
+    "print(f\"✅ 已加载 {len(DEMO_EMAILS)} 封模拟邮件\")\n",
+    "\n",
+    "preview = \"\\n\".join([\n",
+    "    f\"  {i+1}. [{e['from']}] {e['subject'][:50]}...\"\n",
+    "    for i, e in enumerate(DEMO_EMAILS)\n",
+    "])\n",
+    "console.print(Panel.fit(preview, title=\"📧 模拟邮件列表\", style=\"blue\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第3部分:工具定义\n",
+    "\n",
+    "定义两个核心工具:\n",
+    "- **EmailFetchTool**:通过 IMAP 协议获取邮件(支持 QQ/163/126/Gmail/Outlook),或使用模拟数据\n",
+    "- **EmailDigestTool**:用 LLM 对邮件列表进行分类和摘要"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ========================================\n",
+    "# 工具1: 邮件获取工具(IMAP 通用版)\n",
+    "# ========================================\n",
+    "\n",
+    "def _decode_mime_header(header_value: str) -> str:\n",
+    "    if not header_value:\n",
+    "        return \"\"\n",
+    "    parts = decode_header(header_value)\n",
+    "    decoded = \"\"\n",
+    "    for text, charset in parts:\n",
+    "        if isinstance(text, bytes):\n",
+    "            try:\n",
+    "                decoded += text.decode(charset or 'utf-8', errors='replace')\n",
+    "            except (LookupError, UnicodeDecodeError):\n",
+    "                decoded += text.decode('utf-8', errors='replace')\n",
+    "        else:\n",
+    "            decoded += str(text)\n",
+    "    return decoded\n",
+    "\n",
+    "\n",
+    "def _parse_email_body(msg) -> str:\n",
+    "    body = \"\"\n",
+    "    if msg.is_multipart():\n",
+    "        for part in msg.walk():\n",
+    "            content_type = part.get_content_type()\n",
+    "            disposition = str(part.get(\"Content-Disposition\", \"\"))\n",
+    "            if content_type == \"text/plain\" and \"attachment\" not in disposition:\n",
+    "                payload = part.get_payload(decode=True)\n",
+    "                if payload:\n",
+    "                    charset = part.get_content_charset() or 'utf-8'\n",
+    "                    try:\n",
+    "                        body = payload.decode(charset, errors='replace')\n",
+    "                    except (LookupError, UnicodeDecodeError):\n",
+    "                        body = payload.decode('utf-8', errors='replace')\n",
+    "                    break\n",
+    "        if not body:\n",
+    "            for part in msg.walk():\n",
+    "                if (part.get_content_type() == \"text/html\"\n",
+    "                        and \"attachment\" not in str(part.get(\"Content-Disposition\", \"\"))):\n",
+    "                    payload = part.get_payload(decode=True)\n",
+    "                    if payload:\n",
+    "                        charset = part.get_content_charset() or 'utf-8'\n",
+    "                        try:\n",
+    "                            body = payload.decode(charset, errors='replace')\n",
+    "                            body = re.sub(r'<[^>]+>', '', body)\n",
+    "                            body = re.sub(r'\\s+', ' ', body).strip()\n",
+    "                        except Exception:\n",
+    "                            body = payload.decode('utf-8', errors='replace')\n",
+    "                    break\n",
+    "    else:\n",
+    "        payload = msg.get_payload(decode=True)\n",
+    "        if payload:\n",
+    "            charset = msg.get_content_charset() or 'utf-8'\n",
+    "            try:\n",
+    "                body = payload.decode(charset, errors='replace')\n",
+    "            except Exception:\n",
+    "                body = payload.decode('utf-8', errors='replace')\n",
+    "    return body[:3000]\n",
+    "\n",
+    "\n",
+    "class EmailFetchTool(Tool):\n",
+    "    def __init__(self, use_demo: bool = True, demo_emails=None):\n",
+    "        super().__init__(\n",
+    "            name=\"email_fetch\",\n",
+    "            description=\"获取收件箱中的未读邮件\"\n",
+    "        )\n",
+    "        self.use_demo = use_demo\n",
+    "        self.demo_emails = demo_emails or DEMO_EMAILS\n",
+    "\n",
+    "    def _read_imap_config(self) -> tuple:\n",
+    "        server = os.getenv(\"IMAP_SERVER\") or \"\"\n",
+    "        port = int(os.getenv(\"IMAP_PORT\") or \"0\")\n",
+    "        username = os.getenv(\"IMAP_USERNAME\") or \"\"\n",
+    "        password = os.getenv(\"IMAP_PASSWORD\") or \"\"\n",
+    "        if not (username and password and server):\n",
+    "            try:\n",
+    "                with open(\"config/email_config.json\", \"r\", encoding=\"utf-8\") as f:\n",
+    "                    cfg = json.load(f)\n",
+    "                imap_cfg = cfg.get(\"imap\", {})\n",
+    "                server = server or imap_cfg.get(\"server\", \"imap.qq.com\")\n",
+    "                port = port or imap_cfg.get(\"port\", 993)\n",
+    "                username = username or imap_cfg.get(\"username\", \"\")\n",
+    "                password = password or imap_cfg.get(\"password\", \"\")\n",
+    "            except FileNotFoundError:\n",
+    "                pass\n",
+    "        return server or \"imap.qq.com\", port or 993, username, password\n",
+    "\n",
+    "    def _fetch_via_imap(self, max_emails: int = 50, hours: int = 24) -> list:\n",
+    "        server, port, username, password = self._read_imap_config()\n",
+    "        if not username or not password:\n",
+    "            raise ValueError(\"未配置邮箱信息\")\n",
+    "\n",
+    "        console.print(f\"[dim]  连接 {server}:{port}  用户: {username}...[/dim]\")\n",
+    "        mail = imaplib.IMAP4_SSL(server, port)\n",
+    "        mail.login(username, password)\n",
+    "\n",
+    "        # 选择收件箱 — 依次尝试: 无引号, 带引号, readonly\n",
+    "        select_status = None\n",
+    "        for name, rdonly in [(\"INBOX\", False), ('\"INBOX\"', False), (\"INBOX\", True)]:\n",
+    "            select_status, select_data = mail.select(name, readonly=rdonly)\n",
+    "            console.print(f\"[dim]  select({name!r}, readonly={rdonly}) -> {select_status!r}[/dim]\")\n",
+    "            if select_status == \"OK\":\n",
+    "                break\n",
+    "        if select_status != \"OK\":\n",
+    "            raise ValueError(f\"无法选择 INBOX (状态: {select_status!r})\")\n",
+    "\n",
+    "        since_date = (datetime.now() - timedelta(hours=hours)).strftime(\"%d-%b-%Y\")\n",
+    "        search_criteria = f'(UNSEEN SINCE {since_date})'\n",
+    "        status, message_ids = mail.search(None, search_criteria)\n",
+    "        if status != \"OK\":\n",
+    "            mail.logout()\n",
+    "            return []\n",
+    "\n",
+    "        ids = message_ids[0].split()\n",
+    "        ids = list(reversed(ids))[:max_emails]\n",
+    "        emails = []\n",
+    "        for msg_id in ids:\n",
+    "            try:\n",
+    "                status, msg_data = mail.fetch(msg_id, \"(RFC822)\")\n",
+    "                if status != \"OK\":\n",
+    "                    continue\n",
+    "                raw_email = msg_data[0][1]\n",
+    "                msg = email.message_from_bytes(raw_email)\n",
+    "                msg_id_str = msg_id.decode() if isinstance(msg_id, bytes) else msg_id\n",
+    "                emails.append({\n",
+    "                    \"id\": msg_id_str,\n",
+    "                    \"from\": _decode_mime_header(msg.get(\"From\", \"\")),\n",
+    "                    \"subject\": _decode_mime_header(msg.get(\"Subject\", \"(无主题)\")),\n",
+    "                    \"date\": msg.get(\"Date\", \"\"),\n",
+    "                    \"body\": _parse_email_body(msg)\n",
+    "                })\n",
+    "            except Exception as e:\n",
+    "                console.print(f\"[yellow]  ⚠️ 邮件解析失败: {e}[/yellow]\")\n",
+    "                continue\n",
+    "\n",
+    "        mail.logout()\n",
+    "        console.print(f\"[dim]  已断开连接[/dim]\")\n",
+    "        return emails\n",
+    "\n",
+    "    def run(self, hours: int = 24, max_emails: int = 50) -> str:\n",
+    "        if self.use_demo:\n",
+    "            emails = self.demo_emails[:max_emails]\n",
+    "        else:\n",
+    "            try:\n",
+    "                emails = self._fetch_via_imap(max_emails=max_emails, hours=hours)\n",
+    "            except imaplib.IMAP4.error as e:\n",
+    "                console.print(f\"[red]❌ IMAP 连接失败: {e}[/red]\")\n",
+    "                raise RuntimeError(f\"IMAP 连接失败: {e}\") from e\n",
+    "            except ValueError:\n",
+    "                raise\n",
+    "            except Exception as e:\n",
+    "                console.print(f\"[red]❌ 未知错误: {e}[/red]\")\n",
+    "                raise\n",
+    "\n",
+    "        if not emails:\n",
+    "            return json.dumps({\"message\": \"没有新的未读邮件\", \"emails\": []}, ensure_ascii=False, indent=2)\n",
+    "        return json.dumps({\"count\": len(emails), \"fetch_time\": datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\"), \"emails\": emails}, ensure_ascii=False, indent=2)\n",
+    "\n",
+    "    def get_parameters(self):\n",
+    "        from hello_agents.tools import ToolParameter\n",
+    "        return []\n",
+    "\n",
+    "print(\"✅ EmailFetchTool defined (IMAP version)\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ========================================\n",
+    "# 工具2: 邮件摘要与分类工具(LLM驱动)\n",
+    "# ========================================\n",
+    "\n",
+    "class EmailDigestTool(Tool):\n",
+    "    \"\"\"用 LLM 对邮件列表进行分类、摘要和优先级判断\"\"\"\n",
+    "\n",
+    "    CATEGORIES = [\"工作\", \"客户\", \"个人\", \"通知\", \"促销\", \"垃圾\"]\n",
+    "    PRIORITIES = [\"高\", \"中\", \"低\"]\n",
+    "\n",
+    "    def __init__(self, llm=None, use_demo: bool = True, demo_emails=None):\n",
+    "        super().__init__(\n",
+    "            name=\"email_fetch\",\n",
+    "            description=\"获取收件箱中的未读邮件。支持演示模式和IMAP真实接入模式。\"\n",
+    "        )\n",
+    "        self.llm = llm\n",
+    "        self.use_demo = use_demo\n",
+    "        self.demo_emails = demo_emails or DEMO_EMAILS\n",
+    "\n",
+    "    def _build_prompt(self, emails_json: str, count: int) -> str:\n",
+    "        cats = \", \".join(self.CATEGORIES)\n",
+    "        prios = \", \".join(self.PRIORITIES)\n",
+    "        return f\"\"\"你是一个专业的邮件分析助手。请分析以下邮件列表并返回结构化结果。\n",
+    "\n",
+    "邮件数据(JSON格式):\n",
+    "{emails_json}\n",
+    "\n",
+    "分析规则:\n",
+    "1. 分类 - 从 [{cats}] 中选择最合适的一类\n",
+    "2. 优先级 - 从 [{prios}] 中选择:\n",
+    "   高: 需要立即处理(老板指令、客户需求、合同、截止日期)\n",
+    "   中: 需要处理但不紧急(项目讨论、同事沟通、HR通知)\n",
+    "   低: 可忽略(新闻订阅、促销广告、系统通知、垃圾邮件)\n",
+    "3. 一句话摘要 - 用中文提炼核心内容,包含关键动作或截止时间\n",
+    "4. 关键行动 - 若需要回复或行动,用1-2句话说明;否则填\"无\"\n",
+    "\n",
+    "严格按以下JSON格式返回(只返回JSON,不加其他文字):\n",
+    "{{\n",
+    "  \"analyzed_at\": \"当前时间\",\n",
+    "  \"total\": {count},\n",
+    "  \"items\": [\n",
+    "    {{\n",
+    "      \"id\": \"邮件ID\",\n",
+    "      \"from\": \"发件人\",\n",
+    "      \"subject\": \"主题\",\n",
+    "      \"category\": \"分类\",\n",
+    "      \"priority\": \"优先级\",\n",
+    "      \"summary\": \"一句话摘要\",\n",
+    "      \"action\": \"关键行动\"\n",
+    "    }}\n",
+    "  ]\n",
+    "}}\"\"\"\n",
+    "\n",
+    "    def run(self, emails_json: str) -> str:\n",
+    "        try:\n",
+    "            data = json.loads(emails_json)\n",
+    "            count = data.get(\"count\", len(data.get(\"emails\", [])))\n",
+    "            prompt = self._build_prompt(emails_json, count)\n",
+    "            response = self.llm.invoke([{\"role\": \"user\", \"content\": prompt}])\n",
+    "            result = response.content.strip()\n",
+    "            \n",
+    "            if result.startswith(\"```\"):\n",
+    "                result = re.sub(r'^```(?:json)?\\s*', '', result)\n",
+    "                result = re.sub(r'\\s*```$', '', result)\n",
+    "\n",
+    "            parsed = json.loads(result)\n",
+    "            return json.dumps(parsed, ensure_ascii=False, indent=2)\n",
+    "\n",
+    "        except json.JSONDecodeError as e:\n",
+    "            return json.dumps({\n",
+    "                \"error\": \"LLM 返回格式异常\",\n",
+    "                \"detail\": str(e)\n",
+    "            }, ensure_ascii=False, indent=2)\n",
+    "        except Exception as e:\n",
+    "            return json.dumps({\n",
+    "                \"error\": \"分析过程中出错\",\n",
+    "                \"detail\": str(e)\n",
+    "            }, ensure_ascii=False, indent=2)\n",
+    "    def get_parameters(self):\n",
+    "        from hello_agents.tools import ToolParameter\n",
+    "        return []\n",
+    "\n",
+    "\n",
+    "print(\"✅ EmailDigestTool defined\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第4部分:日报生成 Pipeline\n",
+    "\n",
+    "编排完整流程:获取 → 分类摘要 → 统计 → 日报生成 → 保存文件"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ========================================\n",
+    "# 邮件日报生成 Pipeline\n",
+    "# ========================================\n",
+    "\n",
+    "@dataclass\n",
+    "class EmailDigestPipeline:\n",
+    "    \"\"\"邮件日报 Pipeline: 获取(IMAP/演示) -> LLM分类摘要 -> Markdown日报\"\"\"\n",
+    "\n",
+    "    llm: any\n",
+    "    use_demo: bool = True\n",
+    "    fetch_tool: EmailFetchTool = field(init=False)\n",
+    "    digest_tool: EmailDigestTool = field(init=False)\n",
+    "\n",
+    "    def __post_init__(self):\n",
+    "        self.fetch_tool = EmailFetchTool(use_demo=self.use_demo)\n",
+    "        self.digest_tool = EmailDigestTool(llm=self.llm)\n",
+    "\n",
+    "    def run(self, hours: int = 24, max_emails: int = 50) -> str:\n",
+    "        \"\"\"执行完整 Pipeline,返回 Markdown 日报\"\"\"\n",
+    "        mode = \"演示数据\" if self.use_demo else \"IMAP 真实邮箱\"\n",
+    "        console.print(Panel.fit(\n",
+    "            f\"模式: {mode}  |  时间: 最近{hours}h  |  上限: {max_emails}封\",\n",
+    "            title=\"📬 EmailDigestAgent\", style=\"cyan\"\n",
+    "        ))\n",
+    "\n",
+    "        # Step 1: Fetch\n",
+    "        console.print(\"\\n📥 [1/4] 获取邮件...\", style=\"cyan\")\n",
+    "        raw = self.fetch_tool.run(hours=hours, max_emails=max_emails)\n",
+    "        data = json.loads(raw)\n",
+    "        count = data.get(\"count\", len(data.get(\"emails\", [])))\n",
+    "        if count == 0:\n",
+    "            return self._empty_report()\n",
+    "        console.print(f\"   ✅ {count} 封邮件\")\n",
+    "\n",
+    "        # Step 2: LLM digest\n",
+    "        console.print(\"\\n🧠 [2/4] LLM 分类与摘要...\", style=\"cyan\")\n",
+    "        digest_raw = self.digest_tool.run(raw)\n",
+    "        digest = json.loads(digest_raw)\n",
+    "        items = digest.get(\"items\", [])\n",
+    "        if \"error\" in digest:\n",
+    "            console.print(f\"   ⚠️  {digest['error']}\")\n",
+    "        console.print(f\"   ✅ 已分析 {len(items)} 封\")\n",
+    "\n",
+    "        # Step 3: Stats\n",
+    "        console.print(\"\\n📊 [3/4] 统计汇总...\", style=\"cyan\")\n",
+    "        stats = self._compute_stats(items)\n",
+    "        console.print(f\"   🔴高:{stats['priorities'].get('高',0)}  🟡中:{stats['priorities'].get('中',0)}  🟢低:{stats['priorities'].get('低',0)}\")\n",
+    "\n",
+    "        # Step 4: Report\n",
+    "        console.print(\"\\n📝 [4/4] 生成日报...\", style=\"cyan\")\n",
+    "        report = self._generate_report(stats, items)\n",
+    "\n",
+    "        os.makedirs(\"output\", exist_ok=True)\n",
+    "        path = f\"output/email_digest_{datetime.now().strftime('%Y%m%d')}.md\"\n",
+    "        with open(path, 'w', encoding='utf-8') as f:\n",
+    "            f.write(report)\n",
+    "        console.print(f\"\\n📄 已保存: {path}\", style=\"bold green\")\n",
+    "        return report\n",
+    "\n",
+    "    def _compute_stats(self, items):\n",
+    "        cats = Counter(i.get(\"category\", \"other\") for i in items)\n",
+    "        prios = Counter(i.get(\"priority\", \"中\") for i in items)\n",
+    "        actions = sum(1 for i in items if i.get(\"action\", \"无\") != \"无\")\n",
+    "        return {\"total\": len(items), \"categories\": dict(cats),\n",
+    "                \"priorities\": dict(prios), \"need_action\": actions}\n",
+    "\n",
+    "    def _generate_report(self, stats, items):\n",
+    "        now = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n",
+    "        src = \"演示数据\" if self.use_demo else \"IMAP 收件箱\"\n",
+    "        order = {\"高\": 0, \"中\": 1, \"低\": 2}\n",
+    "        sorted_items = sorted(items, key=lambda x: order.get(x.get(\"priority\", \"中\"), 1))\n",
+    "\n",
+    "        lines = [\n",
+    "            f\"# 📬 邮件日报\",\n",
+    "            f\"**生成时间:** {now}  |  **来源:** {src}\",\n",
+    "            f\"---\",\n",
+    "            f\"## 📊 概览\",\n",
+    "            f\"| 指标 | 数值 |\",\n",
+    "            f\"|------|------|\",\n",
+    "            f\"| 总计 | {stats['total']} 封 |\",\n",
+    "            f\"| 🔴 高优先级 | {stats['priorities'].get('高', 0)} |\",\n",
+    "            f\"| 🟡 中优先级 | {stats['priorities'].get('中', 0)} |\",\n",
+    "            f\"| 🟢 低优先级 | {stats['priorities'].get('低', 0)} |\",\n",
+    "            f\"| 📋 需行动 | {stats['need_action']} |\",\n",
+    "            f\"\",\n",
+    "            f\"### 分类分布\",\n",
+    "        ]\n",
+    "        for cat, cnt in stats['categories'].items():\n",
+    "            lines.append(f\"- **{cat}**:{cnt} 封 {'█' * cnt}\")\n",
+    "        lines.append(f\"---\")\n",
+    "\n",
+    "        for pri, label in [(\"高\", \"🔴 高优先级 — 立即处理\"),\n",
+    "                            (\"中\", \"🟡 中优先级 — 今天处理\"),\n",
+    "                            (\"低\", \"🟢 低优先级 — 可忽略\")]:\n",
+    "            pitems = [i for i in sorted_items if i.get(\"priority\") == pri]\n",
+    "            if not pitems:\n",
+    "                continue\n",
+    "            lines.append(f\"## {label}\")\n",
+    "            lines.append(f\"| # | 发件人 | 主题 | 类型 | 一句话摘要 |\")\n",
+    "            lines.append(f\"|---|--------|------|------|-----------|\")\n",
+    "            for idx, item in enumerate(pitems, 1):\n",
+    "                fr = item.get(\"from\", \"\")\n",
+    "                subj = item.get(\"subject\", \"\")\n",
+    "                cat = item.get(\"category\", \"-\")\n",
+    "                summ = item.get(\"summary\", \"-\")\n",
+    "                lines.append(f\"| {idx} | {fr} | {subj} | {cat} | {summ} |\")\n",
+    "            lines.append(\"\")\n",
+    "            if pri in (\"高\", \"中\"):\n",
+    "                acts = [i for i in pitems if i.get(\"action\", \"无\") != \"无\"]\n",
+    "                if acts:\n",
+    "                    lines.append(\"**📋 行动项:**\")\n",
+    "                    for a in acts:\n",
+    "                        lines.append(f\"- [{a.get('category','')}] **{a.get('subject','')}** — {a.get('action','')}\")\n",
+    "                    lines.append(\"\")\n",
+    "\n",
+    "        lines.extend([\"---\", \"*EmailDigestAgent · HelloAgents*\"])\n",
+    "        return \"\\n\".join(lines)\n",
+    "\n",
+    "    def _empty_report(self):\n",
+    "        now = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n",
+    "        return (f\"# 📬 邮件日报\\n\\n**{now}**\\n\\n## 🎉 收件箱干净!\\n\\n\"\n",
+    "                f\"当前没有未读邮件。\\n\\n---\\n*EmailDigestAgent · HelloAgents*\")\n",
+    "\n",
+    "print(\"✅ EmailDigestPipeline defined\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第5部分:功能演示\n",
+    "\n",
+    "### 5.1 演示模式:生成邮件日报"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# 创建 Pipeline(演示模式)并运行\n",
+    "pipeline = EmailDigestPipeline(llm=llm, use_demo=True)\n",
+    "report = pipeline.run(hours=24, max_emails=20)\n",
+    "\n",
+    "# 渲染日报\n",
+    "console.print(\"\\n\" + \"=\" * 60)\n",
+    "console.print(\"📬 邮件日报\", style=\"bold cyan\")\n",
+    "console.print(\"=\" * 60 + \"\\n\")\n",
+    "console.print(Markdown(report))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 5.2 查看保存的日报文件"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import glob\n",
+    "reports = sorted(glob.glob(\"output/email_digest_*.md\"), reverse=True)\n",
+    "if reports:\n",
+    "    print(f\"Latest: {reports[0]}\")\n",
+    "    with open(reports[0], 'r', encoding='utf-8') as f:\n",
+    "        console.print(Markdown(f.read()))\n",
+    "else:\n",
+    "    print(\"No reports yet. Run the pipeline first.\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 5.3 数据统计可视化"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# 分类和优先级统计\n",
+    "emails_data = json.loads(pipeline.fetch_tool.run(hours=24, max_emails=20))\n",
+    "digest_data = json.loads(pipeline.digest_tool.run(json.dumps(emails_data)))\n",
+    "items = digest_data.get(\"items\", [])\n",
+    "\n",
+    "table = Table(title=\"📊 邮件分析统计\")\n",
+    "table.add_column(\"指标\", style=\"cyan\")\n",
+    "table.add_column(\"数值\", style=\"white\")\n",
+    "table.add_column(\"说明\", style=\"dim\")\n",
+    "table.add_row(\"总数\", str(len(items)), \"\")\n",
+    "table.add_row(\"需立即处理(高)\", str(sum(1 for i in items if i.get(\"priority\")==\"高\")), \"🔴\")\n",
+    "table.add_row(\"今天处理(中)\", str(sum(1 for i in items if i.get(\"priority\")==\"中\")), \"🟡\")\n",
+    "table.add_row(\"可延后(低)\", str(sum(1 for i in items if i.get(\"priority\")==\"低\")), \"🟢\")\n",
+    "table.add_row(\"需要行动\", str(sum(1 for i in items if i.get(\"action\",\"无\")!=\"无\")), \"📋\")\n",
+    "console.print(table)\n",
+    "\n",
+    "console.print(\"\\n📂 分类分布:\")\n",
+    "cats = Counter(i.get(\"category\",\"other\") for i in items)\n",
+    "for cat, cnt in cats.most_common():\n",
+    "    bar = \"█\" * (cnt * 3)\n",
+    "    console.print(f\"  {cat:8s}  {cnt:2d}  {bar}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第6部分:真实邮箱接入(IMAP 通用协议)\n",
+    "\n",
+    "支持 **QQ邮箱 / 163 / 126 / Gmail / Outlook** 等所有支持 IMAP 的邮箱。\n",
+    "\n",
+    "### 各邮箱 IMAP 配置速查"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(\"=\" * 60)\n",
+    "print(\"📧 真实邮箱接入指南\")\n",
+    "print(\"=\" * 60)\n",
+    "print()\n",
+    "print(\"【各邮箱 IMAP 配置】\")\n",
+    "print()\n",
+    "print(\"  QQ邮箱:\")\n",
+    "print(\"    server: imap.qq.com  port: 993\")\n",
+    "print(\"    步骤: QQ邮箱网页版 -> 设置 -> 账户 -> 开启IMAP/SMTP服务\")\n",
+    "print(\"          -> 验证密保 -> 获取授权码 (注意不是QQ密码)\")\n",
+    "print()\n",
+    "print(\"  网易163:\")\n",
+    "print(\"    server: imap.163.com  port: 993\")\n",
+    "print(\"    步骤: 网易邮箱网页版 -> 设置 -> POP3/SMTP/IMAP -> 开启IMAP\")\n",
+    "print(\"          -> 获取授权码\")\n",
+    "print()\n",
+    "print(\"  网易126:\")\n",
+    "print(\"    server: imap.126.com  port: 993\")\n",
+    "print(\"    步骤: 同163邮箱\")\n",
+    "print()\n",
+    "print(\"  Gmail:\")\n",
+    "print(\"    server: imap.gmail.com  port: 993\")\n",
+    "print(\"    步骤: Google账户 -> 安全性 -> 两步验证 -> 应用专用密码\")\n",
+    "print()\n",
+    "print(\"  Outlook/Hotmail:\")\n",
+    "print(\"    server: outlook.office365.com  port: 993\")\n",
+    "print(\"    步骤: Microsoft账户 -> 安全性 -> 应用密码\")\n",
+    "print()\n",
+    "print(\"【配置方式二选一】\")\n",
+    "print()\n",
+    "print(\"  方式1 (推荐) - 编辑 .env 文件:\")\n",
+    "print(\"    IMAP_SERVER=imap.qq.com\")\n",
+    "print(\"    IMAP_PORT=993\")\n",
+    "print(\"    IMAP_USERNAME=your_email@qq.com\")\n",
+    "print(\"    IMAP_PASSWORD=你的授权码\")\n",
+    "print()\n",
+    "print(\"  方式2 - 编辑 config/email_config.json:\")\n",
+    "print(\"    修改 imap 段中的 server / port / username / password\")\n",
+    "print()\n",
+    "print(\"【运行真实模式】\")\n",
+    "print(\"  pipeline = EmailDigestPipeline(llm=llm, use_demo=False)\")\n",
+    "print(\"  report = pipeline.run(hours=24, max_emails=50)\")\n",
+    "print(\"  console.print(Markdown(report))\")\n",
+    "print()\n",
+    "print(\"=\" * 60)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 真实模式演示\n",
+    "\n",
+    "请先按上面指南完成配置后再运行。连接失败时会直接报错并展示原因,方便排查。"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ========================================\n",
+    "# 真实 IMAP 模式(取消注释以运行)\n",
+    "# ========================================\n",
+    "#\n",
+    "# 配置好 .env 中的 IMAP_SERVER/IMAP_PORT/IMAP_USERNAME/IMAP_PASSWORD 后运行:\n",
+    "#\n",
+    "# pipeline_real = EmailDigestPipeline(llm=llm, use_demo=False)\n",
+    "# report = pipeline_real.run(hours=24, max_emails=50)\n",
+    "# console.print(Markdown(report))\n",
+    "\n",
+    "print(\"💡 取消上面代码的注释,配置好 .env 中的 IMAP 信息后即可运行真实模式。\")\n",
+    "print(\"如果 IMAP 连接失败,会直接报错并展示原因,方便排查。\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 第7部分:项目总结\n",
+    "\n",
+    "### ✅ 实现功能\n",
+    "\n",
+    "- **邮件获取**:支持 IMAP 协议(QQ/163/126/Gmail/Outlook)+ 模拟数据双模式,真实模式连接失败会直接报错\n",
+    "- **MIME 解码**:正确处理中文主题、发件人名、多 part 邮件和 HTML 邮件\n",
+    "- **LLM 分类**:6 种类型(工作/客户/个人/通知/促销/垃圾)+ 3 级优先级\n",
+    "- **AI 摘要**:语义理解邮件内容,生成一句话中文摘要 + 关键行动项\n",
+    "- **日报生成**:Markdown 日报,按优先级分区,含统计概览和行动清单\n",
+    "- **文件持久化**:自动保存到 output/ 目录\n",
+    "\n",
+    "### 🎯 设计亮点\n",
+    "\n",
+    "- **真实痛点**:收件箱过载是每个职场人的日常困扰\n",
+    "- **IMAP 通用**:不再绑定 Gmail,国内邮箱原生支持\n",
+    "- **严格报错**:真实模式下 IMAP 连接失败会直接抛出异常,方便排查\n",
+    "- **Pipeline 清晰**:获取 -> 分类/摘要 -> 日报,四步职责分明\n",
+    "\n",
+    "### 🔮 未来方向\n",
+    "\n",
+    "- 定时任务(每天早上 8:00 自动运行)\n",
+    "- 日报推送至微信/钉钉\n",
+    "- 周报/月报趋势统计\n",
+    "\n",
+    "---\n",
+    "\n",
+    "<div align=\"center\">\n",
+    "  <strong>📬 EmailDigestAgent — 让 AI 帮你读完收件箱</strong><br>\n",
+    "  HelloAgents · Datawhale 社区共创\n",
+    "</div>"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": ".venv (3.14.2.final.0)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.14.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 3 - 0
Co-creation-projects/Johnx-w-EmailDigestAgent/requirements.txt

@@ -0,0 +1,3 @@
+hello-agents>=0.1.1
+python-dotenv>=1.0.0
+rich>=13.0.0