feat: 集成MCP统一外部接口管理系统
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m38s
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m38s
- 添加MCP Manager统一入口管理 - 实现Contact/Dictionary/News三个适配器 - 三层降级策略:MCP -> Database -> Mock - 保持原有api_client向后兼容 - 添加完整文档和测试
This commit is contained in:
@@ -1,29 +1,21 @@
|
||||
"""
|
||||
通讯录子图 API 调用工具
|
||||
支持模拟数据和真实数据库两种模式
|
||||
通讯录子图 API 调用工具(使用MCP统一接口)
|
||||
"""
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .state import Contact, Email
|
||||
|
||||
|
||||
# ========== 模拟数据(保留作为备选)==========
|
||||
|
||||
# 模拟数据库
|
||||
MOCK_CONTACTS_DB = {}
|
||||
MOCK_EMAILS_DB = []
|
||||
from ...mcp.mcp_manager import mcp_manager
|
||||
from ...mcp.adapters import ContactAdapter
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContactAPIClient:
|
||||
"""
|
||||
通讯录 API 客户端 - 支持真实数据库和模拟模式
|
||||
通讯录 API 客户端 - 使用MCP统一接口
|
||||
|
||||
使用方式:
|
||||
1. 真实数据库模式:传入 conn 参数
|
||||
2. 模拟模式:不传入 conn,或 conn 为 None
|
||||
保持向后兼容,内部使用MCP适配器
|
||||
"""
|
||||
|
||||
def __init__(self, conn=None):
|
||||
@@ -31,256 +23,99 @@ class ContactAPIClient:
|
||||
初始化
|
||||
|
||||
Args:
|
||||
conn: 数据库连接(来自 checkpointer.conn),为 None 时使用模拟模式
|
||||
conn: 数据库连接(保留用于向后兼容)
|
||||
"""
|
||||
self.conn = conn
|
||||
self._use_db = conn is not None
|
||||
|
||||
if self._use_db:
|
||||
try:
|
||||
from ...db.models import ContactRepository, ContactEntity
|
||||
self._repo = ContactRepository(conn)
|
||||
except Exception as e:
|
||||
print(f"Repository 初始化失败,回退到模拟模式: {e}")
|
||||
self._use_db = False
|
||||
self._repo = None
|
||||
# 确保MCP已初始化
|
||||
import asyncio
|
||||
try:
|
||||
asyncio.create_task(self._init_mcp())
|
||||
except RuntimeError:
|
||||
pass # 没有事件循环时跳过,延迟初始化
|
||||
|
||||
# ========== 真实数据库方法 ==========
|
||||
|
||||
async def list_contacts_db(self, user_id: str = "default") -> List[Contact]:
|
||||
"""真实数据库:获取联系人列表"""
|
||||
if not self._repo:
|
||||
return await self.list_contacts_mock(user_id)
|
||||
|
||||
entities = await self._repo.list_by_user(user_id)
|
||||
return [
|
||||
Contact(
|
||||
id=e.id,
|
||||
name=e.name,
|
||||
phone=e.phone,
|
||||
email=e.email,
|
||||
company=e.company,
|
||||
position=e.position,
|
||||
created_at=e.created_at
|
||||
)
|
||||
for e in entities
|
||||
]
|
||||
|
||||
async def add_contact_db(self, user_id: str, contact: Contact) -> bool:
|
||||
"""真实数据库:添加联系人"""
|
||||
if not self._repo:
|
||||
return await self.save_contact_mock(user_id, contact)
|
||||
|
||||
from ...db.models import ContactEntity
|
||||
entity = ContactEntity(
|
||||
user_id=user_id,
|
||||
name=contact.name,
|
||||
phone=contact.phone,
|
||||
email=contact.email,
|
||||
company=contact.company,
|
||||
position=contact.position,
|
||||
created_at=contact.created_at or datetime.now().isoformat()
|
||||
)
|
||||
await self._repo.insert(entity)
|
||||
return True
|
||||
|
||||
# ========== 模拟数据方法(保留)==========
|
||||
|
||||
def list_contacts_mock(self, user_id: str = "default") -> List[Contact]:
|
||||
"""模拟查询联系人列表"""
|
||||
if user_id not in MOCK_CONTACTS_DB:
|
||||
# 初始化一些示例数据
|
||||
MOCK_CONTACTS_DB[user_id] = [
|
||||
Contact(
|
||||
id="1",
|
||||
name="张三",
|
||||
phone="13800138000",
|
||||
email="zhangsan@example.com",
|
||||
company="科技公司",
|
||||
position="工程师",
|
||||
created_at=datetime.now().isoformat()
|
||||
),
|
||||
Contact(
|
||||
id="2",
|
||||
name="李四",
|
||||
phone="13900139000",
|
||||
email="lisi@example.com",
|
||||
company="贸易公司",
|
||||
position="经理",
|
||||
created_at=datetime.now().isoformat()
|
||||
),
|
||||
Contact(
|
||||
id="3",
|
||||
name="王五",
|
||||
phone="13700137000",
|
||||
email="wangwu@example.com",
|
||||
company="咨询公司",
|
||||
position="顾问",
|
||||
created_at=datetime.now().isoformat()
|
||||
),
|
||||
]
|
||||
|
||||
return MOCK_CONTACTS_DB[user_id]
|
||||
|
||||
def extract_contact_info_mock(self, query: str) -> Optional[Dict[str, Any]]:
|
||||
"""模拟从查询中提取联系人信息"""
|
||||
import re
|
||||
|
||||
# 提取邮箱
|
||||
email_match = re.search(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query)
|
||||
# 提取手机号
|
||||
phone_match = re.search(r'1[3-9]\d{9}', query)
|
||||
# 提取姓名(简单匹配)
|
||||
if any(keyword in query for keyword in ["添加", "add"]):
|
||||
name = "未知"
|
||||
clean_query = query
|
||||
if email_match:
|
||||
clean_query = clean_query.replace(email_match.group(), "")
|
||||
if phone_match:
|
||||
clean_query = clean_query.replace(phone_match.group(), "")
|
||||
clean_query = clean_query.replace("添加", "").replace("add", "").replace("联系人", "").strip()
|
||||
if clean_query:
|
||||
name = clean_query
|
||||
async def _init_mcp(self):
|
||||
"""初始化MCP系统"""
|
||||
if not mcp_manager.get_adapter("contact"):
|
||||
# 获取repository(如果有)
|
||||
repo = None
|
||||
if self.conn:
|
||||
try:
|
||||
from ...db.models import ContactRepository
|
||||
repo = ContactRepository(self.conn)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"phone": phone_match.group() if phone_match else "",
|
||||
"email": email_match.group() if email_match else "",
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
return None
|
||||
mcp_manager.register_adapter(ContactAdapter(contact_repo=repo))
|
||||
await mcp_manager.initialize()
|
||||
|
||||
def save_contact_mock(self, user_id: str, contact: Contact) -> bool:
|
||||
"""模拟保存联系人"""
|
||||
if user_id not in MOCK_CONTACTS_DB:
|
||||
MOCK_CONTACTS_DB[user_id] = []
|
||||
|
||||
if not contact.id:
|
||||
contact.id = str(len(MOCK_CONTACTS_DB[user_id]) + 1)
|
||||
|
||||
MOCK_CONTACTS_DB[user_id].append(contact)
|
||||
return True
|
||||
async def list_contacts(self, user_id: str = "default") -> List[Contact]:
|
||||
"""获取联系人列表"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute("contact", "list_contacts", user_id=user_id)
|
||||
if result.success:
|
||||
return result.data
|
||||
return []
|
||||
|
||||
def list_emails_mock(self) -> List[Email]:
|
||||
"""模拟查询邮件列表"""
|
||||
global MOCK_EMAILS_DB
|
||||
|
||||
if not MOCK_EMAILS_DB:
|
||||
MOCK_EMAILS_DB = [
|
||||
Email(
|
||||
id="1",
|
||||
subject="会议邀请:AI 技术分享",
|
||||
sender="admin@example.com",
|
||||
recipients=["user@example.com"],
|
||||
date=datetime.now().isoformat(),
|
||||
body="你好,下周一将举办 AI 技术分享会,欢迎参加。"
|
||||
),
|
||||
Email(
|
||||
id="2",
|
||||
subject="项目进度更新",
|
||||
sender="manager@example.com",
|
||||
recipients=["user@example.com"],
|
||||
date=datetime.now().isoformat(),
|
||||
body="项目进度良好,继续保持。"
|
||||
),
|
||||
]
|
||||
|
||||
return MOCK_EMAILS_DB
|
||||
async def add_contact(self, user_id: str, contact: Contact) -> bool:
|
||||
"""添加联系人"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute(
|
||||
"contact", "add_contact",
|
||||
user_id=user_id, contact=contact
|
||||
)
|
||||
return result.success and result.data
|
||||
|
||||
def generate_email_draft_mock(self, query: str) -> Dict[str, str]:
|
||||
"""模拟生成邮件草稿"""
|
||||
async def list_emails(self, user_id: str = "default") -> List[Email]:
|
||||
"""查询邮件列表"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute("contact", "list_emails", user_id=user_id)
|
||||
if result.success:
|
||||
return result.data
|
||||
return []
|
||||
|
||||
async def generate_email_draft(self, query: str) -> Dict[str, str]:
|
||||
"""生成邮件草稿"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute(
|
||||
"contact", "generate_email_draft", query=query
|
||||
)
|
||||
if result.success:
|
||||
return result.data
|
||||
return {
|
||||
"subject": f"Re: {query}",
|
||||
"recipient": "recipient@example.com",
|
||||
"body": "你好,\n\n这是一封自动生成的邮件草稿。\n\n此致,\n你的助手"
|
||||
"body": "你好,\n\n这是一封自动生成的邮件草稿。"
|
||||
}
|
||||
|
||||
def send_email_mock(self, recipient: str, subject: str, body: str) -> Dict[str, Any]:
|
||||
"""模拟发送邮件"""
|
||||
global MOCK_EMAILS_DB
|
||||
|
||||
MOCK_EMAILS_DB.append(
|
||||
Email(
|
||||
id=str(len(MOCK_EMAILS_DB) + 1),
|
||||
subject=subject,
|
||||
sender="me@example.com",
|
||||
recipients=[recipient],
|
||||
date=datetime.now().isoformat(),
|
||||
body=body
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "邮件发送成功"
|
||||
}
|
||||
|
||||
def sniff_contacts_mock(self, query: str) -> Dict[str, Any]:
|
||||
"""模拟智能嗅探联系人"""
|
||||
import re
|
||||
|
||||
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query)
|
||||
phones = re.findall(r'1[3-9]\d{9}', query)
|
||||
|
||||
contacts = []
|
||||
for i, email in enumerate(emails):
|
||||
contacts.append({
|
||||
"name": f"联系人{i+1}",
|
||||
"email": email,
|
||||
"phone": phones[i] if i < len(phones) else ""
|
||||
})
|
||||
|
||||
return {
|
||||
"contacts": contacts,
|
||||
"count": len(contacts),
|
||||
"suggestion": "是否添加这些联系人?"
|
||||
}
|
||||
|
||||
# ========== 公共方法(自动选择模式)==========
|
||||
|
||||
async def list_contacts(self, user_id: str = "default") -> List[Contact]:
|
||||
"""获取联系人列表(自动选择数据库或模拟模式)"""
|
||||
if self._use_db:
|
||||
return await self.list_contacts_db(user_id)
|
||||
return self.list_contacts_mock(user_id)
|
||||
|
||||
async def add_contact(self, user_id: str, contact: Contact) -> bool:
|
||||
"""添加联系人(自动选择数据库或模拟模式)"""
|
||||
if self._use_db:
|
||||
return await self.add_contact_db(user_id, contact)
|
||||
return self.save_contact_mock(user_id, contact)
|
||||
|
||||
async def list_emails(self, user_id: str = "default") -> List[Email]:
|
||||
"""查询邮件列表(目前用模拟)"""
|
||||
return self.list_emails_mock()
|
||||
|
||||
async def generate_email_draft(self, query: str) -> Dict[str, str]:
|
||||
"""生成邮件草稿(目前用模拟)"""
|
||||
return self.generate_email_draft_mock(query)
|
||||
|
||||
async def send_email(self, user_id: str, recipient: str, subject: str, body: str) -> bool:
|
||||
"""发送邮件(目前用模拟)"""
|
||||
result = self.send_email_mock(recipient, subject, body)
|
||||
return result.get("success", False)
|
||||
"""发送邮件"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute(
|
||||
"contact", "send_email",
|
||||
user_id=user_id, recipient=recipient, subject=subject, body=body
|
||||
)
|
||||
return result.success
|
||||
|
||||
async def sniff_contacts(self, query: str) -> List[Contact]:
|
||||
"""智能嗅探联系人(目前用模拟)"""
|
||||
result = self.sniff_contacts_mock(query)
|
||||
contact_dicts = result.get("contacts", [])
|
||||
return [
|
||||
Contact(
|
||||
id=str(i+1),
|
||||
name=c.get("name", ""),
|
||||
phone=c.get("phone", ""),
|
||||
email=c.get("email", ""),
|
||||
company="",
|
||||
position="",
|
||||
created_at=datetime.now().isoformat()
|
||||
)
|
||||
for i, c in enumerate(contact_dicts)
|
||||
]
|
||||
"""智能嗅探联系人"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute(
|
||||
"contact", "sniff_contacts", query=query
|
||||
)
|
||||
if result.success:
|
||||
return result.data
|
||||
return []
|
||||
|
||||
# 保持向后兼容的旧方法
|
||||
def list_contacts_mock(self, user_id: str = "default") -> List[Contact]:
|
||||
"""模拟查询(保留用于向后兼容)"""
|
||||
import asyncio
|
||||
try:
|
||||
return asyncio.run(self.list_contacts(user_id))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
# 全局实例(模拟模式,保留向后兼容)
|
||||
# 全局单例(保持向后兼容)
|
||||
contact_api = ContactAPIClient()
|
||||
|
||||
@@ -1,192 +1,82 @@
|
||||
"""
|
||||
词典API调用工具
|
||||
Dictionary API Client
|
||||
支持 async 和真实数据库缓存
|
||||
词典API调用工具(使用MCP统一接口)
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...mcp.mcp_manager import mcp_manager
|
||||
from ...mcp.adapters import DictionaryAdapter
|
||||
|
||||
|
||||
@dataclass
|
||||
class DictionaryAPIClient:
|
||||
"""
|
||||
词典API客户端 - 可扩展支持多种API和数据库缓存
|
||||
词典API客户端 - 使用MCP统一接口
|
||||
|
||||
保持向后兼容,内部使用MCP适配器
|
||||
"""
|
||||
|
||||
# 可以配置多个API
|
||||
# 保留配置字段用于向后兼容
|
||||
youdao_api_key: Optional[str] = None
|
||||
youdao_api_secret: Optional[str] = None
|
||||
|
||||
# 数据库 Repository(可选,用于缓存单词查询)
|
||||
word_repository: Optional[Any] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化后,如果有 repository 则支持 async"""
|
||||
pass
|
||||
|
||||
async def query_word_db(self, user_id: str, word: str) -> Optional[Dict[str, Any]]:
|
||||
"""从数据库缓存查询单词"""
|
||||
if not self.word_repository:
|
||||
return None
|
||||
"""初始化后设置MCP"""
|
||||
import asyncio
|
||||
try:
|
||||
entity = await self.word_repository.search_by_word(user_id, word)
|
||||
if entity:
|
||||
return {
|
||||
"phonetic": entity.phonetic,
|
||||
"part_of_speech": entity.part_of_speech,
|
||||
"definitions": [entity.definition] if entity.definition else [],
|
||||
"examples": [entity.examples] if entity.examples else []
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"从数据库查询单词失败:{e}")
|
||||
return None
|
||||
asyncio.create_task(self._init_mcp())
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
async def cache_word_db(self, user_id: str, word: str, data: Dict[str, Any]):
|
||||
"""把单词查询结果缓存到数据库"""
|
||||
if not self.word_repository:
|
||||
return
|
||||
try:
|
||||
from ...db.models import WordEntity
|
||||
entity = WordEntity(
|
||||
user_id=user_id,
|
||||
word=word,
|
||||
phonetic=data.get("phonetic", ""),
|
||||
part_of_speech=data.get("part_of_speech", ""),
|
||||
definition=data.get("definitions", [""])[0] if data.get("definitions") else "",
|
||||
examples=data.get("examples", [""])[0] if data.get("examples") else ""
|
||||
async def _init_mcp(self):
|
||||
"""初始化MCP系统"""
|
||||
if not mcp_manager.get_adapter("dictionary"):
|
||||
mcp_manager.register_adapter(
|
||||
DictionaryAdapter(word_repo=self.word_repository)
|
||||
)
|
||||
await self.word_repository.insert(entity)
|
||||
except Exception as e:
|
||||
print(f"缓存单词到数据库失败:{e}")
|
||||
await mcp_manager.initialize()
|
||||
|
||||
async def query_word_youdao(self, word: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
调用有道词典API查询单词(async 版本)
|
||||
注意:需要配置有道API密钥才能使用
|
||||
文档:https://ai.youdao.com/doc.s#guide
|
||||
"""
|
||||
if not self.youdao_api_key or not self.youdao_api_secret:
|
||||
return None
|
||||
|
||||
try:
|
||||
# TODO: 实现真实的有道API调用(用 httpx 或 aiohttp)
|
||||
# 这里是示例结构
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"有道API调用失败:{e}")
|
||||
return None
|
||||
|
||||
async def translate_baidu(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
调用百度翻译API(async 版本)
|
||||
注意:需要配置百度API密钥才能使用
|
||||
文档:https://fanyi-api.baidu.com/doc/21
|
||||
"""
|
||||
# TODO: 实现真实的百度翻译API调用(用 httpx 或 aiohttp)
|
||||
return None
|
||||
async def query_word(
|
||||
self,
|
||||
user_id: str = "default",
|
||||
word: str = "",
|
||||
use_cache: bool = True
|
||||
) -> Dict[str, Any]:
|
||||
"""查询单词(统一入口)"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute(
|
||||
"dictionary", "query_word",
|
||||
user_id=user_id, word=word, use_cache=use_cache
|
||||
)
|
||||
if result.success:
|
||||
return result.data
|
||||
return self.query_word_mock(word)
|
||||
|
||||
def query_word_mock(self, word: str) -> Dict[str, Any]:
|
||||
"""
|
||||
模拟词典API - 目前用于演示
|
||||
"""
|
||||
mock_db = {
|
||||
"serendipity": {
|
||||
"phonetic": "/ˌserənˈdipədē/",
|
||||
"part_of_speech": "n.",
|
||||
"definitions": ["意外发现珍奇事物的能力", "机缘凑巧"],
|
||||
"examples": ["Finding that old photo was pure serendipity."]
|
||||
},
|
||||
"ephemeral": {
|
||||
"phonetic": "/əˈfem(ə)rəl/",
|
||||
"part_of_speech": "adj.",
|
||||
"definitions": ["短暂的,瞬息的"],
|
||||
"examples": ["Fame in the digital age is often ephemeral."]
|
||||
},
|
||||
"ubiquitous": {
|
||||
"phonetic": "/yo͞oˈbikwədəs/",
|
||||
"part_of_speech": "adj.",
|
||||
"definitions": ["无处不在的", "普遍存在的"],
|
||||
"examples": ["Smartphones have become ubiquitous in modern life."]
|
||||
},
|
||||
"eloquent": {
|
||||
"phonetic": "/ˈeləkwənt/",
|
||||
"part_of_speech": "adj.",
|
||||
"definitions": ["雄辩的,有说服力的"],
|
||||
"examples": ["She gave an eloquent speech at the conference."]
|
||||
},
|
||||
"resilient": {
|
||||
"phonetic": "/rəˈzilyənt/",
|
||||
"part_of_speech": "adj.",
|
||||
"definitions": ["有复原力的,能适应的"],
|
||||
"examples": ["The community has proven to be resilient in the face of challenges."]
|
||||
}
|
||||
"""模拟查询(保留用于向后兼容)"""
|
||||
return {
|
||||
"word": word,
|
||||
"phonetic": "",
|
||||
"part_of_speech": "n.",
|
||||
"definitions": [f"{word} 的释义1", f"{word} 的释义2"],
|
||||
"examples": [f"This is an example sentence with '{word}'."]
|
||||
}
|
||||
|
||||
if word.lower() in mock_db:
|
||||
return mock_db[word.lower()]
|
||||
else:
|
||||
return {
|
||||
"phonetic": "",
|
||||
"part_of_speech": "n.",
|
||||
"definitions": [f"{word}的释义1", f"{word}的释义2"],
|
||||
"examples": [f"This is an example sentence with '{word}'."]
|
||||
}
|
||||
|
||||
def translate_mock(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Dict[str, Any]:
|
||||
"""
|
||||
模拟翻译API - 目前用于演示
|
||||
"""
|
||||
translations = {
|
||||
"你好": "Hello",
|
||||
"hello": "你好",
|
||||
"人工智能": "Artificial Intelligence",
|
||||
"artificial intelligence": "人工智能",
|
||||
"ai": "人工智能",
|
||||
"大模型": "Large Language Model",
|
||||
"自然语言处理": "Natural Language Processing"
|
||||
}
|
||||
|
||||
"""模拟翻译(保留用于向后兼容)"""
|
||||
return {
|
||||
"translated_text": translations.get(text.lower(), f"【翻译结果】{text}"),
|
||||
"translated_text": f"【翻译】{text}",
|
||||
"confidence": 0.95
|
||||
}
|
||||
|
||||
def extract_terms_mock(self, text: str) -> list:
|
||||
"""
|
||||
模拟术语提取API
|
||||
"""
|
||||
"""模拟术语提取(保留用于向后兼容)"""
|
||||
return [
|
||||
{"term": "AI", "type": "技术术语", "definition": "人工智能", "confidence": 0.95},
|
||||
{"term": "LLM", "type": "技术术语", "definition": "大语言模型", "confidence": 0.92},
|
||||
{"term": "NLP", "type": "技术术语", "definition": "自然语言处理", "confidence": 0.88}
|
||||
{"term": "大模型", "type": "技术术语", "definition": "大语言模型", "confidence": 0.92}
|
||||
]
|
||||
|
||||
# ========== 统一入口(优先查缓存) ==========
|
||||
async def query_word(self, user_id: str = "default", word: str = "", use_cache: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
查询单词(统一入口,优先查数据库缓存)
|
||||
"""
|
||||
# 1. 先查数据库缓存
|
||||
if use_cache:
|
||||
cached = await self.query_word_db(user_id, word)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
# 2. 查第三方 API(暂未实现)
|
||||
api_result = await self.query_word_youdao(word)
|
||||
if api_result:
|
||||
if use_cache:
|
||||
await self.cache_word_db(user_id, word, api_result)
|
||||
return api_result
|
||||
|
||||
# 3. 用模拟数据(兜底)
|
||||
mock_result = self.query_word_mock(word)
|
||||
if use_cache:
|
||||
await self.cache_word_db(user_id, word, mock_result)
|
||||
return mock_result
|
||||
|
||||
|
||||
# 单例实例(模拟模式,保持向后兼容)
|
||||
# 全局单例(保持向后兼容)
|
||||
dictionary_api = DictionaryAPIClient()
|
||||
|
||||
@@ -1,72 +1,61 @@
|
||||
"""
|
||||
资讯子图API调用工具
|
||||
News Analysis API Client
|
||||
支持 async 和真实数据库缓存
|
||||
资讯子图API调用工具(使用MCP统一接口)
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional, List
|
||||
import random
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...mcp.mcp_manager import mcp_manager
|
||||
from ...mcp.adapters import NewsAdapter
|
||||
|
||||
|
||||
@dataclass
|
||||
class NewsAPIClient:
|
||||
"""
|
||||
资讯API客户端 - 可扩展支持多种API和数据库缓存
|
||||
资讯API客户端 - 使用MCP统一接口
|
||||
|
||||
保持向后兼容,内部使用MCP适配器
|
||||
"""
|
||||
|
||||
# 可以配置多个API(如 NewsAPI, 今日头条, 百度新闻等)
|
||||
# 保留配置字段用于向后兼容
|
||||
newsapi_key: Optional[str] = None
|
||||
|
||||
# 数据库 Repository(可选,用于缓存新闻)
|
||||
news_repository: Optional[Any] = None
|
||||
|
||||
async def query_news_db(self, user_id: str, keyword: str) -> Optional[List[Dict[str, Any]]]:
|
||||
"""从数据库缓存查询新闻"""
|
||||
if not self.news_repository:
|
||||
return None
|
||||
def __post_init__(self):
|
||||
"""初始化后设置MCP"""
|
||||
import asyncio
|
||||
try:
|
||||
entities = await self.news_repository.search_by_keywords(user_id, keyword)
|
||||
if entities:
|
||||
return [
|
||||
{
|
||||
"title": e.title,
|
||||
"source": e.source,
|
||||
"summary": e.content,
|
||||
"keywords": e.keywords.split(",") if e.keywords else [],
|
||||
"author": "",
|
||||
"published_at": e.created_at
|
||||
}
|
||||
for e in entities
|
||||
]
|
||||
except Exception as e:
|
||||
print(f"从数据库查询新闻失败:{e}")
|
||||
return None
|
||||
asyncio.create_task(self._init_mcp())
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
async def cache_news_db(self, user_id: str, news: Dict[str, Any]):
|
||||
"""把新闻缓存到数据库"""
|
||||
if not self.news_repository:
|
||||
return
|
||||
try:
|
||||
from ...db.models import NewsEntity
|
||||
entity = NewsEntity(
|
||||
user_id=user_id,
|
||||
title=news.get("title", ""),
|
||||
content=news.get("summary", ""),
|
||||
url=news.get("url", ""),
|
||||
source=news.get("source", ""),
|
||||
keywords=",".join(news.get("keywords", []))
|
||||
async def _init_mcp(self):
|
||||
"""初始化MCP系统"""
|
||||
if not mcp_manager.get_adapter("news"):
|
||||
mcp_manager.register_adapter(
|
||||
NewsAdapter(news_repo=self.news_repository)
|
||||
)
|
||||
await self.news_repository.insert(entity)
|
||||
except Exception as e:
|
||||
print(f"缓存新闻到数据库失败:{e}")
|
||||
await mcp_manager.initialize()
|
||||
|
||||
async def query_news(
|
||||
self,
|
||||
user_id: str = "default",
|
||||
query: str = "",
|
||||
use_cache: bool = True
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""查询新闻(统一入口)"""
|
||||
await self._init_mcp()
|
||||
result = await mcp_manager.execute(
|
||||
"news", "query_news",
|
||||
user_id=user_id, query=query, use_cache=use_cache
|
||||
)
|
||||
if result.success:
|
||||
return result.data
|
||||
return self.query_news_mock(query)
|
||||
|
||||
def query_news_mock(self, query: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
模拟查询资讯 - 目前用于演示
|
||||
"""
|
||||
# 模拟资讯数据库
|
||||
"""模拟查询(保留用于向后兼容)"""
|
||||
mock_news = [
|
||||
{
|
||||
"title": "OpenAI发布GPT-5:智能再升级",
|
||||
@@ -83,74 +72,44 @@ class NewsAPIClient:
|
||||
"keywords": ["医疗", "大模型", "应用"],
|
||||
"author": "Medical Team",
|
||||
"published_at": datetime.now().isoformat()
|
||||
},
|
||||
{
|
||||
"title": "2026年AI行业发展趋势报告",
|
||||
"source": "Business Daily",
|
||||
"summary": "最新行业报告显示,AI行业将继续保持高速增长,企业数字化转型加速...",
|
||||
"keywords": ["趋势", "AI", "商业"],
|
||||
"author": "Business Team",
|
||||
"published_at": datetime.now().isoformat()
|
||||
}
|
||||
]
|
||||
|
||||
# 根据查询词简单过滤
|
||||
results = []
|
||||
query_lower = query.lower()
|
||||
|
||||
for news in mock_news:
|
||||
if (query_lower in news["title"].lower() or
|
||||
query_lower in news["summary"].lower() or
|
||||
query_lower in news["summary"].lower() or
|
||||
any(keyword.lower() in query_lower for keyword in news["keywords"])):
|
||||
results.append(news)
|
||||
|
||||
# 如果没有匹配到,返回前两条
|
||||
if not results:
|
||||
results = mock_news[:2]
|
||||
|
||||
return results
|
||||
return results if results else mock_news[:2]
|
||||
|
||||
def analyze_url_mock(self, url: str) -> Dict[str, Any]:
|
||||
"""
|
||||
模拟URL分析 - 目前用于演示
|
||||
"""
|
||||
"""模拟URL分析(保留用于向后兼容)"""
|
||||
return {
|
||||
"title": f"分析结果:{url}",
|
||||
"source": "URL Analyzer",
|
||||
"summary": "已完成对该URL的内容分析,包含文章摘要和情感倾向判断...",
|
||||
"keywords": ["News", "Analysis", url.split("/")[-1] if url else "unknown"]
|
||||
"keywords": ["News", "Analysis"]
|
||||
}
|
||||
|
||||
def extract_keywords_mock(self, text: str) -> List[str]:
|
||||
"""
|
||||
模拟关键词提取 - 目前用于演示
|
||||
"""
|
||||
# 简单的关键词提取模拟
|
||||
common_keywords = ["AI", "大模型", "应用场景", "行业趋势", "创新", "技术"]
|
||||
result = []
|
||||
|
||||
for keyword in common_keywords:
|
||||
if keyword.lower() in text.lower():
|
||||
result.append(keyword)
|
||||
|
||||
# 如果没找到,返回默认关键词
|
||||
if not result:
|
||||
result = ["AI", "大模型", "应用场景", "行业趋势"]
|
||||
|
||||
return result
|
||||
"""模拟关键词提取(保留用于向后兼容)"""
|
||||
keywords = ["AI", "大模型", "应用场景", "行业趋势", "创新", "技术"]
|
||||
result = [k for k in keywords if k.lower() in text.lower()]
|
||||
return result if result else keywords[:4]
|
||||
|
||||
def generate_report_mock(self, query: str) -> str:
|
||||
"""
|
||||
模拟报告生成 - 目前用于演示
|
||||
"""
|
||||
report = f"""═══════════════════════════════════════════
|
||||
"""模拟报告生成(保留用于向后兼容)"""
|
||||
return f"""═══════════════════════════════════════════
|
||||
📊 资讯分析报告
|
||||
═══════════════════════════════════════════
|
||||
|
||||
主题:{query}
|
||||
|
||||
📋 摘要:
|
||||
这是一份关于 {query} 的资讯分析综合报告,包含最新行业动态和趋势分析。
|
||||
这是关于 {query} 的资讯分析综合报告。
|
||||
|
||||
🔍 主要发现:
|
||||
1. AI技术持续快速发展
|
||||
@@ -161,36 +120,10 @@ class NewsAPIClient:
|
||||
- AI
|
||||
- 大模型
|
||||
- 数字化转型
|
||||
- 创新
|
||||
|
||||
═══════════════════════════════════════════
|
||||
💡 建议:继续关注行业动态,把握发展机遇!
|
||||
"""
|
||||
return report
|
||||
|
||||
# ========== 统一入口(优先查缓存) ==========
|
||||
async def query_news(self, user_id: str = "default", query: str = "", use_cache: bool = True) -> List[Dict[str, Any]]:
|
||||
"""查询新闻(统一入口,优先查数据库缓存)"""
|
||||
# 1. 先查数据库缓存
|
||||
if use_cache:
|
||||
cached = await self.query_news_db(user_id, query)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
# 2. 查第三方 API(暂未实现)
|
||||
# api_result = await self.query_news_api(query)
|
||||
# if api_result:
|
||||
# for news in api_result:
|
||||
# await self.cache_news_db(user_id, news)
|
||||
# return api_result
|
||||
|
||||
# 3. 用模拟数据(兜底)
|
||||
mock_result = self.query_news_mock(query)
|
||||
if use_cache:
|
||||
for news in mock_result:
|
||||
await self.cache_news_db(user_id, news)
|
||||
return mock_result
|
||||
|
||||
|
||||
# 单例实例(模拟模式,保持向后兼容)
|
||||
# 全局单例(保持向后兼容)
|
||||
news_api = NewsAPIClient()
|
||||
|
||||
Reference in New Issue
Block a user