首页
/ Directus AI Tools 之 Folders 工具:让 Agent 安全地对 Directus 文件目录做 CRUD 的实现解析

Directus AI Tools 之 Folders 工具:让 Agent 安全地对 Directus 文件目录做 CRUD 的实现解析

2026-09-05 22:15:57作者:宣海椒Queenly

本篇围绕 Directus 仓库中 AI 工具集的 folders 提示词文档(api/src/ai/tools/folders/prompt.md)展开,完整继承该文档定义的四类操作(create / read / update / delete)、关键注意事项与常见错误,并结合其背后的工具定义、Zod 输入/校验 Schema 与 FoldersService 源码,讲清这个"文件夹 CRUD 工具"如何被 AI Agent 调用、如何被权限体系约束,以及写自定义 Directus 工具或对接 MCP 时可以直接复用的设计模式。

一、这份 prompt.md 是什么:AI 工具说明书的"说明书"

在 Directus 的 AI 工具目录 api/src/ai/tools/ 下,每个工具都由三部分组成:

  • prompt.md:写给 LLM 的自然语言指令,说明"这个工具能做什么、怎么调用、有哪些坑";
  • index.ts:工具的 TypeScript 定义,声明输入/输出 Schema 与真正的执行逻辑(handler);
  • index.test.ts:对 handler 各分支行为的单元测试。

folders 工具的提示词正是 prompt.md。它在运行时会被读进工具定义中——在 folders/index.ts 里可以看到这一行:

instructions: requireText(resolve(__dirname, './prompt.md')),

也就是说,prompt.md 不是给人看的文档,而是随工具一起注入给模型的 instructions:模型决定调用 folders 工具时,这份说明就构成它的"操作手册"。这也是理解本文所有内容的视角——文档中的每一句话,最终都要在 index.ts 的 Schema 校验与 handler 逻辑里兑现

工具的整体注册位置在 ai/tools/index.tsALL_TOOLS 数组中,foldersfilesitemsassets 等并列,构成 Directus 内置 AI 能力(AI Chat / MCP)可用的工具集。

二、继承自原文档:Folders 工具的能力面与调用格式

2.1 工具定位

原文档第一句即给出定位:

Perform CRUD operations on Directus Folders. Folders are used to organize files in Directus.

对应在源码中,工具注册时的 description 与检索 keywords 分别是(见 folders/index.ts):

description:
  'Reads and changes Directus file folders. Use to organize files into folder hierarchies or inspect existing folder records.',
keywords: ['directories', 'media folders', 'file organization', 'parent folder'],

keywords 用于帮助路由/检索逻辑把用户意图("整理媒体目录"、"建个父文件夹")匹配到该工具;annotations.destructiveHint: true 则显式标记本工具包含破坏性操作(delete),供上层决定是否做二次确认。

2.2 四个可用动作

原文档列出的 Available Actions 与源码中 FoldersValidateSchema(一个按 action 字段区分的 discriminated union,见 folders/index.ts)一一对应:

action 原文档说明 源码中的必填/可选参数(Zod 定义)
create Add new folders records data 必填:单个 folder 对象或数组;query 可选
read List/query metadata or get specific items by ID keys 可选(主键数组);query 可选
update Modify existing metadata data 必填(单个对象,批量时为数组走 batch 分支);keys 可选;query 可选
delete Remove folders by keys keys 必填,且必须是数组

注意 z.strictObject 的用法:校验层使用严格对象,未声明的字段会直接拒绝,这保证了模型传入多余字段时会被拦截而不是静默忽略。

2.3 创建文件夹的调用示例

原文档给出的标准调用格式如下:

{
	"action": "create",
	"data": {
		"name": "Product Images",
		"parent": "parent-folder-uuid"
	}
}

其中 data 的对象结构由 ai/tools/schema.ts 中的 FolderItemInputSchema 定义:

export const FolderItemInputSchema = z.object({
	id: PrimaryKeyInputSchema.optional(),   // 可选,显式指定主键
	name: z.string(),                        // 必填,文件夹名称
	parent: z.string().optional(),          // 可选,父文件夹 UUID
});

结合 folders/index.ts 的 create 分支,完整调用链是:

  1. buildSanitizedQueryFromArgs 先对可选的 query 做权限化清洗(fields 缺省为 '*');
  2. toArray(args.data) 把单个对象或数组统一成数组,交给 service.createMany(data)
  3. 再用返回的 savedKeysservice.readMany(savedKeys, sanitizedQuery) 读回刚创建的记录返回给模型。

