no message

This commit is contained in:
zhishi 2026-04-01 18:23:00 +08:00
parent 0de504a138
commit ba5c776482
10 changed files with 79 additions and 291 deletions

View File

@ -1,233 +1,3 @@
# 决策层 Agent 技能指令
你是视频制作项目的**决策层 Agent****只负责决策和任务派发**:理解用户意图、拆解任务、调度执行层与监督层、把控质量。
你是唯一与用户直接对接的 Agent执行层和监督层只接收你派发的指令。
**核心原则:**
- **决策层不执行具体任务**,不读取工作区数据(不调用 get_flowData不直接操作任何资产或分镜数据。所有具体工作由执行层完成。
- **决策层不做执行层的判断**,执行层返回什么结论就基于该结论决策下一步。
## 核心职责
1. **需求分析**:解析用户请求,判断属于流水线哪个阶段
2. **任务拆解**:将复杂请求分解为可执行的子任务
3. **调度执行**:通过 `run_sub_agent_execution` 派发任务到执行层
4. **质量管控**:通过 `run_sub_agent_supervision` 调用监督层审核产出物
5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆
---
## 制作流水线
五个阶段**必须按顺序执行**
```
阶段1: 衍生资产分析 → 阶段2: 衍生资产生成(可选) → 阶段3: 导演规划 → 阶段4: 构建分镜表 → 阶段5: 生成分镜
```
### 全局约束
- **资产约束**阶段3、4、5 只能使用资产库中已存在的资产含阶段1已写入的衍生资产
- **异步操作**阶段2的图片生成、阶段5的分镜图片生成均为异步操作派发后告知用户等待即可
- **审核规则**仅阶段3导演规划和阶段4构建分镜表需要审核执行完毕后自动派发监督层
---
### 阶段1衍生资产分析
| 项 | 说明 |
|----|------|
| 派发 | 执行层分析剧本,识别并写入衍生资产信息 |
| 输出 | 衍生资产分析报告 + 衍生资产写入结果(或"无需衍生"结论) |
| 前置条件 | 剧本和资产已存在于工作区 |
| 审核 | 不需要 |
**决策层行为:**
| 执行层返回 | 决策层操作 |
|-----------|-----------|
| "不需要衍生资产" | 向用户简要告知直接进入阶段3 |
| 衍生资产清单(已写入) | 展示给用户,询问是否确认生成图片 |
**用户确认分支(仅有新增资产时):**
| 用户反馈 | 操作 |
|----------|------|
| 确认全部生成 | 进入阶段2 |
| 部分生成 | 将用户选择的子集传递给阶段2 |
| 跳过 | 直接进入阶段3告知后续仅使用现有资产 |
| 调整清单 | 重新派发分析或将调整后清单传递给阶段2 |
> 约束阶段1必须完成衍生资产信息写入分析结果需展示给用户确认是否进入图片生成且不可自动进入阶段2。
---
### 阶段2衍生资产生成可选
| 项 | 说明 |
|----|------|
| 派发 | 执行层对已写入的衍生资产生成图片 |
| 输入 | 用户确认需要生成图片的衍生资产清单来自阶段1 |
| 输出 | 图片生成启动 |
| 前置条件 | 阶段1完成且用户确认生成 |
| 审核 | 不需要 |
**决策层行为:** 将用户确认的资产清单或子集派发给执行层。返回确认后告知用户图片生成中直接进入阶段3。
---
### 阶段3导演规划
| 项 | 说明 |
|----|------|
| 派发 | 执行层制定导演拍摄计划 |
| 输出 | 导演拍摄计划(执行层通过 set_plane 同步到前端) |
| 质量门 | 计划覆盖全部剧情、节奏合理、与资产匹配 |
| 前置条件 | 阶段1完成含跳过阶段2的情况 |
| 审核 | **需要** → 执行完毕后自动派发监督层 |
**阶段特有约束:** 规划中引用的角色、道具、场景必须在资产列表中存在。
---
### 阶段4构建分镜表
| 项 | 说明 |
|----|------|
| 派发 | 执行层将剧本拆分为分镜,生成结构化分镜表 |
| 输出 | 结构化分镜表(执行层通过 set_flowData 保存) |
| 质量门 | 分镜拆分粒度合理、字段完整、关联资产正确 |
| 前置条件 | 阶段3导演规划完成 |
| 审核 | **需要** → 执行完毕后自动派发监督层 |
**阶段特有约束:** `associateAssetsIds` 中的索引必须指向资产库中实际存在的资产。
---
### 阶段5构建分镜面板
| 项 | 说明 |
|----|------|
| 派发 | 执行层调将分镜表拆分生成分镜面板 |
| 输出 | 生成的分镜面板 |
| 前置条件 | 阶段4完成且用户确认 |
| 审核 | 不需要 |
### 阶段6生成分镜
| 项 | 说明 |
|----|------|
| 派发 | 执行层调用图片生成接口生成分镜图片 |
| 输出 | 生成的分镜图片 |
| 前置条件 | 阶段5完成且用户确认 |
| 审核 | 不需要 |
**阶段特有约束:** `generate_storyboard_images({ ids: [真实分镜ID列表] })` 中的 ids参数 必须指向分镜面板中实际存在的分镜Id。
**决策层行为:** 执行层返回确认后,告知用户图片生成已启动,流程结束。
---
## 调度与派发规范
### 派发指令要求
**派发给执行层和监督层的任务指令正文严格不超过100字。** 执行层已具备完整技能指令,只需告知任务类型和关键参数。
### 执行层派发
使用 `run_sub_agent_execution` 调用执行层:
```
run_sub_agent_execution(
prompts: "<按模板构建的具体指令>"
)
```
### 审核派发与结果处理
阶段3或阶段4执行完毕后
1. 将执行层返回的确认消息展示给用户
2. **紧接着自动调用监督层审核**(无需等待用户指示)
```
run_sub_agent_supervision(
prompts: "请审核【{阶段名}】的产出物。审核维度:{维度列表}"
)
```
监督层审核完毕后将报告展示给用户。决策层**等待用户回复**,根据反馈操作:
| 用户反馈 | 操作 |
|----------|------|
| 通过 / 下一阶段 | 派发下一阶段任务 |
| 需要修复 | 根据用户指示构建修复指令,派发执行层 |
| 重做 | 重新派发当前阶段任务 |
### 调度决策树
| 用户请求 | 处理规则 |
|----------|----------|
| 明确指定阶段 | 检查前置条件 → 派发该阶段 |
| "从头开始" / "完整制作" | 从阶段1顺序执行 |
| "继续" / "下一步" | `deepRetrieve` 获取进度 → 从当前阶段继续 |
| "修改/优化 X" | 定位对应阶段 → 派发修改任务 |
| 模糊请求 | `deepRetrieve` 获取进度 → 从当前阶段继续 |
---
## 指令模板
### 执行派发格式
```
你是执行层Agent请执行【{任务类型}】任务。
目标:{一句话目标}
上下文:{必要数据摘要}
要求:
1. {具体步骤1}
2. {具体步骤2}
约束:{特殊约束条件}
```
### 修复派发格式
```
你是执行层Agent请修复【{任务类型}】的以下问题。
用户确认的修复项:
1. {问题} → 修改为:{方案}
保持其余内容不变。
```
> 修复指令中只包含用户明确确认要修的项,不包含用户未回应或跳过的问题。
---
## 记忆检索策略
在以下场景使用 `deepRetrieve`
1. **新会话开始**:检索项目当前进度、已完成阶段
2. **用户提到之前的内容**:检索相关历史产出摘要
3. **质量问题追溯**:检索之前的审核结果和修改记录
4. **判断前置条件**:检索各阶段是否已完成
> `deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。
---
## 与用户交互规范
1. **进度汇报**:每完成一个阶段,汇报结果摘要和下一步计划
2. **审核结果展示**阶段3、4由监督层审核后展示报告等待用户反馈
3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定
4. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节
---
## 错误处理
| 场景 | 处理 |
|------|------|
| 执行层返回错误 | 分析原因调整指令重新派发最多重试2次 |
| 监督层发现质量问题 | 等待用户确认修复方案 → 派发修复指令 |
| 前置条件不满足 | 提示用户需先完成哪个阶段 |
| 记忆检索无结果 | 请求用户提供必要上下文 |
你是一个测试流程助手,你专门生成假数据:
你必须使用XML格式写入工作区分镜面板<storyboardItem prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds="[该分镜所需的资产ID列表]" shouldGenerateImage="是否需要生成分镜图片 true/false, 默认为true" /></storyboardItem>
现在请根据哟用户指令直接输出xml假数据

View File

@ -379,7 +379,7 @@ add_deriveAsset({
---
## 五、生成分镜图片
## 五、生成分镜面板与生成分镜图片
### 工具
@ -393,11 +393,11 @@ add_deriveAsset({
### 执行流程
1. 获取 `script``stoaryTable`
2. 使用XML格式写入工作区分镜面板<storyboard><items prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds=[资产ID列表]/></storyboard>
3. 先获取 `storyboard`数据 再调用 `generate_storyboard_images({ ids: [真实分镜ID列表] })` 生成分镜图片(异步,发起即返回)
2. 使用XML格式写入工作区分镜面板<storyboardItem prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds="[该分镜所需的资产ID列表]" shouldGenerateImage="是否需要生成分镜图片 true/false, 默认为true" /></storyboardItem>
3. 生成分镜面板后,先获取 `storyboard`数据 再调用 `generate_storyboard_images({ ids: [真实分镜ID列表] })` 生成分镜图片(异步,发起即返回)
### 约束
- 前置条件:分镜表已构建完成且用户已确认
- 图片必须与分镜描述匹配
- 你必须使用XML格式写入工作区分镜面板<storyboard><items prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds=[资产ID列表]/></storyboard>
- 你必须使用XML格式写入工作区分镜面板<storyboardItem prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds="[该分镜所需的资产ID列表]" shouldGenerateImage="是否需要生成分镜图片 true/false, 默认为true" /></storyboardItem>

View File

@ -138,7 +138,7 @@ function createSubAgent(parentCtx: AgentContext) {
"你必须使用如下XML格式写入工作区\n```",
"拍摄计划:<scriptPlan>内容</scriptPlan>",
"分镜表:<storyboardTable>内容</storyboardTable>",
"分镜面板:<storyboard> <items prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds=[该分镜所需的资产ID列表] /></storyboard>",
"分镜面板:<storyboardItem prompt=提示词内容 track=分组 duration=视频推荐时间 associateAssetsIds=[该分镜所需的资产ID列表] /></storyboardItem>",
"```",
].join("\n");
// "剧本:<script>内容</script>",

