classifyEmail / app.py
wanggenfu's picture
Update app.py
01e2dc9 verified
import gradio as gr
import os
import json
from typing import TypedDict, List, Dict, Any, Optional
from langgraph.graph import StateGraph, START, END
# from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
# from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
from huggingface_hub import InferenceClient
class EmailState(TypedDict):
# 正在处理的电子邮件
email: Dict[str, Any] # 包含主题、发件人、正文等。
# 分析与决策
is_spam: Optional[bool]
spam_reason: Optional[str]
email_category: Optional[str]
# 响应生成
draft_response: Optional[str]
# 处理元数据
messages: List[Dict[str, Any]] # 跟踪与 LLM 的对话以进行分析
# Initialize our LLM
# model = ChatOpenAI(temperature=0)
# model = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")
hf_token=os.getenv("hf_token")
model = InferenceClient(
model="Qwen/Qwen2.5-Coder-32B-Instruct",
token=hf_token,
timeout=30
)
def query_llm(prompt: str):
response = model.chat.completions.create(messages=[
{
"role": "user",
"content": prompt
}],temperature=0)
return response.choices[0].message
def read_email(state: EmailState):
"""Alfred reads and logs the incoming email"""
email = state["email"]
# 在这里我们可能会做一些初步的预处理
print(f"Alfred is processing an email from {email['sender']} with subject: {email['subject']}")
# 这里不需要更改状态
return {}
def classify_email(state: EmailState):
"""Alfred uses an LLM to determine if the email is spam or legitimate"""
email = state["email"]
# 为 LLM 准备提示
prompt = f"""
As Alfred the butler, analyze this email and determine if it is spam or legitimate.
Email:
From: {email['sender']}
Subject: {email['subject']}
Body: {email['body']}
First, determine if this email is spam. If it is spam, explain why.
If it is legitimate, categorize it (inquiry, complaint, thank you, etc.).
Please answer strictly in the following format:
Email category: inquiry, complaint, thank you, spam, etc.
Is spam:
Reason for spam:
"""
# Call the LLM
# messages = [HumanMessage(content=prompt)]
response = query_llm(prompt)
# 解析响应的简单逻辑(在实际应用中,您需要更强大的解析)
response_text = response.content.lower()
# print(f"test response_text:{response_text}")
is_spam = "is spam: yes" in response_text
# print(f"test is_spam:{is_spam and "reasoning:" in response_text}")
# print(f"test reasoning:{response_text.split("reasoning:")[1].strip()}")
# 如果是垃圾邮件,请提取原因,输出的格式可能不同,需要根据提示词固定格式。
spam_reason = None
spam_reason_keyword="reason for spam:"
if is_spam and spam_reason_keyword in response_text:
spam_reason = response_text.split(spam_reason_keyword)[1].strip()
# print(f"test spam_reason:{spam_reason}")
# 确定类别是否合法
email_category = None
if not is_spam:
categories = ["inquiry", "complaint", "thank you", "request", "information"]
for category in categories:
if category in response_text:
email_category = category
break
# 更新消息以进行追踪
new_messages = state.get("messages", []) + [
{"role": "user", "content": prompt},
{"role": "assistant", "content": response.content}
]
# 返回状态更新
return {
"is_spam": is_spam,
"spam_reason": spam_reason,
"email_category": email_category,
"messages": new_messages
}
def route_email(state: EmailState) -> str:
"""Determine the next step based on spam classification"""
# print(f"route_email,state={state}")
if state["is_spam"]:
return "spam"
else:
return "legitimate"
def handle_spam(state: EmailState):
"""Alfred discards spam email with a note"""
# print(f"handle_spam,state={state}")
# print(f"is_spam:{state.get("is_spam","none")}")
print(f"Alfred has marked the email as spam. Reason: {state.get("spam_reason","none")}")
print("The email has been moved to the spam folder.")
# 我们已处理完这封电子邮件
return {}
def draft_response(state: EmailState):
"""Alfred drafts a preliminary response for legitimate emails"""
email = state["email"]
category = state["email_category"] or "general"
# 为 LLM 准备提示词
prompt = f"""
As Alfred the butler, draft a polite preliminary response to this email.
Email:
From: {email['sender']}
Subject: {email['subject']}
Body: {email['body']}
This email has been categorized as: {category}
Draft a brief, professional response that Mr. Hugg can review and personalize before sending.
"""
# Call the LLM
# messages = [HumanMessage(content=prompt)]
response = query_llm(prompt)
# 更新消息以进行追踪
new_messages = state.get("messages", []) + [
{"role": "user", "content": prompt},
{"role": "assistant", "content": response.content}
]
# 返回状态更新
return {
"draft_response": response.content,
"messages": new_messages
}
def notify_mr_hugg(state: EmailState):
"""Alfred notifies Mr. Hugg about the email and presents the draft response"""
email = state["email"]
print("\n" + "="*50)
print(f"Sir, you've received an email from {email['sender']}.")
print(f"Subject: {email['subject']}")
print(f"Category: {state['email_category']}")
print("\nI've prepared a draft response for your review:")
print("-"*50)
print(state["draft_response"])
print("="*50 + "\n")
# 我们已处理完这封电子邮件
return {}
# 创建 graph
email_graph = StateGraph(EmailState)
# 添加 nodes
email_graph.add_node("read_email", read_email)
email_graph.add_node("classify_email", classify_email)
email_graph.add_node("handle_spam", handle_spam)
email_graph.add_node("draft_response", draft_response)
email_graph.add_node("notify_mr_hugg", notify_mr_hugg)
# 添加 edges - 定义流程
email_graph.add_edge(START, "read_email")
email_graph.add_edge("read_email", "classify_email")
# 从 classify_email 添加条件分支
email_graph.add_conditional_edges(
"classify_email",
route_email,
{
"spam": "handle_spam",
"legitimate": "draft_response"
}
)
# 添加最后的 edges
email_graph.add_edge("handle_spam", END)
email_graph.add_edge("draft_response", "notify_mr_hugg")
email_graph.add_edge("notify_mr_hugg", END)
# 编译 graph
compiled_graph = email_graph.compile()
# 合法电子邮件示例
legitimate_email = {
"sender": "john.smith@example.com",
"subject": "Question about your services",
"body": "Dear Mr. Hugg, I was referred to you by a colleague and I'm interested in learning more about your consulting services. Could we schedule a call next week? Best regards, John Smith"
}
# 垃圾邮件示例
spam_email = {
"sender": "winner@lottery-intl.com",
"subject": "YOU HAVE WON $5,000,000!!!",
"body": "CONGRATULATIONS! You have been selected as the winner of our international lottery! To claim your $5,000,000 prize, please send us your bank details and a processing fee of $100."
}
def classify_email(json_input):
email = json.loads(json_input)
result = compiled_graph.invoke({
"email": email,
"is_spam": None,
"spam_reason": None,
"email_category": None,
"draft_response": None,
"messages": []
})
return f"is_spam:{result.get("is_spam")}\nspam_reason:{result.get("spam_reason")}"
# ===================== 核心处理函数(模拟邮件分析) =====================
def classify_email(sender: str, subject: str, body: str) -> tuple[str, str]:
if sender.strip() == "" or subject.strip() == "" or body.strip() == "" :
return "true", "sender、subject、subject为空"
email = {
"sender": sender,
"subject": subject,
"body": body
}
result = compiled_graph.invoke({
"email": email,
"is_spam": None,
"spam_reason": None,
"email_category": None,
"draft_response": None,
"messages": []
})
return result.get("is_spam"), result.get("spam_reason")
# ===================== Gradio 表单界面构建 =====================
with gr.Blocks(title="邮件分析表单", theme=gr.themes.Soft()) as demo:
# 标题说明
gr.Markdown("### 📧 邮件垃圾信息分析表单")
gr.Markdown("请输入以下3项信息,提交后自动分析邮件是否为垃圾邮件")
# 表单区域:3个文本输入框
with gr.Row(): # 行布局,可选:改为 gr.Column() 垂直布局
with gr.Column(scale=1):
sender_input = gr.Textbox(
label="1. 发件人邮箱地址",
placeholder="john.smith@gmail.com",
lines=1,
max_lines=1
)
with gr.Column(scale=2):
subject_input = gr.Textbox(
label="2. 邮件主题",
placeholder="预约会议",
lines=1,
max_lines=1
)
# 第三个文本框:多行输入(邮件内容)
content_input = gr.Textbox(
label="3. 邮件内容",
placeholder="我想预约下周二的咨询会议",
lines=5,
max_lines=10
)
# 提交按钮
submit_btn = gr.Button("📤 提交分析", variant="primary", size="lg")
# 分割线
# gr.Divider()
# 结果展示区域(结构化输出)
gr.Markdown("### 📝 分析结果")
with gr.Row():
is_spam_output = gr.Textbox(label="是否垃圾邮件", interactive=False)
reason_output = gr.Textbox(label="判定原因", lines=3, interactive=False)
# 绑定按钮与处理函数
submit_btn.click(
fn=classify_email,
inputs=[sender_input, subject_input, content_input], # 3个输入框
outputs=[is_spam_output, reason_output] # 2个输出框
)
# 重置按钮(可选)
reset_btn = gr.Button("🔄 清空输入")
reset_btn.click(
fn=lambda: ("", "", "", "", "", ""), # 重置所有输入输出
inputs=[],
outputs=[sender_input, subject_input, content_input, is_spam_output, reason_output]
)
# ===================== 启动界面 =====================
if __name__ == "__main__":
# server_name="0.0.0.0" 允许局域网访问,server_port 自定义端口
demo.launch()
# demo = gr.Interface(fn=classify_email, inputs="text", outputs="text")
# demo.launch()