You need to enable JavaScript to run this app.
文档中心
火山方舟

火山方舟

复制全文
下载 pdf
Managed Agent
[进阶] 构建可持续迭代的 AI 研究交付工作台
复制全文
下载 pdf
[进阶] 构建可持续迭代的 AI 研究交付工作台
用一个长期 Session 承载复杂研究任务,实时观察 Agent 的工具调用和执行状态,并把每轮结果沉淀为可审阅、可继续优化的完整交付物。​
你将构建什么​
「研究工作台」接受一个研究目标和若干资料链接,由方舟 Managed Agents 在云端执行长任务。用户能够看到 Agent 正在调用什么工具、当前处理到哪一步,并在 Session 完成后直接审阅完整报告。​
如果首版报告不够好,用户可以继续提出「补强证据」「增加方案对比」或「重写结论」。系统不会重新创建任务,而是继续向原 Session 发送消息,生成 V2、V3,同时保留旧版本。​
最终应用具备以下能力:​
  • 接收飞书文档、网页和业务背景;​
  • 展示工具调用、阶段消息和 Session 状态;​
  • 以 session.status_idle 判断本轮完整输出已经结束;​
  • 在同一个 Session 中持续追加优化要求;​
  • 保存每轮完整交付物,支持版本切换和导出;​
  • 提供当前 Session 的方舟控制台入口;​
  • 用户确认后再执行发布等外部写操作。​
工作原理​
​研究工作台调用链路​​
​
对象​
在项目中的职责​
Agent​
定义模型、系统指令、工具和 Skills​
Environment​
Agent 的云端运行环境,决定网络和执行能力​
Session​
一项业务任务的连续工作空间,初次请求和后续优化复用同一个 Session​
Event​
Session 中的用户输入、工具调用、Agent 消息和状态变化​
Skill​
可复用的工作方法,例如研究规划、证据核验和交付审查​
Report Version​
每个用户轮次结束后保存的一份完整交付物​
​
一次完整运行遵循以下顺序:​
  1. 应用创建 Session;​
  1. 应用打开 Session Events SSE 流,并等待 : ready;​
  1. 向 Session 发送 user.message;​
  1. Agent 规划任务、调用工具并产生 Events;​
  1. 应用展示 agent.tool_use 和阶段消息;​
  1. Session 进入 idle;​
  1. 应用提取最后的完整交付物并生成版本;​
  1. 用户审阅后可以继续向原 Session 发送下一轮要求。​
10 分钟快速开始​
本章完成一件事:创建 Session、发送研究任务、观察事件,并在 Session 进入 idle 后输出最终报告。​
准备环境​
你需要:​
  • Node.js 20 或更高版本;​
  • 一个可用的方舟 Managed Agents Agent;​
  • Agent 对应的 Environment;​
  • 可用的 ARK API Key。​
