Managed Agent(MA)支持内置工具、Custom Tool 和 MCP 工具。工具类型和基础配置请参见 Tools。 内置工具适合在托管 sandbox 中执行文件读写、命令执行、网页检索等操作;Custom Tool 适合将少量业务函数交给 Agent 调用;MCP 工具适合接入已经按 MCP 协议封装的外部能力。
在企业应用中,开发者通常还需要让 Agent 调用业务系统中的专用操作,例如查询订单、创建工单、调用库存系统、访问内网风控接口,或触发企业已有的图像、视频、数据处理服务。如果这些能力都通过 MCP server 接入,需要额外部署服务、维护 HTTP endpoint 和鉴权链路;如果让 Agent 直接通过命令行调用内部接口,则缺少稳定的参数 schema、权限边界和结果格式约束。
Custom Tool 用于解决这类“将少量业务函数交给 Agent 调用”的场景。开发者只需要在 Agent 中声明工具名称、用途和输入 schema,运行时由业务侧接收工具调用事件、执行真实业务逻辑,并将工具结果回传给 Session。Agent 可以基于工具结果继续推理和回复用户。
本文介绍 Custom Tool 的通用接入流程,并通过 4 个多媒体示例说明如何声明工具、触发调用和回传结果:
前提条件
您需要准备:
- 已开通 Managed Agent,并已获取可用的 API Key。
- 已明确需要暴露给 Agent 的业务操作,包括操作名称、用途、输入参数、返回结果和错误处理方式。
- 已准备业务侧执行逻辑。该逻辑可以运行在您的服务端、内部系统或任务队列中,用于接收工具调用事件、执行实际操作,并向 Session 回传工具结果。
- 如果工具需要访问内部系统,请提前配置鉴权、权限控制、审计日志和超时策略。
使用流程
使用 Custom Tool 的一般流程如下:
- 声明工具:创建 Agent 时,在 tools 中添加 type: custom 的工具,填写 name、description 和 input_schema。
- 创建 Session:基于该 Agent 创建 Session。
- 发送用户消息:向 Session 发送 user.message。
- 接收工具调用事件:当 Agent 判断需要调用自定义工具时,Session 事件流中会出现 agent.custom_tool_use 事件。该事件包含工具名称、工具输入参数和事件 ID。
- 等待 Session 暂停:Session 随后进入 idle,并发出 session.status_idle 事件。该事件的 stop_reason.type 为 requires_action,stop_reason.event_ids 中包含需要业务侧处理的事件 ID。
- 执行业务逻辑:业务侧根据 agent.custom_tool_use 的 name 和 input 执行对应操作。
- 回传工具结果:业务侧发送 user.custom_tool_result 事件,使用 custom_tool_use_id 关联第 4 步中的工具调用事件 ID。
- 继续运行:所有阻塞事件处理完成后,Session 从 idle 切回 running,Agent 基于工具结果继续生成回复。
如果 stop_reason.event_ids 中包含多个事件 ID,需要为每个阻塞事件回传结果。建议将同一轮阻塞事件对应的多个 user.custom_tool_result 放在同一个 events 数组中发送。
声明自定义工具
自定义工具通过 Agent 的 tools 字段声明。每个工具至少包含以下字段:
声明工具时,建议遵循以下原则:
- description 明确说明工具应该在什么场景下使用,以及不应该在什么场景下使用。
- input_schema 只暴露业务必要参数,不暴露 API Key、内部服务地址等敏感配置。
- 对枚举值、数字范围、必填字段和 additionalProperties 做明确约束。
- 工具返回值保持结构化,便于 Agent 判断是否成功、是否需要重试,以及如何向用户解释结果。
以下示例响应仅展示与流程相关的关键字段,实际响应字段以 API 返回为准。
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/agents" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"name": "CustomToolDemo",
"id": "doubao-seed-2-1-pro-260628"
"system": "你是一个可以调用业务工具的助手。需要外部业务能力时,调用已声明的 custom tool。不要编造工具结果。",
"description": "根据订单 ID 查询订单状态。仅当用户要求查询订单信息时使用。",
"required": ["order_id"],
"additionalProperties": false
响应示例:
"id": "agent-20260729153010-****",
"created_at": "2026-07-29T15:30:10Z",
"name": "CustomToolDemo",
"id": "doubao-seed-2-1-pro-260628"
创建 Session
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"agent": "agent-20260729153010-****",
"environment_id": "env-20260729152900-****",
"title": "Custom Tool 演示"
响应示例:
"id": "sesn-20260729153122-****",
"agent": "agent-20260729153010-****",
"environment_id": "env-20260729152900-****",
场景一:Custom Tool 生图
本场景演示如何通过 Custom Tool 接入企业已有的生图服务。完整链路包括:声明 generate_image 工具、创建 Agent 和 Session、发送用户消息、接收工具调用事件、执行生图逻辑并回传图片结果。
创建 Agent 时,在 tools 中声明生图工具:
"name": "generate_image",
"description": "调用业务侧生图服务生成图片。当用户明确要求创建、绘制或生成图片时使用;不要用于回答纯文本问题。prompt 需要描述主体、场景、风格和关键约束;size 决定输出尺寸;n 决定返回图片数量。",
"description": "完整的生图提示词。"
"description": "输出尺寸,例如 2048x2048。"
"description": "是否添加水印。"
"description": "返回图片数量。"
"additionalProperties": false
创建包含该工具的 Agent 并创建 Session 后,发送生图请求:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"text": "生成一张 2048x2048 的产品海报:一只透明玻璃咖啡杯放在木质桌面上,清晨自然光,背景有浅景深绿植,干净商业摄影风格。"
模型产生 agent.custom_tool_use:
"id": "sevt-20260729153201-****",
"type": "agent.custom_tool_use",
"name": "generate_image",
"prompt": "透明玻璃咖啡杯放在木质桌面上,清晨自然光,背景有浅景深绿植,干净商业摄影风格,产品海报构图",
随后 Session 进入 idle,stop_reason.type 为 requires_action:
"id": "sevt-20260729153202-****",
"type": "session.status_idle",
"type": "requires_action",
"sevt-20260729153201-****"
业务侧执行器调用生图服务,并回传工具结果:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729153201-****",
"text": "{\"ok\":true,\"image_count\":1,\"delivery\":\"base64\",\"request_id\":\"req-20260729153215-****\",\"usage\":{\"generated_images\":1}}"
"media_type": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAA...****"
模型拿到结果后即可回复用户,例如:
"type": "agent.message",
"text": "图片已生成,画面为清晨自然光下的玻璃咖啡杯产品海报。"
场景二:多图 Base64 回传
本场景演示一个工具调用返回多个多模态内容块。可以复用场景一中的 generate_image 工具声明,但需要在 input_schema 中为 n 设置合理范围,例如 minimum: 1、maximum: 4。
创建包含该工具的 Agent 和 Session 后,用户可以要求一次生成多张图片:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"text": "为同一个智能音箱生成 3 张电商主图,分别展示正面、45 度角和使用场景。"
Agent 生成的工具调用事件示例如下:
"id": "sevt-20260729154001-****",
"type": "agent.custom_tool_use",
"name": "generate_image",
"prompt": "同一个智能音箱的 3 张电商主图,白色背景,柔和阴影,分别展示正面、45 度角和使用场景",
回传结果时,文本块只放机器可读摘要,不重复塞入 Base64;每张图独立使用一个 image/base64 内容块:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729154001-****",
"text": "{\"ok\":true,\"image_count\":3,\"delivery\":\"base64\",\"request_ids\":[\"req-20260729154011-****\",\"req-20260729154018-****\",\"req-20260729154025-****\"],\"usage\":{\"generated_images\":3}}"
"media_type": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAA...image_1...****"
"media_type": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAA...image_2...****"
"media_type": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAA...image_3...****"
这种结构有两个好处:
- 文本 JSON 便于 Agent 判断工具是否成功、生成了几张图、是否需要重试。
- 原生 image 内容块便于模型直接消费图像模态,不需要从文本中解析大段 Base64。
场景三:File ID 参考生视频
本场景演示如何让 Custom Tool 接收 File ID,并由业务侧把文件解析为视频生成服务可读取的参考素材。适用于参考图、参考视频、参考音频等素材已经通过 Files API 上传的场景。
创建 Agent 时,声明 generate_video 工具:
"name": "generate_video",
"description": "提交异步生视频任务。当用户要求生成视频、基于参考素材生成视频或图生视频时使用。调用成功只表示任务已提交;需要通过 get_video_task 查询完成状态。",
"description": "视频提示词,描述主体、动作、场景、镜头和风格。"
"description": "生视频模型 ID 或 Endpoint ID。"
"reference_image_sources": {
"enum": ["url", "file", "tos"]
"additionalProperties": false
"description": "参考图片来源列表。"
"description": "视频时长,单位秒。"
"description": "画面比例,例如 16:9、9:16、1:1。"
"wait_for_completion": {
"description": "是否等待任务完成后再回传工具结果。"
"additionalProperties": false
创建包含该工具的 Agent 和 Session 后,用户可以在消息中引用 File ID:
用户请求:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"text": "用 file-20260729160108-**** 这张参考图生成一个 5 秒 16:9 视频:杯子旁边的蒸汽缓慢升起,镜头轻微推进,清晨暖光。"
模型调用 generate_video:
"id": "sevt-20260729160130-****",
"type": "agent.custom_tool_use",
"name": "generate_video",
"model": "doubao-seedance-2-0-260128",
"prompt": "参考图中的杯子保持主体一致,蒸汽缓慢升起,镜头轻微推进,清晨暖光,商业摄影质感",
"reference_image_sources": [
"file_id": "file-20260729160108-****"
"wait_for_completion": false
业务侧执行器可以把 File ID 解析为视频生成服务可访问的素材,再提交异步任务。首次回传通常只表示任务已受理:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729160130-****",
"text": "{\"ok\":true,\"task_id\":\"cgt-20260729160142-****\",\"model\":\"doubao-seedance-2-0-260128\",\"status\":\"queued\",\"video_url\":null}"
用户稍后查询任务:
"id": "sevt-20260729160810-****",
"type": "agent.custom_tool_use",
"name": "get_video_task",
"task_id": "cgt-20260729160142-****",
"model": "doubao-seedance-2-0-260128"
任务完成时的回传示例:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729160810-****",
"text": "{\"ok\":true,\"task_id\":\"cgt-20260729160142-****\",\"model\":\"doubao-seedance-2-0-260128\",\"status\":\"succeeded\",\"video_url\":\"https://example-cdn.volces.com/video/cgt-20260729160142-****.mp4\",\"usage\":{\"duration\":5}}"
视频结果建议以文本 JSON 返回 video_url。当前不要把 MP4 二进制作为 document/base64 塞回 Tool Result;这会显著放大事件体积,也可能无法被模型消费层按视频模态解析。
场景四:查询并理解视频
本场景演示如何把异步任务查询和后续视频理解串联起来。查询任务和理解视频内容是两个动作:
- get_video_task 负责确认任务状态,并拿到 video_url。
- 内置多模态或视频理解工具负责读取 video_url 并总结画面内容。
创建 Agent 时,声明任务查询工具:
"name": "get_video_task",
"description": "查询异步视频任务。当用户要求查看视频生成进度、确认任务是否完成或获取视频结果时使用。任务未完成时只能说明仍在生成;任务成功且包含 video_url 时,才能说明视频已完成。",
"description": "视频任务 ID。"
"description": "创建任务时使用的模型 ID,可省略。"
"required": ["task_id"],
"additionalProperties": false
创建包含该工具的 Agent 和 Session 后,用户请求查询并理解视频:
curl -X POST "https://ark.cn-beijing.volces.com/api/v3/sessions/sesn-20260729153122-****/events" \
-H "Authorization: Bearer $ARK_API_KEY" \
-H "Content-Type: application/json" \
"text": "查询任务 cgt-20260729160142-****。如果视频已经完成,请理解视频内容并总结镜头、主体动作和视觉风格。"
模型先调用 get_video_task:
"id": "sevt-20260729162000-****",
"type": "agent.custom_tool_use",
"name": "get_video_task",
"task_id": "cgt-20260729160142-****",
"model": "doubao-seedance-2-0-260128"
业务侧执行器回传视频 URL:
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729162000-****",
"text": "{\"ok\":true,\"task_id\":\"cgt-20260729160142-****\",\"model\":\"doubao-seedance-2-0-260128\",\"status\":\"succeeded\",\"video_url\":\"https://example-cdn.volces.com/video/cgt-20260729160142-****.mp4\"}"
接下来,Agent 应使用环境中可用的内置视频理解能力读取该 video_url。如果您的环境把该能力暴露为内置工具,可以在 Agent 的 system prompt 中明确要求:
当 get_video_task 返回 status=succeeded 且包含 video_url,并且用户要求理解视频内容时,继续调用环境中可用的内置多模态或视频理解工具读取 video_url;不要仅凭 prompt 或 task_id 猜测视频内容。
最终回复示例:
"type": "agent.message",
"text": "任务已完成。视频主体是一只透明玻璃咖啡杯,蒸汽从杯口缓慢升起;镜头从中景轻微推进到近景,背景绿植保持浅景深虚化,整体是清晨暖光下的干净商业摄影风格。"
如果当前环境没有视频理解内置工具,Agent 应说明只能确认 video_url 已生成,不能声称已经理解视频画面。
业务侧执行器参考逻辑
业务侧执行器需要维护一个待处理工具调用表。收到 agent.custom_tool_use 时先记录事件;收到 session.status_idle 且 stop_reason.type 为 requires_action 时,按 event_ids 找到对应工具调用,执行并批量回传。
def process_event(event):
event_type = event.get("type")
if event_type == "agent.custom_tool_use":
pending[event["id"]] = event
if event_type != "session.status_idle":
stop_reason = event.get("stop_reason") or {}
if stop_reason.get("type") != "requires_action":
for event_id in stop_reason.get("event_ids", []):
tool_event = pending.pop(event_id)
result = execute_custom_tool(tool_event)
"type": "user.custom_tool_result",
"custom_tool_use_id": event_id,
"is_error": not result["ok"],
"content": to_tool_result_content(result),
post_session_events({"events": results})
常见问题
input_schema 是否可以传字符串?
不可以。input_schema 必须以 JSON 对象传入,且顶层 type 仅支持 object。
Custom Tool 名称有什么限制?
建议使用 1 到 64 位的英文字母、数字、下划线或连字符,例如 generate_image、get_video_task。同一个 Agent 最多配置 8 个 Custom Tool。
为什么视频结果只返回 video_url?
视频生成通常是异步任务,MP4 体积较大,直接作为 Tool Result 回传不适合。推荐返回文本 JSON,让 Agent、前端或后续内置视频理解工具通过 video_url 继续处理。
多个 Custom Tool 同时触发时怎么回传?
以 stop_reason.event_ids 为准。该数组里的所有工具调用都完成后,放在同一个 events 数组中批量发送:
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729163001-****",
"text": "{\"ok\":true,\"value\":\"alpha\"}"
"type": "user.custom_tool_result",
"custom_tool_use_id": "sevt-20260729163002-****",
"text": "{\"ok\":true,\"value\":\"beta\"}"
调试建议
- 业务侧执行器启动后先订阅或轮询 Session 事件,再发送用户消息,避免错过 requires_action。
- 工具执行失败时也要回传 user.custom_tool_result,并设置 is_error: true。
- 不要在日志或文档中打印真实 API Key、完整签名 URL、完整 Base64 或未脱敏资源 ID。
- 生图和生视频后端建议使用独立的最小权限 API Key,便于审计和轮换。