首页
/ Directus trigger-flow AI 工具:程序化触发 Flows 的参数契约、校验规则与执行管线深度解析

Directus trigger-flow AI 工具:程序化触发 Flows 的参数契约、校验规则与执行管线深度解析

2026-09-05 14:39:37作者:牧宁李

本文围绕 Directus 内置 AI 工具 trigger-flow 的设计文档 prompt.md 展开,完整继承其中的参数说明、流程类型、校验规则与常见工作流,并结合 工具实现Zod 参数 SchemaFlowManager 源码,解析该工具从参数校验到流程执行的完整调用链。读完后,你将掌握:AI 工具如何安全地以编程方式触发 Directus 手动流程、参数如何映射到流程内部的 $trigger 上下文,以及如何避开文档列出的六类常见错误。

1. trigger-flow 在 Directus AI 工具体系中的定位

trigger-flow 是 Directus 内置 AI 能力(工具注册表)中的一个工具,用于"程序化执行 Flows":触发手动流程、向流程传参、并将多个流程串联成复杂自动化。该文档 prompt.md 并非普通的说明文档,而是工具运行时加载的指令文本:在 trigger-flow/index.ts 中,工具通过 requireText(resolve(__dirname, './prompt.md')) 将该文件内容挂到工具的 instructions 字段上,供大模型在调用前理解参数约束。

从源码结构看,工具通过 defineTool 定义,核心元数据为:

  • name: 'trigger-flow';
  • description: 'Runs an active manual Directus flow. Use when the user asks to trigger, execute, or test an existing manual flow.';
  • keywords: ['run flow', 'execute flow', 'manual flow', 'trigger automation', 'webhook flow'],用于工具的关键词检索;
  • annotations.title: 'Directus - Trigger Flow'

