509 lines
16 KiB
Python
509 lines
16 KiB
Python
"""
|
||
AI Steward Service - 主动式 AI 管家核心服务
|
||
|
||
核心功能:
|
||
1. 零配置项目创建 - 从一句话生成完整 SSTC 链路
|
||
2. 主动信号捕获 - 从多个源自动抓取并评估
|
||
3. 认知冲突检测 - 自动识别并消解团队认知差异
|
||
4. 阻塞自动解决 - 主动识别和解决开发阻塞
|
||
5. 个性化进度简报 - 为每个角色生成定制化简报
|
||
"""
|
||
import asyncio
|
||
import json
|
||
from datetime import datetime, timedelta, timezone
|
||
from typing import Any, Dict, List, Optional
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy.future import select
|
||
from sqlalchemy import text
|
||
|
||
from ..database import get_db
|
||
from ..models import (
|
||
User, Project, Signal, TruthNode, Task,
|
||
ProjectDomain, CognitiveConflict, UserSkillBinding
|
||
)
|
||
from .llm import LLMService
|
||
|
||
|
||
class AIStewardService:
|
||
"""主动式 AI 管家服务"""
|
||
|
||
def __init__(self, db: AsyncSession):
|
||
self.db = db
|
||
self.llm = LLMService()
|
||
|
||
async def create_project_from_natural_language(
|
||
self,
|
||
input_text: str,
|
||
creator_id: int,
|
||
tenant_id: int = 1
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
零配置项目创建 - 从一句话生成完整 SSTC 链路
|
||
|
||
输入示例:
|
||
"我们要做一个 AI 客服工具,基于 GPT-4o mini,对标 XX 竞品,3 周上线"
|
||
|
||
输出:完整的 SSTC 链路
|
||
- 竞品信号分析
|
||
- 宪章(TruthNode)
|
||
- 任务拆解(Task)
|
||
- 依赖关系
|
||
- 风险预警
|
||
"""
|
||
# Step 0: 获取下一个可用的项目 ID(SQLite 兼容)
|
||
from sqlalchemy import text
|
||
next_id = (await self.db.execute(text("SELECT COALESCE(MAX(id),0)+1 FROM projects"))).scalar() or 1
|
||
|
||
# Step 1: AI 解析用户输入,提取关键信息
|
||
analysis = await self._analyze_project_input(input_text, tenant_id)
|
||
|
||
# Step 2: 自动生成项目
|
||
project = Project(
|
||
id=next_id,
|
||
name=analysis.get('project_name', '未命名项目')[:50],
|
||
title=analysis.get('project_title', input_text[:50]),
|
||
description=input_text,
|
||
owner_id=creator_id,
|
||
status='active',
|
||
domain_id=analysis.get('domain_id'),
|
||
assigned_role=analysis.get('primary_role', 'backend'),
|
||
signal_count=0,
|
||
tenant_id=tenant_id
|
||
)
|
||
self.db.add(project)
|
||
await self.db.flush()
|
||
|
||
# Step 3: 生成信号(竞品分析)
|
||
signal = await self._generate_competitive_signal(
|
||
project.id,
|
||
analysis,
|
||
tenant_id
|
||
)
|
||
|
||
# Step 4: 生成宪章(核心真理节点)
|
||
charter = await self._generate_charter(
|
||
project.id,
|
||
analysis,
|
||
creator_id,
|
||
tenant_id
|
||
)
|
||
charter_id = int(charter['id']) # Convert string ID to int
|
||
|
||
# Step 5: 生成任务链
|
||
tasks = await self._generate_task_chain(
|
||
project.id,
|
||
charter_id,
|
||
analysis,
|
||
tenant_id
|
||
)
|
||
|
||
# Step 6: 生成风险预警规则
|
||
risks = await self._generate_risk_alert_rules(
|
||
project.id,
|
||
charter_id,
|
||
analysis,
|
||
tenant_id
|
||
)
|
||
|
||
await self.db.commit()
|
||
|
||
return {
|
||
'project': {
|
||
'id': str(project.id),
|
||
'name': project.name,
|
||
'title': project.title,
|
||
'description': project.description,
|
||
'status': project.status
|
||
},
|
||
'signal': signal,
|
||
'charter': charter,
|
||
'tasks': tasks,
|
||
'risks': risks,
|
||
'analysis': analysis
|
||
}
|
||
|
||
async def _analyze_project_input(self, input_text: str, tenant_id: int) -> Dict[str, Any]:
|
||
"""使用 AI 分析项目输入,提取关键信息"""
|
||
prompt = f"""分析以下项目需求,提取关键信息并以 JSON 格式返回:
|
||
|
||
用户输入:{input_text}
|
||
|
||
请提取以下信息:
|
||
{{
|
||
"project_name": "项目名称(简短)",
|
||
"project_title": "项目标题",
|
||
"tech_stack": ["技术栈列表"],
|
||
"target_audience": "目标用户",
|
||
"timeline": "时间周期",
|
||
"primary_role": "主要角色类型(ai_engineer/backend/frontend)",
|
||
"domain_id": 1,
|
||
"key_features": ["核心功能列表"],
|
||
"competitors": ["竞品列表"],
|
||
"success_metrics": ["成功指标"]
|
||
}}
|
||
|
||
只返回 JSON,不要其他内容。"""
|
||
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
)
|
||
try:
|
||
# 清理可能的 markdown 代码块标记
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
analysis = json.loads(response.strip())
|
||
except:
|
||
# 降级处理
|
||
analysis = {
|
||
'project_name': input_text[:20],
|
||
'project_title': input_text[:50],
|
||
'tech_stack': ['GPT-4o-mini'],
|
||
'target_audience': '通用',
|
||
'timeline': '3周',
|
||
'primary_role': 'ai_engineer',
|
||
'domain_id': 1,
|
||
'key_features': [],
|
||
'competitors': [],
|
||
'success_metrics': []
|
||
}
|
||
|
||
return analysis
|
||
|
||
async def _generate_competitive_signal(
|
||
self,
|
||
project_id: int,
|
||
analysis: Dict[str, Any],
|
||
tenant_id: int
|
||
) -> Dict[str, Any]:
|
||
"""生成竞品分析信号"""
|
||
from sqlalchemy import text
|
||
|
||
# 获取下一个可用的 signal ID
|
||
next_signal_id = (await self.db.execute(text("SELECT COALESCE(MAX(id),0)+1 FROM signals"))).scalar() or 1
|
||
|
||
competitors = analysis.get('competitors', [])
|
||
if not competitors:
|
||
competitors = ['行业同类产品']
|
||
|
||
prompt = f"""基于以下项目信息,生成竞品分析信号:
|
||
|
||
项目:{analysis.get('project_title')}
|
||
技术栈:{', '.join(analysis.get('tech_stack', []))}
|
||
竞品:{', '.join(competitors)}
|
||
|
||
请生成:
|
||
1. 竞品现状分析
|
||
2. 差异化机会
|
||
3. 技术风险评估
|
||
|
||
以 JSON 格式返回:
|
||
{{
|
||
"content": "信号内容(200字以内)",
|
||
"impact_score": 0.9,
|
||
"priority": "high",
|
||
"strategic_analysis": {{"is_strategic": true, "threat_level": "MEDIUM"}}
|
||
}}"""
|
||
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
project_id=project_id,
|
||
)
|
||
try:
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
signal_data = json.loads(response.strip())
|
||
except:
|
||
signal_data = {
|
||
'content': f'竞品分析:{", ".join(competitors)} 的技术现状和差异化机会',
|
||
'impact_score': 0.8,
|
||
'priority': 'high',
|
||
'strategic_analysis': {'is_strategic': True}
|
||
}
|
||
|
||
signal = Signal(
|
||
id=next_signal_id,
|
||
source='AI-Steward',
|
||
content=signal_data['content'],
|
||
impact_score=signal_data.get('impact_score', 0.8),
|
||
priority=signal_data.get('priority', 'high'),
|
||
processed=True,
|
||
strategic_analysis=json.dumps(signal_data.get('strategic_analysis', {})),
|
||
tasks_generated=json.dumps([]),
|
||
tenant_id=tenant_id
|
||
)
|
||
self.db.add(signal)
|
||
await self.db.flush()
|
||
|
||
# 创建信号与项目的关联
|
||
from ..models import ProjectSignalLink
|
||
next_link_id = (await self.db.execute(text("SELECT COALESCE(MAX(id),0)+1 FROM project_signal_links"))).scalar() or 1
|
||
|
||
link = ProjectSignalLink(
|
||
id=next_link_id,
|
||
project_id=project_id,
|
||
signal_id=signal.id,
|
||
link_type='competitive_analysis',
|
||
relevance_score=0.9,
|
||
tenant_id=tenant_id
|
||
)
|
||
self.db.add(link)
|
||
await self.db.flush()
|
||
|
||
return {
|
||
'id': str(signal.id),
|
||
'content': signal.content,
|
||
'impact_score': signal.impact_score
|
||
}
|
||
|
||
async def _generate_charter(
|
||
self,
|
||
project_id: int,
|
||
analysis: Dict[str, Any],
|
||
creator_id: int,
|
||
tenant_id: int
|
||
) -> Dict[str, Any]:
|
||
"""生成项目宪章(核心真理节点)"""
|
||
prompt = f"""基于以下项目信息,生成项目宪章:
|
||
|
||
项目:{analysis.get('project_title')}
|
||
描述:{analysis.get('project_title')}
|
||
技术栈:{', '.join(analysis.get('tech_stack', []))}
|
||
核心功能:{', '.join(analysis.get('key_features', []))}
|
||
成功指标:{', '.join(analysis.get('success_metrics', []))}
|
||
|
||
请生成包含以下内容的宪章:
|
||
1. 项目目标(明确、可衡量)
|
||
2. 成功标准(具体指标)
|
||
3. 技术约束
|
||
4. 时间节点
|
||
|
||
以 JSON 格式返回:
|
||
{{
|
||
"title": "宪章标题",
|
||
"content": "宪章完整内容(包含上述4个方面)",
|
||
"success_criteria": ["标准1", "标准2"],
|
||
"constraints": ["约束1", "约束2"]
|
||
}}"""
|
||
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
project_id=project_id,
|
||
)
|
||
try:
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
charter_data = json.loads(response.strip())
|
||
except:
|
||
charter_data = {
|
||
'title': f'{analysis.get("project_name")}项目宪章',
|
||
'content': f'项目目标:{analysis.get("project_title")}\n成功标准:按时上线',
|
||
'success_criteria': ['按时交付', '功能完整'],
|
||
'constraints': ['时间限制']
|
||
}
|
||
|
||
raw_content = charter_data.get('content', '')
|
||
if isinstance(raw_content, dict):
|
||
charter_content = dict(raw_content)
|
||
else:
|
||
charter_content = {
|
||
'rationale': str(raw_content),
|
||
'success_criteria': charter_data.get('success_criteria', []),
|
||
'constraints': charter_data.get('constraints', []),
|
||
'signal_ids': [],
|
||
}
|
||
|
||
# 获取下一个可用的 truth_node ID
|
||
from sqlalchemy import text
|
||
next_charter_id = (await self.db.execute(text("SELECT COALESCE(MAX(id),0)+1 FROM truth_nodes"))).scalar() or 1
|
||
|
||
charter = TruthNode(
|
||
id=next_charter_id,
|
||
node_type='charter',
|
||
project_id=project_id,
|
||
created_by=creator_id,
|
||
title=charter_data.get('title', '项目宪章'),
|
||
content=charter_content,
|
||
version=1,
|
||
status='active',
|
||
tenant_id=tenant_id
|
||
)
|
||
self.db.add(charter)
|
||
await self.db.flush()
|
||
|
||
return {
|
||
'id': str(charter.id),
|
||
'title': charter.title,
|
||
'content': charter_content
|
||
}
|
||
|
||
async def _generate_task_chain(
|
||
self,
|
||
project_id: int,
|
||
charter_id: int,
|
||
analysis: Dict[str, Any],
|
||
tenant_id: int
|
||
) -> List[Dict[str, Any]]:
|
||
"""生成任务链"""
|
||
prompt = f"""基于以下信息,生成完整的任务链:
|
||
|
||
项目:{analysis.get('project_title')}
|
||
技术栈:{', '.join(analysis.get('tech_stack', []))}
|
||
时间周期:{analysis.get('timeline', '3周')}
|
||
|
||
请生成:
|
||
1. 任务列表(按优先级排序)
|
||
2. 每个任务包含:名称、类型、预估工时、依赖关系、技能要求
|
||
|
||
以 JSON 格式返回:
|
||
{{
|
||
"tasks": [
|
||
{{
|
||
"title": "任务名称",
|
||
"type": "backend/frontend/ai_engineer",
|
||
"estimated_hours": 8,
|
||
"description": "任务描述",
|
||
"priority": "high/medium/low",
|
||
"required_skills": ["技能1", "技能2"]
|
||
}}
|
||
]
|
||
}}"""
|
||
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
project_id=project_id,
|
||
)
|
||
try:
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
tasks_data = json.loads(response.strip())
|
||
tasks_list = tasks_data.get('tasks', [])
|
||
except:
|
||
# 降级生成基础任务
|
||
tasks_list = [
|
||
{
|
||
'title': '技术方案设计',
|
||
'type': analysis.get('primary_role', 'backend'),
|
||
'estimated_hours': 16,
|
||
'description': '完成项目技术方案设计',
|
||
'priority': 'high',
|
||
'required_skills': ['系统设计']
|
||
},
|
||
{
|
||
'title': '核心功能开发',
|
||
'type': analysis.get('primary_role', 'backend'),
|
||
'estimated_hours': 40,
|
||
'description': '完成核心功能开发',
|
||
'priority': 'high',
|
||
'required_skills': ['开发']
|
||
}
|
||
]
|
||
|
||
tasks = []
|
||
from sqlalchemy import text
|
||
next_task_id = (await self.db.execute(text("SELECT COALESCE(MAX(id),0)+1 FROM tasks"))).scalar() or 1
|
||
|
||
for i, task_data in enumerate(tasks_list):
|
||
task = Task(
|
||
id=next_task_id,
|
||
truth_node_id=charter_id,
|
||
project_id=project_id,
|
||
assignee_id=None, # AI 推荐稍后分配
|
||
title=task_data.get('title'),
|
||
description=task_data.get('description', ''),
|
||
required_skill_tag_ids=task_data.get('required_skills', []), # TypeDecorator 会自动处理
|
||
status='pending',
|
||
priority=task_data.get('priority', 'medium').upper(),
|
||
estimated_hours=task_data.get('estimated_hours', 8),
|
||
actual_hours=0,
|
||
quality_score=0,
|
||
tenant_id=tenant_id
|
||
)
|
||
self.db.add(task)
|
||
await self.db.flush()
|
||
next_task_id += 1
|
||
|
||
tasks.append({
|
||
'id': str(task.id),
|
||
'title': task.title,
|
||
'type': task_data.get('type'),
|
||
'estimated_hours': task.estimated_hours,
|
||
'priority': task.priority
|
||
})
|
||
|
||
return tasks
|
||
|
||
async def _generate_risk_alert_rules(
|
||
self,
|
||
project_id: int,
|
||
charter_id: int,
|
||
analysis: Dict[str, Any],
|
||
tenant_id: int
|
||
) -> List[Dict[str, Any]]:
|
||
"""生成风险预警规则"""
|
||
risks = [
|
||
{
|
||
'type': 'schedule_delay',
|
||
'description': '任务超时预警',
|
||
'threshold_hours': analysis.get('estimated_hours', 40) * 1.2,
|
||
'severity': 'medium'
|
||
},
|
||
{
|
||
'type': 'quality_drop',
|
||
'description': '质量下降预警',
|
||
'threshold': 0.85,
|
||
'severity': 'high'
|
||
},
|
||
{
|
||
'type': 'skill_mismatch',
|
||
'description': '技能匹配度不足',
|
||
'threshold': 0.6,
|
||
'severity': 'medium'
|
||
}
|
||
]
|
||
|
||
# 获取下一个可用的 cognitive_conflict ID
|
||
from sqlalchemy import text
|
||
next_conflict_id = (await self.db.execute(text("SELECT COALESCE(MAX(id),0)+1 FROM cognitive_conflicts"))).scalar() or 1
|
||
|
||
# 创建认知冲突记录作为风险预警
|
||
for risk in risks:
|
||
conflict = CognitiveConflict(
|
||
id=next_conflict_id,
|
||
truth_node_id=charter_id, # 关联到宪章
|
||
conflict_type=risk['type'],
|
||
description=risk['description'],
|
||
involved_user_ids=json.dumps([]),
|
||
status='active',
|
||
tenant_id=tenant_id
|
||
)
|
||
self.db.add(conflict)
|
||
next_conflict_id += 1
|
||
|
||
return risks
|
||
|
||
|
||
# 全局服务实例
|
||
def get_ai_steward_service(db: AsyncSession) -> AIStewardService:
|
||
"""获取 AI Steward 服务实例"""
|
||
return AIStewardService(db)
|