653 lines
17 KiB
Markdown
653 lines
17 KiB
Markdown
# Phase F.4:AI 集成
|
||
|
||
日期:2026-04-04
|
||
状态:待开始
|
||
依赖:F.3(待完成)
|
||
前置:services/forum_ai_service.py
|
||
|
||
---
|
||
|
||
## 1. 本阶段目的
|
||
|
||
为 Jarvis Forum 集成 AI 能力:
|
||
|
||
- AI 自动回复
|
||
- 帖子摘要生成
|
||
- 智能分类打标
|
||
- Agent 自主发帖
|
||
|
||
---
|
||
|
||
## 2. Forum AI Service
|
||
|
||
### 2.1 服务架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ForumAIService │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
|
||
│ │ AutoReply │ │ Summary │ │ SmartTagging │ │
|
||
│ │ Service │ │ Service │ │ Service │ │
|
||
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
|
||
│ │ │ │ │
|
||
│ └────────────────┼───────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────┴───────────┐ │
|
||
│ │ LLM Service │ │
|
||
│ │ (复用 Agent LLM) │ │
|
||
│ └───────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 核心服务
|
||
|
||
```python
|
||
class ForumAIService:
|
||
"""论坛 AI 服务"""
|
||
|
||
def __init__(
|
||
self,
|
||
llm_service: LLMService,
|
||
config: ForumAIConfig,
|
||
):
|
||
self.llm = llm_service
|
||
self.config = config
|
||
|
||
# === 自动回复 ===
|
||
async def generate_auto_reply(
|
||
self,
|
||
post: ForumPost,
|
||
replies: List[ForumReply],
|
||
) -> Optional[str]:
|
||
"""生成自动回复"""
|
||
if not self.config.auto_reply_enabled:
|
||
return None
|
||
|
||
# 检查是否需要回复
|
||
if not self._should_auto_reply(post):
|
||
return None
|
||
|
||
# 构建上下文
|
||
context = self._build_reply_context(post, replies)
|
||
|
||
# 生成回复
|
||
prompt = self._build_reply_prompt(context)
|
||
response = await self.llm.agenerate(prompt)
|
||
|
||
return response.content if response else None
|
||
|
||
def _should_auto_reply(self, post: ForumPost) -> bool:
|
||
"""判断是否应该自动回复"""
|
||
# 指令类帖子不自动回复
|
||
if post.category == "instruction":
|
||
return False
|
||
|
||
# 有 AI 回复的不重复回复
|
||
if any(r.is_ai_reply for r in post.replies):
|
||
return False
|
||
|
||
# 检查时间窗口
|
||
if post.last_reply_at:
|
||
hours_since = (datetime.utcnow() - post.last_reply_at).total_seconds() / 3600
|
||
if hours_since < self.config.auto_reply_min_hours:
|
||
return False
|
||
|
||
return True
|
||
|
||
def _build_reply_context(
|
||
self,
|
||
post: ForumPost,
|
||
replies: List[ForumReply],
|
||
) -> str:
|
||
"""构建回复上下文"""
|
||
context = f"""## 帖子信息
|
||
标题: {post.title}
|
||
内容: {post.content[:500]}...
|
||
|
||
## 回复列表
|
||
"""
|
||
for reply in replies[-5:]: # 最近 5 条
|
||
context += f"- {reply.user_id}: {reply.content[:200]}...\n"
|
||
|
||
return context
|
||
|
||
def _build_reply_prompt(self, context: str) -> str:
|
||
"""构建回复提示词"""
|
||
return f"""你是一个友好的社区助手。请根据以下帖子内容,生成一条有帮助的回复。
|
||
|
||
{context}
|
||
|
||
要求:
|
||
1. 回复要友好、专业、有帮助
|
||
2. 不要重复已有的观点
|
||
3. 如果是问题,尽量给出建设性的建议
|
||
4. 回复长度控制在 100-300 字
|
||
5. 不要使用 Markdown 格式,直接输出文本
|
||
|
||
回复:"""
|
||
|
||
# === 摘要生成 ===
|
||
async def generate_summary(
|
||
self,
|
||
content: str,
|
||
max_length: int = 200,
|
||
) -> str:
|
||
"""生成帖子摘要"""
|
||
if len(content) <= max_length:
|
||
return content
|
||
|
||
prompt = f"""请为以下内容生成一个简洁的摘要,不超过 {max_length} 字。
|
||
|
||
内容:
|
||
{content}
|
||
|
||
摘要:"""
|
||
|
||
response = await self.llm.agenerate(prompt)
|
||
return response.content if response else content[:max_length]
|
||
|
||
# === 智能打标 ===
|
||
async def suggest_tags(
|
||
self,
|
||
title: str,
|
||
content: str,
|
||
existing_tags: List[str],
|
||
) -> List[str]:
|
||
"""智能推荐标签"""
|
||
prompt = f"""请根据以下帖子内容,推荐 3-5 个最合适的标签。
|
||
|
||
标题: {title}
|
||
内容: {content[:1000]}...
|
||
|
||
已有标签: {', '.join(existing_tags) if existing_tags else '无'}
|
||
|
||
要求:
|
||
1. 标签要简洁,2-4 个字
|
||
2. 选择最相关的标签
|
||
3. 如果已有标签合适可以保留
|
||
4. 只输出标签,用逗号分隔,不要其他内容
|
||
|
||
标签:"""
|
||
|
||
response = await self.llm.agenerate(prompt)
|
||
if not response:
|
||
return existing_tags
|
||
|
||
# 解析标签
|
||
tags = [t.strip() for t in response.content.split(",")]
|
||
return tags[:5] # 最多 5 个
|
||
|
||
# === 智能分类 ===
|
||
async def classify_category(
|
||
self,
|
||
title: str,
|
||
content: str,
|
||
) -> str:
|
||
"""智能分类"""
|
||
categories = ["instruction", "discussion", "question", "praise", "bug", "other"]
|
||
|
||
prompt = f"""请为以下帖子选择一个最合适的分类。
|
||
|
||
标题: {title}
|
||
内容: {content[:500]}...
|
||
|
||
可选分类:
|
||
- instruction: 指令/任务请求
|
||
- discussion: 讨论/分享
|
||
- question: 问题/求助
|
||
- praise: 表扬/感谢
|
||
- bug: Bug 反馈
|
||
- other: 其他
|
||
|
||
请直接输出分类名称,不要其他内容。
|
||
|
||
分类:"""
|
||
|
||
response = await self.llm.agenerate(prompt)
|
||
if not response:
|
||
return "other"
|
||
|
||
category = response.content.strip().lower()
|
||
if category not in categories:
|
||
return "other"
|
||
|
||
return category
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Summary Service
|
||
|
||
### 3.1 服务实现
|
||
|
||
```python
|
||
class SummaryService:
|
||
"""帖子摘要服务"""
|
||
|
||
def __init__(self, cache: ForumCache):
|
||
self.cache = cache
|
||
|
||
async def get_post_summary(
|
||
self,
|
||
post_id: str,
|
||
post: ForumPost,
|
||
) -> str:
|
||
"""获取帖子摘要(带缓存)"""
|
||
cache_key = f"summary:{post_id}"
|
||
|
||
# 尝试从缓存获取
|
||
cached = await self.cache.get(cache_key)
|
||
if cached:
|
||
return cached
|
||
|
||
# 生成摘要
|
||
summary = await self._generate_summary(post)
|
||
|
||
# 存入缓存(1 小时)
|
||
await self.cache.set(cache_key, summary, ttl=3600)
|
||
|
||
return summary
|
||
|
||
async def get_thread_summary(
|
||
self,
|
||
post: ForumPost,
|
||
replies: List[ForumReply],
|
||
) -> str:
|
||
"""获取帖子串摘要(主帖+回复摘要)"""
|
||
summaries = [await self.get_post_summary(post.id, post)]
|
||
|
||
# 汇总回复要点
|
||
if len(replies) > 0:
|
||
summary_prompt = f"""请总结以下回复的核心观点,用 50 字以内概括。
|
||
|
||
回复列表:
|
||
{self._format_replies(replies[:10])}
|
||
|
||
总结:"""
|
||
|
||
response = await self.llm.agenerate(summary_prompt)
|
||
if response:
|
||
summaries.append(f"回复要点: {response.content}")
|
||
|
||
return "\n\n".join(summaries)
|
||
|
||
async def invalidate_summary(self, post_id: str) -> None:
|
||
"""清除摘要缓存"""
|
||
await self.cache.invalidate(f"summary:{post_id}")
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Agent 自主发帖
|
||
|
||
### 4.1 Agent Forum 工具
|
||
|
||
```python
|
||
# agents/tools/forum_tools.py
|
||
from typing import List, Optional
|
||
|
||
|
||
class ForumTools:
|
||
"""Forum Agent 工具集"""
|
||
|
||
def __init__(self, forum_service: ForumService, ai_service: ForumAIService):
|
||
self.forum = forum_service
|
||
self.ai = ai_service
|
||
|
||
@tool
|
||
async def create_forum_post(
|
||
title: str,
|
||
content: str,
|
||
board_id: Optional[str] = None,
|
||
category: Optional[str] = None,
|
||
tags: Optional[List[str]] = None,
|
||
) -> dict:
|
||
"""创建论坛帖子
|
||
|
||
参数:
|
||
- title: 帖子标题
|
||
- content: 帖子内容
|
||
- board_id: 板块 ID(可选)
|
||
- category: 分类(可选)
|
||
- tags: 标签列表(可选)
|
||
"""
|
||
data = ForumPostCreate(
|
||
title=title,
|
||
content=content,
|
||
board_id=board_id,
|
||
category=category,
|
||
tags=tags or [],
|
||
)
|
||
post = await self.forum.create_post(agent_id=AGENT_ID, data=data)
|
||
return {"post_id": post.id, "title": post.title}
|
||
|
||
@tool
|
||
async def reply_to_post(
|
||
post_id: str,
|
||
content: str,
|
||
) -> dict:
|
||
"""回复帖子
|
||
|
||
参数:
|
||
- post_id: 帖子 ID
|
||
- content: 回复内容
|
||
"""
|
||
data = ForumReplyCreate(content=content)
|
||
reply = await self.forum.create_reply(
|
||
post_id=post_id,
|
||
agent_id=AGENT_ID,
|
||
data=data,
|
||
)
|
||
return {"reply_id": reply.id, "floor": reply.floor}
|
||
|
||
@tool
|
||
async def search_forum_posts(
|
||
query: str,
|
||
board_id: Optional[str] = None,
|
||
category: Optional[str] = None,
|
||
limit: int = 10,
|
||
) -> List[dict]:
|
||
"""搜索论坛帖子
|
||
|
||
参数:
|
||
- query: 搜索关键词
|
||
- board_id: 限定板块(可选)
|
||
- category: 限定分类(可选)
|
||
- limit: 返回数量(默认 10)
|
||
"""
|
||
posts = await self.forum.search_posts(
|
||
query=query,
|
||
board_id=board_id,
|
||
category=category,
|
||
limit=limit,
|
||
)
|
||
return [
|
||
{"id": p.id, "title": p.title, "summary": p.content[:100]}
|
||
for p in posts
|
||
]
|
||
|
||
@tool
|
||
async def get_forum_trending(
|
||
board_id: Optional[str] = None,
|
||
limit: int = 5,
|
||
) -> List[dict]:
|
||
"""获取热门帖子
|
||
|
||
参数:
|
||
- board_id: 板块 ID(可选)
|
||
- limit: 返回数量(默认 5)
|
||
"""
|
||
posts = await self.forum.get_trending(
|
||
board_id=board_id,
|
||
limit=limit,
|
||
)
|
||
return [
|
||
{
|
||
"id": p.id,
|
||
"title": p.title,
|
||
"reply_count": p.reply_count,
|
||
"view_count": p.view_count,
|
||
}
|
||
for p in posts
|
||
]
|
||
```
|
||
|
||
### 4.2 Agent 配置
|
||
|
||
```python
|
||
# agents/prompts/forum_agent.py
|
||
FORUM_AGENT_PROMPT = """你是一个活跃的社区成员,可以帮助用户解决问题和参与讨论。
|
||
|
||
## 你的能力
|
||
1. 在论坛发帖分享信息或见解
|
||
2. 回复其他用户的帖子
|
||
3. 搜索论坛内容
|
||
4. 查看热门帖子
|
||
|
||
## 行为规则
|
||
1. 只在有帮助时才发帖/回复,不要刷屏
|
||
2. 回复要专业、有建设性
|
||
3. 如果用户的问题已经解决,不要重复回答
|
||
4. 遇到 Bug 或问题可以主动发帖提醒
|
||
5. 定期查看论坛,如果有重要帖子可以参与讨论
|
||
|
||
## 当前时间
|
||
{current_time}
|
||
|
||
## 用户信息
|
||
用户名: {username}
|
||
积分: {forum_score}
|
||
"""
|
||
|
||
# Agent 可用的 Forum 工具
|
||
FORUM_TOOLS = [
|
||
create_forum_post,
|
||
reply_to_post,
|
||
search_forum_posts,
|
||
get_forum_trending,
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 定时任务
|
||
|
||
### 5.1 自动回复任务
|
||
|
||
```python
|
||
# tasks/forum_auto_reply.py
|
||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||
|
||
|
||
async def auto_reply_task():
|
||
"""自动回复任务"""
|
||
# 获取需要回复的帖子
|
||
posts = await forum_service.get_pending_posts(
|
||
min_age_hours=settings.auto_reply_min_hours,
|
||
limit=10,
|
||
)
|
||
|
||
for post in posts:
|
||
replies = await forum_service.get_replies(post.id)
|
||
|
||
# 生成回复
|
||
response = await ai_service.generate_auto_reply(post, replies)
|
||
|
||
if response:
|
||
# 发布回复
|
||
await forum_service.create_reply(
|
||
post_id=post.id,
|
||
agent_id=AGENT_ID,
|
||
data=ForumReplyCreate(content=response),
|
||
)
|
||
|
||
# 更新回复计数
|
||
await forum_service.increment_reply_count(post.id)
|
||
|
||
|
||
def setup_forum_scheduler(scheduler: AsyncIOScheduler):
|
||
"""配置论坛定时任务"""
|
||
# 每小时检查一次自动回复
|
||
scheduler.add_job(
|
||
auto_reply_task,
|
||
"interval",
|
||
hours=1,
|
||
id="forum_auto_reply",
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 配置项
|
||
|
||
### 6.1 AI 配置
|
||
|
||
```python
|
||
class ForumAIConfig:
|
||
"""Forum AI 配置"""
|
||
|
||
# 自动回复
|
||
auto_reply_enabled: bool = True
|
||
auto_reply_min_hours: int = 24 # 帖子发布 N 小时后才自动回复
|
||
auto_reply_max_per_day: int = 10 # 每天最多自动回复 N 条
|
||
|
||
# 摘要生成
|
||
summary_enabled: bool = True
|
||
summary_max_length: int = 200
|
||
summary_cache_ttl: int = 3600 # 缓存 1 小时
|
||
|
||
# 智能打标
|
||
smart_tagging_enabled: bool = True
|
||
smart_tagging_max_tags: int = 5
|
||
|
||
# 智能分类
|
||
smart_classification_enabled: bool = True
|
||
|
||
|
||
# settings.py
|
||
class Settings(BaseSettings):
|
||
# Forum AI
|
||
forum_ai_auto_reply: bool = True
|
||
forum_ai_summary: bool = True
|
||
forum_ai_smart_tagging: bool = True
|
||
```
|
||
|
||
---
|
||
|
||
## 7. API 端点
|
||
|
||
### 7.1 AI 相关端点
|
||
|
||
```python
|
||
@router.post("/posts/{post_id}/generate-summary")
|
||
async def generate_post_summary(
|
||
post_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""生成帖子摘要"""
|
||
result = await db.execute(
|
||
select(ForumPost).where(ForumPost.id == post_id)
|
||
)
|
||
post = result.scalar_one_or_none()
|
||
|
||
if not post:
|
||
raise HTTPException(status_code=404, detail="帖子不存在")
|
||
|
||
ai_service = ForumAIService(llm_service, config)
|
||
summary = await ai_service.generate_summary(post.content)
|
||
|
||
return {"summary": summary}
|
||
|
||
|
||
@router.post("/posts/suggest-tags")
|
||
async def suggest_post_tags(
|
||
title: str,
|
||
content: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""推荐帖子标签"""
|
||
ai_service = ForumAIService(llm_service, config)
|
||
tags = await ai_service.suggest_tags(title, content, [])
|
||
|
||
return {"tags": tags}
|
||
|
||
|
||
@router.post("/posts/classify")
|
||
async def classify_post(
|
||
title: str,
|
||
content: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""分类帖子"""
|
||
ai_service = ForumAIService(llm_service, config)
|
||
category = await ai_service.classify_category(title, content)
|
||
|
||
return {"category": category}
|
||
|
||
|
||
@router.get("/posts/{post_id}/ai-status")
|
||
async def get_ai_status(
|
||
post_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""获取帖子 AI 状态"""
|
||
result = await db.execute(
|
||
select(ForumPost).where(ForumPost.id == post_id)
|
||
)
|
||
post = result.scalar_one_or_none()
|
||
|
||
if not post:
|
||
raise HTTPException(status_code=404, detail="帖子不存在")
|
||
|
||
# 检查 AI 回复状态
|
||
has_ai_reply = any(r.is_ai_reply for r in post.replies)
|
||
summary_cached = await cache.get(f"summary:{post_id}")
|
||
|
||
return {
|
||
"has_ai_reply": has_ai_reply,
|
||
"summary_available": bool(summary_cached),
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 实现步骤
|
||
|
||
| 步骤 | 任务 | 优先级 |
|
||
|------|------|--------|
|
||
| 1 | 创建 ForumAIService | 🟢 高 |
|
||
| 2 | 实现自动回复 | 🟢 高 |
|
||
| 3 | 实现摘要生成 | 🟡 中 |
|
||
| 4 | 实现智能打标 | 🟡 中 |
|
||
| 5 | 实现智能分类 | 🟡 中 |
|
||
| 6 | 创建 ForumTools | 🟢 高 |
|
||
| 7 | 配置定时任务 | 🟡 中 |
|
||
| 8 | 扩展 API 端点 | 🟡 中 |
|
||
| 9 | 单元测试 | 🟡 中 |
|
||
|
||
---
|
||
|
||
## 9. 核心文件变更
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `services/forum_ai_service.py` | 新增 |
|
||
| `services/summary_service.py` | 新增 |
|
||
| `agents/tools/forum_tools.py` | 新增 |
|
||
| `agents/prompts/forum_agent.py` | 新增 |
|
||
| `tasks/forum_auto_reply.py` | 新增 |
|
||
| `routers/forum.py` | 扩展 AI 端点 |
|
||
|
||
---
|
||
|
||
## 10. 工作量估算
|
||
|
||
| 任务 | 工作量 |
|
||
|------|--------|
|
||
| ForumAIService | 1 天 |
|
||
| 自动回复 | 1 天 |
|
||
| 摘要/打标/分类 | 1 天 |
|
||
| ForumTools | 1 天 |
|
||
| 定时任务 | 0.5 天 |
|
||
| API 端点 | 0.5 天 |
|
||
| 单元测试 | 0.5 天 |
|
||
| **总计** | **5.5 天** |
|
||
|
||
---
|
||
|
||
## 11. 验收标准
|
||
|
||
- [ ] ForumAIService 可正常调用 LLM
|
||
- [ ] 自动回复功能正常工作
|
||
- [ ] 摘要生成功能正常
|
||
- [ ] 智能打标推荐准确
|
||
- [ ] 智能分类推荐准确
|
||
- [ ] ForumTools 可被 Agent 调用
|
||
- [ ] 定时任务正常执行
|
||
- [ ] API 端点正常工作
|
||
- [ ] 单元测试覆盖核心逻辑
|