Reactance0083/pydantic-ai-email-linear-auto-triage

GitHub: Reactance0083/pydantic-ai-email-linear-auto-triage

一个基于 FastAPI 的预览版脚手架,使用 Claude AI 自动将收件箱中的邮件分类、提取关键信息并转化为带有优先级的 Linear 工单。

Stars: 1 | Forks: 0

## 当前状态 本仓库是一个预览版/未经验证的入门项目。它不是活跃的旗舰产品,目前也未经过商业验证。在进行全新的商业就绪检查并移除此通知之前,请勿将其视为可直接购买的生产包。 # Email→Linear Issue 自动分拣 使用 AI 驱动的分拣自动将收到的电子邮件转换为已确定优先级的 Linear issue。从原始邮件内容中提取客户信息、优先级和问题类型,然后通过 Slack 针对紧急事项发送通知,并在您的 Linear 工作区中即时创建结构化的工单。 ## 概述 此模板提供了一个预览版 FastAPI webhook 服务,它可以: - 通过 SMTP 转发或 Gmail API 集成接收电子邮件 - 使用 Claude AI 提取优先级、客户和问题分类 - 创建包含丰富元数据和适当链接的 Linear issue - 为高优先级工单发送 Slack 警报 - 存储分拣决策以供审计和完善 **为什么使用它?** - **节约成本:** 消除每月 20-40 美元的 Zapier 费用及人工分拣开销 - **速度:** 30 秒的邮件到工单流水线,而人工路由需要 5 分钟 - **一致性:** AI 驱动的分类减少了优先级分配中的人为错误 - **可扩展性:** 基于 Pydantic AI 构建,易于自定义分拣逻辑 ## 它的功能 ### 邮件摄取 - 接收原始邮件 POST payload(SMTP webhook 格式或解析后的 JSON) - 提取发件人、主题、正文和附件元数据 - 支持纯文本和 HTML 邮件正文 ### AI 驱动的分拣 使用 Claude 从邮件内容中提取: - **优先级**(urgent/high/normal/low) - **客户标识符**(邮件域、姓名、账户 ID) - **问题类型**(bug/feature-request/support/billing) - **摘要**(根据主题 + 正文上下文自动生成) - **建议的指派人**(基于问题类型模式,可选) ### Linear 集成 - 在您的 Linear 工作区中创建 issue - 将原始邮件作为评论附加到 issue 中 - 根据分拣输出设置优先级和状态 - 链接到客户/团队项目(可配置) - 支持用于邮件元数据的自定义字段 ### Slack 通知 - 将紧急/高优先级的工单发布到指定频道 - 包含客户信息、issue 链接和优先级徽章 - 可选的主题回复以供后续更新 ### 审计与历史记录 - 将所有分拣决策存储在 SQLite(或配置的 DB)中 - 支持性能监控和模型完善 - 支持手动覆盖和反馈循环 ## 前置条件 - **Python 3.11+** - **Linear API token**(在 [Settings > API > Personal API Keys](https://linear.app/settings/api) 中创建) - **Claude API key**(来自 [Anthropic Console](https://console.anthropic.com/)) - **Slack webhook URL**(可选,来自 [Slack Apps](https://api.slack.com/apps)) - **Gmail API 凭据**或 SMTP 中继服务(可选,用于邮件摄取) ### 可选项 - Docker + Docker Compose(用于容器化部署) - PostgreSQL(用于生产数据库,默认为 SQLite) ## 设置 ### 1. 克隆并安装依赖 ``` git clone https://github.com/yourusername/email-linear-triage.git cd email-linear-triage python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate pip install -r requirements.txt ``` ### 2. 创建环境文件 在项目根目录创建 `.env`: ``` # API Keys ANTHROPIC_API_KEY=sk-ant-... LINEAR_API_KEY=lin_api_... SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL # Optional # Linear 配置 LINEAR_TEAM_ID=acme # Your Linear team slug (e.g., 'acme' from linear.app/acme) LINEAR_PROJECT_ID=INB # Project key for incoming emails (default: 'INB') LINEAR_DEFAULT_STATUS=backlog # Initial status for new issues # Email 配置 SMTP_SECRET_TOKEN=your-secret-token-here # For webhook authentication EMAIL_DOMAIN=yourdomain.com # Database(可选) DATABASE_URL=sqlite:///./triage.db # Or: postgresql://user:pass@localhost/triage # Feature Flags ENABLE_SLACK_NOTIFICATIONS=true ENABLE_AUTO_ASSIGN=false TRIAGE_MODEL=claude-3-5-sonnet-20241022 # Claude model to use ``` ### 3. 初始化数据库 ``` python -m alembic upgrade head ``` 或对于 SQLite(自动创建): ``` python -c "from app.db import init_db; init_db()" ``` ### 4. 运行服务器 ``` uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload ``` 服务器运行在 `http://localhost:8000` ### 5. 配置邮件路由 **选项 A:SMTP 转发**(推荐) - 在您的邮件提供商处设置转发规则: - **From:** `tickets@yourdomain.com` - **To:** `{your-server}/webhook/email`(带身份验证) **选项 B:Gmail API** - 在 Google Cloud Console 中启用 [Gmail API](https://developers.google.com/gmail/api/quickstart/python) - 将凭据 JSON 下载到 `./credentials.json` - 应用会定期自动获取带标签的邮件 **选项 C:手动测试** ``` curl -X POST http://localhost:8000/webhook/email \ -H "X-Webhook-Token: your-secret-token-here" \ -H "Content-Type: application/json" \ -d '{ "from": "customer@example.com", "subject": "Payment processing is broken", "body": "Hi, our recurring invoices havent charged for 2 days. This is urgent!", "timestamp": "2024-01-15T14:30:00Z" }' ``` ### 6. 设置 Linear Webhook(可选,用于未来的集成) 在 Linear Settings > Integrations > Webhooks 中,添加: - **URL:** `{your-server}/webhook/linear-event` - **Events:** Issue created、issue updated - 可用于通过邮件回复关闭 issue ## 用法 ### 基础的邮件到 Issue 流程 1. **邮件到达** `tickets@yourdomain.com`(通过 SMTP 转发) 2. **Webhook 处理程序** 接收 POST 请求并验证 token 3. **Claude 分拣** 对邮件进行分类(2-5 秒) 4. **创建 Linear issue** 并附带提取的元数据 5. **发送 Slack 通知**(如果为 urgent/high) 6. **返回响应**,包含 issue URL ### 示例:发送邮件 ``` curl -X POST http://localhost:8000/webhook/email \ -H "X-Webhook-Token: your-secret-token-here" \ -H "Content-Type: application/json" \ -d '{ "from": "sarah@acmecorp.com", "subject": "[BUG] Dashboard crashes on mobile", "body": "When I open the dashboard on iPhone, it instantly crashes. Happens every time. Our team cant work.", "timestamp": "2024-01-15T09:30:00Z" }' ``` **响应(201 Created):** ``` { "status": "success", "linear_issue_id": "INB-234", "linear_issue_url": "https://linear.app/acme/issue/INB-234", "triage_result": { "priority": "urgent", "issue_type": "bug", "customer_domain": "acmecorp.com", "summary": "Dashboard mobile app crashes on iOS", "suggested_assignee": "eng-mobile" }, "slack_notification_sent": true, "processing_time_ms": 3200 } ``` ## API 端点 ### POST `/webhook/email` **摄取原始邮件并创建 Linear issue** **请求头:** ``` X-Webhook-Token: {SMTP_SECRET_TOKEN} Content-Type: application/json ``` **请求体:** ``` { "from": "customer@example.com", "subject": "Issue title", "body": "Email body text", "html_body": "

