将tools解耦

This commit is contained in:
ACT丶流星雨 2026-03-23 12:28:43 +08:00
parent c148fc31f2
commit 2b18e066ee
6 changed files with 254 additions and 127 deletions

View File

@ -7,20 +7,6 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
短剧漫剧制作的指挥层,负责整体决策和协调。始终以用户当前指令为最终目标推进:默认直接协调执行,只有用户明确提出需要新增或修改拍摄计划时,才进入计划编辑与确认流程。
## 可用工具
| 工具 | 说明 |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `activate_skill` | 激活技能,加载完整指令和资源列表到上下文 |
| `read_skill_file` | 读取已激活技能目录下的参考资料文件 |
| `deepRetrieve` | 深度检索记忆,通过关键词回忆历史对话详情 |
| `run_sub_agent` | 启动子 Agent 执行任务(可用:`executionAI``supervisionAI` |
| `get_flowData` | 获取工作区数据key: `script` 剧本 / `scriptPlan` 拍摄计划 / `assets` 资产列表 / `storyboardTable` 分镜表) |
| `get_flowData_schema` | 获取工作区数据的类型结构 |
| `set_flowData` | 保存数据到工作区lodash 路径) |
| `generate_assets_images` | 生成衍生资产图片(传入资产 id 列表) |
| `generate_storyboard_images` | 生成分镜图(传入剧本文本) |
## 核心工作流程(必须严格遵循)
### 首先:判断用户意图
@ -30,34 +16,30 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
- **用户发起执行类需求**(如"开始制作第 4 集"、"继续生成分镜"、"提取角色资产" → 直接进入阶段三,按用户目标执行
- **用户明确要求新增/修改拍摄计划**(如"给我出一版拍摄计划"、"第 2 步改一下"、"加一个镜头" → 进入阶段二,更新 `scriptPlan` 并与用户确认
- **用户确认拍摄计划**(如"可以"、"确认"、"开始吧"、"没问题" → 在不重做计划的前提下进入阶段三执行
**禁止**:在用户未提出计划诉求时,主动生成或反复重生成拍摄计划。
### 阶段一:收集信息(仅首次进入会话或上下文不足时触发)
1. 调用 `get_flowData`key: `script`)获取当前剧本内容
2. 调用 `get_flowData`key: `scriptPlan`)获取已有拍摄计划(可能为空)
3. 调用 `get_flowData`key: `assets`)获取资产数据,了解项目现状
4. 调用 `deepRetrieve` 检索相关历史记忆,了解已完成的工作进度
5. 使用 `read_skill_file` 加载 `references/plan.md` 获取计划制定规范
1. 调用 `deepRetrieve` 检索相关历史记忆,了解已完成的工作进度
### 阶段二:编辑拍摄计划并对话确认(仅用户明确提出时触发)
1. 根据剧本内容、工作区数据、历史记忆和用户需求,新增或修改**拍摄计划**scriptPlan
2. 调用 `set_flowData`key: `scriptPlan`, value: 最新拍摄计划文本)将拍摄计划同步到前端工作区
1. 根据历史记忆和用户需求,新增或修改**拍摄计划**
2. 调用 `set_plane` 将拍摄计划同步到前端工作区
3. **将拍摄计划回复给用户**,请求确认
4. 输出拍摄计划后**停止并等待用户回复**,不要自行继续
5. 如果用户要求调整:
- 根据用户反馈修改拍摄计划
- 再次调用 `set_flowData`key: `scriptPlan`, value: 修改后的拍摄计划)同步到前端
- 再次调用 `set_plane` 同步到前端
- 重新回复修改后的拍摄计划,继续等待确认
- **循环此过程**,直到用户明确确认
### 阶段三:按用户目标执行(默认阶段)
以用户当前指令为目标,优先执行用户要求;若 `scriptPlan` 已存在则按其作为参考,不存在时也可直接执行当前任务。需要分步时再拆解为执行步骤,并按顺序调用 `run_sub_agent` 工具
以用户当前指令为目标,优先执行用户要求;若拍摄计划已存在则按其作为参考,不存在时也可直接执行当前任务。需要分步时再拆解为执行步骤,并按顺序调用 `run_sub_agent`
1. 每次调用 `run_sub_agent` 时,选择 `executionAI` 作为子 Agent将当前步骤的任务描述作为 `prompt` 参数传入
1. 每次调用 `run_sub_agent` 时,选择 `executionAI` 作为子 Agent将当前步骤的任务描述作为 `prompt` 传入
2. 检查返回结果是否符合预期,不符合则调整指令重试
3. 将上一步的输出作为上下文传入下一步(如有依赖)
4. 全部步骤完成后,向用户汇报整体结果
@ -69,14 +51,6 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
- 关注步骤间的依赖关系,确保顺序合理
- 利用 `deepRetrieve` 检索历史记忆,避免重复已完成的工作
- **用户目标优先**:默认直接响应并推进用户当前任务,不要为了流程完整性而强制先生成计划
- **计划按需维护**:仅当用户明确要求新增/修改拍摄计划时,才更新 `scriptPlan`,且每次改动都调用 `set_flowData` 同步到前端
- **提取衍生资产后**:计划中必须包含"询问用户是否生成资产图片"步骤。若用户确认,执行层将调用 `generate_assets_images` 工具批量生成衍生资产图片
- **生成分镜表后**:计划中必须包含"询问用户是否生成分镜图片"步骤。若用户确认,执行层将调用 `generate_storyboard_images` 工具生成分镜图
## 参考资料
使用 `read_skill_file` 按需加载:
- [生成计划](references/plan.md) — 计划制定规范和回复模板
**注意**:按需加载参考资料,不要一次性全部加载。
- **计划按需维护**:仅当用户明确要求新增/修改拍摄计划时,才更新拍摄计划,且每次改动都调用 `set_plane` 同步到前端
- **提取衍生资产后**:计划中必须包含"询问用户是否生成资产图片"步骤。若用户确认,执行层将调用相应工具批量生成衍生资产图片
- **生成分镜表后**:计划中必须包含"询问用户是否生成分镜图片"步骤。若用户确认,执行层将调用相应工具生成分镜图

