feat: Skill 系统 - 完整 Skill 管理与 AI 自动选择模式#152
Open
aiastia wants to merge 31 commits into
Open
Conversation
aiastia
commented
May 22, 2026
Contributor
- 新增 Skill CRUD API(skills.py)含 skill_type 分类和 apply-to-chapter 流式处理
- 新增 Skill 管理前端页面(SkillManage.tsx)
- 新增 skill_loader.py 支持 skill_type 分类加载
- 新增 skill_tools_loader.py 实现 AI 自动选择最适合的 Skill
- 单章生成和批量生成均支持 skill_key='auto' 自动模式
- 前端单章/批量生成 Skill 下拉框添加 🤖 AI 自动选择选项
- 章节生成支持 reasoning_effort 思考模式参数
- 新增 story-deslop Skill(去AI味润色)
- 新增 Prompt Workshop 页面
- 新增 Skill CRUD API(skills.py)含 skill_type 分类和 apply-to-chapter 流式处理 - 新增 Skill 管理前端页面(SkillManage.tsx) - 新增 skill_loader.py 支持 skill_type 分类加载 - 新增 skill_tools_loader.py 实现 AI 自动选择最适合的 Skill - 单章生成和批量生成均支持 skill_key='auto' 自动模式 - 前端单章/批量生成 Skill 下拉框添加 🤖 AI 自动选择选项 - 章节生成支持 reasoning_effort 思考模式参数 - 新增 story-deslop Skill(去AI味润色) - 新增 Prompt Workshop 页面
1. 修复 description 解析:YAML | 多行块在末尾时正则匹配失败,回退到裸行匹配导致显示异常 2. 修复推理模型兼容:max_tokens 从 200 调至 500,避免 reasoning_tokens 用完导致截断 3. 优化上下文截断:提取章节概要/情感基调/叙事目标等关键信息,而非暴力截取前3000字
1. 修复 description 解析:YAML | 多行块在末尾时正则匹配失败,回退到裸行匹配导致显示异常 2. 修复推理模型兼容:max_tokens 从 200 调至 500,避免 reasoning_tokens 用完导致截断 3. 优化上下文截断:提取章节概要/情感基调/叙事目标等关键信息,而非暴力截取前3000字
- 在角色校验和组织校验期间添加心跳保活机制 - 使用asyncio.create_task将AI调用放到后台执行 - 主循环每5秒发送一次心跳包保持SSE连接活跃 - 避免长时间AI调用导致代理/浏览器断开连接
- 在角色校验和组织校验期间添加心跳保活机制 - 使用asyncio.create_task将AI调用放到后台执行 - 主循环每5秒发送一次心跳包保持SSE连接活跃 - 避免长时间AI调用导致代理/浏览器断开连接
- 将 embedding 编码移到线程池执行,避免阻塞 asyncio 事件循环 - 给 batch_add_memories 添加 60s 超时保护 - 给 analyze_chapter_background 的向量写入添加 120s 外层超时 - 启动时自动清理孤儿分析任务(running/pending -> failed)
- OUTLINE_CREATE 和 OUTLINE_CONTINUE 中增加严格区分规则 - 只有3个以上成员的正式团体才能标记为organization - 单个人物、亲属称谓、两人小组必须标记为character - 防止AI将'XX的母亲'等个人角色错误识别为组织
- 基于 origin/main 合并 pr-to-upstream-5 (Skill 系统 + 修复) - 使用 -X theirs 策略自动解决冲突 - 保留版本号 1.5.1(上游版本)
- 基于 origin/main 合并 fork/skill (Skill 系统 + logger + Docker 改进) - 使用 -X theirs 策略自动解决冲突 - 保留上游版本号 1.5.1
后端:
- chapter_context_service: 新增 _format_outline_structure_to_text 工具函数和 _OUTLINE_FIELD_LABELS 标签映射
- 1-1 模式 _build_outline_from_structure: 自动把额外字段注入到 {outline_content} 占位符
- 1-N 模式 _build_chapter_outline_1n: 自动把额外字段作为分章上下文
- outlines.py _build_outline_continuation_context: 大纲续写时把已有额外字段作为风格参考
- plot_expansion_service.py: 大纲展开时继承额外字段到 expansion_plan
前端:
- Outline.tsx: 扩展类型定义为 [key: string]: unknown 支持任意字段
- 新增 KNOWN_STRUCTURE_KEYS 白名单 + EXTRA_FIELD_LABELS 中文标签映射
- 编辑表单新增"自定义字段"区域,支持查看/添加/编辑/删除任意字段
- 卡片展示区自动渲染"额外设定"块(grid 布局,移动端自适应)
- 保存逻辑:先剔除原有额外字段,再用 editExtraFields 重新合并
效果: AI 生成大纲时返回的 obstacle_type、hook_type、chapter_breath 等字段会自动被
后续章节生成、分章展开、大纲续写使用,无需修改任何提示词模板
后端:
- chapter_context_service: 新增 _format_outline_structure_to_text 工具函数和 _OUTLINE_FIELD_LABELS 标签映射
- 1-1 模式 _build_outline_from_structure: 自动把额外字段注入到 {outline_content} 占位符
- 1-N 模式 _build_chapter_outline_1n: 自动把额外字段作为分章上下文
- outlines.py _build_outline_continuation_context: 大纲续写时把已有额外字段作为风格参考
- plot_expansion_service.py: 大纲展开时继承额外字段到 expansion_plan
前端:
- Outline.tsx: 扩展类型定义为 [key: string]: unknown 支持任意字段
- 新增 KNOWN_STRUCTURE_KEYS 白名单 + EXTRA_FIELD_LABELS 中文标签映射
- 编辑表单新增"自定义字段"区域,支持查看/添加/编辑/删除任意字段
- 卡片展示区自动渲染"额外设定"块(grid 布局,移动端自适应)
- 保存逻辑:先剔除原有额外字段,再用 editExtraFields 重新合并
效果: AI 生成大纲时返回的 obstacle_type、hook_type、chapter_breath 等字段会自动被
后续章节生成、分章展开、大纲续写使用,无需修改任何提示词模板
backend (skill_loader.py):
- 重写 create_skill_files/update_skill_files 函数签名
- _parse_yaml_frontmatter 增加 triggers 字段解析
- create/update 时把 triggers 持久化到 SKILL.md frontmatter
- 用 % 格式化替代 f-string,避免 body 中 {xxx} 被错误求值
frontend (SkillManage.tsx):
- 创建表单补充 triggers 字段
- category 改为从 skill_type 自动映射
backend (skill_loader.py):
- 重写 create_skill_files/update_skill_files 函数签名
- _parse_yaml_frontmatter 增加 triggers 字段解析
- create/update 时把 triggers 持久化到 SKILL.md frontmatter
- 用 % 格式化替代 f-string,避免 body 中 {xxx} 被错误求值
frontend (SkillManage.tsx):
- 创建表单补充 triggers 字段
- category 改为从 skill_type 自动映射
问题根因: - 数据库 Character.traits 字段(JSON 字符串)期望存扁平字符串列表 - 但 AI 生成数据时偶发产生嵌套列表(如 ["特征1", ["副职业:xxx"]]) - 导出时 CharacterExportData.traits: Optional[List[str]] 严格校验 遇到嵌套子列表就抛 ValidationError → 500 Internal Server Error 修复方案: - 新增 _normalize_traits_to_string_list 工具函数(递归扁平化) - 应用到 4 个关键路径: * _export_characters:项目整体导出(直接修复日志中的 500) * export_characters:角色/组织卡片单独导出 * _import_characters:项目整体导入(避免脏数据复制) * import_characters:角色/组织卡片单独导入 兼容性: - 不修改 schema、不修改数据库结构 - 完全向后兼容 - 容错 None / 空字符串 / 非法 JSON / dict / 数字布尔等异常输入 测试: - 重现日志中的实际异常数据并验证修复 - 12 项单元测试全部通过(含 Pydantic ValidationError 重现与修复验证)
问题根因: - 数据库 Character.traits 字段(JSON 字符串)期望存扁平字符串列表 - 但 AI 生成数据时偶发产生嵌套列表(如 ["特征1", ["副职业:xxx"]]) - 导出时 CharacterExportData.traits: Optional[List[str]] 严格校验 遇到嵌套子列表就抛 ValidationError → 500 Internal Server Error 修复方案: - 新增 _normalize_traits_to_string_list 工具函数(递归扁平化) - 应用到 4 个关键路径: * _export_characters:项目整体导出(直接修复日志中的 500) * export_characters:角色/组织卡片单独导出 * _import_characters:项目整体导入(避免脏数据复制) * import_characters:角色/组织卡片单独导入 兼容性: - 不修改 schema、不修改数据库结构 - 完全向后兼容 - 容错 None / 空字符串 / 非法 JSON / dict / 数字布尔等异常输入 测试: - 重现日志中的实际异常数据并验证修复 - 12 项单元测试全部通过(含 Pydantic ValidationError 重现与修复验证)
问题:服务启动时 '后台任务表检查失败: cannot access local variable datetime' 根因:Python 函数作用域陷阱 - 顶层已 from datetime import datetime - lifespan 函数内第二个 try 块又 from datetime import datetime, timedelta - Python 编译器把 datetime 标记为整个函数的局部变量 - 第一个 try 块中 datetime.now() 访问未初始化的局部变量 → UnboundLocalError 修复:删除函数内冗余的 from datetime import(顶层已可用,timedelta 未使用)
问题:服务启动时 '后台任务表检查失败: cannot access local variable datetime' 根因:Python 函数作用域陷阱 - 顶层已 from datetime import datetime - lifespan 函数内第二个 try 块又 from datetime import datetime, timedelta - Python 编译器把 datetime 标记为整个函数的局部变量 - 第一个 try 块中 datetime.now() 访问未初始化的局部变量 → UnboundLocalError 修复:删除函数内冗余的 from datetime import(顶层已可用,timedelta 未使用)
generate_single_chapter_for_batch 函数内部使用 batch_id 变量做取消检测, 但函数签名未声明该参数。现在: - 函数签名:新增 batch_id: Optional[str] = None 参数 - 调用处:execute_batch_generation_in_order 传入 batch_id=batch_id 修复批量生成流式期间的取消检测失效问题。
generate_single_chapter_for_batch 函数内部使用 batch_id 变量做取消检测, 但函数签名未声明该参数。现在: - 函数签名:新增 batch_id: Optional[str] = None 参数 - 调用处:execute_batch_generation_in_order 传入 batch_id=batch_id 修复批量生成流式期间的取消检测失效问题。
用户自定义提示词模板可能包含代码未预期的变量(如 {rhythm_position}),
原实现使用 template.format(**kwargs) 会抛出 KeyError。
现在改为安全替换策略:
- 优先尝试标准 format(效率高)
- 失败时用正则逐个替换已知变量,未知变量保留原样
- 正确处理转义大括号 {{ }}
用户自定义提示词模板可能包含代码未预期的变量(如 {rhythm_position}),
原实现使用 template.format(**kwargs) 会抛出 KeyError。
现在改为安全替换策略:
- 优先尝试标准 format(效率高)
- 失败时用正则逐个替换已知变量,未知变量保留原样
- 正确处理转义大括号 {{ }}
This reverts commit 747d363.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.