修正 #123 PR改为ZOD+jsonSchema联合形式

This commit is contained in:
ACT丶流星雨 2026-04-26 10:55:01 +08:00
parent 5fe2c964ee
commit 9eaf792630
10 changed files with 160 additions and 207 deletions

View File

@ -1 +1 @@
1.1.5 1.1.6

View File

@ -1,4 +1,5 @@
import { Socket } from "socket.io"; import { Socket } from "socket.io";
import { z } from "zod";
import { tool, jsonSchema } from "ai"; import { tool, jsonSchema } from "ai";
import u from "@/utils"; import u from "@/utils";
import Memory from "@/utils/agent/memory"; import Memory from "@/utils/agent/memory";
@ -137,14 +138,11 @@ async function createSubAgent(parentCtx: AgentContext) {
return fullResponse; return fullResponse;
} }
const promptInput = jsonSchema<{ prompt: string }>({ const promptInput = z
type: "object", .object({
properties: { prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
prompt: { type: "string", description: "交给子Agent的任务简约描述100字以内" }, })
}, .toJSONSchema();
required: ["prompt"],
additionalProperties: false,
});
const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first(); const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first();
if (!projectInfo) throw new Error(`项目不存在ID: ${resTool.data.projectId}`); if (!projectInfo) throw new Error(`项目不存在ID: ${resTool.data.projectId}`);
@ -199,7 +197,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//衍生资产分析与信息写入 //衍生资产分析与信息写入
const run_sub_agent_derive_assets = tool({ const run_sub_agent_derive_assets = tool({
description: "运行执行subAgent来完成衍生资产分析与信息写入相关任务", description: "运行执行subAgent来完成衍生资产分析与信息写入相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_derive_assets.md"); const skill = path.join(u.getPath("skills"), "production_execution_derive_assets.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -221,7 +219,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//衍生资产图片生成 //衍生资产图片生成
const run_sub_agent_generate_assets = tool({ const run_sub_agent_generate_assets = tool({
description: "运行执行subAgent来完成衍生资产图片生成相关任务", description: "运行执行subAgent来完成衍生资产图片生成相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_generate_assets.md"); const skill = path.join(u.getPath("skills"), "production_execution_generate_assets.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -243,7 +241,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//拍摄计划 //拍摄计划
const run_sub_agent_director_plan = tool({ const run_sub_agent_director_plan = tool({
description: "运行执行subAgent来完成导演规划相关任务", description: "运行执行subAgent来完成导演规划相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_director_plan.md"); const skill = path.join(u.getPath("skills"), "production_execution_director_plan.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -268,7 +266,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//分镜图生成 //分镜图生成
const run_sub_agent_storyboard_gen = tool({ const run_sub_agent_storyboard_gen = tool({
description: "运行执行subAgent来完成分镜图生成相关任务", description: "运行执行subAgent来完成分镜图生成相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_gen.md"); const skill = path.join(u.getPath("skills"), "production_execution_storyboard_gen.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -302,7 +300,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//分镜面板写入 //分镜面板写入
const run_sub_agent_storyboard_panel = tool({ const run_sub_agent_storyboard_panel = tool({
description: "运行执行subAgent来完成分镜面板写入相关任务", description: "运行执行subAgent来完成分镜面板写入相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_panel.md"); const skill = path.join(u.getPath("skills"), "production_execution_storyboard_panel.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -328,7 +326,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//分镜表写入 //分镜表写入
const run_sub_agent_storyboard_table = tool({ const run_sub_agent_storyboard_table = tool({
description: "运行执行subAgent来完成分镜表构建相关任务", description: "运行执行subAgent来完成分镜表构建相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_table.md"); const skill = path.join(u.getPath("skills"), "production_execution_storyboard_table.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -352,7 +350,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const run_sub_agent_supervision = tool({ const run_sub_agent_supervision = tool({
description: "运行监督层subAgent执行独立任务完成后返回结果", description: "运行监督层subAgent执行独立任务完成后返回结果",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_agent_supervision.md"); const skill = path.join(u.getPath("skills"), "production_agent_supervision.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");

View File

@ -52,7 +52,7 @@ export const flowDataSchema = z.object({
export type FlowData = z.infer<typeof flowDataSchema>; export type FlowData = z.infer<typeof flowDataSchema>;
const flowDataKeys = Object.keys(flowDataSchema.shape) as (keyof FlowData)[]; const keySchema = z.enum(Object.keys(flowDataSchema.shape) as [keyof FlowData, ...Array<keyof FlowData>]);
const flowDataKeyLabels = Object.fromEntries( const flowDataKeyLabels = Object.fromEntries(
Object.entries(flowDataSchema.shape).map(([key, schema]) => [key, (schema as z.ZodTypeAny).description ?? key]), Object.entries(flowDataSchema.shape).map(([key, schema]) => [key, (schema as z.ZodTypeAny).description ?? key]),
) as Record<keyof FlowData, string>; ) as Record<keyof FlowData, string>;
@ -69,14 +69,13 @@ export default (toolCpnfig: ToolConfig) => {
const tools: Record<string, Tool> = { const tools: Record<string, Tool> = {
get_flowData: tool({ get_flowData: tool({
description: "获取工作区数据", description: "获取工作区数据",
inputSchema: jsonSchema<{ key: keyof FlowData }>({ inputSchema: jsonSchema<{ key: keyof FlowData }>(
type: "object", z
properties: { .object({
key: { type: "string", enum: flowDataKeys as string[], description: "数据key" }, key: keySchema.describe("数据key"),
}, })
required: ["key"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ key }) => { execute: async ({ key }) => {
const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`); const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`);
console.log("[tools] get_flowData", key); console.log("[tools] get_flowData", key);
@ -89,22 +88,20 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
add_deriveAsset: tool({ add_deriveAsset: tool({
description: "新增或更新衍生资产", description: "新增或更新衍生资产",
inputSchema: jsonSchema<{ assetsId: number; id: number | null; name: string; desc: string }>({ inputSchema: jsonSchema<{ assetsId: number; id: number | null; name: string; desc: string }>(
type: "object", z
properties: { .object({
assetsId: { type: "number", description: "关联的资产ID" }, assetsId: z.number().describe("关联的资产ID"),
id: { type: ["number", "null"], description: "衍生资产ID,如果新增则为空" }, id: z.number().nullable().describe("衍生资产ID,如果新增则为空"),
name: { type: "string", description: "衍生资产名称" }, name: z.string().describe("衍生资产名称"),
desc: { type: "string", description: "衍生资产描述" }, desc: z.string().describe("衍生资产描述"),
}, })
required: ["assetsId", "id", "name", "desc"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async (raw) => { execute: async (raw) => {
// 容错LLM 偶尔传 "null" 字符串或空串,统一规范为 null // 容错LLM 偶尔传 "null" 字符串或空串,统一规范为 null
const idRaw = raw.id as unknown; const idRaw = raw.id as unknown;
const normalizedId = const normalizedId = idRaw === "null" || idRaw === "" || idRaw === undefined ? null : (idRaw as number | null);
idRaw === "null" || idRaw === "" || idRaw === undefined ? null : (idRaw as number | null);
const deriveAsset = { ...raw, id: normalizedId }; const deriveAsset = { ...raw, id: normalizedId };
const thinking = msg.thinking("正在操作资产..."); const thinking = msg.thinking("正在操作资产...");
@ -139,15 +136,14 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
del_deriveAsset: tool({ del_deriveAsset: tool({
description: "删除衍生资产", description: "删除衍生资产",
inputSchema: jsonSchema<{ assetsId: number; id: number }>({ inputSchema: jsonSchema<{ assetsId: number; id: number }>(
type: "object", z
properties: { .object({
assetsId: { type: "number", description: "关联的资产ID" }, assetsId: z.number().describe("关联的资产ID"),
id: { type: "number", description: "衍生资产ID" }, id: z.number().describe("衍生资产ID"),
}, })
required: ["assetsId", "id"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ assetsId, id }) => { execute: async ({ assetsId, id }) => {
const thinking = msg.thinking("正在操作资产..."); const thinking = msg.thinking("正在操作资产...");
const { scriptId } = resTool.data; const { scriptId } = resTool.data;
@ -162,14 +158,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
generate_deriveAsset: tool({ generate_deriveAsset: tool({
description: "生成衍生资产图片", description: "生成衍生资产图片",
inputSchema: jsonSchema<{ ids: number[] }>({ inputSchema: jsonSchema<{ ids: number[] }>(
type: "object", z
properties: { .object({
ids: { type: "array", items: { type: "number" }, description: "需要生成的 衍生资产ID" }, ids: z.array(z.number()).describe("需要生成的 衍生资产ID"),
}, })
required: ["ids"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ ids }) => { execute: async ({ ids }) => {
const thinking = msg.thinking("正在生成衍生资产..."); const thinking = msg.thinking("正在生成衍生资产...");
new Promise((resolve) => socket.emit("generateDeriveAsset", { ids }, (res: any) => resolve(res))) new Promise((resolve) => socket.emit("generateDeriveAsset", { ids }, (res: any) => resolve(res)))
@ -189,14 +184,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
generate_storyboard: tool({ generate_storyboard: tool({
description: "生成分镜图片", description: "生成分镜图片",
inputSchema: jsonSchema<{ ids: number[] }>({ inputSchema: jsonSchema<{ ids: number[] }>(
type: "object", z
properties: { .object({
ids: { type: "array", items: { type: "number" }, description: "必须获取真实的分镜ID支持批量生成" }, ids: z.array(z.number()).describe("必须获取真实的分镜ID支持批量生成"),
}, })
required: ["ids"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ ids }) => { execute: async ({ ids }) => {
const thinking = msg.thinking("正在生成分镜..."); const thinking = msg.thinking("正在生成分镜...");
new Promise((resolve) => socket.emit("generateStoryboard", { ids }, (res: any) => resolve(res))) new Promise((resolve) => socket.emit("generateStoryboard", { ids }, (res: any) => resolve(res)))

View File

@ -1,5 +1,6 @@
import { Socket } from "socket.io"; import { Socket } from "socket.io";
import { tool, jsonSchema } from "ai"; import { tool, jsonSchema } from "ai";
import { z } from "zod";
import u from "@/utils"; import u from "@/utils";
import Memory from "@/utils/agent/memory"; import Memory from "@/utils/agent/memory";
import useTools from "@/agents/scriptAgent/tools"; import useTools from "@/agents/scriptAgent/tools";
@ -132,18 +133,15 @@ function createSubAgent(parentCtx: AgentContext) {
return fullResponse; return fullResponse;
} }
const promptInput = jsonSchema<{ prompt: string }>({ const promptInput = z
type: "object", .object({
properties: { prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
prompt: { type: "string", description: "交给子Agent的任务简约描述100字以内" }, })
}, .toJSONSchema();
required: ["prompt"],
additionalProperties: false,
});
const run_sub_agent_storySkeleton = tool({ const run_sub_agent_storySkeleton = tool({
description: "运行执行subAgent来完成故事骨架相关任务", description: "运行执行subAgent来完成故事骨架相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md"); const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -163,7 +161,7 @@ function createSubAgent(parentCtx: AgentContext) {
const run_sub_agent_adaptationStrategy = tool({ const run_sub_agent_adaptationStrategy = tool({
description: "运行执行subAgent来完成改编策略相关任务", description: "运行执行subAgent来完成改编策略相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md"); const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");

View File

@ -16,7 +16,7 @@ export const planData = z.object({
export type planData = z.infer<typeof planData>; export type planData = z.infer<typeof planData>;
const planDataKeys = Object.keys(planData.shape) as (keyof planData)[]; const keySchema = z.enum(Object.keys(planData.shape) as [keyof planData, ...Array<keyof planData>]);
const planDataKeyLabels = Object.fromEntries( const planDataKeyLabels = Object.fromEntries(
Object.entries(planData.shape).map(([key, schema]) => [key, (schema as z.ZodTypeAny).description ?? key]), Object.entries(planData.shape).map(([key, schema]) => [key, (schema as z.ZodTypeAny).description ?? key]),
) as Record<keyof planData, string>; ) as Record<keyof planData, string>;
@ -33,14 +33,13 @@ export default (toolCpnfig: ToolConfig) => {
const tools: Record<string, Tool> = { const tools: Record<string, Tool> = {
get_novel_events: tool({ get_novel_events: tool({
description: "获取章节事件", description: "获取章节事件",
inputSchema: jsonSchema<{ chapterIndexs: number[] }>({ inputSchema: jsonSchema<{ chapterIndexs: number[] }>(
type: "object", z
properties: { .object({
chapterIndexs: { type: "array", items: { type: "number" }, description: "章节的编号" }, chapterIndexs: z.array(z.number()).describe("章节的编号"),
}, })
required: ["chapterIndexs"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ chapterIndexs }) => { execute: async ({ chapterIndexs }) => {
console.log("[tools] get_novel_events", chapterIndexs); console.log("[tools] get_novel_events", chapterIndexs);
const thinking = msg.thinking("正在查询章节事件..."); const thinking = msg.thinking("正在查询章节事件...");
@ -59,14 +58,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
get_planData: tool({ get_planData: tool({
description: "获取工作区数据", description: "获取工作区数据",
inputSchema: jsonSchema<{ key: keyof planData }>({ inputSchema: jsonSchema<{ key: keyof planData }>(
type: "object", z
properties: { .object({
key: { type: "string", enum: planDataKeys as string[], description: "数据key" }, key: keySchema.describe("数据key"),
}, })
required: ["key"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ key }) => { execute: async ({ key }) => {
console.log("[tools] get_planData", key); console.log("[tools] get_planData", key);
const thinking = msg.thinking(`正在获取${planDataKeyLabels[key]}工作区数据...`); const thinking = msg.thinking(`正在获取${planDataKeyLabels[key]}工作区数据...`);
@ -79,14 +77,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
get_novel_text: tool({ get_novel_text: tool({
description: "获取小说章节原始文本内容", description: "获取小说章节原始文本内容",
inputSchema: jsonSchema<{ chapterIndex: string }>({ inputSchema: jsonSchema<{ chapterIndex: string }>(
type: "object", z
properties: { .object({
chapterIndex: { type: "string", description: "章节编号" }, chapterIndex: z.string().describe("章节编号"),
}, })
required: ["chapterIndex"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ chapterIndex }) => { execute: async ({ chapterIndex }) => {
console.log("[tools] get_novel_text", "[tools] get_novel_text", chapterIndex); console.log("[tools] get_novel_text", "[tools] get_novel_text", chapterIndex);
const thinking = msg.thinking(`正在获取小说章节原文...`); const thinking = msg.thinking(`正在获取小说章节原文...`);
@ -100,14 +97,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
get_script_content: tool({ get_script_content: tool({
description: "获取剧本本内容", description: "获取剧本本内容",
inputSchema: jsonSchema<{ ids: string[] }>({ inputSchema: jsonSchema<{ ids: string[] }>(
type: "object", z
properties: { .object({
ids: { type: "array", items: { type: "string" }, description: "脚本id" }, ids: z.array(z.string()).describe("脚本id"),
}, })
required: ["ids"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ ids }) => { execute: async ({ ids }) => {
console.log("[tools] get_script_content", "[tools] get_script_content", ids); console.log("[tools] get_script_content", "[tools] get_script_content", ids);
const thinking = msg.thinking(`正在获取脚本内容...`); const thinking = msg.thinking(`正在获取脚本内容...`);

View File

@ -22,40 +22,37 @@ export default router.post(
.whereIn("id", assetsIds) .whereIn("id", assetsIds)
.andWhere("projectId", projectId) .andWhere("projectId", projectId)
.select("id", "name", "describe"); .select("id", "name", "describe");
console.log("%c Line:20 🍎 assetsData", "background:#b03734", assetsData);
const audioData = await u.db("o_assets").where("type", "audio").whereNull("assetsId").andWhere("projectId", projectId).select("id", "name", "describe"); const audioData = await u
console.log("%c Line:26 🍋 audioData", "background:#ea7e5c", audioData); .db("o_assets")
.where("type", "audio")
.whereNull("assetsId")
.andWhere("projectId", projectId)
.select("id", "name", "describe");
async function processGroup() { async function processGroup() {
try { try {
const resultTool = tool({ const resultTool = tool({
description: "返回结果时必须调用这个工具", description: "返回结果时必须调用这个工具",
inputSchema: jsonSchema<{ result: { id: number; audioIds: number[] }[] }>({ inputSchema: jsonSchema<{ result: { id: number; audioIds: number[] }[] }>(
type: "object", z
properties: { .object({
result: { result: z
type: "array", .array(
description: "适配的音色列表id为资产idaudioIds为适配的音频id 无适配内容可以为 空数组", z.object({
items: { id: z.number(),
type: "object", audioIds: z.array(z.number()).describe("适配的音频id 无适配内容可以为 空数组"),
properties: { }),
id: { type: "number" }, )
audioIds: { type: "array", items: { type: "number" }, description: "适配的音频id 无适配内容可以为 空数组" }, .describe("适配的音色列表id为资产idaudioIds为适配的音频id 无适配内容可以为 空数组"),
}, })
required: ["id", "audioIds"], .toJSONSchema(),
additionalProperties: false, ),
},
},
},
required: ["result"],
additionalProperties: false,
}),
execute: async ({ result }) => { execute: async ({ result }) => {
console.log("[tools] extractAssets result", result); console.log("[tools] extractAssets result", result);
for (const item of result) { for (const item of result) {
await u.db("o_assetsRole2Audio").where("assetsRoleId", item.id).delete() await u.db("o_assetsRole2Audio").where("assetsRoleId", item.id).delete();
if (item.audioIds.length) if (item.audioIds.length)
await u.db("o_assetsRole2Audio").insert(item.audioIds.map(i => ({ assetsRoleId: item.id, assetsAudioId: i }))) await u.db("o_assetsRole2Audio").insert(item.audioIds.map((i) => ({ assetsRoleId: item.id, assetsAudioId: i })));
} }
return "无需回复用户任何内容"; return "无需回复用户任何内容";
}, },
@ -69,21 +66,20 @@ export default router.post(
}, },
{ {
role: "user", role: "user",
content: `音频内容:${audioData.map(i => `Id:${i.id},音色名称:${i.name},描述:${i.describe}`).join("\n")}\n\n content: `音频内容:${audioData.map((i) => `Id:${i.id},音色名称:${i.name},描述:${i.describe}`).join("\n")}\n\n
${assetsData.map(i => `ID:${i.id},名称:${i.name},描述:${i.describe}`).join("\n")}\n\n ${assetsData.map((i) => `ID:${i.id},名称:${i.name},描述:${i.describe}`).join("\n")}\n\n
`, `,
}, },
], ],
tools: { resultTool }, tools: { resultTool },
}); });
console.log("%c Line:44 🍞 text", "background:#f5ce50", text); console.log("%c Line:44 🍞 text", "background:#f5ce50", text);
} catch (e) { } catch (e) {
console.error(`提取失败:`, e); console.error(`提取失败:`, e);
return; return;
} }
} }
await processGroup() await processGroup();
res.status(200).send(success()); res.status(200).send(success());
}, },
); );

View File

@ -131,10 +131,7 @@ export default router.post(
} }
// 去重:相同 scriptId + assetId 只保留一条 // 去重:相同 scriptId + assetId 只保留一条
const uniqueRows = [ const uniqueRows = [...new Map(scriptAssetRows.map((r) => [`${r.scriptId}_${r.assetId}`, r])).values()];
...new Map(scriptAssetRows.map((r) => [`${r.scriptId}_${r.assetId}`, r])).values(),
];
// 先删除本批 scriptId 的旧关联,再插入新的 // 先删除本批 scriptId 的旧关联,再插入新的
await u.db("o_scriptAssets").whereIn("scriptId", batchScriptIds).delete(); await u.db("o_scriptAssets").whereIn("scriptId", batchScriptIds).delete();
@ -188,43 +185,19 @@ export default router.post(
try { try {
const resultTool = tool({ const resultTool = tool({
description: "返回结果时必须调用这个工具", description: "返回结果时必须调用这个工具",
inputSchema: jsonSchema<{ newAssets: NewAsset[]; existingAssetRefs: ExistingAssetRef[] }>({ inputSchema: jsonSchema<{ newAssets: NewAsset[]; existingAssetRefs: ExistingAssetRef[] }>(
type: "object", z
properties: { .object({
newAssets: { newAssets: z
type: "array", .array(NewAssetSchema)
description: "新发现的资产列表(不在已有资产列表中的),需要完整的 prompt、name、desc、type 和使用该资产的 scriptIds", .describe("新发现的资产列表(不在已有资产列表中的),需要完整的 prompt、name、desc、type 和使用该资产的 scriptIds"),
items: { existingAssetRefs: z
type: "object", .array(ExistingAssetRefSchema)
properties: { .describe("已有资产的引用列表(在已有资产列表中已存在的),只需给出资产名称和使用该资产的 scriptIds"),
name: { type: "string", description: "资产名称,仅为名称不做其他任何表述" }, })
desc: { type: "string", description: "资产描述" }, .toJSONSchema(),
type: { type: "string", enum: ["role", "tool", "scene"], description: "资产类型" }, ),
scriptIds: { type: "array", items: { type: "number" }, description: "使用该资产的剧本id数组" },
},
required: ["name", "desc", "type", "scriptIds"],
additionalProperties: false,
},
},
existingAssetRefs: {
type: "array",
description: "已有资产的引用列表(在已有资产列表中已存在的),只需给出资产名称和使用该资产的 scriptIds",
items: {
type: "object",
properties: {
name: { type: "string", description: "已有资产的名称,必须与已有资产列表中的名称完全一致" },
scriptIds: { type: "array", items: { type: "number" }, description: "使用该资产的剧本id数组" },
},
required: ["name", "scriptIds"],
additionalProperties: false,
},
},
},
required: ["newAssets", "existingAssetRefs"],
additionalProperties: false,
}),
execute: async ({ newAssets, existingAssetRefs }) => { execute: async ({ newAssets, existingAssetRefs }) => {
if (newAssets?.length) collectedNew = newAssets; if (newAssets?.length) collectedNew = newAssets;
if (existingAssetRefs?.length) collectedExisting = existingAssetRefs; if (existingAssetRefs?.length) collectedExisting = existingAssetRefs;
return "无需回复用户任何内容"; return "无需回复用户任何内容";

View File

@ -57,14 +57,13 @@ export default router.post(
const getWeatherTool = tool({ const getWeatherTool = tool({
description: "Get the weather in a location", description: "Get the weather in a location",
inputSchema: jsonSchema<{ location: string }>({ inputSchema: jsonSchema<{ location: string }>(
type: "object", z
properties: { .object({
location: { type: "string", description: "The location to get the weather for" }, location: z.string().describe("The location to get the weather for"),
}, })
required: ["location"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ location }) => { execute: async ({ location }) => {
return { return {
location, location,

View File

@ -3,6 +3,7 @@ import { v4 as uuidv4 } from "uuid";
import { getEmbedding, cosineSimilarity } from "./embedding"; import { getEmbedding, cosineSimilarity } from "./embedding";
import type { memories as MemoryRow } from "@/types/database"; import type { memories as MemoryRow } from "@/types/database";
import { tool, jsonSchema } from "ai"; import { tool, jsonSchema } from "ai";
import { z } from "zod";
// ── 可调配置默认值 ── // ── 可调配置默认值 ──
const DEFAULTS: { const DEFAULTS: {
@ -200,14 +201,13 @@ class Memory {
return { return {
deepRetrieve: tool({ deepRetrieve: tool({
description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具", description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具",
inputSchema: jsonSchema<{ keyword: string }>({ inputSchema: jsonSchema<{ keyword: string }>(
type: "object", z
properties: { .object({
keyword: { type: "string", description: "要检索的关键词" }, keyword: z.string().describe("要检索的关键词"),
}, })
required: ["keyword"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ keyword }) => { execute: async ({ keyword }) => {
const results = await this.deepRetrieve(keyword); const results = await this.deepRetrieve(keyword);
if (results.length === 0) return { found: false, message: "未找到相关记忆" }; if (results.length === 0) return { found: false, message: "未找到相关记忆" };

View File

@ -1,3 +1,4 @@
import { z } from "zod";
import { tool, jsonSchema } from "ai"; import { tool, jsonSchema } from "ai";
import path from "path"; import path from "path";
import isPathInside from "is-path-inside"; import isPathInside from "is-path-inside";
@ -184,14 +185,13 @@ export function createSkillTools(skills: { name: string; description: string }[]
return { return {
activate_skill: tool({ activate_skill: tool({
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skillNames.join(", ")}`, description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skillNames.join(", ")}`,
inputSchema: jsonSchema<{ name: string }>({ inputSchema: jsonSchema<{ name: string }>(
type: "object", z
properties: { .object({
name: { type: "string", enum: skillNames, description: "要激活的技能名称" }, name: z.enum(skillNames as [string, ...string[]]).describe("要激活的技能名称"),
}, })
required: ["name"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ name }) => { execute: async ({ name }) => {
if (activated.has(name)) { if (activated.has(name)) {
console.log(`⚡[主技能] 技能 "${name}" 已激活,跳过重复注入`); console.log(`⚡[主技能] 技能 "${name}" 已激活,跳过重复注入`);
@ -226,14 +226,13 @@ export function createSkillTools(skills: { name: string; description: string }[]
}), }),
read_skill_file: tool({ read_skill_file: tool({
description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。", description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。",
inputSchema: jsonSchema<{ filePath: string }>({ inputSchema: jsonSchema<{ filePath: string }>(
type: "object", z
properties: { .object({
filePath: { type: "string", description: "资源文件的相对路径,来自 activate_skill 返回的 skill_resources" }, filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"),
}, })
required: ["filePath"], .toJSONSchema(),
additionalProperties: false, ),
}),
execute: async ({ filePath }) => { execute: async ({ filePath }) => {
const normalizedInputPath = toUnixPath(filePath).trim(); const normalizedInputPath = toUnixPath(filePath).trim();
if (!normalizedInputPath) { if (!normalizedInputPath) {