用 DSPy 把示例邮件整理为结构化摘要与待办

一封订单确认邮件、一次生产故障告警和一份会议邀请,关键信息的形态很不一样。要让后续程序使用这些信息,需要把“这是什么邮件”“提到了哪些实体”“需要做什么”分别转换为稳定字段。DSPy 官方的 Email Information Extraction 教程用四个模块完成这件事:分类、实体抽取、摘要、行动项生成。

本文按原教程完整整理数据模型、Signature、主模块、三封示例邮件、预期输出和后续改进,并对密钥读取、金额输出及结果解释作静态修订。示例只处理程序内置的虚构邮件,没有接入邮箱、发送邮件或自动执行待办。本次未运行模型调用,也未验证模型输出准确率。

原页未提供可确认的个人作者署名,发布与维护方为 DSPy,页脚为 © 2026 DSPy。核对日期为 2026 年 10 月 5 日;/current/ 是持续更新文档,示例没有固定 DSPy、Pydantic 或模型服务版本,复现时应记录实际依赖与模型配置。

DSPy 邮件处理流程示意:主题、正文和发件人进入分类器,类别指导实体抽取,实体与邮件生成摘要,分类、摘要及实体共同生成待办,最终返回结构化 Prediction 供人工复核。
原创流程示意图。每封邮件依次经过四个模型模块,图中的输出需要与原邮件核对。

这个程序能输出哪些信息

原教程的目标是分类订单确认、支持请求、会议邀请等邮件,抽取日期、金额、产品名与联系方式,判断紧急度和是否需要行动,再把这些内容组织成一致的数据结构。它展示了如何组合模块,不等于已经证明能稳健覆盖所有格式的真实邮件。

原文的前置要求是熟悉 DSPy 的模块与 Signature,安装 Python 3.9 或更高版本,并准备 OpenAI API 密钥或其他受支持模型服务的访问方式。这是教程给出的最低基线;当前安装包是否仍支持具体 Python 版本,需要在安装时核对。本文保留原例的 openai/gpt-4o-mini 模型标识,用于说明配置,未对其当前可用性或费用作保证。

安装与可选追踪

在独立的 Python 环境中安装 DSPy:

python -m pip install dspy

原教程还建议使用 MLflow 观察提示、调用链和实验过程。这是可选的可观测性配置,不是完成四阶段抽取所必需。以下将原文 Notebook 的 %pip 改为普通终端命令,并给版本比较字符串加引号,避免 shell 把 > 当成重定向:

python -m pip install "mlflow>=3.0.0"
mlflow ui --port 5000 --backend-store-uri sqlite:///mlruns.db

在另一个终端启动 UI 后,让 Notebook 或程序连接本机追踪服务,再启用 DSPy 自动追踪:

import mlflow

mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("DSPy")
mlflow.dspy.autolog()

原页提供 MLflow DSPy 集成文档 作为进一步参考。追踪可能记录邮件内容、提示和模型输出;即使服务地址为 localhost,记录依然会保存在本地数据库。实际使用真实邮件前,应确定谁能访问这些记录、如何脱敏和保留,而不是默认把业务邮件全部写入日志。

原教程 MLflow 追踪界面:EmailProcessor 四个 ChainOfThought 分支及虚构会议邀请输入。
原教程 MLflow 追踪截图。来源:DSPy,© 2026 DSPy;展示虚构会议邀请的四阶段调用链,不是本次执行记录。

第一步:明确要抽取的数据类型

邮件类别由枚举限定为订单确认、支持请求、会议邀请、简报、推广、发票、物流通知与其他。紧急度分为 low、medium、high、critical。每个实体使用 Pydantic 模型,包含实体类型、字符串值和置信度:

import dspy
from typing import List, Optional, Literal
from datetime import datetime
from pydantic import BaseModel
from enum import Enum