View File

@ -22,7 +22,7 @@ description: >
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
2. 根据[衍生资产提取](references/derive-assets-extraction.md)文档中的提取原则,分析剧本内容,为每个角色资产识别出关联的衍生资产(道具、服饰、法器、座驾、场景物件等)
3. 对每个有衍生状态的资产调用 `set_flowData({ key: "assets[N].derive", value: derive数组 })` 逐个保存
3. 对每个有衍生状态的资产调用 `set_flowData_assets` 保存
4. 告知用户提取完成,列出为每个角色提取的衍生资产概要
5. **询问用户是否需要生成衍生资产图片**
- 如果用户确认需要,收集所有需要生成图片的资产 id调用 `generate_assets_images({ ids: [资产id列表] })` 生成图片

View File

@ -52,7 +52,7 @@ export async function decisionAI(ctx: AgentContext) {
...skill.tools,
...memory.getTools(),
run_sub_agent: runSubAgent(ctx),
...useTools(ctx.socket),
...useTools(ctx.resTool),
},
onFinish: async (completion) => {
await memory.add("decisionAI", completion.text);
@ -81,7 +81,7 @@ export async function executionAI(ctx: AgentContext) {
tools: {
...skill.tools,
...memory.getTools(),
...useTools(ctx.socket),
...useTools(ctx.resTool),
},
onFinish: async (completion) => {
await memory.add("executionAI", completion.text);

View File

@ -1,60 +1,151 @@
import { tool, Tool } from "ai";
import { z, toJSONSchema } from "zod";
import { z } from "zod";
import _ from "lodash";
import { Socket } from "socket.io";
import ResTool from "@/socket/resTool";
const deriveSchema = z.object({ name: z.string().min(1).max(20), desc: z.string().min(1).max(100) });
const assetSchema = z.object({ assetsId: z.string(), name: z.string(), desc: z.string(), src: z.string(), derive: z.array(deriveSchema).optional() });
const storyboardTableSchema = z.string().describe("分镜表的markdown文本");
const flowDataSchema = z.object({ script: z.string(), scriptPlan: z.string(), assets: z.array(assetSchema), storyboardTable: storyboardTableSchema });
const deriveAssetSchema = z.object({
id: z.number().describe("衍生资产ID,如果新增则为空").optional(),
assetsId: z.string().describe("关联的资产ID"),
name: z.string().describe("衍生资产名称"),
desc: z.string().describe("衍生资产描述"),
src: z.string().describe("衍生资产资源路径"),
state: z.enum(["未生成", "生成中", "已完成", "生成失败"]).describe("衍生资产生成状态"),
});
const assetItemSchema = z.object({
assetsId: z.string().describe("资产唯一标识"),
name: z.string().describe("资产名称"),
desc: z.string().describe("资产描述"),
src: z.string().describe("资产资源路径"),
derive: z.array(deriveAssetSchema).describe("衍生资产列表"),
});
const storyboardSchema = z.object({
id: z.number().describe("分镜ID"),
title: z.string().describe("分镜标题"),
description: z.string().describe("分镜描述"),
camera: z.string().describe("镜头信息"),
duration: z.number().describe("持续时长(秒)"),
frameMode: z.enum(["firstFrame", "endFrame", "linesSoundEffects"]).describe("帧模式: 首帧/尾帧/台词音效"),
prompt: z.string().describe("生成提示词"),
lines: z.string().nullable().describe("台词内容"),
sound: z.string().nullable().describe("音效内容"),
associateAssetsIds: z.array(z.number()).describe("关联资产ID列表"),
src: z.string().nullable().describe("分镜资源路径"),
});
const workbenchDataSchema = z.object({
name: z.string().describe("项目名称"),
duration: z.string().describe("视频时长"),
resolution: z.string().describe("分辨率"),
fps: z.string().describe("帧率"),
cover: z.string().optional().describe("封面图片路径"),
gradient: z.string().optional().describe("渐变色配置"),
});
const posterItemSchema = z.object({
id: z.number().describe("海报ID"),
image: z.string().describe("海报图片路径"),
});
const flowDataSchema = z.object({
script: z.string().describe("剧本内容"),
scriptPlan: z.string().describe("拍摄计划"),
assets: z.array(assetItemSchema).describe("衍生资产"),
storyboardTable: z.string().describe("分镜表"),
storyboard: z.array(storyboardSchema).describe("分镜列表"),
workbench: workbenchDataSchema.describe("工作台配置"),
poster: z
.object({
items: z.array(posterItemSchema).describe("海报项目列表"),
})
.describe("海报配置"),
});
type FlowData = z.infer<typeof flowDataSchema>;
const keySchema = z.object({
key: z
.enum(["script", "scriptPlan", "assets", "storyboardTable"])
.describe("script=剧本,scriptPlan=拍摄计划,assets=资产列表,storyboardTable=分镜表"),
});
const valueSchema = z
.union([z.string(), z.array(assetSchema), assetSchema, z.array(deriveSchema), z.array(storyboardTableSchema)])
.describe("路径对应的值");
const keySchema = z.enum(Object.keys(flowDataSchema.shape) as [keyof FlowData, ...Array<keyof FlowData>]);
const flowDataKeyLabels = Object.fromEntries(
Object.entries(flowDataSchema.shape).map(([key, schema]) => [key, (schema as z.ZodTypeAny).description ?? key]),
) as Record<keyof FlowData, string>;
export default (socket: Socket, toolsNames?: string[]) => {
export default (resTool: ResTool, toolsNames?: string[]) => {
const { socket } = resTool;
const tools: Record<string, Tool> = {
get_flowData: tool({
description: "获取工作区数据",
inputSchema: keySchema,
inputSchema: z.object({
key: keySchema.describe("数据key"),
}),
execute: async ({ key }) => {
resTool.systemMessage(`正在阅读 ${flowDataKeyLabels[key]} 数据...`);
console.log("[tools] get_flowData", key);
const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res)));
return flowData[key];
},
}),
get_flowData_schema: tool({
description: "获取工作区数据的类型结构,在使用set_flowData前应先调用",
inputSchema: keySchema,
execute: async ({ key }) => {
console.log("[tools] get_flowData_schema", key);
return toJSONSchema(flowDataSchema.shape[key]);
set_flowData_script: tool({
description: "保存剧本内容到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.script }),
execute: async ({ value }) => {
console.log("[tools] set_flowData script", value);
resTool.systemMessage("正在保存 剧本 数据");
socket.emit("setFlowData", { key: "script", value });
return true;
},
}),
set_flowData: tool({
description: "保存数据到工作区,key为lodash路径,先调用get_flowData_schema了解可用路径和类型",
inputSchema: z.object({
key: z.string().describe("lodash路径,如 script、assets[0].derive"),
value: valueSchema,
}),
execute: async ({ key, value }) => {
console.log("[tools] set_flowData", key, value);
const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res)));
const backup = _.cloneDeep(_.get(flowData, key));
_.set(flowData, key, value);
const r = flowDataSchema.safeParse(flowData);
if (!r.success) {
_.set(flowData, key, backup);
return { error: r.error.issues.map((i) => `[${i.path.join(".")}] ${i.message}`).join("; ") };
}
socket.emit("setFlowData", { key, value });
set_flowData_scriptPlan: tool({
description: "保存拍摄计划到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.scriptPlan }),
execute: async ({ value }) => {
console.log("[tools] set_flowData scriptPlan", value);
resTool.systemMessage("正在保存 拍摄计划 数据");
socket.emit("setFlowData", { key: "scriptPlan", value });
return true;
},
}),
set_flowData_assets: tool({
description: "保存衍生资产列表到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.assets }),
execute: async ({ value }) => {
console.log("[tools] set_flowData assets", value);
resTool.systemMessage("正在保存 衍生资产 数据");
socket.emit("setFlowData", { key: "assets", value });
return true;
},
}),
set_flowData_storyboardTable: tool({
description: "保存分镜表到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.storyboardTable }),
execute: async ({ value }) => {
console.log("[tools] set_flowData storyboardTable", value);
resTool.systemMessage("正在保存 分镜表 数据...");
socket.emit("setFlowData", { key: "storyboardTable", value });
return true;
},
}),
set_flowData_storyboard: tool({
description: "保存分镜列表到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.storyboard }),
execute: async ({ value }) => {
console.log("[tools] set_flowData storyboard", value);
resTool.systemMessage("正在保存 分镜列表 数据...");
socket.emit("setFlowData", { key: "storyboard", value });
return true;
},
}),
set_flowData_workbench: tool({
description: "保存工作台配置数据到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.workbench }),
execute: async ({ value }) => {
console.log("[tools] set_flowData workbench", value);
resTool.systemMessage("正在保存 工作台配置 数据...");
socket.emit("setFlowData", { key: "workbench", value });
return true;
},
}),
set_flowData_poster: tool({
description: "保存海报配置到工作区",
inputSchema: z.object({ value: flowDataSchema.shape.poster }),
execute: async ({ value }) => {
console.log("[tools] set_flowData poster", value);
resTool.systemMessage("正在保存 海报 数据...");
socket.emit("setFlowData", { key: "poster", value });
return true;
},
}),
@ -62,23 +153,23 @@ export default (socket: Socket, toolsNames?: string[]) => {
generate_storyboard_images: tool({
description: `生成一组图片任务,支持图片间的依赖关系(以图生图)。
- images: 图片任务数组
- id: 图片唯一标识符
- prompt: 图片生成提示词
- referenceIds: 依赖的参考图id数组[]
- assetIds: 参考的资产图id数组
- images: 图片任务数组
- id: 图片唯一标识符
- prompt: 图片生成提示词
- referenceIds: 依赖的参考图id数组[]
- assetIds: 参考的资产图id数组
1. referenceIds中的id必须存在于images数组中
2. A依赖BB依赖A
3.
1. referenceIds中的id必须存在于images数组中
2. A依赖BB依赖A
3.
images: [
{id: "cat", prompt: "一只橘猫", referenceIds: [], assetIds: []},
{id: "dog", prompt: "风格相同的金毛犬", referenceIds: ["cat"], assetIds: []}
]`,
images: [
{id: "cat", prompt: "一只橘猫", referenceIds: [], assetIds: []},
{id: "dog", prompt: "风格相同的金毛犬", referenceIds: ["cat"], assetIds: []}
]`,
inputSchema: z.object({
images: z.array(
z.object({

View File

@ -1,10 +1,8 @@
import u from "@/utils";
import { Socket } from "socket.io";
type Role = "developer" | "system" | "assistant" | "user";
class ResTool {
constructor(private socket: Socket) {}
constructor(public socket: Socket) {}
textMessage(name: string = "AI") {
const messageId = u.uuid();

View File

@ -1,4 +1,4 @@
// @db-hash bea1bd617996a9e12ad951edcce03880
// @db-hash bd46e7c381481a74efedc662a4f9049f
//该文件由脚本自动生成,请勿手动修改
export interface memories {
@ -35,12 +35,18 @@ export interface o_assets {
'projectId'?: number | null;
'prompt'?: string | null;
'remark'?: string | null;
'scriptId'?: number | null;
'sonId'?: number | null;
'startTime'?: number | null;
'state'?: string | null;
'type'?: string | null;
}
export interface o_chatHistory {
'data'?: string | null;
'id'?: number;
'novel'?: string | null;
'projectId'?: number | null;
'type'?: string | null;
}
export interface o_event {
'createTime'?: number | null;
'detail'?: string | null;
@ -61,10 +67,33 @@ export interface o_image {
'assetsId'?: number | null;
'filePath'?: string | null;
'id'?: number;
'model'?: string | null;
'resolution'?: string | null;
'projectId'?: number | null;
'scriptId'?: number | null;
'state'?: string | null;
'type'?: string | null;
'videoId'?: number | null;
}
export interface o_model {
'apiKey'?: string | null;
'baseUrl'?: string | null;
'createTime'?: number | null;
'id'?: number;
'index'?: number | null;
'manufacturer'?: string | null;
'model'?: string | null;
'modelType'?: string | null;
'type'?: string | null;
}
export interface o_myTasks {
'describe'?: string | null;
'id'?: number;
'model'?: string | null;
'projectId'?: number | null;
'reason'?: string | null;
'relatedObjects'?: string | null;
'startTime'?: number | null;
'state'?: string | null;
'taskClass'?: string | null;
}
export interface o_novel {
'chapter'?: string | null;
@ -97,6 +126,15 @@ export interface o_project {
'userId'?: number | null;
'videoRatio'?: string | null;
}
export interface o_prompts {
'code'?: string | null;
'customValue'?: string | null;
'defaultValue'?: string | null;
'id'?: number;
'name'?: string | null;
'parentCode'?: string | null;
'type'?: string | null;
}
export interface o_script {
'content'?: string | null;
'createTime'?: number | null;
@ -104,31 +142,40 @@ export interface o_script {
'name'?: string | null;
'projectId'?: number | null;
}
export interface o_scriptAssets {
'assetsId'?: number | null;
'id'?: number;
'scriptId'?: number | null;
}
export interface o_scriptOutline {
'id'?: number;
'outlineId'?: number | null;
'scriptId'?: number | null;
}
export interface o_setting {
'key'?: string | null;
'value'?: string | null;
}
export interface o_storyboard {
'associateAssetsIds'?: string | null;
'camera'?: string | null;
'createTime'?: number | null;
'duration'?: string | null;
'filePath'?: string | null;
'frameType'?: string | null;
export interface o_skills {
'id'?: number;
'name'?: string | null;
'startTime'?: number | null;
}
export interface o_storyboard {
'createTime'?: number | null;
'id'?: number;
'mode'?: string | null;
'model'?: string | null;
'name'?: string | null;
'prompt'?: string | null;
'resolution'?: string | null;
'scriptId'?: number | null;
'sound'?: string | null;
}
export interface o_storyboardFlow {
'flowData': string;
'id'?: number;
'stroryboardId': number;
}
export interface o_storyboardScript {
'id'?: number;
'scriptId'?: number | null;
'storyboardId'?: number | null;
}
export interface o_tasks {
'describe'?: string | null;
'id'?: number;
@ -157,27 +204,36 @@ export interface o_vendorConfig {
'version'?: string | null;
}
export interface o_video {
'configId'?: number | null;
'errorReason'?: string | null;
'filePath'?: string | null;
'firstFrame'?: string | null;
'id'?: number;
'scriptId'?: number | null;
'state'?: string | null;
'storyboardId'?: number | null;
'time'?: number | null;
}
export interface o_videoConfig {
'audio'?: number | null;
'createTime'?: number | null;
'data'?: string | null;
'duration'?: number | null;
'id'?: number;
'mode'?: string | null;
'model'?: string | null;
'prompt'?: string | null;
'resolution'?: string | null;
'storyboardId'?: number | null;
'scriptId'?: number | null;
'state'?: number | null;
'storyboardImgs'?: string | null;
'time'?: number | null;
}
export interface o_videoConfig {
'aiConfigId'?: number | null;
'audioEnabled'?: number | null;
'createTime'?: number | null;
'duration'?: number | null;
'endFrame'?: string | null;
'id'?: number;
'images'?: string | null;
'manufacturer'?: string | null;
'mode'?: string | null;
'projectId'?: number | null;
'prompt'?: string | null;
'resolution'?: string | null;
'scriptId'?: number | null;
'selectedResultId'?: number | null;
'startFrame'?: string | null;
'updateTime'?: number | null;
'videoId'?: number | null;
}
export interface DB {
@ -185,18 +241,26 @@ export interface DB {
"o_agentDeploy": o_agentDeploy;
"o_artStyle": o_artStyle;
"o_assets": o_assets;
"o_chatHistory": o_chatHistory;
"o_event": o_event;
"o_eventChapter": o_eventChapter;
"o_flowData": o_flowData;
"o_image": o_image;
"o_model": o_model;
"o_myTasks": o_myTasks;
"o_novel": o_novel;
"o_outline": o_outline;
"o_outlineNovel": o_outlineNovel;
"o_project": o_project;
"o_prompts": o_prompts;
"o_script": o_script;
"o_scriptAssets": o_scriptAssets;
"o_scriptOutline": o_scriptOutline;
"o_setting": o_setting;
"o_skills": o_skills;
"o_storyboard": o_storyboard;
"o_storyboardFlow": o_storyboardFlow;
"o_storyboardScript": o_storyboardScript;
"o_tasks": o_tasks;
"o_user": o_user;
"o_vendorConfig": o_vendorConfig;