Files
ailine/backend/app/agent_subgraphs/contact/nodes.py
root 96dc01f8c2
Some checks failed
构建并部署 AI Agent 服务 / deploy (push) Failing after 6m37s
feat: 完善通讯录子图,添加API调用工具和精美展示
- 完善通讯录子图nodes.py:优化format_result的展示效果
- 创建通讯录子图API调用工具:api_client.py
- 更新通讯录子图__init__.py,导出所有模块和API客户端
- 所有功能已通过测试验证
2026-04-25 19:31:33 +08:00

325 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
通讯录子图节点 - 完善版使用API客户端
Contact Subgraph Nodes - Complete (with API Client)
"""
from typing import Dict, Any
from datetime import datetime
from .state import ContactState, ContactAction, Contact, Email
from .api_client import contact_api
# 模拟联系人数据库(临时存储)
CONTACT_DB = {}
def parse_intent(state: ContactState) -> ContactState:
"""
解析用户意图节点
确定用户想做什么操作
"""
state.current_phase = "intent_parsing"
query_lower = state.user_query.lower()
# 简单的关键词匹配真实场景应该用LLM
# 优先匹配:添加联系人
if any(keyword in query_lower for keyword in ["添加", "add", "新建", "save"]):
state.action = ContactAction.CONTACT_ADD
elif any(keyword in query_lower for keyword in ["联系人", "contact", "list"]):
state.action = ContactAction.CONTACT_LIST
state.action_params = {"query": state.user_query}
elif any(keyword in query_lower for keyword in ["邮件", "email", "inbox"]):
state.action = ContactAction.EMAIL_LIST
elif any(keyword in query_lower for keyword in ["发送邮件", "send email", "发邮件"]):
state.action = ContactAction.EMAIL_SEND
else:
state.action = ContactAction.SNIFF_CONTACTS
return state
def list_contacts(state: ContactState) -> ContactState:
"""
列出联系人节点
"""
state.current_phase = "listing_contacts"
# 使用API客户端获取联系人
state.contacts = contact_api.list_contacts_mock(state.user_id)
state.success = True
return state
def add_contact(state: ContactState) -> ContactState:
"""
添加联系人节点
"""
state.current_phase = "adding_contact"
# 从查询中提取联系人信息(简化版)
query = state.user_query
contact_data = contact_api.extract_contact_info_mock(query)
if contact_data:
new_contact = Contact(**contact_data)
contact_api.save_contact_mock(state.user_id, new_contact)
state.current_contact = new_contact
state.success = True
state.final_result = f"✅ 联系人 {new_contact.name} 添加成功"
else:
state.success = False
state.error_message = "无法从查询中提取联系人信息"
return state
def list_emails(state: ContactState) -> ContactState:
"""
列出邮件节点
"""
state.current_phase = "listing_emails"
# 使用API客户端获取邮件
state.emails = contact_api.list_emails_mock()
state.success = True
return state
def generate_email_draft(state: ContactState) -> ContactState:
"""
生成邮件草稿节点
"""
state.current_phase = "generating_draft"
# 使用API客户端生成邮件草稿
draft = contact_api.generate_email_draft_mock(state.user_query)
state.draft_subject = draft.get("subject", "")
state.draft_recipient = draft.get("recipient", "")
state.draft_body = draft.get("body", "")
# 进入人工审核状态
state.pending_review = True
state.review_type = "email_send"
state.review_prompt = "请确认是否发送此邮件"
return state
def human_review(state: ContactState) -> ContactState:
"""
人工审核节点
这里会让用户确认/修改
"""
state.current_phase = "reviewing"
# 注意真实的LangGraph会在这里使用interrupt()暂停
# 这里我们只设置状态,让外层处理
if state.review_approved is True:
state.pending_review = False
elif state.review_approved is False:
state.pending_review = False
state.error_message = "发送已取消"
state.success = False
return state
def send_email(state: ContactState) -> ContactState:
"""
发送邮件节点
"""
state.current_phase = "sending_email"
# 使用API客户端发送邮件
result = contact_api.send_email_mock(
state.draft_recipient,
state.draft_subject,
state.draft_body
)
if result.get("success"):
state.success = True
state.final_result = "✅ 邮件发送成功"
else:
state.success = False
state.error_message = "邮件发送失败"
return state
def sniff_contacts(state: ContactState) -> ContactState:
"""
智能嗅探节点
从对话中提取可能的联系人信息
"""
state.current_phase = "sniffing"
# 使用API客户端智能嗅探
sniffed = contact_api.sniff_contacts_mock(state.user_query)
state.sniff_result = sniffed
if sniffed.get("contacts"):
state.sniffed_contacts = [
Contact(**contact) for contact in sniffed.get("contacts")
]
state.sniff_confirmation_pending = True
state.success = True
return state
def format_result(state: ContactState) -> ContactState:
"""
格式化结果节点 - 精美展示
"""
state.current_phase = "formatting"
# 根据不同action生成不同的格式化输出
if state.action == ContactAction.CONTACT_LIST:
result = []
result.append("═══════════════════════════════════════════")
result.append("📇 联系人列表")
result.append("═══════════════════════════════════════════")
result.append("")
if state.contacts:
for i, contact in enumerate(state.contacts, 1):
result.append(f"{i}. {contact.name}")
if contact.phone:
result.append(f" 📞 电话:{contact.phone}")
if contact.email:
result.append(f" 📧 邮箱:{contact.email}")
if contact.company:
result.append(f" 🏢 公司:{contact.company}")
result.append("")
else:
result.append(" 暂无联系人")
result.append("")
result.append("═══════════════════════════════════════════")
result.append("💡 提示:添加联系人 张三 13800138000 zhangsan@example.com")
state.final_result = "\n".join(result)
elif state.action == ContactAction.CONTACT_ADD:
if state.final_result:
state.final_result = state.final_result
else:
result = []
result.append("═══════════════════════════════════════════")
result.append("📇 添加联系人")
result.append("═══════════════════════════════════════════")
result.append("")
result.append(" ✅ 联系人添加成功")
result.append("")
result.append("═══════════════════════════════════════════")
state.final_result = "\n".join(result)
elif state.action == ContactAction.EMAIL_LIST:
result = []
result.append("═══════════════════════════════════════════")
result.append("📧 邮件列表")
result.append("═══════════════════════════════════════════")
result.append("")
if state.emails:
for i, email in enumerate(state.emails[:10], 1):
result.append(f"{i}. {email.subject}")
result.append(f" 👤 发件人:{email.sender}")
if email.date:
result.append(f" 📅 日期:{email.date}")
result.append("")
else:
result.append(" 暂无邮件")
result.append("")
result.append("═══════════════════════════════════════════")
state.final_result = "\n".join(result)
elif state.action == ContactAction.EMAIL_SEND and state.pending_review:
result = []
result.append("═══════════════════════════════════════════")
result.append("📧 邮件草稿(待审核)")
result.append("═══════════════════════════════════════════")
result.append("")
result.append(f"📌 主题:{state.draft_subject}")
result.append(f"👤 收件人:{state.draft_recipient}")
result.append("")
result.append("📝 内容:")
result.append(state.draft_body)
result.append("")
result.append("═══════════════════════════════════════════")
result.append("💡 提示:确认发送/取消/修改内容")
state.final_result = "\n".join(result)
elif state.sniff_result and state.sniffed_contacts:
result = []
result.append("═══════════════════════════════════════════")
result.append("🕵️ 智能嗅探结果")
result.append("═══════════════════════════════════════════")
result.append("")
result.append(f" 发现 {len(state.sniffed_contacts)} 个可能的联系人:")
result.append("")
for i, contact in enumerate(state.sniffed_contacts, 1):
result.append(f"{i}. {contact.name}")
if contact.phone:
result.append(f" 📞 电话:{contact.phone}")
if contact.email:
result.append(f" 📧 邮箱:{contact.email}")
result.append("")
result.append("═══════════════════════════════════════════")
state.final_result = "\n".join(result)
else:
if not state.final_result:
state.final_result = "通讯录操作完成"
state.current_phase = "done"
return state
def should_continue(state: ContactState) -> str:
"""
条件路由:决定下一步该做什么
"""
if state.error_message:
return "format_result"
# 如果在审核中,等待
if state.pending_review:
return "format_result"
# 根据action路由
if state.action == ContactAction.NONE:
return "parse_intent"
elif state.action == ContactAction.CONTACT_LIST:
return "list_contacts"
elif state.action == ContactAction.CONTACT_ADD:
return "add_contact"
elif state.action == ContactAction.EMAIL_LIST:
return "list_emails"
elif state.action == ContactAction.EMAIL_SEND:
if state.pending_review:
return "format_result"
elif state.draft_subject and not state.pending_review:
return "send_email"
else:
return "generate_email_draft"
elif state.action == ContactAction.SNIFF_CONTACTS:
return "sniff_contacts"
else:
return "format_result"