class EmailType(str, Enum):
    ORDER_CONFIRMATION = "order_confirmation"
    SUPPORT_REQUEST = "support_request"
    MEETING_INVITATION = "meeting_invitation"
    NEWSLETTER = "newsletter"
    PROMOTIONAL = "promotional"
    INVOICE = "invoice"
    SHIPPING_NOTIFICATION = "shipping_notification"
    OTHER = "other"

class UrgencyLevel(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"

class ExtractedEntity(BaseModel):
    entity_type: str
    value: str
    confidence: float

entity_type 描述实体的类别,如产品、订单号或追踪号码;value 保留提取值。这里的 confidence 只是模型输出的浮点数,原例没有范围校验,也没有用标注数据做校准。因此它不能被解释为“正确概率”,更不应仅凭数值较高就自动批准付款或变更计划。

原文导入了 List、Literal 和 datetime,但后续程序没有使用它们。为便于与原例逐段对应,这里保留导入;它们不是额外的处理能力。

第二步:为四个阶段建立 Signature

DSPy Signature 描述输入和输出的含义与类型。分类器读取主题、正文及发件人,返回邮件类别、紧急度和说明。实体抽取器接收整封邮件及已判定类别,输出实体、一个可空金额、重要日期和联系方式。行动项生成器读取类别、紧急度、摘要和实体,输出是否行动、待办列表、可空截止时间及优先级。摘要器把主题、正文和实体整理为两三句话。

class ClassifyEmail(dspy.Signature):
    """Classify the type and urgency of an email based on its content."""

    email_subject: str = dspy.InputField(desc="The subject line of the email")
    email_body: str = dspy.InputField(desc="The main content of the email")
    sender: str = dspy.InputField(desc="Email sender information")

    email_type: EmailType = dspy.OutputField(desc="The classified type of email")
    urgency: UrgencyLevel = dspy.OutputField(desc="The urgency level of the email")
    reasoning: str = dspy.OutputField(desc="Brief explanation of the classification")

class ExtractEntities(dspy.Signature):
    """Extract key entities and information from email content."""

    email_content: str = dspy.InputField(desc="The full email content including subject and body")
    email_type: EmailType = dspy.InputField(desc="The classified type of email")

    key_entities: list[ExtractedEntity] = dspy.OutputField(desc="List of extracted entities with type, value, and confidence")
    financial_amount: Optional[float] = dspy.OutputField(desc="Any monetary amounts found (e.g., '$99.99')")
    important_dates: list[str] = dspy.OutputField(desc="List of important dates found in the email")
    contact_info: list[str] = dspy.OutputField(desc="Relevant contact information extracted")

class GenerateActionItems(dspy.Signature):
    """Determine what actions are needed based on the email content and extracted information."""

    email_type: EmailType = dspy.InputField()
    urgency: UrgencyLevel = dspy.InputField()
    email_summary: str = dspy.InputField(desc="Brief summary of the email content")
    extracted_entities: list[ExtractedEntity] = dspy.InputField(desc="Key entities found in the email")

    action_required: bool = dspy.OutputField(desc="Whether any action is required")
    action_items: list[str] = dspy.OutputField(desc="List of specific actions needed")
    deadline: Optional[str] = dspy.OutputField(desc="Deadline for action if applicable")
    priority_score: int = dspy.OutputField(desc="Priority score from 1-10")

class SummarizeEmail(dspy.Signature):
    """Create a concise summary of the email content."""

    email_subject: str = dspy.InputField()
    email_body: str = dspy.InputField()
    key_entities: list[ExtractedEntity] = dspy.InputField()

    summary: str = dspy.OutputField(desc="A 2-3 sentence summary of the email's main points")

这种拆分有助于看清每个阶段的依赖,也暴露了模型边界:类别一旦判断错误,实体抽取和行动建议可能沿着错误方向继续;摘要如果漏掉否定或条件,后面的待办也可能失真。类型注解约束了输出形态,不会自动保证事实正确。

金额字段只有一个 Optional[float],不足以完整表达多笔金额、币种、税费和折扣;原例打印美元符号只是因为示例订单是美元,不能据此处理所有真实邮件。日期目前是字符串,发件人也是输入文本,没有邮件签名或身份认证。priority_score 的描述写着 1–10,但仅注解为 int,没有真正施加范围限制。

第三步:连接四个模块

EmailProcessor 为四个 Signature 分别创建 dspy.ChainOfThought 实例。一次调用先分类,再把主题、发件人和正文拼成完整内容抽取实体;之后生成摘要,最后生成行动项。结果统一放进 dspy.Prediction。

class EmailProcessor(dspy.Module):
    """A comprehensive email processing system using DSPy."""

    def __init__(self):
        super().__init__()

        # Initialize our processing components
        self.classifier = dspy.ChainOfThought(ClassifyEmail)
        self.entity_extractor = dspy.ChainOfThought(ExtractEntities)
        self.action_generator = dspy.ChainOfThought(GenerateActionItems)
        self.summarizer = dspy.ChainOfThought(SummarizeEmail)

    def forward(self, email_subject: str, email_body: str, sender: str = ""):
        """Process an email and extract structured information."""

        # Step 1: Classify the email
        classification = self.classifier(
            email_subject=email_subject,
            email_body=email_body,
            sender=sender
        )

        # Step 2: Extract entities
        full_content = f"Subject: {email_subject}\n\nFrom: {sender}\n\n{email_body}"
        entities = self.entity_extractor(
            email_content=full_content,
            email_type=classification.email_type
        )

        # Step 3: Generate summary
        summary = self.summarizer(
            email_subject=email_subject,
            email_body=email_body,
            key_entities=entities.key_entities
        )

        # Step 4: Determine actions
        actions = self.action_generator(
            email_type=classification.email_type,
            urgency=classification.urgency,
            email_summary=summary.summary,
            extracted_entities=entities.key_entities
        )

        # Step 5: Structure the results
        return dspy.Prediction(
            email_type=classification.email_type,
            urgency=classification.urgency,
            summary=summary.summary,
            key_entities=entities.key_entities,
            financial_amount=entities.financial_amount,
            important_dates=entities.important_dates,
            action_required=actions.action_required,
            action_items=actions.action_items,
            deadline=actions.deadline,
            priority_score=actions.priority_score,
            reasoning=classification.reasoning,
            contact_info=entities.contact_info
        )

顺序很重要。实体抽取依赖分类器的 email_type;摘要使用原始主题、正文与抽取实体;行动项生成器使用摘要及实体,并没有再次接收整封原始邮件。这个设计节省了最后阶段的输入,但也意味着摘要中的遗漏会影响行动建议。涉及精确期限、金额或条件时,应保留原文片段并人工回查。

Prediction 汇集了类别、紧急度、摘要、实体、金额、重要日期、是否需要行动、行动列表、截止时间、优先分数、分类说明和联系方式。原示例只是返回这些字段,没有把它们写入任务系统,也没有调用邮件发送接口。

四个模块通常意味着每封邮件有多次模型请求;重试、缓存和适配器行为会进一步影响实际请求数。处理三封示例也不是“只调用一次模型”。部署前应明确调用成本、速率限制、超时、重试以及失败时保留部分结果还是整封失败的策略;原教程没有实现这些生产处理。

第四步:使用三封内置邮件

第一封是 TechStore 发给 John Smith 的订单确认:订单号 12345,一台深空灰色 14 英寸 MacBook Pro,总额 2,399 美元,预计 2024 年 12 月 15 日送达,并附有追踪号码与客服邮箱。第二封是生产环境中断告警:所有用户无法访问平台,开始时间为下午 2:30 EST,要求 DevOps 团队立即加入紧急电话。第三封是 Sarah Johnson 发出的第四季度规划会议邀请:2024 年 12 月 20 日 14:00–16:00 EST,地点为 Conference Room A,要求 12 月 18 日前确认参加。

这些日期和地址是原教程的历史虚构样本,不代表当前存在的订单、告警或会议。下面保留三封英文原始邮件,便于对照输出。与原文相比,代码先检查已由安全方式注入的 OPENAI_API_KEY,不再把占位密钥写回环境变量;金额判断改用 is not None,避免漏显示零金额;输出枚举的 .value,使显示值与示例标签一致;另外打印待办与优先分数,让已返回字段可以被看到。

import os
def run_email_processing_demo():
    """Demonstration of the email processing system."""

    # 从安全注入的运行环境读取,不写入或打印密钥。
    if not os.environ.get("OPENAI_API_KEY"):
        raise RuntimeError("请先安全设置 OPENAI_API_KEY")
    lm = dspy.LM(model='openai/gpt-4o-mini')
    dspy.configure(lm=lm)

    # Create our email processor
    processor = EmailProcessor()

    # Sample emails for testing
    sample_emails = [
        {
            "subject": "Order Confirmation #12345 - Your MacBook Pro is on the way!",
            "body": """Dear John Smith,

Thank you for your order! We're excited to confirm that your order #12345 has been processed.

Order Details:
- MacBook Pro 14-inch (Space Gray)
- Order Total: $2,399.00
- Estimated Delivery: December 15, 2024
- Tracking Number: 1Z999AA1234567890

If you have any questions, please contact our support team at support@techstore.com.

Best regards,
TechStore Team""",
            "sender": "orders@techstore.com"
        },
        {
            "subject": "URGENT: Server Outage - Immediate Action Required",
            "body": """Hi DevOps Team,

We're experiencing a critical server outage affecting our production environment.

Impact: All users unable to access the platform
Started: 2:30 PM EST

Please join the emergency call immediately: +1-555-123-4567

This is our highest priority.

Thanks,
Site Reliability Team""",
            "sender": "alerts@company.com"
        },
        {
            "subject": "Meeting Invitation: Q4 Planning Session",
            "body": """Hello team,

You're invited to our Q4 planning session.

When: Friday, December 20, 2024 at 2:00 PM - 4:00 PM EST
Where: Conference Room A

Please confirm your attendance by December 18th.

Best,
Sarah Johnson""",
            "sender": "sarah.johnson@company.com"
        }
    ]

    # Process each email and display results
    print("🚀 Email Processing Demo")
    print("=" * 50)

    for i, email in enumerate(sample_emails):
        print(f"\n📧 EMAIL {i+1}: {email['subject'][:50]}...")

        # Process the email
        result = processor(
            email_subject=email["subject"],
            email_body=email["body"],
            sender=email["sender"]
        )

        # Display key results
        print(f"   📊 Type: {result.email_type.value}")
        print(f"   🚨 Urgency: {result.urgency.value}")
        print(f"   📝 Summary: {result.summary}")

        if result.financial_amount is not None:
            print(f"   💰 Amount: ${result.financial_amount:,.2f}")

        print(f"   Priority: {result.priority_score}")
        for item in result.action_items:
            print(f"   Action: {item}")

        if result.action_required:
            print(f"   ✅ Action Required: Yes")
            if result.deadline:
                print(f"   ⏰ Deadline: {result.deadline}")
        else:
            print(f"   ✅ Action Required: No")

# Run the demo
if __name__ == "__main__":
    run_email_processing_demo()

密钥应在运行环境或密钥管理系统中设置,不能写进源代码、截图、Notebook 输出或版本库。环境变量读取只避免了教程中的硬编码方式,并不意味着凭据或邮件已经获得完整保护。只要使用外部模型服务,邮件内容就会发送给所配置的服务商;真实邮件的使用权限、数据保留和追踪设置都需要先确认。

怎样阅读原文的预期输出

原文给出的第一封结果为 order_confirmation、紧急度 low,摘要包含收件人、订单号、产品、金额、送达日期与客服信息,金额为 2,399 美元,标记无需行动。第二封为 other、critical,摘要说明生产故障、影响与紧急电话,标记需要行动,截止时间为 Immediately。第三封为 meeting_invitation、medium,摘要包含会议时间、地点和确认要求,标记需要行动,截止时间为 December 18th。

以下是源文提供的示例输出,不是本次执行记录,也不保证每次模型调用都会逐字一致。本文新增的待办和优先级打印项不在原文这段预期输出中,因此不捏造对应数值:

🚀 Email Processing Demo
==================================================

📧 EMAIL 1: Order Confirmation #12345 - Your MacBook Pro is on...
   📊 Type: order_confirmation
   🚨 Urgency: low
   📝 Summary: The email confirms John Smith's order #12345 for a MacBook Pro 14-inch in Space Gray, totaling $2,399.00, with an estimated delivery date of December 15, 2024. It includes a tracking number and contact information for customer support.
   💰 Amount: $2,399.00
   ✅ Action Required: No

📧 EMAIL 2: URGENT: Server Outage - Immediate Action Required...
   📊 Type: other
   🚨 Urgency: critical
   📝 Summary: The Site Reliability Team has reported a critical server outage that began at 2:30 PM EST, preventing all users from accessing the platform. They have requested the DevOps Team to join an emergency call immediately to address the issue.
   ✅ Action Required: Yes
   ⏰ Deadline: Immediately

📧 EMAIL 3: Meeting Invitation: Q4 Planning Session...
   📊 Type: meeting_invitation
   🚨 Urgency: medium
   📝 Summary: Sarah Johnson has invited the team to a Q4 planning session on December 20, 2024, from 2:00 PM to 4:00 PM EST in Conference Room A. Attendees are asked to confirm their participation by December 18th.
   ✅ Action Required: Yes
   ⏰ Deadline: December 18th

这些结果有几处值得读者主动检查。订单预计送达日期不必然是用户行动截止日期。会议发生在 12 月 20 日,但确认参加的期限是 12 月 18 日。故障邮件中的“立即”需要保留相对语义,而不是模型自行编造一个绝对日期。没有币种字段时,不能把所有金额自动写成美元。枚举缺少“生产故障”类别,所以原例归为 other,这属于分类体系覆盖范围,不一定是模型故障。

从示例走向可靠程序,还需要哪些工作

原教程的下一步包括扩展类别、接入 Gmail API、Outlook 或 IMAP、尝试不同模型与优化策略、加入多语言支持,以及优化整体性能。这些都是后续方向,本文没有执行邮箱接入、付费请求或部署。

在这些方向之外,静态审核还发现几个应先补上的约束。输入邮件属于不可信内容,可能含有“忽略之前规则”等提示注入;当前代码只是把文本交给模型,没有隔离策略、攻击测试或权限边界。把邮件内容当作数据而非指令、在输出后进行模式和业务校验、保留原文证据并由人工确认高影响事项,都应成为设计的一部分。不能把模型返回的 action_items 直接接到具有发送、付款、删除或管理权限的工具。

置信度与优先分数可通过更严格的数据模型做范围校验,但范围正确不代表判断正确。要评价分类和抽取质量,应准备带人工标签的样本,覆盖不同语言、转发链、HTML 邮件、多金额、模糊日期、否定表达及恶意指令,再定义字段级指标与人工复核规则。原教程没有提供这样的评测集,也没有展示优化器训练或性能数字,因此不能声称程序已经优化到生产水平。

原文的软件项目采用 MIT License,版权声明为 Copyright (c) 2023 Stanford Future Data Systems;完整许可见文末,亦可见 DSPy LICENSE;本文不把项目代码许可自动扩大为对所有第三方模型输出或邮件素材的权利声明。此处使用的是原教程自带虚构示例,中文翻译及编辑标注:未完纪,2026-10-05;原文版权与署名保留。代码仅作静态检查,没有运行或向任何邮箱发送消息。

版权与许可全文

以下保留本页涉及的来源材料或示例代码的版权、许可条件与免责声明;各自适用范围依原声明。中文翻译及编辑标注:未完纪,2026-10-05。

source-license.txt

MIT License

Copyright (c) 2023 Stanford Future Data Systems

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is

furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

SOFTWARE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容