HTML version (optional)

", "timestamp": "2024-01-15T10:00:00Z", "attachments": [ { "filename": "screenshot.png", "content_base64": "iVBORw0KGgoAAAANS...", "mime_type": "image/png" } ] } ``` **响应(201 Created):** ``` { "status": "success|error", "linear_issue_id": "INB-123", "linear_issue_url": "string", "triage_result": { "priority": "urgent|high|normal|low", "issue_type": "bug|feature|support|billing", "customer_domain": "string", "customer_name": "string (optional)", "summary": "string", "suggested_assignee": "string (optional)" }, "slack_notification_sent": boolean, "error": "string (if status='error')" } ``` ### POST `/api/triage/override/{issue_id}` **手动覆盖 AI 分拣决策** **请求头:** ``` X-API-Key: {LINEAR_API_KEY} Content-Type: application/json ``` **请求体:** ``` { "priority": "high", "issue_type": "bug", "notes": "Manually corrected from 'low' due to context" } ``` **响应(200 OK):** ``` { "status": "updated", "triage_record_id": "uuid", "changes": { "priority": {"old": "normal", "new": "high"} } } ``` ### GET `/api/triage/history` **检索分拣历史记录和指标** **查询参数:** - `limit=50`(默认) - `offset=0` - `priority_filter=urgent|high|normal|low`(可选) - `date_from=2024-01-01`(可选) - `date_to=2024-01-31`(可选) **响应(200 OK):** ``` { "total_processed": 342, "results": [ { "id": "uuid", "email_from": "customer@example.com", "linear_issue_id": "INB-234", "priority": "high", "issue_type": "bug", "created_at": "2024-01-15T09:30:00Z", "processing_time_ms": 3200, "model_confidence": 0.94 } ], "statistics": { "avg_processing_time_ms": 2800, "priority_distribution": { "urgent": 15, "high": 87, "normal": 198, "low": 42 }, "issue_type_distribution": { "bug": 124, "feature": 56, "support": 142, "billing": 20 } } } ``` ### GET `/health` **服务健康检查** **响应(200 OK):** ``` { "status": "healthy", "timestamp": "2024-01-15T10:00:00Z", "dependencies": { "anthropic": "ok", "linear": "ok", "slack": "ok", "database": "ok" } } ``` ## 配置 ### 环境变量 | 变量 | 必需 | 默认值 | 描述 | |----------|----------|---------|-------------| | `ANTHROPIC_API_KEY` | ✓ | — | 来自 Anthropic Console 的 Claude API key | | `LINEAR_API_KEY` | ✓ | — | 来自 Settings > API 的 Linear API token | | `LINEAR_TEAM_ID` | ✓ | — | Linear team slug(例如,'acme') | | `LINEAR_PROJECT_ID` | | `INB` | 用于新 issue 的 Linear 项目 key | | `LINEAR_DEFAULT_STATUS` | | `backlog` | 初始 issue 状态(backlog/todo/in_progress) | | `SMTP_SECRET_TOKEN` | ✓ | — | 用于 webhook 身份验证的 secret token | | `SLACK_WEBHOOK_URL` | | — | Slack webhook URL(留空则禁用) | | `ENABLE_SLACK_NOTIFICATIONS` | | `true` | 为 urgent/high 发布通知 | | `ENABLE_AUTO_ASSIGN` | | `false` | 根据问题类型自动指派 | | `TRIAGE_MODEL` | | `claude-3-5-sonnet-20241022` | Claude 模型(使用 3-opus-20250219 获得最高准确度) | | `DATABASE_URL` | | `sqlite:///./triage.db` | PostgreSQL 或 SQLite 连接字符串 | | `GMAIL_CREDENTIALS_PATH` | | `./credentials.json` | Gmail API 凭据路径(如果使用 Gmail) | | `EMAIL_DOMAIN` | | — | 您的邮件域(用于 reply-to 头) | | `LOG_LEVEL` | | `INFO` | 日志记录级别(DEBUG/INFO/WARNING/ERROR) | ### Pydantic AI 配置 编辑 `app/config.py` 进行自定义: ``` # Claude model 设置 TRIAGE_MODEL = "claude-3-5-sonnet-20241022" # Change to claude-3-opus-20250219 for higher accuracy # Triage 分类阈值 PRIORITY_KEYWORDS = { "urgent": ["critical", "down", "broken", "asap", "emergency"], "high": ["bug", "broken", "failing", "urgent"], "normal": ["feature", "improve"], "low": ["typo", "minor", "nice-to-have"] } # Linear 字段映射 LINEAR_PRIORITY_MAP = { "urgent": 4, # Urgent in Linear "high": 3, "normal": 2, "low": 1 } ``` ## 自定义 ### 更改分拣提示词 编辑 `app/agents/triage_agent.py`: ``` TRIAGE_SYSTEM_PROMPT = """You are an expert customer support triage system... Analyze the email and extract: 1. Priority (urgent/high/normal/low) - consider customer tone, service impact, frequency 2. Issue type (bug/feature/support/billing) - classify by nature 3. Customer identifier - extract domain or company name 4. Concise summary - max 10 words 5. Suggested team - based on issue type """ ``` ### 添加自定义 Issue 字段 在 `app/models/triage.py` 中,扩展 `TriageResult`: ``` class TriageResult(BaseModel): priority: str issue_type: str customer_domain: str summary: str custom_field_1: str | None = None # Add your field ``` 然后更新 Claude 提示词以提取它,并在 `app/integrations/linear.py` 中更新 Linear 创建逻辑: ``` custom_field_id = "LIN_CUSTOM_1" issue_data["fieldValues"].append({ "fieldId": custom_field_id, "value": triage_result.custom_field_1 }) ``` ### 将 Issue 路由到不同项目 在 `app/integrations/linear.py` 中,修改项目选择: ``` def get_target_project(triage_result: TriageResult) -> str: if triage_result.issue_type == "billing": return "BIL" # Billing project elif triage_result.customer_domain == "enterprise.com": return "ENT" # Enterprise project return settings.LINEAR_PROJECT_ID ``` ### 自定义 Slack 消息 在 `app/integrations/slack.py` 中,编辑 Slack payload: ``` blocks = [ { "type": "section", "text": { "type": "mrkdwn", "text": f"🔴 *URGENT: {triage_result.summary}*\nCustomer: {triage_result.customer_domain}\n<{issue_url}|View in Linear>" } } ] ``` ### 使用不同的 Claude 模型 获得**更高的准确度**(更慢 + 更贵): ``` TRIAGE_MODEL=claude-3-opus-20250219 python -m uvicorn app.main:app ``` **更低成本**(更快): ``` TRIAGE_MODEL=claude-3-5-haiku-20241022 python -m uvicorn app.main:app ``` ### 添加数据库持久化 从 SQLite 切换到 PostgreSQL: ``` pip install psycopg2-binary export DATABASE_URL=postgresql://user:password@localhost:5432/triage python -m alembic upgrade head ``` ## 测试 ### 运行单元测试 ``` pytest tests/ -v ``` ### 本地测试分拣 Agent ``` python -m app.agents.triage_agent --email-from "customer@example.com" --subject "Payment failed" --body "We can't process payments today" ``` ### 模拟邮件 Webhook ``` python scripts/test_email_webhook.py ``` ## 部署 ### Docker Compose ``` docker-compose up -d ``` 查看 `docker-compose.yml` 了解生产环境配置(PostgreSQL、环境变量)。 ### Heroku ``` git push heroku main heroku config:set ANTHROPIC_API_KEY=sk-ant-... heroku config:set LINEAR_API_KEY=lin_api_... ``` ### AWS Lambda ``` pip install aws-wsgi # 有关 container image 设置,请参见 Dockerfile.lambda ``` ## 故障排除 **"Invalid Linear API Key"** - 在 [Settings > API > Personal API Keys](https://linear.app/settings/api) 中验证 token - 确保 token 具有 `read` 和 `write` 权限 **"Claude rate limit exceeded"** - 升级 Anthropic 套餐或实现请求队列 - 在高峰时段批量处理邮件 **"Slack notification not sent"** - 验证 `SLACK_WEBHOOK_URL` 已设置且有效 - 检查 Slack 工作区 webhook 权限 - 设置 `ENABLE_SLACK_NOTIFICATIONS=false` 以跳过错误 **"Database connection error"** - 对于 SQLite:确保 `triage.db` 目录可写 - 对于 PostgreSQL:验证主机/端口/凭据 - 运行 `python -c "from app.db import init_db; init_db()"` 以重新初始化 ## 性能指标 在 Sonnet 3.5 上的典型性能: - **邮件解析:** 50ms - **Claude 分拣:** 2-4s(网络 + 推理) - **Linear issue 创建:** 300-800ms - **Slack 通知:** 200-500ms - **端到端总计:** 2.5-6s ## 许可证 MIT 许可证 — 有关详细信息,请参阅 LICENSE 文件。 基于 [Pydantic AI](https://github.com/pydantic/pydantic-ai)、[FastAPI](https://fastapi.tiangolo.com/) 和 [Linear API](https://linear.app/docs) 构建。
标签:AI分发, AV绕过, FastAPI, Linear, Slack机器人, Webhook, 力导向图, 工单系统, 测试用例, 网络研究, 请求拦截, 逆向工具