它与其他内置工具一起被汇总进 ALL_TOOLS,由 ToolRegistry 统一挂载:模型先通过根工具 search 检索并加载工具详情,再经 execute 分发到具体工具的 handler。值得注意的是,trigger-flow 没有标记 admin: true(可对比 flows 工具admin: true),即非管理员账户在可见性过滤(#isToolVisible)通过后也能使用该工具,但后续执行仍受 Directus 权限体系约束。

2. 关键概念:文档定义的四条核心原则

原文档在 "Key Concepts" 一节给出了使用该工具前必须建立的四条认知,这也是整个工具的调用哲学:

  1. 前置条件(Prerequisite):触发前必须先用 flows 工具读取流程的完整定义(flows.read),因为后续所有校验(集合范围、必填字段)都依赖这份定义;
  2. 手动流程(Manual Flows):trigger: "manual" 的流程专为 UI 或本工具触发而设计;
  3. 流程串联(Flow Chaining):任意流程都可以被触发,数据通过 $trigger.body 接收,从而支持流程嵌套调用;
  4. 校验(Validation):工具会校验集合支持性(collection 是否在流程的 options.collections 内)、必填字段、以及选择要求(requireSelection)。

3. 参数契约:文档参数表与真实 Zod Schema 对照

3.1 文档中的参数说明

原文档给出的概念性参数结构如下:

{
	"flowDefinition": {}, // FULL flow object from flows.read
	"flowId": "uuid", // Flow ID to trigger
	"collection": "name", // Collection context
	"keys": ["id1"], // Item IDs (required if flow needs selection)
	"method": "POST", // GET or POST (default: GET)
	"data": {}, // Optional payload data
	"query": {}, // Optional query parameters
	"headers": {} // Optional headers
}

3.2 实际生效的输入 Schema

需要特别指出的是,真正被注册表解析、校验的契约schema.ts 中的两个 Zod Schema 为准:

export const TriggerFlowInputSchema = z.object({
	id: PrimaryKeyInputSchema,          // number | string,流程主键
	collection: z.string(),             // 必填,集合上下文
	keys: z.array(PrimaryKeyInputSchema).optional(),
	headers: z.record(z.string(), z.any()).optional(),
	query: z.record(z.string(), z.any()).optional(),
	data: z.record(z.string(), z.any()).optional(),
});

export const TriggerFlowValidateSchema = z.strictObject({ /* 同上,strictObject 禁止多余字段 */ });

即实际必填参数只有 id(流程主键)和 collection 两个,keysheadersquerydata 均可选。注册表在执行前会用 validateSchema.safeParse 做严格解析(#parseInput),多余字段会直接报 InvalidPayloadError——这正对应文档中"Common Mistakes"第 6 条:不要传字符串化的 JSON,要用原生对象(dataheadersquery 都必须是对象)。

3.3 文档与实现的差异点(重要)

将文档参数表与 handler 实现 对照后,存在三处需要在实操中注意的差异:

文档概念参数 实际实现 说明
flowId id Schema 字段名为 id,类型为 number | string
flowDefinition 不传 工具 handler 内部会用 FlowsService.readOne 自行按 id 读取流程,并强制过滤 status: 'active'trigger: 'manual'
method: GET/POST 固定 POST handler 调用 flowManager.runWebhookFlowmethod 恒为 'POST'

"先用 flows 工具读取流程定义"这一要求,在实现层面的对应是:工具自身也会通过 flowsService.readOne(args.id, { filter: { status: { _eq: 'active' }, trigger: { _eq: 'manual' } } }) 重新取一次流程(用于提取必填字段),而不依赖模型传入的完整定义对象。因此文档中"ALWAYS read the flow first"更多是面向模型的编排纪律:只有先读取过定义,模型才知道该传哪些 keysdata 字段。

4. 三类流程的调用示例(继承原文档)

4.1 需要选择项的手动流程(requireSelection: true 或未设置)

{
	"flowDefinition": {
		"id": "abc-123",
		"trigger": "manual",
		"options": {
			"collections": ["products", "orders"],
			"requireSelection": true,
			"fields": [
				{ "field": "reason", "name": "Reason", "meta": { "required": true } }
			]
		}
	},
	"flowId": "abc-123",
	"collection": "products",
	"keys": ["prod-1", "prod-2"], // REQUIRED
	"data": { "reason": "Bulk update" } // Required field
}

4.2 无需选择项的手动流程(requireSelection: false)

{
	"flowDefinition": {
		"id": "xyz-456",
		"trigger": "manual",
		"options": { "collections": ["reports"], "requireSelection": false }
	},
	"flowId": "xyz-456",
	"collection": "reports",
	"keys": [], // requireSelection: false 时可为空
	"data": { "type": "monthly" }
}

4.3 Webhook / Operation 触发型流程

{
	"flowDefinition": {
		"id": "webhook-flow",
		"trigger": "webhook",
		"options": { "collections": ["*"] }
	},
	"flowId": "webhook-flow",
	"collection": "any_collection",
	"method": "POST",
	"data": { "custom": "payload" },
	"headers": { "X-Custom-Header": "value" }
}

从源码结构看,Webhook 流程的真实触发入口是 REST 路由 controllers/flows.ts 中的 GET/POST /trigger/:pk,而 AI 工具 handler 读取流程时带了 trigger: { _eq: 'manual' } 过滤条件,即经由 trigger-flow 工具实际执行的是 active 状态的手动流程;文档中 Webhook 示例应理解为对"流程触发模型"的完整展示(说明任意触发类型的流程都可被程序化触达,数据都汇入 $trigger)。

4.4 服务端校验链:文档规则与源码逐条对应

文档列出的 5 条校验规则中,前 4 条由服务端执行,第 5 条是工具层附加校验。以 FlowManager.load 中注册的手动触发 handler 为例,逐条印证:

  1. 集合支持性:enabledCollections 为空或 targetCollection 不在其内 → 抛 ForbiddenError(源码中即 Specified collection must be one of: ... 警告 + 403);
  2. 选择要求:requireSelection 取值逻辑为 flow.options?.['requireSelection'] ?? true,即未显式设置为 false 时一律视为需要选择(这正是文档"Common Mistakes"第 5 条"不要假设 requireSelection,要显式检查"的依据);要求选择时,keys 必须是数组且非空,否则 403;
  3. 认证要求:isUnauthenticated(accountability) 为真时直接拒绝——手动流程仅允许认证用户触发;
  4. 权限要求:非管理员(accountability.admin === false)需通过策略/权限检查获取目标集合的 read 权限,并且会用 getService(targetCollection).readMany(targetKeys) 回读所有传入的 keys,只要有一个 key 不可见就整体拒绝(防止借流程枚举不可见数据);
  5. 必填字段:工具层在 index.ts 中从 flow.options.fields 里挑出 meta.required === true 的字段,逐一检查 args.data 是否包含该 key,缺失则抛 InvalidPayloadError({ reason: 'Required field "xxx" is missing' })

5. 执行管线:从工具调用到 runWebhookFlow

工具 handler 的执行路径可概括为三步(源码):

第一步:读取流程并做必填字段预检

const flowsService = new FlowsService({ schema, accountability });
const flow = await flowsService.readOne(args.id, {
	filter: { status: { _eq: 'active' }, trigger: { _eq: 'manual' } },
	fields: ['options'],
});

第二步:组装伪请求并复用 Webhook 执行通道

const { result } = await flowManager.runWebhookFlow(
	`POST-${args.id}`,
	{
		path: `/trigger/${args.id}`,
		query: args.query ?? {},
		method: 'POST',
		body: { collection: args.collection, keys: args.keys, ...(args.data ?? {}) },
		headers: args.headers ?? {},
	},
	{ accountability, schema },
);

这里的关键设计是:手动流程与 Webhook 流程共享同一条执行通道FlowManager.load 中,manual 触发型的 handler 以 `POST-${flow.id}` 为键注册进 webhookFlowHandlers,与 webhook 触发型的 `${method}-${flow.id}` 并存。runWebhookFlow(见 flows.ts#L134-L151)先等待 reloadQueue 空闲(保证流程定义热加载完成),再按 `${method}-${id}` 查找 handler;找不到则记日志并抛 ForbiddenError。注意 body 的组装方式:collectionkeys 被提升到 body 顶层,data 展开后与它们平级——这直接决定了流程内部如何取数(下一节)。

第三步:返回结果

handler 最终以 { type: 'text', data: result } 形式返回,其中 resultexecuteFlow 根据 flow.options['return'] 决定:manual 流程默认 return = '$last'(加载时补写,见 flows.ts#L359-L360),'$all' 返回完整的 keyedData,其他表达式用 micromustache 的 get 从上下文取值。若流程开启 async,则立即返回 undefined 结果;若 error_on_reject === true 且最后一步被 reject,则把最后结果作为错误抛出。

6. 被触发流程中的数据访问

流程执行时,executeFlow 构造了如下上下文变量:

const keyedData: Record<string, unknown> = {
	[TRIGGER_KEY]: data,           // $trigger
	[LAST_KEY]: data,              // $last
	[ACCOUNTABILITY_KEY]: context?.['accountability'] ?? null, // $accountability
	[ENV_KEY]: this.envs,          // $env(受 FLOWS_ENV_ALLOW_LIST 白名单控制)
};

结合第 5 节的 body 组装方式,文档中"Data Access in Triggered Flow"一节的映射关系可精确落位:

流程内变量 来源(工具参数)
$trigger.body data 参数的展开字段,以及顶层的 collectionkeys
$trigger.body.collection collection 参数
$trigger.body.keys keys 参数
$trigger.query query 参数
$trigger.headers headers 参数
$trigger.method / $trigger.path 固定为 POST/trigger/<flowId>
$accountability 当前用户/权限上下文,由注册表注入
$last 每执行完一个 operation 即更新为该步骤输出,可用于跨步骤引用
$env 白名单内的环境变量

文档还提到:若 data 传入数组,流程会对数组每一项各执行一次(For-Each 语义);流程可通过 trigger 的 return 选项返回数据;权限上遵循流程的 accountability 配置(源码印证:flow.accountability !== null 时写入 Activity 记录,为 'all' 时再写 Revisions,见 flows.ts#L428-L483)。

7. 常见工作流(继承原文档三个实战场景)

7.1 导出选中项(Export Selected Items)

// Step 1: 先用 flows 工具拿流程定义
flows.read({ filter: { name: { _eq: "Export Items" } } })

// Step 2: 带选择触发
{
	"collection": "products",
	"keys": ["1", "2", "3"],
	"data": { "format": "csv", "email": "user@example.com" }
}

7.2 无选择批量处理(Process Batch Without Selection)

{
	"collection": "orders",
	"keys": [], // 流程 requireSelection: false 时传空数组
	"data": { "status": "pending", "date_range": "last_30_days" }
}

7.3 流程串联(Chain Flows Together)

{
	"collection": "notifications",
	"data": {
		"parent_result": "{{ $last }}", // 引用父流程最后一步的输出
		"step": 2
	}
}

串联的本质:父流程的某个 operation(如 trigger 类型)调用子流程,子流程的 $trigger.body 即父流程注入的数据,$last 提供跨步骤引用能力。

8. 重要注意事项与常见错误(继承原文档)

Important Notes:

  • 先读后触发:触发前务必读取流程定义,确认 options.collectionsoptions.fieldsrequireSelection;
  • 通配集合:collections 中的 "*" 表示接受任意集合(服务端对 manual 流程实际校验 enabledCollections.includes(targetCollection),通配需由流程配置显式支持);
  • 必填字段:检查 options.fieldsmeta.required 的输入项;
  • For-Each:data 传数组时流程对每项各执行一次;
  • 返回值:由 trigger 的 return 选项决定,manual 流程默认 $last;
  • 权限:遵循流程 accountability 配置与用户策略/权限。

六类常见错误(原文档 "Common Mistakes" 全量继承):

  1. 不先读取流程定义就触发;
  2. 流程需要选择时遗漏 keys;
  3. 忽略流程配置中的必填字段;
  4. 使用了不在流程 collections 列表中的集合;
  5. 主观假设 requireSelection 的取值而不显式检查;
  6. 传递字符串化的 JSON 而非原生对象。

9. 测试用例提供的行为佐证

index.test.ts 为该工具提供了可验证的行为基线:

  • 最小参数调用:仅传 { id: 'flow-123', collection: 'articles' } 时,断言 runWebhookFlow 收到的正是 POST-flow-123path: /trigger/flow-123method: 'POST'query: {}headers: {} 以及 body 仅含 collection(见 index.test.ts#L43-L78);
  • 错误传播:FlowsService.readOne 抛出 Forbidden(如无读权限或流程非 active/manual)时,工具原样向调用方传播该错误;
  • 必填字段缺失:options.fieldsrequired: true 字段而 data 未提供时,断言抛出 Invalid payload. Required field "title" is missing.;多字段场景只校验并报告第一个缺失项;
  • 工具配置:断言工具名为 trigger-flow、非 admin 工具(admin 未定义)、具有描述与输入/校验 Schema。

10. 决策树:触发前的检查顺序

原文档末尾给出的决策树与源码校验顺序完全一致,可作为实操检查单:

1. 用 flows 工具读取流程定义
2. 检查触发类型:
   - manual → 检查 requireSelection
   - webhook/operation → keys 可选
3. 校验 collection 是否在 flow.options.collections 内
4. 若 requireSelection !== false → keys 必填且非空
5. 检查 flow.options.fields 中的必填数据字段
6. 以全部通过校验的参数发起触发

适用前提与限制:本文结论基于当前仓库版本的实现——工具经 FlowsService 强制过滤 status: 'active'trigger: 'manual',执行通道复用 POST-${flowId} 的 webhook handler,参数契约以 TriggerFlowInputSchema/TriggerFlowValidateSchema 为准;文档中 flowDefinition/flowId/method 等概念化参数名用于指导模型编排,实际调用时请使用第 3.2 节的真实字段。

登录后查看全文
热门项目推荐
相关项目推荐