📬 邮件日报 - 2026-07-02
【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents
📊 概览
- 总邮件数: 10
- 高优先级: 3 | 中优先级: 4 | 低优先级: 3
🔴 高优先级
| # | 发件人 | 主题 | 类型 | 一句话摘要 |
|---|---|---|---|---|
| 1 | boss@corp.com | 紧急:Q3预算确认 | 工作 | 老板要求今天内确认Q3预算方案 |
...
🟡 中优先级 | 🟢 低优先级 | 🗑️ 垃圾/促销
(按优先级分区展示)
## 4. Pipeline 总体结构:EmailDigestPipeline 整个系统由一个 dataclass `EmailDigestPipeline` 编排,构造时自动实例化两个工具: ```python @dataclass class EmailDigestPipeline: """邮件日报 Pipeline: 获取(IMAP/演示) -> LLM分类摘要 -> Markdown日报""" llm: any use_demo: bool = True fetch_tool: EmailFetchTool = field(init=False) digest_tool: EmailDigestTool = field(init=False) def __post_init__(self): self.fetch_tool = EmailFetchTool(use_demo=self.use_demo) self.digest_tool = EmailDigestTool(llm=self.llm) def run(self, hours: int = 24, max_emails: int = 50) -> str: # Step 1: Fetch —— 获取邮件,空收件箱直接返回"干净"日报 # Step 2: LLM digest —— 分类、定级、一句话摘要 # Step 3: Stats —— 分类/优先级 Counter 统计 # Step 4: Report —— 生成 Markdown 并保存到 output/email_digest_YYYYMMDD.md执行流程上,run()依次打印[1/4] 获取邮件、[2/4] LLM 分类与摘要、[3/4] 统计汇总、[4/4] 生成日报四个进度标记;若取回的邮件数为 0,会跳过 LLM 调用,直接返回一份"收件箱干净"的空日报_empty_report()。最终日报自动写入:
os.makedirs("output", exist_ok=True) path = f"output/email_digest_{datetime.now().strftime('%Y%m%d')}.md"即每天一份日报文件,重复运行会覆盖当天的文件。
为什么用 Tool 而不是 ReAct?
从源码结构看,两个工具类都继承 HelloAgents 的Tool基类(可参考仓库内框架实现的 Tool 基类),但它们的get_parameters()均返回空列表——这意味着本项目并没有把它们暴露给 LLM 做 function calling,而是作为由 Pipeline 直接顺序调用的轻量步骤组件。这是一个有代表性的设计取舍:对于"获取→分析→输出"这类步骤确定、无需 LLM 自主决策工具调用的任务,显式 Pipeline 比 ReAct 循环更可控、更省 token;而 Tool 抽象则保留了统一接口,便于日后接入 Agent 框架。
5. EmailFetchTool:IMAP 通用获取与 MIME 解码
EmailFetchTool是最贴近"工程脏活"的组件,实现了三个关键能力。
5.1 配置读取:环境变量优先,配置文件兜底
def _read_imap_config(self) -> tuple: server = os.getenv("IMAP_SERVER") or "" port = int(os.getenv("IMAP_PORT") or "0") username = os.getenv("IMAP_USERNAME") or "" password = os.getenv("IMAP_PASSWORD") or "" if not (username and password and server): try: with open("config/email_config.json", "r", encoding="utf-8") as f: cfg = json.load(f) imap_cfg = cfg.get("imap", {}) server = server or imap_cfg.get("server", "imap.qq.com") port = port or imap_cfg.get("port", 993) username = username or imap_cfg.get("username", "") password = password or imap_cfg.get("password", "") except FileNotFoundError: pass return server or "imap.qq.com", port or 993, username, password优先级为:.env环境变量 > config/email_config.json 的imap段 > 默认值imap.qq.com:993。若用户名或密码为空,直接抛出ValueError("未配置邮箱信息"),不做静默降级。
5.2 连接与搜索:健壮性细节
mail = imaplib.IMAP4_SSL(server, port) mail.login(username, password) # 选择收件箱 — 依次尝试: 无引号, 带引号, readonly for name, rdonly in [("INBOX", False), ('"INBOX"', False), ("INBOX", True)]: select_status, select_data = mail.select(name, readonly=rdonly) if select_status == "OK": break since_date = (datetime.now() - timedelta(hours=hours)).strftime("%d-%b-%Y") search_criteria = f'(UNSEEN SINCE {since_date})' status, message_ids = mail.search(None, search_criteria)三个值得注意的实现细节:
- select 三级回退:不同 IMAP 服务器对
INBOX的接受形式不一致,代码依次尝试无引号、带引号、只读模式,任一成功即退出; - 只取未读 + 时间窗过滤:
UNSEEN SINCE组合保证日报只覆盖"最近 N 小时的新未读邮件",避免每次重读历史邮件; - 倒序取上限:
ids = list(reversed(ids))[:max_emails]保证优先处理最新邮件。
异常策略上,run()捕获imaplib.IMAP4.error后包装为RuntimeError抛出,连接失败会直接报错并展示原因(README 中也特别强调了这一点),而不是吞掉异常返回空结果——这对排查授权码错误、端口不通等问题至关重要。
5.3 MIME 解码与正文提取
真实邮件头常常是=?UTF-8?B?...?=编码的,正文可能是 multipart 嵌套结构,因此工具实现了两个辅助函数:
_decode_mime_header():逐段调用decode_header解码 From/Subject,遇到未知字符集时回退到utf-8 + errors='replace',保证中文主题、发件人名不乱码;_parse_email_body():优先提取text/plain正文(跳过 attachment 部分);找不到时回退到text/html并用正则re.sub(r'<[^>]+>', '', body)剥掉 HTML 标签;最后统一return body[:3000],把正文截断到 3000 字符再送入 LLM——这是对输入 token 成本的一道硬约束。
解析出的每封邮件统一为四字段结构:
{"id": msg_id_str, "from": ..., "subject": ..., "date": ..., "body": ...}工具的输出是 JSON 字符串:演示模式或 IMAP 模式都返回{"count": N, "fetch_time": "...", "emails": [...]};无邮件时返回{"message": "没有新的未读邮件", "emails": []},作为 Pipeline 各步骤之间的数据契约。
6. EmailDigestTool:LLM 驱动的分类与摘要
EmailDigestTool是整个系统的"智能核心"。它不做关键词匹配,而是把整批邮件 JSON 交给 LLM,用一段结构化提示词约束输出:
CATEGORIES = ["工作", "客户", "个人", "通知", "促销", "垃圾"] PRIORITIES = ["高", "中", "低"] def _build_prompt(self, emails_json: str, count: int) -> str: return f"""你是一个专业的邮件分析助手。请分析以下邮件列表并返回结构化结果。 邮件数据(JSON格式): {emails_json} 分析规则: 1. 分类 - 从 [{cats}] 中选择最合适的一类 2. 优先级 - 从 [{prios}] 中选择: 高: 需要立即处理(老板指令、客户需求、合同、截止日期) 中: 需要处理但不紧急(项目讨论、同事沟通、HR通知) 低: 可忽略(新闻订阅、促销广告、系统通知、垃圾邮件) 3. 一句话摘要 - 用中文提炼核心内容,包含关键动作或截止时间 4. 关键行动 - 若需要回复或行动,用1-2句话说明;否则填"无" 严格按以下JSON格式返回(只返回JSON,不加其他文字):..."""提示词设计上,优先级判定给出了明确的语义锚点("老板指令、客户需求、合同、截止日期"→高),这显著降低了 LLM 定级的随意性。输出契约是严格的 JSON Schema 风格:
{ "analyzed_at": "当前时间", "total": 10, "items": [ { "id": "邮件ID", "from": "发件人", "subject": "主题", "category": "分类", "priority": "优先级", "summary": "一句话摘要", "action": "关键行动" } ] }run()中做了两层 LLM 输出的防御性处理,这是接入真实模型时非常实用的经验:
response = self.llm.invoke([{"role": "user", "content": prompt}]) result = response.content.strip() if result.startswith("```"): result = re.sub(r'^```(?:json)?\s*', '', result) result = re.sub(r'\s*```$', '', result) parsed = json.loads(result)- 很多模型会把 JSON 包在 markdown 代码围栏里返回,这里先剥离
```json/```再解析; - 若
json.JSONDecodeError或任何异常发生,不抛出而是返回{"error": "...", "detail": "..."}结构,让 Pipeline 能继续走完并提示用户,而不是整个日报生成崩溃。
7. 配置文件详解:config/email_config.json
除.env外,项目还提供了一份 JSON 配置文件作为备用配置源与速查手册,完整内容如下(config/email_config.json):
{ "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"] } }字段说明:
| 字段 | 作用 |
|---|---|
imap.server/imap.port | IMAP 服务器地址与端口(993 为 IMAP over SSL 标准端口,对应代码中的IMAP4_SSL) |
imap.username/imap.password | 邮箱地址与授权码(注意:QQ/163 不是登录密码,而是各邮箱"设置"里生成的 IMAP 授权码) |
imap.max_emails_per_fetch | 单次获取的邮件上限参考值,运行时由run(max_emails=...)实际控制 |
presets | 五大主流邮箱的服务器预设速查,方便切换 |
classification_categories | 6 类分类的语义定义(work/client/personal/notification/promotion/spam),与EmailDigestTool的 6 个中文分类一一对应 |
priority_rules | 高/低优先级的关键词参考(紧急、deadline、合同 → 高;newsletter、周报、promotion → 低) |
从源码结构看,classification_categories与priority_rules目前作为语义参考保存在配置文件中,实际的分类判定由 LLM 在提示词中完成;这两个段更像是给 LLM 提示词设计或未来"规则预筛 + LLM 精判"混合策略预留的结构化依据。
8. 真实邮箱接入:各邮箱 IMAP 配置速查
支持 QQ邮箱 / 163 / 126 / Gmail / Outlook 等所有支持 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段中的server/port/username/password。
配置完成后,use_demo=False即可切换为真实模式。Notebook 第 6 部分还内置了一个"接入指南"打印单元格,运行后会输出各邮箱的完整配置步骤文本,便于在 Notebook 内直接查阅。
真实模式的调用方式(Notebook 中默认注释掉,配置完成后取消注释即可运行):
pipeline_real = EmailDigestPipeline(llm=llm, use_demo=False) report = pipeline_real.run(hours=24, max_emails=50) console.print(Markdown(report))9. 日报生成:统计、分区与行动项
日报的生成逻辑在_generate_report()中,几个关键设计:
统计(_compute_stats)使用Counter汇总:
def _compute_stats(self, items): cats = Counter(i.get("category", "other") for i in items) prios = Counter(i.get("priority", "中") for i in items) actions = sum(1 for i in items if i.get("action", "无") != "无") return {"total": len(items), "categories": dict(cats), "priorities": dict(prios), "need_action": actions}注意priorities缺省回退为"中"——即使 LLM 漏掉某封邮件的定级,也不会导致报表分区缺失。
报告版式按"概览 → 分类分布 → 按优先级分区 → 行动项"组织:
- 概览表:总数、🔴 高 / 🟡 中 / 🟢 低优先级数量、📋 需行动数量;
- 分类分布:每类用一个
█柱状条可视化,如工作 3 ███; - 优先级分区:邮件按"高/中/低"排序后分三节展示,各节标题带处置语义("立即处理"/"今天处理"/"可忽略"),每节一张
| # | 发件人 | 主题 | 类型 | 一句话摘要 |表格,空分区自动跳过; - 行动项清单:仅高/中优先级分区会追加
**📋 行动项:**列表,把action字段非"无"的邮件汇总成- [分类] **主题** — 行动说明形式,直接变成可勾选的 to-do。
Notebook 第 5 部分提供了三种验证方式:直接渲染日报(console.print(Markdown(report)))、从output/目录读回最新日报文件、以及用rich.table.Table+Counter输出"总数 / 需立即处理 / 今天处理 / 可延后 / 需要行动"的统计表格和分类柱状图。
10. 项目文件结构与延伸阅读
当前仓库中该项目的实际文件布局(以目录实况为准):
Co-creation-projects/Johnx-w-EmailDigestAgent/ ├── main.ipynb # 主 Jupyter Notebook(环境配置→模拟数据→工具定义→Pipeline→演示→IMAP接入→总结) ├── requirements.txt # Python 依赖列表 ├── README.md # 项目说明文档 └── config/ └── email_config.json # 邮箱/分类/优先级配置【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考