一.项目
1.1 项目定位
EmailAgent(MailFriend)是一个基于LangGraph AgentMiddleware的智能邮件助手系统,核心目标是:
通过自然语言对话完成邮箱认证、收件检查、邮件发送
演示 LangGraph 中间件机制(动态工具选择 + 动态提示词切换)
提供类 ChatGPT 的 SSE 流式交互体验
1.2 技术栈
| 层级 | 技术选型 | 版本/配置 |
|---|---|---|
| LLM | DashScope qwen3.7-plus | OpenAI 兼容模式 |
| Agent 框架 | LangGraph create_agent + AgentMiddleware | langchain>=1.3.14 |
| Web 框架 | FastAPI + uvicorn | fastapi>=0.141.1 |
| 前端 | 原生 HTML/CSS/JS(无框架) | SSE + ReadableStream |
| 数据库 | SQLite 3 | sessions + messages 两表 |
| Checkpointer | InMemorySaver | 不跨重启持久化 |
| 可观测性 | LangSmith Tracing | 已接入 |
1.3 核心功能(当前全为 mock)
| # | 功能 | 工具函数 | 当前实现 | 真实需要 |
|---|---|---|---|---|
| 1 | 邮箱认证 | authenticate(email, password) | 硬编码xxx@qq.com/123456 | 数据库查询 + bcrypt 哈希 |
| 2 | 检查收件箱 | check_email() | 固定返回 "No new emails" | IMAP 协议拉取真实邮件 |
| 3 | 发送邮件 | send_email(email, subject, body) | 固定返回 "Email sent successfully" | SMTP 协议真实发送 |
1.4 项目效果
二.问题解决汇总
核心问题详解:
2.1 会话发起无响应
SSE 无回应 — 后端三个叠加 Bug
修改前 # Bug 1 — 错误 key event_type = chunk.get("event_type") # 永远 None # Bug 2 — yield dict yield {"event": "message", "data": payload} # Bug 3 — 路由内 new @router.post("/stream") async def chat_stream(request): agent = EmailAgent() return StreamingResponse(agent.generate_sse(...))2.1.1 chunk key 混淆
LangGraph v2 的 stream chunk 结构为:
{"type": "messages"|"updates", "ns": [...], "data": ...}最初代码用chunk.get("event_type")取事件类型,但正确的 key 是type。event_type永远返回None,所有 chunk 都被跳过。
2.1.2 yield 格式不符合 SSE 规范
FastAPIStreamingResponse期望的是字符串/字节。yield dict 后被str()转成{'event': 'message', ...}(Python repr 格式),前端JSON.parse全部失败。
2.1.3 路由每次新建空实例
EmailAgent()是空壳——self.agent和self.checkpointer都是None。虽然generate_sse内部有if not self.agent: await self.init()兜底,但每次请求都重新初始化意味着 checkpointer 不共享、对话状态不保留。
修改后 # Bug 1 — 正确 key event_type = chunk.get("type") # "messages" | "updates" # Bug 2 — yield SSE 格式字符串 yield f"data: {json.dumps({'event': 'message', 'data': payload}, ensure_ascii=False)}\n\n" # Bug 3 — 使用 lifespan 单例 from app.agents.email_agent import email_agent # 模块级单例 @router.post("/stream") async def chat_stream(request: ChatRequest): return StreamingResponse( email_agent.generate_sse( thread_id=request.thread_id, message=request.message or "", interrupt_decision=request.interrupt_decision, ), media_type="text/event-stream", )2.2 电子邮件密码均正确但未收到回复
原本使用thinking 模型延迟 40s+ → 首响应极慢
| 模型 | 首 token 延迟 | 适用场景 | 决策 |
|---|---|---|---|
qwen3-vl-32b-thinking | 20-40s | 深度推理、代码生成 | ❌ 不适合 SSE 聊天 |
qwen3.7-plus | ~2s | 通用对话、指令跟随 | ✅ 选定 |
qwen-plus | ~1s | 轻量对话 | 备选 |
2.3 邮件显示成功发送,但实际该邮箱并未收到
架构机制是成品,业务功能是空壳。
Middleware 动态工具选择 + 动态提示词 + SSE 流式三件套已跑通,但 authenticate/check_email/send_email 三个核心工具全是 mock,需要分别接入数据库认证、IMAP、SMTP 才能真正可用。