也就是说 create 的返回值不是主键,而是完整的文件夹记录(或在无读权限时为 null)——这一点在测试 folders/index.test.ts 中被精确验证:createMany 返回 ['folder-1'],而工具最终输出的是 [{ id: 'folder-1', name: 'test-folder' }]

三、逐动作拆解:handler 的分支逻辑与返回约定

folders 工具的 handler(folders/index.ts)按 action 分四支,全部委托给 FoldersService。逐一拆解如下。

3.1 read:keys 优先,否则按 query 查

if (args.keys) {
	result = await service.readMany(args.keys, sanitizedQuery);
} else {
	result = await service.readByQuery(sanitizedQuery);
}
  • 传了 keys 就走主键精确读取(readMany);
  • 不传 keys 就退化为按 query 全量/条件查询(readByQuery),此时 query 支持 Directus 标准查询参数(filter、sort、limit、fields 等),经 sanitizeQuery 清洗后执行。

readOnly 属性也被显式声明为 input.action === 'read',上层可据此对 read 动作放宽确认策略,而 create/update/delete 则会被视为写操作。

3.2 update:三种定位路径,按参数形态自动切换

update 分支是四个动作里最有信息量的一个(folders/index.ts):

if (Array.isArray(args.data)) {
	updatedKeys = await service.updateBatch(args.data);        // ① data 是数组 → 逐条批量更新
} else if (args.keys) {
	updatedKeys = await service.updateMany(args.keys, args.data); // ② 有 keys → 用同一份 data 更新多条
} else {
	updatedKeys = await service.updateByQuery(sanitizedQuery, args.data); // ③ 都没有 → 按 query 条件更新
}

三个分支的触发条件互斥且可预测:

传参形态 调用的服务方法 适用场景
data 为数组(每项带 id updateBatch 一次改多个不同字段/不同目标
data 为对象 + keys updateMany 同一份改动应用到多个文件夹
data 为对象,无 keys,配 query updateByQuery 条件式更新,如"把所有 name 含 X 的文件夹改名"

更新完成后同样会 readMany(updatedKeys, sanitizedQuery) 读回结果。测试文件 folders/index.test.ts 对这三条分支各写了一个用例,并断言"只命中目标分支、其余分支方法未被调用",是核对分支边界最可靠的依据。

3.3 delete:必须提供 keys 数组

delete 分支最简:

const deletedKeys = await service.deleteMany(args.keys);

FoldersValidateSchema 中 delete 的 keys必填的 z.array(PrimaryKeyValidateSchema)folders/index.ts)——这正是原文档"Important Notes"与"常见错误"反复强调"keys 必须是数组"的原因:Schema 层就不接受字符串。返回值是被删除的主键列表(见 测试用例)。

此外 handler 末尾有兜底:

throw new Error('Invalid action.');

由于 TypeScript 类型收窄,理论上不可达,但测试仍显式验证了它对非法 action 的拒绝行为(测试)。

四、原文档 Important Notes 的源码级验证

原文档有三条 Important Notes,逐条都能在源码中找到对应证据。

4.1 "Folders are virtual":目录不存在于存储适配器

Folders are not mirrored with the storage adaptor, only stored in the database.

从源码结构看,FoldersService 继承自 ItemsService<Folder> 并固定绑定系统集合 directus_foldersservices/folders.ts):

export class FoldersService extends ItemsService<Folder> {
	constructor(options: AbstractServiceOptions) {
		super('directus_folders', options);
	}
}

它的所有读写都是数据库层面的 CRUD,与 StorageDriver(S3/GCS/本地盘等)完全解耦。对使用者的实际含义:删除/移动文件夹不会触碰底层文件,文件对象的 folder 外键关系由数据层维护。

4.2 "Permissions":访问控制由 accountability 贯穿

工具 handler 拿到调用上下文后,把 schemaaccountability 原样注入服务:

const service = new FoldersService({ schema, accountability });

随后每一个 readByQuery/readMany/updateByQuery 等调用都在该 accountability 下执行;buildSanitizedQueryFromArgs 内部的 sanitizeQuery 也会基于 schema 与 accountability 过滤模型可能越权请求的字段(ai/tools/utils.ts)。因此原文档那句"If you don't see something that the user says you should have access to, it could be a permissions issue"不是空话——create 后 readMany 读不回来、返回 null,就是权限过滤的典型表现(边缘用例 should handle null result from readMany after create 专门覆盖了这一情形)。