安装依赖并创建本地配置:​
​
pnpm install​
cp .env.example .env​
​
填写 .env:​
​
ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/v3​
ARK_API_KEY=ark-替换为你的_API_Key​
ARK_AGENT_ID=替换为你的_Agent_ID​
ARK_ENVIRONMENT_ID=替换为你的_Environment_ID​
​
注意​
ARK API Key 只应由服务端读取,不要放进 VITE_* 变量、React 代码或浏览器请求。​
创建 Session、建立事件流并发送任务​
下面是完整的最小示例:​
​
import "dotenv/config";​
​
const baseUrl = (​
process.env.ARK_BASE_URL ||​
"https://ark.cn-beijing.volces.com/api/v3"​
).replace(/\/$/, "");​
​
const apiKey = process.env.ARK_API_KEY;​
const agentId = process.env.ARK_AGENT_ID;​
const environmentId = process.env.ARK_ENVIRONMENT_ID;​
​
if (!apiKey || !agentId || !environmentId) {​
throw new Error(​
"请设置 ARK_API_KEY、ARK_AGENT_ID 和 ARK_ENVIRONMENT_ID",​
);​
}​
​
async function request(path, init = {}) {​
const response = await fetch(`${baseUrl}${path}`, {​
...init,​
headers: {​
Authorization: `Bearer ${apiKey}`,​
"Content-Type": "application/json",​
...init.headers,​
},​
});​
if (!response.ok) {​
throw new Error(​
`${init.method || "GET"} ${path} 失败:${await response.text()}`,​
);​
}​
return response;​
}​
​
async function createSession() {​
const response = await request("/sessions", {​
method: "POST",​
body: JSON.stringify({​
agent: { id: agentId },​
environment_id: environmentId,​
}),​
});​
const session = await response.json();​
return session.id ?? session.session_id;​
}​
​
async function sendMessage(sessionId, text) {​
await request(`/sessions/${sessionId}/events`, {​
method: "POST",​
body: JSON.stringify({​
events: [{​
type: "user.message",​
content: [{ type: "text", text }],​
}],​
}),​
});​
}​
​
function textFromEvent(event) {​
return event.content​
?.filter((item) => item.type === "text" && item.text)​
.map((item) => item.text)​
.join("\n") || "";​
}​
​
async function runTurn(sessionId, text) {​
const response = await request(​
`/sessions/${sessionId}/events/stream`,​
{ headers: { Accept: "text/event-stream" } },​
);​
​
const reader = response.body.getReader();​
const decoder = new TextDecoder();​
let buffer = "";​
let finalMessage = "";​
let messageSent = false;​
​
try {​
while (true) {​
const { done, value } = await reader.read();​
buffer += decoder.decode(value, { stream: !done });​
const lines = buffer.split(/\r?\n/);​
buffer = lines.pop() || "";​
​
for (const line of lines) {​
if (!messageSent && line === ": ready") {​
await sendMessage(sessionId, text);​
messageSent = true;​
continue;​
}​
if (!line.startsWith("data:")) continue;​
const payload = line.slice(5).trim();​
if (!payload) continue;​
​
let event;​
try {​
event = JSON.parse(payload);​
} catch {​
continue;​
}​
​
if (event.type === "agent.tool_use") {​
console.log(`工具调用:${event.name || "unknown"}`);​
}​
if (event.type === "agent.message") {​
finalMessage = textFromEvent(event) || finalMessage;​
}​
if (event.type === "session.status_idle") {​
return finalMessage;​
}​
if (event.type === "session.status_terminated") {​
throw new Error("Session 已终止");​
}​
}​
​
if (done) {​
const reason = messageSent​
? "没有收到 session.status_idle"​
: "没有收到 : ready";​
throw new Error(`事件流已经结束,但${reason}`);​
}​
}​
} finally {​
await reader.cancel().catch(() => {});​
}​
}​
​
const sessionId = await createSession();​
console.log(`Session:${sessionId}`);​
​
const report = await runTurn(​
sessionId,​
`研究方舟 Managed Agents 的 Session 与 Events 机制,​
形成一份包含关键发现、证据、接入建议、风险和下一步的 Markdown 报告。`,​
);​
​
console.log(report);​
​
运行:​
​
node run-session.mjs​
​
预期看到:​
​
Session:sesn-...​
工具调用:web_search​
工具调用:read​
工具调用:write​
​
# 方舟 Managed Agents 接入研究​
...​
​
agent.message 只是交付物候选。Agent 可能先返回阶段消息,再继续调用工具;只有收到 session.status_idle,应用才把最后的完整消息视为本轮结果。​
至此,快速路径已经完成。后续章节用于把最小示例升级为真实业务应用。​
构建完整研究 Agent​
一个真实研究 Agent 不能只依赖一段很长的系统提示。本项目把稳定、可复用的工作方法拆成五个 Skill:​
​
Skill​
负责的问题​
主要输出​
research-planning​
任务边界、问题树和证据要求​
研究计划​
source-verification​
来源权威性、时效性、冲突和引用支持关系​
Claim Ledger​
product-synthesis​
方案选项、统一标准下的取舍、MVP 和建议​
决策建议​
api-doc-authoring​
字段、约束、示例、错误、重试和幂等​
可实现 API 文档​
delivery-review​
事实、完整性、一致性、安全性和发布质量​
发布门禁结论​
​
每个 Skill 是一个独立目录,至少包含 SKILL.md:​
​
skills/​
├── research-planning/SKILL.md​
├── source-verification/SKILL.md​
├── product-synthesis/SKILL.md​
├── api-doc-authoring/SKILL.md​
└── delivery-review/SKILL.md​
​
将每个 Skill 目录分别打包:​
​
mkdir -p skill-packages​
for skill_dir in skills/*; do​
skill_name=$(basename "$skill_dir")​
(cd "$skill_dir" && zip -r "../../skill-packages/$skill_name.zip" .)​
done​
​
上传一个 Skill:​
​
import { readFile } from "node:fs/promises";​
​
const form = new FormData();​
form.append(​
"files",​
new Blob([await readFile("skill-packages/research-planning.zip")], {​
type: "application/zip",​
}),​
"research-planning.zip",​
);​
​
const response = await fetch(`${ARK_BASE_URL}/skills`, {​
method: "POST",​
headers: { Authorization: `Bearer ${ARK_API_KEY}` },​
body: form,​
});​
​
const skill = await response.json();​
​
记录返回的 id 和 latest_version。创建或更新 Agent 时提交完整的 Skills 列表。​
本项目的系统指令重点约束三件事:​
​
1. 先规划研究问题和证据标准,再开始收集资料。​
2. 只能把有来源支持的内容写成事实;推断和需要进一步核实的内容必须明确标注。​
3. 最终返回一份完整 Markdown;发布、发送、删除或修改外部系统前必须等待用户确认。​
​
创建 Agent 时,将工具和 Skills 一起配置:​
​
await fetch(`${ARK_BASE_URL}/agents`, {​
method: "POST",​
headers: {​
Authorization: `Bearer ${ARK_API_KEY}`,​
"Content-Type": "application/json",​
},​
body: JSON.stringify({​
name: "research-workbench-agent",​
description: "面向产品与研发团队的研究交付 Agent。",​
model: {​
id: "doubao-seed-2-1-pro-260628",​
speed: "standard",​
},​
system: systemPrompt,​
tools: [{​
type: "agent_toolset_20260916",​
default_config: {​
enabled: true,​
permission_policy: { type: "always_allow" },​
},​
}],​
skills: uploadedSkills.map((skill) => ({​
type: "custom",​
skill_id: skill.id,​
version: skill.latest_version,​
})),​
}),​
});​
​
依次上传五个 Skills 后,使用各响应中的 id 和 latest_version 组装 Agent 请求。模型和工具版本可能随环境变化,应以当前方舟控制台和 API 返回为准。​
嵌入真实 App​
一个任务始终复用一个 Session​
业务系统需要保存 taskId → sessionId 映射。用户继续优化时,向原 Session 发送新的 user.message:​
​
const followUp = `请基于当前 Session 中已有研究和上一版交付物继续优化。​
​
用户的新要求:补充三种实现方案的对比,并明确推荐方案的 MVP 边界。​
​
要求:​
1. 保留仍然成立的事实和引用。​
2. 不要只返回修改建议,要返回一份可独立使用的完整 Markdown。​
3. 明确标注仍缺少证据或需要用户确认的信息。`;​
​
const nextReport = await runTurn(existingSessionId, followUp);​
​
runTurn 会先为新一轮建立 SSE,再发送消息并等待 session.status_idle。同一个 Session 正在运行时不要继续发送下一轮要求。前端应禁用发送按钮,服务端也应拒绝并发轮次。​
用 turnId 组织对话和事件​
每个用户消息创建一个 turnId。工具调用、阶段消息、完成事件和最终报告都归属到这一轮:​
​
interface TaskMessage {​
id: string;​
turnId: string;​
role: "user" | "agent";​
kind: "brief" | "follow-up" | "delivery" | "status";​
content: string;​
versionId?: string;​
createdAt: string;​
}​
​
interface ReportVersion {​
id: string;​
number: number;​
content: string;​
instruction: string;​
createdAt: string;​
}​
​
本轮进入 idle 后,追加一个新版本,不要覆盖上一版:​
​
const version = {​
id: crypto.randomUUID(),​
number: task.reportVersions.length + 1,​
content: completedReport,​
instruction: currentUserMessage,​
createdAt: new Date().toISOString(),​
};​
​
task.reportVersions.push(version);​
task.activeReportVersionId = version.id;​
​
把执行过程和最终交付分开呈现​
推荐的交互方式是:​
  • 左侧按用户轮次展示协作对话;​
  • 用户消息右对齐,Agent 最终交付左对齐;​
  • agent.tool_use 和阶段消息放入「处理中」区域;​
  • 运行时展开过程,收到 idle 后自动折叠;​
  • 右侧独立滚动并渲染完整 Markdown;​
  • 报告顶部提供版本选择、导出和人工确认发布;​
  • 页面展示当前 Session ID 和方舟控制台链接。​
​
function arkSessionConsoleUrl(sessionId, region, projectName) {​
return `https://ark.volcengine.com/region:cn-beijing/ark/region:${encodeURIComponent(region)}`​
+ `/managed-agents/sessions/${encodeURIComponent(sessionId)}`​
+ `?projectName=${encodeURIComponent(projectName)}`;​
}​
​
生产化改造要点​
本章是进阶内容。最小示例已经跑通后,再根据部署环境逐项加入。​
Serverless 环境使用轮询对账​
本地 Node 服务可以持续消费 Event Stream。Serverless 请求不应等待整个长任务,可以使用「快速提交 + 前端轮询 + 服务端对账」:​
  1. 创建 Session 并发送消息后立即返回;​
  1. 前端定时调用任务详情接口;​
  1. 服务端读取 Session 当前状态和事件历史;​
  1. Session 仍为 running 时继续等待;​
  1. Session 为 idle 时提取本轮最终结果并生成版本。​
​
const [session, history] = await Promise.all([​
client.getSession(task.sessionId),​
client.listEvents(task.sessionId),​
]);​
​
if (session.status === "running") return task;​
​
const idleObserved =​
session.status === "idle" ||​
hasSessionIdleForCurrentTurn(history);​
​
if (!idleObserved) {​
throw new Error(`Session 当前状态为 ${session.status ?? "unknown"}`);​
}​
​
const content =​
latestWrittenArtifactForCurrentTurn(history)?.content ??​
latestAgentMessageForCurrentTurn(history);​
​
事件历史可能不会完整保留某个实时状态事件,因此恢复流程同时检查历史中的 session.status_idle 和 Session 当前 status。​
恢复文件型交付物​
Agent 有时会把报告写入 /mnt/session/outputs/report.md,随后只回复「文件已生成」。浏览器无法直接访问这个云端路径。​
优先检查 write 工具事件,从 event.input.file_path 和 event.input.content 提取正文。如果只有路径,就向同一 Session 发送一次补充请求:​
​
请读取刚才生成的交付文件,并把完整 Markdown 正文作为本轮最终​
agent.message 返回。不要再次只返回路径、摘要、字数或完成通知。​
​
补充请求仍然要等待新的 session.status_idle。​
为中断和重启设计恢复​
应用需要保留 Session ID。浏览器关闭或服务重启后,不要重复提交任务,而应:​
  1. 查询 GET /sessions/{session_id};​
  1. 读取 GET /sessions/{session_id}/events;​
  1. Session 为 running 时继续观察;​
  1. Session 为 idle 时恢复最后的完整交付;​
  1. 对已经处理过的源事件做幂等去重。​
保护凭证和外部写操作​
  • ARK API Key 只保留在服务端;​
  • 生产环境为每个用户或租户校验任务访问权限;​
  • 对发布、发送、删除和修改业务数据设置人工确认;​
  • 不要把长期访问 Token 保留在页面 URL、日志或截图中;​
  • 测试完成后归档无用 Session,并轮换演示期间使用过的凭证。​
部署业务应用时,只向服务端配置调用 Managed Agents 所需的环境变量:​
​
ARK_BASE_URL​
ARK_API_KEY​
ARK_AGENT_ID​
ARK_ENVIRONMENT_ID​
​
注意​
不要把 ARK API Key 或其他第三方凭据写入前端环境变量。发布、发送等外部写操作应由服务端执行,并在调用前校验用户授权。​
验收结果​
​
场景​
通过标准​
最小调用​
创建 Session,看到工具事件,idle 后输出完整 Markdown​
持续优化​
第二轮复用原 Session,生成 V2,V1 仍然可以查看​
中间过程​
工具调用和阶段消息运行时可见,完成后可以折叠​
完成边界​
没有 idle 时不生成最终版本;恢复时可以用 Session 当前状态对账​
文件型产物​
返回 /mnt/session/outputs/... 时恢复正文,而不是只展示路径​
页面恢复​
刷新或服务重启后,可以根据 Session ID 恢复执行状态和结果​
事件审计​
每个任务页面能打开对应 Session 的方舟控制台​
数据安全​
浏览器看不到 ARK API Key,外部写操作需要人工确认​
​
扩展方向​
这套模式适合任何需要「长任务 + 可观察过程 + 人工审阅 + 多轮优化」的业务:​
  • 方案投标和 RFP 响应;​
  • 合同、政策和行业研究;​
  • 销售洞察与客户方案;​
  • 故障复盘和技术调研;​
  • PRD、API 设计和上线评审;​
  • 竞品研究与持续监控。​
把「研究目标和 Skill」替换成你的业务方法,把最终确认动作接入你的系统,就可以将这个案例嵌入自己的 App 或软件。​
​
最近更新时间:2026.10.28 19:53:17
这个页面对您有帮助吗?
有用
有用
无用
无用