View File

@ -12,13 +12,14 @@ export default router.post(
validateFields({
projectId: z.number(),
novelIds: z.array(z.number()),
concurrentCount: z.number().min(1).optional(),
}),
async (req, res) => {
const { projectId, novelIds } = req.body;
const { projectId, novelIds, concurrentCount = 5 } = req.body;
const [allChapters, novel] = await Promise.all([
u.db("o_novel").where("projectId", projectId).whereIn("id", novelIds),
Promise.resolve(new u.cleanNovel()),
Promise.resolve(new u.cleanNovel(concurrentCount)),
]);
if (allChapters.length === 0) {
return res.status(400).send(success("没有对应章节"));

View File

@ -14,9 +14,10 @@ export default router.post(
assetIds: z.array(z.number()),
projectId: z.number(),
scriptId: z.number(),
concurrentCount: z.number().min(1).optional(),
}),
async (req, res) => {
const { assetIds, projectId, scriptId } = req.body;
const { assetIds, projectId, scriptId, concurrentCount = 5 } = req.body;
const projectSettingData = await u.db("o_project").where("id", projectId).select("imageModel", "imageQuality", "artStyle").first();
@ -53,8 +54,24 @@ export default router.post(
prompt: scenePrompt,
},
};
const imageData = [];
// 先批量为所有 assets 创建 image 记录并标记为"生成中"
const imageIdMap: Record<number, number> = {};
for (const item of assetsDataArr) {
const [imageId] = await u.db("o_image").insert({
assetsId: item.id,
type: item.type,
state: "生成中",
resolution: projectSettingData?.imageQuality,
model: projectSettingData?.imageModel,
});
imageIdMap[item.id!] = imageId;
await u.db("o_assets").where("id", item.id).update({ imageId: imageId });
}
const imageData: { id: number; state: string; src: string }[] = [];
res.status(200).send(success("开始生成资产图片"));
const generateSingleAsset = async (item: (typeof assetsDataArr)[number]) => {
const imageId = imageIdMap[item.id!];
const typeConfig = promptRecord[item.type!] || promptRecord["role"];
const { text } = await u.Ai.Text("universalAi").invoke({
@ -67,13 +84,6 @@ export default router.post(
],
});
const [imageId] = await u.db("o_image").insert({
assetsId: item.id,
type: item.type,
state: "生成中",
resolution: projectSettingData?.imageQuality,
model: projectSettingData?.imageModel,
});
const imageBase64 = imageUrlRecord[item.assetsId!] ? await urlToBase64(imageUrlRecord[item.assetsId!]) : null;
try {
const repeloadObj = {
@ -93,29 +103,33 @@ export default router.post(
projectId: projectId,
},
);
const savePath = `/${projectId}/assets/${scriptId}/${u.uuid()}.jpg`;
const savePath = `/${projectId}/assets/${scriptId}/${item.type}/${u.uuid()}.jpg`;
await imageCls.save(savePath);
// 更新对应数据库
await u.db("o_assets").where("id", item.id).update({ imageId: imageId, prompt: text });
await u.db("o_assets").where("id", item.id).update({ prompt: text });
await u.db("o_image").where({ id: imageId }).update({ state: "已完成", filePath: savePath });
imageData.push({
id: item.id,
return {
id: item.id!,
state: "已完成",
src: await u.oss.getFileUrl(savePath),
});
};
} catch (e) {
await u
.db("o_image")
.where({ id: imageId })
.update({ state: "生成失败", reason: u.error(e).message });
imageData.push({
id: item.id,
return {
id: item.id!,
state: "生成失败",
src: "",
});
};
}
}
};
return res.status(200).send(success(imageData));
// 按 concurrentCount 分批并发执行
for (let i = 0; i < assetsDataArr.length; i += concurrentCount) {
const batch = assetsDataArr.slice(i, i + concurrentCount);
const batchResults = await Promise.all(batch.map(generateSingleAsset));
imageData.push(...batchResults);
}
},
);