补充一个旁证:FoldersService.buildTree 中对非 admin 用户会先对根目录做 validateAccess 的 read 权限检查,再逐层构建目录树(services/folders.ts),说明整个 folders 域对权限约束是一致的。

4.3 "Folder Hierarchy":删除受父子层级约束

Deleting a folder requires it to be empty or will cascade based on settings

文件夹树由 parent 字段(指向另一条 directus_folders 记录的 UUID)构成。buildTree 中可见 parent → children 的索引构建逻辑(services/folders.ts)。AI 工具侧没有暴露级联策略参数,因此对 Agent 的实际建议就是原文档给的:删除前先确认目录为空,层级冲突与级联行为交由数据库层/权限层裁决,工具只负责透传错误。

五、原文档 Mistakes to Avoid:两条易错点对照 Schema

原文档末尾列出两条常见错误,它们恰好都能映射到 Schema 定义上。

  1. keys 期望数组,即使只有一项。 FoldersValidateSchemaFoldersInputSchema 中 keys 一律声明为 z.array(PrimaryKeyValidateSchema)folders/index.ts 对应的定义见 folders/index.ts)。传字符串 "folder-1" 会在校验阶段失败,正确写法是 ["folder-1"]

  2. Tags 必须是数组["tag1", "tag2"] 而非 "tag1, tag2"。 这条针对的是模型把列表"压扁成逗号分隔字符串"的常见幻觉。从源码结构看,FolderItemInputSchema 当前只声明了 id/name/parent 三个字段,name 为必填字符串(ai/tools/schema.ts);原文档 update 说明中提到的 title/description/tags 等元数据字段属于该集合在库层面的完整字段面,若启用则必须遵守"数组即数组"的 JSON 形态,Zod 校验层对错误形态会直接拒收。

另外值得注意一条文档未明说但源码保证的健壮性:模型经常把 data/keys/query 传成字符串化的 JSON,工具框架提供了 coerceJsonFields 在验证前把这些字段尝试 parseJSON 还原(ai/tools/utils.ts),降低了一次因序列化形态错误导致的整次调用失败。

六、输入/输出 Schema 与工具元信息速查

把散落在各处的定义汇总成一张速查表,便于二次开发或对接时引用:

定义位置 内容
工具名 folders/index.ts folders
输入 Schema folders/index.ts action 枚举 + 可选 query / keys 数组 / data 数组
校验 Schema folders/index.ts action 的 discriminated union,strictObject 严格模式
输出 Schema folders/index.ts data 为 folder 记录数组、主键数组或 null
只读判定 folders/index.ts action === 'read' 时为只读
破坏性标记 folders/index.ts destructiveHint: true
底层服务 services/folders.ts FoldersService extends ItemsService<Folder>,绑定 directus_folders 集合
工具注册 ai/tools/index.ts ALL_TOOLS 数组第 5 项

七、测试即契约:用 index.test.ts 校验行为

folders/index.test.ts 通过 vi.mock 替换 FoldersService,把 handler 的每种分支固化为可回归的断言,覆盖点包括:

  • create 单条/多条,验证 createMany 收到的始终是数组([folderData]),且随后用返回主键调 readManyL42-L92);
  • read 的 keys / query 两条互斥路径(L94-L140);
  • update 的 batch / keys / query 三条互斥路径(L142-L217);
  • delete 透传 keys 并返回被删主键(L219-L241);
  • 非法 action 抛错、服务异常透传、create 后读回为 null 的降级输出(L244-L309)。

阅读这份测试是理解 prompt.md 中每条约定"到底由谁保证"的最快路径:凡是文档承诺的行为,都能在对应断言中找到落点。

八、实践要点小结

  • 调用格式action 四选一;keys 恒为数组;createdata 可单对象可数组;delete 必须带 keys
  • 返回值约定:create/update 返回读回的完整记录,delete 返回被删主键,无读权限时可能为 null
  • 权限心智:一切读写都发生在当前 accountability 之下,"查不到"优先怀疑权限而非数据不存在。
  • 存储心智:文件夹是纯数据库概念,不随存储适配器镜像,操作文件夹不影响真实文件。
  • 扩展心智:若你要为 Directus 新增自定义工具,prompt.md + defineTool + discriminatedUnion 校验 + Service 委托 + 分支化测试 这一整套 folders 工具 的实现模式可以直接照搬。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391