234 lines
8.4 KiB
Python
234 lines
8.4 KiB
Python
|
|
"""职业生成服务"""
|
|||
|
|
from typing import Dict, Any, List
|
|||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
|
from sqlalchemy import select
|
|||
|
|
import json
|
|||
|
|
|
|||
|
|
from app.models.project import Project
|
|||
|
|
from app.models.career import Career
|
|||
|
|
from app.services.ai_service import AIService
|
|||
|
|
from app.logger import get_logger
|
|||
|
|
|
|||
|
|
logger = get_logger(__name__)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CareerService:
|
|||
|
|
"""职业相关业务逻辑服务"""
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
async def get_career_generation_prompt(
|
|||
|
|
project: Project,
|
|||
|
|
main_career_count: int = 2,
|
|||
|
|
sub_career_count: int = 6
|
|||
|
|
) -> str:
|
|||
|
|
"""
|
|||
|
|
构建职业体系生成的提示词
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
project: 项目对象
|
|||
|
|
main_career_count: 主职业数量
|
|||
|
|
sub_career_count: 副职业数量
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
完整的提示词
|
|||
|
|
"""
|
|||
|
|
project_context = f"""
|
|||
|
|
项目信息:
|
|||
|
|
- 书名:{project.title}
|
|||
|
|
- 类型:{project.genre or '未设定'}
|
|||
|
|
- 主题:{project.theme or '未设定'}
|
|||
|
|
- 时间背景:{project.world_time_period or '未设定'}
|
|||
|
|
- 地理位置:{project.world_location or '未设定'}
|
|||
|
|
- 氛围基调:{project.world_atmosphere or '未设定'}
|
|||
|
|
- 世界规则:{project.world_rules or '未设定'}
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
user_requirements = f"""
|
|||
|
|
生成要求:
|
|||
|
|
- 主职业数量:{main_career_count}个
|
|||
|
|
- 副职业数量:{sub_career_count}个
|
|||
|
|
- 主职业必须严格符合世界观规则,体现核心能力体系
|
|||
|
|
- 副职业可以更加自由灵活,包含生产、辅助、特殊类型
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
prompt = f"""{project_context}
|
|||
|
|
|
|||
|
|
{user_requirements}
|
|||
|
|
|
|||
|
|
请为这个小说项目生成完整的职业体系。返回JSON格式,结构如下:
|
|||
|
|
|
|||
|
|
{{
|
|||
|
|
"main_careers": [
|
|||
|
|
{{
|
|||
|
|
"name": "职业名称",
|
|||
|
|
"description": "职业描述(100-200字)",
|
|||
|
|
"category": "职业分类(如:战斗系、法术系、体修系等)",
|
|||
|
|
"stages": [
|
|||
|
|
{{"level": 1, "name": "阶段名称", "description": "阶段描述"}},
|
|||
|
|
{{"level": 2, "name": "阶段名称", "description": "阶段描述"}},
|
|||
|
|
...(共10个阶段)
|
|||
|
|
],
|
|||
|
|
"max_stage": 10,
|
|||
|
|
"requirements": "职业要求(如:需要特定天赋、资质等)",
|
|||
|
|
"special_abilities": "特殊能力描述",
|
|||
|
|
"worldview_rules": "世界观规则关联(说明该职业如何融入世界观)",
|
|||
|
|
"attribute_bonuses": {{"strength": "+10%", "intelligence": "+5%"}}
|
|||
|
|
}}
|
|||
|
|
],
|
|||
|
|
"sub_careers": [
|
|||
|
|
{{
|
|||
|
|
"name": "副职业名称",
|
|||
|
|
"description": "职业描述",
|
|||
|
|
"category": "生产系/辅助系/特殊系",
|
|||
|
|
"stages": [
|
|||
|
|
{{"level": 1, "name": "阶段名称", "description": "阶段描述"}},
|
|||
|
|
...(5-8个阶段)
|
|||
|
|
],
|
|||
|
|
"max_stage": 5,
|
|||
|
|
"requirements": "职业要求",
|
|||
|
|
"special_abilities": "特殊能力"
|
|||
|
|
}}
|
|||
|
|
]
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
重要注意事项:
|
|||
|
|
1. 主职业的阶段设定要详细,体现明确的成长路径,阶段名称要有特色
|
|||
|
|
2. 根据小说类型选择合适的职业:
|
|||
|
|
- 修仙类:剑修、体修、法修、符修等,阶段如:炼气、筑基、金丹、元婴...
|
|||
|
|
- 玄幻类:战士、法师、刺客等,阶段如:见习、初级、中级、高级...
|
|||
|
|
- 都市异能:异能者分类,阶段如:觉醒、初阶、中阶、高阶...
|
|||
|
|
- 科幻未来:基因战士、机甲师等,阶段如:E级、D级、C级、B级...
|
|||
|
|
3. 副职业要有实用性和趣味性,如:炼丹师、炼器师、阵法师、驯兽师、医师等
|
|||
|
|
4. 所有职业都要符合项目的整体世界观设定
|
|||
|
|
5. 阶段描述要简洁明了,体现该阶段的核心特征
|
|||
|
|
6. **只返回纯JSON对象,不要添加任何解释文字或markdown标记**
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
return prompt
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
async def parse_and_save_careers(
|
|||
|
|
career_data: Dict[str, Any],
|
|||
|
|
project_id: str,
|
|||
|
|
db: AsyncSession
|
|||
|
|
) -> Dict[str, List[str]]:
|
|||
|
|
"""
|
|||
|
|
解析AI返回的职业数据并保存到数据库
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
career_data: AI返回的职业数据(已解析为dict)
|
|||
|
|
project_id: 项目ID
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
{"main_careers": [...], "sub_careers": [...]} 创建的职业名称列表
|
|||
|
|
"""
|
|||
|
|
result = {
|
|||
|
|
"main_careers": [],
|
|||
|
|
"sub_careers": []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 保存主职业
|
|||
|
|
for idx, career_info in enumerate(career_data.get("main_careers", [])):
|
|||
|
|
try:
|
|||
|
|
stages_json = json.dumps(career_info.get("stages", []), ensure_ascii=False)
|
|||
|
|
attribute_bonuses = career_info.get("attribute_bonuses")
|
|||
|
|
attribute_bonuses_json = json.dumps(attribute_bonuses, ensure_ascii=False) if attribute_bonuses else None
|
|||
|
|
|
|||
|
|
career = Career(
|
|||
|
|
project_id=project_id,
|
|||
|
|
name=career_info.get("name", f"未命名主职业{idx+1}"),
|
|||
|
|
type="main",
|
|||
|
|
description=career_info.get("description"),
|
|||
|
|
category=career_info.get("category"),
|
|||
|
|
stages=stages_json,
|
|||
|
|
max_stage=career_info.get("max_stage", 10),
|
|||
|
|
requirements=career_info.get("requirements"),
|
|||
|
|
special_abilities=career_info.get("special_abilities"),
|
|||
|
|
worldview_rules=career_info.get("worldview_rules"),
|
|||
|
|
attribute_bonuses=attribute_bonuses_json,
|
|||
|
|
source="ai"
|
|||
|
|
)
|
|||
|
|
db.add(career)
|
|||
|
|
await db.flush()
|
|||
|
|
result["main_careers"].append(career.name)
|
|||
|
|
logger.info(f" ✅ 创建主职业:{career.name}")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f" ❌ 创建主职业失败:{str(e)}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 保存副职业
|
|||
|
|
for idx, career_info in enumerate(career_data.get("sub_careers", [])):
|
|||
|
|
try:
|
|||
|
|
stages_json = json.dumps(career_info.get("stages", []), ensure_ascii=False)
|
|||
|
|
attribute_bonuses = career_info.get("attribute_bonuses")
|
|||
|
|
attribute_bonuses_json = json.dumps(attribute_bonuses, ensure_ascii=False) if attribute_bonuses else None
|
|||
|
|
|
|||
|
|
career = Career(
|
|||
|
|
project_id=project_id,
|
|||
|
|
name=career_info.get("name", f"未命名副职业{idx+1}"),
|
|||
|
|
type="sub",
|
|||
|
|
description=career_info.get("description"),
|
|||
|
|
category=career_info.get("category"),
|
|||
|
|
stages=stages_json,
|
|||
|
|
max_stage=career_info.get("max_stage", 5),
|
|||
|
|
requirements=career_info.get("requirements"),
|
|||
|
|
special_abilities=career_info.get("special_abilities"),
|
|||
|
|
worldview_rules=career_info.get("worldview_rules"),
|
|||
|
|
attribute_bonuses=attribute_bonuses_json,
|
|||
|
|
source="ai"
|
|||
|
|
)
|
|||
|
|
db.add(career)
|
|||
|
|
await db.flush()
|
|||
|
|
result["sub_careers"].append(career.name)
|
|||
|
|
logger.info(f" ✅ 创建副职业:{career.name}")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f" ❌ 创建副职业失败:{str(e)}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
await db.commit()
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
async def get_project_careers_summary(project_id: str, db: AsyncSession) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
获取项目职业体系摘要
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
project_id: 项目ID
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
职业体系摘要信息
|
|||
|
|
"""
|
|||
|
|
result = await db.execute(
|
|||
|
|
select(Career).where(Career.project_id == project_id)
|
|||
|
|
)
|
|||
|
|
careers = result.scalars().all()
|
|||
|
|
|
|||
|
|
main_careers = []
|
|||
|
|
sub_careers = []
|
|||
|
|
|
|||
|
|
for career in careers:
|
|||
|
|
career_info = {
|
|||
|
|
"id": career.id,
|
|||
|
|
"name": career.name,
|
|||
|
|
"category": career.category,
|
|||
|
|
"max_stage": career.max_stage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if career.type == "main":
|
|||
|
|
main_careers.append(career_info)
|
|||
|
|
else:
|
|||
|
|
sub_careers.append(career_info)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"main_careers": main_careers,
|
|||
|
|
"sub_careers": sub_careers,
|
|||
|
|
"total_count": len(careers)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 创建全局服务实例
|
|||
|
|
career_service = CareerService()
|