View File

@ -24,13 +24,16 @@ export default router.post(
episodesId: number;
} = req.body;
const sqlData = await u.db("o_agentWorkData").where("projectId", String(projectId)).andWhere("episodesId", String(episodesId)).first();
if (data.storyboard && data.storyboard.length)
const filterDatas = data.storyboard.filter((i) => !i.id);
if (data.storyboard && data.storyboard.length && !filterDatas.length)
await Promise.all(
data.storyboard.map(async (i, index) => {
await u.db("o_storyboard").where("id", i.id).update({
index: index,
});
}),
data.storyboard
.filter((i) => i.id)
.map(async (i, index) => {
await u.db("o_storyboard").where("id", i.id).update({
index: index,
});
}),
);
if (!sqlData) {
await u.db("o_agentWorkData").insert({

View File

@ -16,16 +16,19 @@ export default router.post(
storyboardIds: z.array(z.number()),
projectId: z.number(),
scriptId: z.number(),
concurrentCount: z.number().min(1).optional(),
}),
async (req, res) => {
const {
storyboardIds,
projectId,
scriptId,
concurrentCount = 5,
}: {
storyboardIds: number[];
projectId: number;
scriptId: number;
concurrentCount: number;
} = req.body;
if (!storyboardIds || storyboardIds.length === 0) return res.status(400).send(error("storyboardIds不能为空"));
// 当没有 storyboardIds 时,通过 AI 生成新的分镜面板数据
@ -47,6 +50,9 @@ export default router.post(
}
assetRecord[item.storyboardId].push(item.imageId);
});
await u.db("o_storyboard").whereIn("id", finalStoryboardIds).update({
state: "生成中",
});
res.status(200).send(
success(
storyboardData.map((i) => ({
@ -58,16 +64,14 @@ export default router.post(
})),
),
);
for (const item of storyboardData) {
const generateTask = async (item: (typeof storyboardData)[number]) => {
const repeloadObj = {
prompt: item.prompt!,
size: projectSettingData?.imageQuality as "1K" | "2K" | "4K",
aspectRatio: "16:9" as `${number}:${number}`,
};
await u.db("o_storyboard").where("id", item.id).update({
state: "生成中",
});
u.Ai.Image(projectSettingData?.imageModel as `${string}:${string}`)
await u.Ai.Image(projectSettingData?.imageModel as `${string}:${string}`)
.run(
{
imageBase64: await getAssetsImageBase64(assetRecord[item.id!] || []),
@ -97,6 +101,12 @@ export default router.post(
state: "生成失败",
});
});
};
// 按 concurrentCount 控制并发数,分批执行
for (let i = 0; i < storyboardData.length; i += concurrentCount) {
const batch = storyboardData.slice(i, i + concurrentCount);
await Promise.all(batch.map(generateTask));
}
},
);

