开发工具
工具是指可由 Atomemo 应用调用的第三方服务或本地函数,提供完整的 API 实现能力。例如,添加在线搜索、图像生成等额外功能。
在本指南中,我们将使用 演示工具 作为示例来演示如何开发工具插件。
目录结构
工具通常位于插件项目的 src/tools/ 目录中。
my-plugin/
src/
tools/
demo.ts
search.ts开发工具
要创建工具,需要定义一个满足 ToolDefinition 接口的对象。
1. 导入依赖
首先,导入必要的类型和实用工具。
import type { ToolDefinition } from "@choiceopen/atomemo-plugin-sdk-js/types"2. 定义工具
工具定义需要以下几个关键属性:
- name: 工具的唯一标识符(例如 "demo-tool")。必须在插件内唯一。
- display_name: 向用户显示的名称(支持 i18n)。
- description: 工具功能的简要描述(支持 i18n)。
- icon: 代表该工具的表情符号或图像 URL。
- parameters: 工具所需的输入参数列表。
- invoke: 执行工具逻辑的异步函数。
- skill: 描述工具输入与输出的说明文档(字符串,可选,建议使用 Markdown 格式)。
3. 参数
参数使用 parameters 数组定义。每个参数描述一个用户可以配置的输入字段或 AI 可以填充的字段。
parameters: [
{
name: "location",
type: "string",
required: true, // 参数是否为必需
display_name: { en_US: "Location", zh_Hans: "位置" },
ui: {
component: "input", // UI 组件类型(例如 input、select、textarea)
hint: { en_US: "Enter a city, region, or country", zh_Hans: "输入城市、地区或国家" },
placeholder: { en_US: "New York", zh_Hans: "上海" },
support_expression: true, // 允许使用变量/表达式
width: "full",
},
ai: {
llm_description: { en_US: "Location for the search or lookup", zh_Hans: "搜索或查询使用的位置" },
},
},
]如果 ui.support_expression 为 true,你也可以添加 ai.llm_description,帮助 AI 理解该参数的用途。
完整指南 :有关使用不同类型的字段、UI 组件和验证规则定义参数,请参见 声明式参数定义参考文档。
4. 实现(Invoke)
invoke 函数是逻辑所在的地方。它接收应用传递的参数,同时还会注入一个由 SDK 提供的运行时上下文对象,并返回一个 JSON 可序列化的结果。
注意: 如果想让节点显示错误状态,在
invoke中必须抛出 JavaScript 的Error(例如:throw new Error('失败原因'))。仅返回{ error: "lorem" }是无效的,无法让节点进入错误状态。
在当前 SDK / Schema 中,invoke 的完整函数签名是:
async invoke({ args, context }) {
// ...
}args:本次调用的输入数据args.parameters:根据你在parameters中定义的字段解析后的参数值args.credentials:根据credential_id类型参数解析出来的凭证数据(以credential_id值作为 key)
context:由 SDK 注入的运行时辅助工具,目前主要提供安全处理文件引用的files能力
一个最基础的示例:
async invoke({ args, context }) {
// 通过 args.parameters 访问参数
const location = args.parameters.location
// 如需使用 context.files,可以在这里调用(见下一节)
// 返回一个 JSON 可序列化的对象
return {
message: `Testing the plugin with location: ${location}`,
}
}5. 使用 context.files 处理文件
当工具的参数中包含 file_ref 类型,或者你希望从工具中返回一个文件结果时,应使用 context.files 提供的辅助方法,而不是将文件当作普通对象随意读写。
当前 SDK 中,context.files 暴露的主要方法包括:
context.files.parseFileRef(input):校验未知输入并将其收窄为合法的file_refcontext.files.download(fileRef):从 OSS / 远程存储下载文件内容,返回带有content的file_refcontext.files.attachRemoteUrl(fileRef):为 OSS 文件引用补充可下载的远程 URLcontext.files.upload(fileRef, { prefixKey? }):上传内存中的文件,并返回带有res_key/remote_url的 OSS 文件引用
5.1 从参数中读取文件并下载内容
以官方 Google Drive「上传文件」工具为例(google-drive-upload-file),其参数中包含一个 file_ref 类型的 file 字段:
const fileRef = context.files.parseFileRef(args.parameters.file)
const downloaded = await context.files.download(fileRef)
const originalFilename = downloaded.filename
const bytes = new Uint8Array(
Buffer.from(downloaded.content ?? "", "base64"),
)这里有几个关键点:
- 先使用
parseFileRef:确保传入的参数确实是一个符合 Schema 的file_ref - 再调用
download:获取真实的文件内容(Base64 编码)的同时,仍然保持类型安全
你的自定义工具如果也需要读取文件内容,可以采用同样的模式:
async invoke({ args, context }) {
const fileRef = context.files.parseFileRef(args.parameters.file)
const downloaded = await context.files.download(fileRef)
return {
filename: downloaded.filename,
mime_type: downloaded.mime_type,
size: downloaded.size,
}
}5.2 在工具中生成文件并作为结果返回
如果你的工具在内存中生成了一个文件(例如从 Google Drive 下载一个文件,再交给 Atomemo 继续使用),你可以构造一个 source: "mem" 的 file_ref,然后通过 context.files.upload 交给 Atomemo 管理:
官方 Google Drive「下载文件」工具(google-drive-download-file)的核心逻辑示例:
const bytes = new Uint8Array(arrayBuffer)
const contentBase64 = Buffer.from(bytes).toString("base64")
const fileRef: FileRef = {
__type__: "file_ref",
source: "mem",
filename,
content: contentBase64,
mime_type: contentType,
extension,
size: bytes.length,
res_key: null,
remote_url: null,
}
const uploadResult = await context.files.upload(fileRef, {})
return uploadResult在你自己的工具中,只要遵循相同的结构即可:
async invoke({ args, context }) {
const fileRef = {
__type__: "file_ref",
source: "mem",
filename: "report.txt",
extension: ".txt",
mime_type: "text/plain",
size: Buffer.byteLength("hello"),
content: Buffer.from("hello").toString("base64"),
res_key: null,
remote_url: null,
}
// 将内存中的文件交给 Atomemo 管理,并返回一个持久化的 file_ref
return await context.files.upload(fileRef, { prefixKey: "reports/" })
}实践建议:当你的工具需要处理文件(无论是作为输入还是输出)时,优先参考官方 Google Drive 插件的
upload-a-file和download-a-file实现,保持与官方插件同样的模式和约定,可以最大化复用 Atomemo 已有的文件存储与权限体系。
完整示例
以下是 src/tools/demo.ts 的完整代码:
import type { ToolDefinition } from "@choiceopen/atomemo-plugin-sdk-js/types"
export const demoTool = {
name: "demo-tool",
display_name: { en_US: "Demo Tool", zh_Hans: "演示工具" },
description: { en_US: "A demo tool for testing", zh_Hans: "用于测试的演示工具" },
icon: "🧰",
parameters: [
{
name: "location",
type: "string",
required: true,
display_name: { en_US: "Location", zh_Hans: "位置" },
ui: {
component: "input",
hint: { en_US: "Enter a city, region, or country", zh_Hans: "输入城市、地区或国家" },
placeholder: { en_US: "New York", zh_Hans: "上海" },
support_expression: true,
width: "full",
},
ai: {
llm_description: { en_US: "Location for the search or lookup", zh_Hans: "搜索或查询使用的位置" },
},
},
],
async invoke({ args, context }) {
return {
message: `Testing the plugin with location: ${args.parameters.location}`,
}
},
} satisfies ToolDefinition注册工具
定义完成后,你需要在插件的主入口文件(通常是 src/index.ts)中注册该工具。
import { createPlugin } from "@choiceopen/atomemo-plugin-sdk-js"
import { demoTool } from "./tools/demo"
// ... 初始化插件
const plugin = await createPlugin({
// ...
})
// 注册工具
plugin.addTool(demoTool)
// 运行插件
plugin.run()参考
- 类型定义:
@choiceopen/atomemo-plugin-schema/types中的ToolDefinition - Schema:
@choiceopen/atomemo-plugin-schema/schema中的ToolDefinitionSchema