# AI 提示词系统服务 > **文档版本**:v1.1 > **最后更新**:2026-02-03 > **变更说明**:修复技术栈合规性问题(日志格式统一) > **符合规范**:jointo-tech-stack v1.0 --- ## 技术规范说明 本文档遵循 jointo-tech-stack 规范: - **UUID 规范**:所有 UUID 字段使用 UUID v7(应用层生成),符合 ADR 001 规范 - **时间戳规范**:所有时间字段使用 TIMESTAMPTZ 类型,符合 ADR 006 规范 - **枚举类型**:使用 SMALLINT 存储,Python 使用 IntEnum - **无物理外键**:应用层保证引用完整性 - **日志格式**:使用 %-formatting,错误日志包含 exc_info=True - **异步编程**:所有数据库操作使用 async/await --- ## 目录 1. [服务概述](#服务概述) 2. [核心功能](#核心功能) 3. [数据库设计](#数据库设计) 4. [服务实现](#服务实现) 5. [API 接口](#api-接口) 6. [数据模型](#数据模型) 7. [使用示例](#使用示例) --- ## 服务概述 AI 提示词系统服务负责管理系统级的 AI 提示词模板,为各种 AI 生成场景提供统一的提示词管理。 ### 职责 - 提示词 CRUD 操作(系统管理员专用) - 提示词版本管理 - Skills 配置管理 - 提示词查询和获取 - 默认提示词管理 ### 设计原则 - **系统级管理**:仅供系统管理员维护,不对普通用户开放 - **版本控制**:支持提示词版本管理和回滚 - **Skills 集成**:支持配置多个 skill 文件 - **类型分类**:按使用场景分类(剧本、分镜、角色、场景、道具等) - **应用层验证**:遵循 jointo-tech-stack 规范,无物理外键约束 --- ## 核心功能 ### 1. 提示词管理 - **创建提示词**:创建新的提示词模板 - **更新提示词**:更新提示词内容和配置 - **删除提示词**:禁用提示词(is_active = false) - **查询提示词**:按类型、版本、状态查询 ### 2. 版本管理 - **创建新版本**:基于现有提示词创建新版本 - **版本激活**:激活指定版本的提示词 - **版本历史**:查看提示词的所有版本 - **版本对比**:对比不同版本的差异 ### 3. Skills 配置 - **配置 Skills**:为提示词配置使用的 skill 文件 - **Skills 验证**:验证 skill 文件路径有效性 - **Skills 优先级**:设置 skills 的加载顺序 ### 4. 默认提示词 - **设置默认**:为每种类型设置默认提示词 - **获取默认**:根据类型获取默认提示词 - **回退机制**:当指定提示词不存在时使用默认提示词 --- ## 数据库设计 ### ai_prompts_system 表结构 ```sql -- Python 枚举定义(app/models/ai_prompt_system.py) -- class PromptType(IntEnum): -- SCREENPLAY = 1 # 剧本解析 -- STORYBOARD = 2 # 分镜生成 -- CHARACTER = 3 # 角色生成 -- SCENE = 4 # 场景生成 -- PROP = 5 # 道具生成 -- RESOURCE = 6 # 资源生成 -- GENERAL = 7 # 通用 CREATE TABLE ai_prompts_system ( prompt_id UUID PRIMARY KEY, name TEXT NOT NULL, prompt_type SMALLINT NOT NULL, -- 应用层校验:1-7,便于后续扩展 prompt_content TEXT NOT NULL, skills_data JSONB NOT NULL DEFAULT '{"skills": []}', version TEXT NOT NULL, description TEXT, is_active BOOLEAN NOT NULL DEFAULT true, is_default BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- 唯一约束:同一名称的不同版本 CONSTRAINT ai_prompts_system_name_version_unique UNIQUE (name, version) NULLS NOT DISTINCT, -- 唯一约束:每种类型只能有一个默认提示词 CONSTRAINT ai_prompts_system_default_unique UNIQUE (prompt_type, is_default) WHERE is_default = true AND is_active = true ); -- 索引 CREATE INDEX idx_ai_prompts_system_type ON ai_prompts_system (prompt_type); CREATE INDEX idx_ai_prompts_system_is_active ON ai_prompts_system (is_active) WHERE is_active = true; CREATE INDEX idx_ai_prompts_system_is_default ON ai_prompts_system (is_default) WHERE is_default = true; CREATE INDEX idx_ai_prompts_system_name ON ai_prompts_system (name); CREATE INDEX idx_ai_prompts_system_type_active ON ai_prompts_system (prompt_type, is_active) WHERE is_active = true; CREATE INDEX idx_ai_prompts_system_skills_data_gin ON ai_prompts_system USING GIN (skills_data); CREATE INDEX idx_ai_prompts_system_name_trgm ON ai_prompts_system USING GIN (name gin_trgm_ops); -- 触发器 CREATE TRIGGER update_ai_prompts_system_updated_at BEFORE UPDATE ON ai_prompts_system FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 列注释 COMMENT ON TABLE ai_prompts_system IS 'AI 提示词系统表(系统级管理)'; COMMENT ON COLUMN ai_prompts_system.prompt_id IS '提示词唯一标识符(UUID v7)'; COMMENT ON COLUMN ai_prompts_system.name IS '提示词名称'; COMMENT ON COLUMN ai_prompts_system.prompt_type IS '提示词类型:1=剧本 2=分镜 3=角色 4=场景 5=道具 6=资源 7=通用'; COMMENT ON COLUMN ai_prompts_system.prompt_content IS '提示词内容'; COMMENT ON COLUMN ai_prompts_system.skills_data IS 'Skills 配置(JSONB 数组)'; COMMENT ON COLUMN ai_prompts_system.version IS '版本号(如 1.0.0)'; COMMENT ON COLUMN ai_prompts_system.description IS '提示词说明'; COMMENT ON COLUMN ai_prompts_system.is_active IS '是否启用'; COMMENT ON COLUMN ai_prompts_system.is_default IS '是否为默认提示词'; COMMENT ON COLUMN ai_prompts_system.created_at IS '创建时间'; COMMENT ON COLUMN ai_prompts_system.updated_at IS '最后更新时间'; ``` ### skills_data 结构 ```json { "skills": [ { "skill_name": "jointo-tech-stack", "file_path": ".claude/skills/jointo-tech-stack/references/database.md", "url": "https://example.com/skills/jointo-tech-stack", "version": "1.0", "enabled": true, "priority": 1, "description": "项目技术栈规范" }, { "skill_name": "screenplay-parser", "file_path": ".claude/skills/screenplay-parser/SKILL.md", "url": null, "version": "2.0", "enabled": true, "priority": 2, "description": "剧本解析规则" } ] } ``` ### 关联表改造 #### storyboard_images 表 ```sql -- 添加 ai_prompt_id 字段 ALTER TABLE storyboard_images ADD COLUMN ai_prompt_id UUID; CREATE INDEX idx_storyboard_images_ai_prompt_id ON storyboard_images (ai_prompt_id) WHERE ai_prompt_id IS NOT NULL; COMMENT ON COLUMN storyboard_images.ai_prompt_id IS 'AI 提示词 ID(应用层验证,可选)'; ``` ### 设计说明 1. **UUID v7 主键**:应用层生成,符合 jointo-tech-stack 规范 2. **无物理外键**:应用层保证引用完整性 3. **版本管理**:name + version 唯一约束,支持多版本共存 4. **默认提示词**:每种类型只能有一个激活的默认提示词 5. **Skills 配置**:JSONB 数组,支持多个 skill 组合 6. **软禁用**:使用 is_active 字段,不物理删除 7. **全文搜索**:使用 pg_trgm 扩展支持提示词名称模糊搜索 --- ## 服务实现 ### AIPromptSystemService 类 ```python # app/services/ai_prompt_system_service.py from typing import List, Optional, Dict, Any from uuid import UUID from sqlalchemy.ext.asyncio import AsyncSession from app.models.ai_prompt_system import AIPromptSystem, PromptType from app.repositories.ai_prompt_system_repository import AIPromptSystemRepository from app.schemas.ai_prompt_system import ( AIPromptSystemCreate, AIPromptSystemUpdate, AIPromptSystemResponse ) from app.core.exceptions import NotFoundError, ValidationError from app.core.logging import get_logger logger = get_logger(__name__) class AIPromptSystemService: def __init__(self, db: AsyncSession): self.repository = AIPromptSystemRepository(db) self.db = db async def create_prompt( self, prompt_data: AIPromptSystemCreate ) -> AIPromptSystem: """创建提示词""" logger.info( "创建提示词: 名称=%s, 类型=%s, 版本=%s", prompt_data.name, prompt_data.prompt_type, prompt_data.version ) try: # 1. 验证 skills_data 格式 self._validate_skills_data(prompt_data.skills_data) # 2. 检查名称+版本是否已存在 existing = await self.repository.get_by_name_version( prompt_data.name, prompt_data.version ) if existing: raise ValidationError( f"提示词 {prompt_data.name} 版本 {prompt_data.version} 已存在" ) # 3. 如果设置为默认,取消同类型其他默认提示词 if prompt_data.is_default: await self._unset_default_prompts(prompt_data.prompt_type) # 4. 创建提示词 prompt = AIPromptSystem( name=prompt_data.name, prompt_type=prompt_data.prompt_type, prompt_content=prompt_data.prompt_content, skills_data=prompt_data.skills_data, version=prompt_data.version, description=prompt_data.description, is_active=prompt_data.is_active, is_default=prompt_data.is_default ) created_prompt = await self.repository.create(prompt) logger.info( "提示词创建成功: ID=%s, 名称=%s", created_prompt.prompt_id, created_prompt.name ) return created_prompt except ValidationError: raise except Exception as e: logger.error( "创建提示词失败: 名称=%s, 错误=%s", prompt_data.name, str(e), exc_info=True ) raise async def get_prompt_by_id( self, prompt_id: UUID ) -> AIPromptSystem: """获取提示词详情""" logger.info("获取提示词详情: ID=%s", prompt_id) prompt = await self.repository.get_by_id(prompt_id) if not prompt: raise NotFoundError(f"提示词不存在: {prompt_id}") return prompt async def get_default_prompt( self, prompt_type: PromptType ) -> Optional[AIPromptSystem]: """获取默认提示词""" logger.info("获取默认提示词: 类型=%s", prompt_type) prompt = await self.repository.get_default_by_type(prompt_type) if not prompt: logger.warning("未找到默认提示词: 类型=%s", prompt_type) return prompt async def list_prompts( self, prompt_type: Optional[PromptType] = None, is_active: Optional[bool] = None, name: Optional[str] = None, page: int = 1, page_size: int = 20 ) -> Dict[str, Any]: """获取提示词列表""" logger.info( "获取提示词列表: 类型=%s, 激活=%s, 名称=%s, 页码=%d", prompt_type, is_active, name, page ) try: prompts = await self.repository.list_prompts( prompt_type=prompt_type, is_active=is_active, name=name, page=page, page_size=page_size ) total = await self.repository.count_prompts( prompt_type=prompt_type, is_active=is_active, name=name ) logger.info( "提示词列表获取成功: 总数=%d, 当前页=%d", total, page ) return { 'items': prompts, 'total': total, 'page': page, 'page_size': page_size, 'total_pages': (total + page_size - 1) // page_size } except Exception as e: logger.error( "获取提示词列表失败: 错误=%s", str(e), exc_info=True ) raise async def update_prompt( self, prompt_id: UUID, prompt_data: AIPromptSystemUpdate ) -> AIPromptSystem: """更新提示词""" logger.info("更新提示词: ID=%s", prompt_id) try: # 1. 检查提示词是否存在 prompt = await self.repository.get_by_id(prompt_id) if not prompt: raise NotFoundError(f"提示词不存在: {prompt_id}") # 2. 验证 skills_data 格式(如果提供) if prompt_data.skills_data is not None: self._validate_skills_data(prompt_data.skills_data) # 3. 如果设置为默认,取消同类型其他默认提示词 if prompt_data.is_default and not prompt.is_default: await self._unset_default_prompts(prompt.prompt_type) # 4. 更新提示词 update_data = prompt_data.model_dump(exclude_unset=True) updated_prompt = await self.repository.update(prompt_id, update_data) logger.info( "提示词更新成功: ID=%s, 名称=%s", updated_prompt.prompt_id, updated_prompt.name ) return updated_prompt except (NotFoundError, ValidationError): raise except Exception as e: logger.error( "更新提示词失败: ID=%s, 错误=%s", prompt_id, str(e), exc_info=True ) raise async def delete_prompt( self, prompt_id: UUID ) -> None: """删除提示词(软删除,设置 is_active = false)""" logger.info("删除提示词: ID=%s", prompt_id) try: # 检查提示词是否存在 prompt = await self.repository.get_by_id(prompt_id) if not prompt: raise NotFoundError(f"提示词不存在: {prompt_id}") # 如果是默认提示词,不允许删除 if prompt.is_default: raise ValidationError("默认提示词不能删除,请先取消默认设置") # 软删除 await self.repository.update(prompt_id, {'is_active': False}) logger.info("提示词删除成功: ID=%s", prompt_id) except (NotFoundError, ValidationError): raise except Exception as e: logger.error( "删除提示词失败: ID=%s, 错误=%s", prompt_id, str(e), exc_info=True ) raise async def create_version( self, base_prompt_id: UUID, new_version: str, prompt_content: Optional[str] = None, skills_data: Optional[Dict[str, Any]] = None, description: Optional[str] = None ) -> AIPromptSystem: """基于现有提示词创建新版本""" logger.info( "创建提示词新版本: 基础ID=%s, 新版本=%s", base_prompt_id, new_version ) try: # 1. 获取基础提示词 base_prompt = await self.repository.get_by_id(base_prompt_id) if not base_prompt: raise NotFoundError(f"基础提示词不存在: {base_prompt_id}") # 2. 检查新版本是否已存在 existing = await self.repository.get_by_name_version( base_prompt.name, new_version ) if existing: raise ValidationError( f"提示词 {base_prompt.name} 版本 {new_version} 已存在" ) # 3. 验证 skills_data 格式(如果提供) if skills_data is not None: self._validate_skills_data(skills_data) # 4. 创建新版本 new_prompt = AIPromptSystem( name=base_prompt.name, prompt_type=base_prompt.prompt_type, prompt_content=prompt_content or base_prompt.prompt_content, skills_data=skills_data or base_prompt.skills_data, version=new_version, description=description or f"基于版本 {base_prompt.version} 创建", is_active=True, is_default=False # 新版本默认不是默认提示词 ) created_prompt = await self.repository.create(new_prompt) logger.info( "提示词新版本创建成功: ID=%s, 版本=%s", created_prompt.prompt_id, created_prompt.version ) return created_prompt except (NotFoundError, ValidationError): raise except Exception as e: logger.error( "创建提示词新版本失败: 基础ID=%s, 错误=%s", base_prompt_id, str(e), exc_info=True ) raise async def get_versions( self, name: str ) -> List[AIPromptSystem]: """获取提示词的所有版本""" logger.info("获取提示词版本历史: 名称=%s", name) versions = await self.repository.get_versions_by_name(name) logger.info( "版本历史获取成功: 名称=%s, 版本数=%d", name, len(versions) ) return versions async def set_default( self, prompt_id: UUID ) -> AIPromptSystem: """设置为默认提示词""" logger.info("设置默认提示词: ID=%s", prompt_id) try: # 1. 检查提示词是否存在 prompt = await self.repository.get_by_id(prompt_id) if not prompt: raise NotFoundError(f"提示词不存在: {prompt_id}") # 2. 检查是否已激活 if not prompt.is_active: raise ValidationError("只能将激活的提示词设置为默认") # 3. 取消同类型其他默认提示词 await self._unset_default_prompts(prompt.prompt_type) # 4. 设置为默认 updated_prompt = await self.repository.update( prompt_id, {'is_default': True} ) logger.info( "默认提示词设置成功: ID=%s, 类型=%s", prompt_id, prompt.prompt_type ) return updated_prompt except (NotFoundError, ValidationError): raise except Exception as e: logger.error( "设置默认提示词失败: ID=%s, 错误=%s", prompt_id, str(e), exc_info=True ) raise # 私有方法 def _validate_skills_data(self, skills_data: Dict[str, Any]) -> None: """验证 skills_data 格式""" if not isinstance(skills_data, dict): raise ValidationError("skills_data 必须是字典类型") if 'skills' not in skills_data: raise ValidationError("skills_data 必须包含 'skills' 字段") if not isinstance(skills_data['skills'], list): raise ValidationError("skills_data.skills 必须是数组类型") for skill in skills_data['skills']: if not isinstance(skill, dict): raise ValidationError("每个 skill 必须是字典类型") required_fields = ['skill_name', 'enabled'] for field in required_fields: if field not in skill: raise ValidationError(f"skill 缺少必需字段: {field}") async def _unset_default_prompts(self, prompt_type: PromptType) -> None: """取消同类型其他默认提示词""" await self.repository.unset_default_by_type(prompt_type) ``` --- ## API 接口 ### 1. 创建提示词 ``` POST /api/v1/admin/ai-prompts ``` **权限**:系统管理员 **请求体**: ```json { "name": "剧本解析提示词", "prompt_type": 1, "prompt_content": "你是一个专业的剧本解析助手...", "skills_data": { "skills": [ { "skill_name": "jointo-tech-stack", "file_path": ".claude/skills/jointo-tech-stack/references/database.md", "url": null, "version": "1.0", "enabled": true, "priority": 1 } ] }, "version": "1.0.0", "description": "用于解析剧本的提示词模板", "is_active": true, "is_default": true } ``` **响应**: ```json { "code": 200, "message": "Success", "data": { "prompt_id": "019d1234-5678-7abc-def0-111111111111", "name": "剧本解析提示词", "prompt_type": 1, "prompt_content": "你是一个专业的剧本解析助手...", "skills_data": { "skills": [...] }, "version": "1.0.0", "description": "用于解析剧本的提示词模板", "is_active": true, "is_default": true, "created_at": "2026-01-30T10:00:00Z", "updated_at": "2026-01-30T10:00:00Z" } } ``` ### 2. 获取提示词列表 ``` GET /api/v1/admin/ai-prompts?prompt_type=1&is_active=true&page=1&page_size=20 ``` **权限**:系统管理员 **查询参数**: - `prompt_type`:提示词类型(可选) - `is_active`:是否激活(可选) - `name`:名称搜索(可选) - `page`:页码(默认 1) - `page_size`:每页数量(默认 20) **响应**: ```json { "code": 200, "message": "Success", "data": { "items": [ { "prompt_id": "019d1234-5678-7abc-def0-111111111111", "name": "剧本解析提示词", "prompt_type": 1, "version": "1.0.0", "is_active": true, "is_default": true, "created_at": "2026-01-30T10:00:00Z" } ], "total": 10, "page": 1, "page_size": 20, "total_pages": 1 } } ``` ### 3. 获取提示词详情 ``` GET /api/v1/admin/ai-prompts/{prompt_id} ``` **权限**:系统管理员 **响应**:同创建提示词响应 ### 4. 更新提示词 ``` PATCH /api/v1/admin/ai-prompts/{prompt_id} ``` **权限**:系统管理员 **请求体**: ```json { "prompt_content": "更新后的提示词内容...", "description": "更新说明", "is_active": true } ``` ### 5. 删除提示词 ``` DELETE /api/v1/admin/ai-prompts/{prompt_id} ``` **权限**:系统管理员 **响应**: ```json { "code": 200, "message": "提示词已删除" } ``` ### 6. 创建新版本 ``` POST /api/v1/admin/ai-prompts/{prompt_id}/versions ``` **权限**:系统管理员 **请求体**: ```json { "new_version": "1.1.0", "prompt_content": "新版本的提示词内容...", "description": "版本 1.1.0 更新说明" } ``` ### 7. 获取版本历史 ``` GET /api/v1/admin/ai-prompts/versions?name=剧本解析提示词 ``` **权限**:系统管理员 **响应**: ```json { "code": 200, "message": "Success", "data": [ { "prompt_id": "019d1234-5678-7abc-def0-111111111111", "name": "剧本解析提示词", "version": "1.0.0", "is_active": true, "is_default": false, "created_at": "2026-01-30T10:00:00Z" }, { "prompt_id": "019d1234-5678-7abc-def0-222222222222", "name": "剧本解析提示词", "version": "1.1.0", "is_active": true, "is_default": true, "created_at": "2026-01-30T11:00:00Z" } ] } ``` ### 8. 设置默认提示词 ``` POST /api/v1/admin/ai-prompts/{prompt_id}/set-default ``` **权限**:系统管理员 ### 9. 获取默认提示词(公开接口) ``` GET /api/v1/ai-prompts/default?prompt_type=1 ``` **权限**:无需认证(内部服务调用) **响应**: ```json { "code": 200, "message": "Success", "data": { "prompt_id": "019d1234-5678-7abc-def0-111111111111", "name": "剧本解析提示词", "prompt_type": 1, "prompt_content": "你是一个专业的剧本解析助手...", "skills_data": { "skills": [...] }, "version": "1.0.0" } } ``` --- ## 数据模型 ### Pydantic Schema ```python # app/schemas/ai_prompt_system.py from pydantic import BaseModel, Field, field_validator from uuid import UUID from typing import Optional, Dict, Any, List from datetime import datetime from enum import IntEnum class PromptType(IntEnum): """提示词类型""" SCREENPLAY = 1 STORYBOARD = 2 CHARACTER = 3 SCENE = 4 PROP = 5 RESOURCE = 6 GENERAL = 7 class SkillConfig(BaseModel): """Skill 配置""" skill_name: str = Field(..., description="Skill 名称") file_path: Optional[str] = Field(None, description="文件路径") url: Optional[str] = Field(None, description="URL") version: Optional[str] = Field(None, description="版本号") enabled: bool = Field(True, description="是否启用") priority: int = Field(1, description="优先级") description: Optional[str] = Field(None, description="说明") class SkillsData(BaseModel): """Skills 数据""" skills: List[SkillConfig] = Field(default_factory=list, description="Skills 列表") class AIPromptSystemBase(BaseModel): """提示词基础模型""" name: str = Field(..., description="提示词名称", min_length=1, max_length=200) prompt_type: PromptType = Field(..., description="提示词类型") prompt_content: str = Field(..., description="提示词内容", min_length=1) skills_data: Dict[str, Any] = Field( default_factory=lambda: {"skills": []}, description="Skills 配置" ) version: str = Field(..., description="版本号", min_length=1, max_length=50) description: Optional[str] = Field(None, description="提示词说明") is_active: bool = Field(True, description="是否启用") is_default: bool = Field(False, description="是否为默认提示词") class AIPromptSystemCreate(AIPromptSystemBase): """创建提示词请求""" pass class AIPromptSystemUpdate(BaseModel): """更新提示词请求""" prompt_content: Optional[str] = Field(None, description="提示词内容") skills_data: Optional[Dict[str, Any]] = Field(None, description="Skills 配置") description: Optional[str] = Field(None, description="提示词说明") is_active: Optional[bool] = Field(None, description="是否启用") is_default: Optional[bool] = Field(None, description="是否为默认提示词") class AIPromptSystemResponse(AIPromptSystemBase): """提示词响应""" prompt_id: UUID = Field(..., description="提示词 ID") created_at: datetime = Field(..., description="创建时间") updated_at: datetime = Field(..., description="更新时间") class Config: from_attributes = True class AIPromptSystemListItem(BaseModel): """提示词列表项""" prompt_id: UUID name: str prompt_type: PromptType version: str is_active: bool is_default: bool created_at: datetime class Config: from_attributes = True class CreateVersionRequest(BaseModel): """创建版本请求""" new_version: str = Field(..., description="新版本号") prompt_content: Optional[str] = Field(None, description="提示词内容") skills_data: Optional[Dict[str, Any]] = Field(None, description="Skills 配置") description: Optional[str] = Field(None, description="版本说明") ``` ### SQLModel Model ```python # app/models/ai_prompt_system.py from sqlmodel import SQLModel, Field, Column from sqlalchemy import Text, TIMESTAMP, Index from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB from uuid import UUID from datetime import datetime from typing import Optional, Dict, Any from enum import IntEnum class PromptType(IntEnum): """提示词类型""" SCREENPLAY = 1 STORYBOARD = 2 CHARACTER = 3 SCENE = 4 PROP = 5 RESOURCE = 6 GENERAL = 7 class AIPromptSystem(SQLModel, table=True): """AI 提示词系统表""" __tablename__ = "ai_prompts_system" prompt_id: UUID = Field( sa_column=Column(PG_UUID(as_uuid=True), primary_key=True) ) name: str = Field(sa_column=Column(Text, nullable=False)) prompt_type: int = Field(sa_column=Column("prompt_type", nullable=False)) prompt_content: str = Field(sa_column=Column(Text, nullable=False)) skills_data: Dict[str, Any] = Field( default_factory=lambda: {"skills": []}, sa_column=Column(JSONB, nullable=False, server_default='{"skills": []}') ) version: str = Field(sa_column=Column(Text, nullable=False)) description: Optional[str] = Field(default=None, sa_column=Column(Text)) is_active: bool = Field(default=True, sa_column=Column("is_active", nullable=False)) is_default: bool = Field(default=False, sa_column=Column("is_default", nullable=False)) created_at: datetime = Field( sa_column=Column(TIMESTAMP(timezone=True), nullable=False) ) updated_at: datetime = Field( sa_column=Column(TIMESTAMP(timezone=True), nullable=False) ) __table_args__ = ( Index('idx_ai_prompts_system_type', 'prompt_type'), Index('idx_ai_prompts_system_is_active', 'is_active'), Index('idx_ai_prompts_system_is_default', 'is_default'), Index('idx_ai_prompts_system_name', 'name'), Index('idx_ai_prompts_system_type_active', 'prompt_type', 'is_active'), ) ``` --- ## 使用示例 ### 1. AI Service 调用提示词 ```python # app/services/ai_service.py from app.services.ai_prompt_system_service import AIPromptSystemService from app.models.ai_prompt_system import PromptType class AIService: async def parse_screenplay( self, screenplay_id: UUID, screenplay_content: str, prompt_id: Optional[UUID] = None ): """解析剧本""" # 1. 获取提示词 prompt_service = AIPromptSystemService(self.db) if prompt_id: # 使用指定的提示词 prompt = await prompt_service.get_prompt_by_id(prompt_id) else: # 使用默认提示词 prompt = await prompt_service.get_default_prompt(PromptType.SCREENPLAY) if not prompt: raise ValidationError("未找到剧本解析的默认提示词") # 2. 构建完整提示词(包含 skills) full_prompt = self._build_prompt_with_skills( prompt.prompt_content, prompt.skills_data ) # 3. 调用 AI 模型 result = await self._call_ai_model(full_prompt, screenplay_content) return result def _build_prompt_with_skills( self, prompt_content: str, skills_data: Dict[str, Any] ) -> str: """构建包含 skills 的完整提示词""" skills = skills_data.get('skills', []) enabled_skills = [s for s in skills if s.get('enabled', True)] # 按优先级排序 enabled_skills.sort(key=lambda x: x.get('priority', 999)) # 构建 skills 引用 skills_text = "\n\n".join([ f"参考文档 {i+1}: {skill['skill_name']}\n" f"路径: {skill.get('file_path', 'N/A')}" for i, skill in enumerate(enabled_skills) ]) return f"{prompt_content}\n\n{skills_text}" ``` ### 2. 管理员创建提示词 ```python # 示例:创建剧本解析提示词 prompt_data = AIPromptSystemCreate( name="剧本解析提示词", prompt_type=PromptType.SCREENPLAY, prompt_content=""" 你是一个专业的剧本解析助手。请分析以下剧本内容,提取以下信息: 1. 角色列表(包括角色名、描述、类型) 2. 场景列表(包括场景编号、标题、地点、时间) 3. 道具列表(包括道具名、描述、重要性) 4. 角色标签(如年龄段、状态等) 5. 场景标签(如时代、氛围等) 6. 道具标签(如状态、版本等) 7. 分镜脚本(包括镜号、描述、对白、景别、运镜等) 请以 JSON 格式返回结果。 """, skills_data={ "skills": [ { "skill_name": "jointo-tech-stack", "file_path": ".claude/skills/jointo-tech-stack/references/database.md", "version": "1.0", "enabled": True, "priority": 1, "description": "数据库设计规范" } ] }, version="1.0.0", description="用于解析剧本的提示词模板", is_active=True, is_default=True ) prompt_service = AIPromptSystemService(db) prompt = await prompt_service.create_prompt(prompt_data) ``` ### 3. 创建新版本 ```python # 基于现有提示词创建新版本 new_prompt = await prompt_service.create_version( base_prompt_id=prompt.prompt_id, new_version="1.1.0", prompt_content="优化后的提示词内容...", description="版本 1.1.0:优化了角色识别逻辑" ) ``` ### 4. 设置默认提示词 ```python # 将新版本设置为默认 await prompt_service.set_default(new_prompt.prompt_id) ``` --- ## 相关文档 - [AI Service 文档](./ai-service.md) - [Screenplay Service 文档](../project/screenplay-service.md) - [AI 解析剧本工作流](../../workflows/screenplay-ai-parse-workflow.md) - [数据库设计规范](../../../../.claude/skills/jointo-tech-stack/references/database.md) --- ## 变更历史 **v1.1 (2026-02-03)** - ✅ 修复日志格式为 %-formatting - ✅ 统一错误处理,添加 exc_info=True - ✅ 完善技术规范说明 **v1.0 (2026-01-30)** - ✅ 初始版本 - ✅ 系统级提示词管理 - ✅ 版本管理功能 - ✅ Skills 配置支持 - ✅ 默认提示词机制 - ✅ 符合 jointo-tech-stack 规范 - ✅ 无物理外键约束,应用层验证 - ✅ UUID v7 主键 - ✅ SMALLINT 枚举类型 - ✅ 完整的日志记录 --- **文档版本**:v1.1 **最后更新**:2026-02-03 **符合规范**:jointo-tech-stack v1.0