View File

@ -21,7 +21,8 @@ export default router.post(
.update({
filePath: new URL(url).pathname,
flowId,
state: "已完成",
});
res.status(200).send(success({ message: "更新提示词成功" }));
res.status(200).send(success({ message: "更新分镜成功" }));
},
);

View File

@ -54,7 +54,7 @@ export default router.post(
validateFields({
scriptIds: z.array(z.number()),
projectId: z.number(),
groupSize: z.number().min(1).max(10).optional(),
groupSize: z.number().min(1).optional(),
}),
async (req, res) => {
const { scriptIds, projectId, groupSize = 5 } = req.body;

View File

@ -1,21 +1,11 @@
<<<<<<< HEAD
// @db-hash 2c7f828da2621d74d1b0d147d4ba4342
=======
// @db-hash a27fff5b05e1c9ef490898f17703079a
>>>>>>> bf124d07e35f3d45958f5c2e64a650df7249f737
// @db-hash f82eb99171699f051830710c1f816b59
//该文件由脚本自动生成,请勿手动修改
export interface _o_vendorConfig_old_20260401 {
'author'?: string | null;
'code'?: string | null;
'createTime'?: number | null;
'description'?: string | null;
'icon'?: string | null;
'id'?: string;
'inputs'?: string | null;
'inputValues'?: string | null;
'models'?: string | null;
'name'?: string | null;
export interface _o_videoTrack_old_20260401 {
'id'?: number;
'projectId'?: number | null;
'scriptId'?: number | null;
'videoId'?: number | null;
}
export interface memories {
'content': string;
@ -213,7 +203,6 @@ export interface o_vendorConfig {
'code'?: string | null;
'createTime'?: number | null;
'description'?: string | null;
'enableEnglish'?: number | null;
'icon'?: string | null;
'id'?: string;
'inputs'?: string | null;
@ -242,7 +231,7 @@ export interface o_videoTrack {
}
export interface DB {
"_o_vendorConfig_old_20260401": _o_vendorConfig_old_20260401;
"_o_videoTrack_old_20260401": _o_videoTrack_old_20260401;
"memories": memories;
"o_agentDeploy": o_agentDeploy;
"o_agentWorkData": o_agentWorkData;