feat: 接入 DeepSeek AI 分析公告是否为中国电信可承接项目
- 新增 ai_enabled/ai_api_key/ai_base_url/ai_model 等配置项,通过 .env 管理 - 新增 app/services/ai_analyzer.py — DeepSeek API 调用 + 详情页正文提取 - 新增 extract_page_content() 从详情页抓取正文供 AI 分析 - Announcement 模型新增 ai_relevant / ai_analysis 字段 - 流水线集成 AI 分析步骤(关键词匹配后、通知前) - AI 标记为可承接的项目额外发送 markdown 着重通知 - 创建 alembic 迁移版本 6e8f4c2d1b0a
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from app.config import settings
|
||||
from app.crawler.base import PipelineConfig, PipelineResult
|
||||
from app.services.filter_service import dedup_by_hash
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PostCrawlPipeline:
|
||||
def __init__(self, db_session, notification_service):
|
||||
@@ -41,6 +45,10 @@ class PostCrawlPipeline:
|
||||
if to_notify:
|
||||
to_notify = await self._exclude_sent(to_notify)
|
||||
|
||||
# 5.5 AI 分析(可选)
|
||||
if settings.ai_enabled and to_notify:
|
||||
await self._ai_analyze(to_notify)
|
||||
|
||||
# 6. Notify
|
||||
if config.notify_mode == "all":
|
||||
result.notified = await self._send_notifications(to_notify)
|
||||
@@ -129,3 +137,52 @@ class PostCrawlPipeline:
|
||||
def _match_keywords(announcement: dict[str, Any], keywords: list[str]) -> bool:
|
||||
text = f"{announcement.get('title', '')} {announcement.get('purchase_name', '')}"
|
||||
return any(kw in text for kw in keywords)
|
||||
|
||||
async def _ai_analyze(self, announcements: list[dict[str, Any]]) -> None:
|
||||
"""对公告列表执行 AI 分析,将结果附加到每条公告的 ai_result 字段,并更新数据库"""
|
||||
from app.services.ai_analyzer import AiAnalyzer
|
||||
|
||||
analyzer = AiAnalyzer()
|
||||
logger.info("AI 分析开始:共 %d 条公告", len(announcements))
|
||||
|
||||
results = await analyzer.analyze_batch(announcements)
|
||||
|
||||
ai_updates = []
|
||||
for ann, ai_result in zip(announcements, results):
|
||||
ann["ai_result"] = {
|
||||
"is_relevant": ai_result.is_relevant,
|
||||
"reason": ai_result.reason,
|
||||
"business_type": ai_result.business_type,
|
||||
}
|
||||
if ai_result.error:
|
||||
logger.warning("AI 分析失败 [%s]: %s", ann.get("title", "")[:30], ai_result.error)
|
||||
else:
|
||||
ai_updates.append({
|
||||
"content_hash": ann["content_hash"],
|
||||
"ai_relevant": ai_result.is_relevant,
|
||||
"ai_analysis": ai_result.reason,
|
||||
})
|
||||
if ai_result.is_relevant:
|
||||
logger.info("AI 标记可承接项目: %s (%s)", ann.get("title", "")[:40], ai_result.business_type)
|
||||
|
||||
# 批量更新数据库中的 AI 分析结果
|
||||
if ai_updates:
|
||||
await self._update_ai_results(ai_updates)
|
||||
|
||||
async def _update_ai_results(self, updates: list[dict[str, Any]]) -> None:
|
||||
"""批量更新公告的 AI 分析结果到数据库"""
|
||||
from sqlalchemy import update
|
||||
|
||||
from app.models.announcement import Announcement
|
||||
|
||||
for u in updates:
|
||||
stmt = (
|
||||
update(Announcement)
|
||||
.where(Announcement.content_hash == u["content_hash"])
|
||||
.values(
|
||||
ai_relevant=u["ai_relevant"],
|
||||
ai_analysis=u["ai_analysis"],
|
||||
)
|
||||
)
|
||||
await self.db.execute(stmt)
|
||||
await self.db.commit()
|
||||
|
||||
Reference in New Issue
Block a user