下面是一个语言无关的骨架。重点不是代码本身,而是四个明确的对象:任务、状态、工具注册表、完成条件。
1. 定义状态
type AgentState = {
taskId: string
goal: string
status: "running" | "waiting_human" | "done" | "failed"
steps: Step[]
observations: Observation[]
retries: number
}
type Step = {
tool: string
args: Record<string, unknown>
reason: string
}
状态必须可序列化。这样进程重启、人工接管、换模型,都能从同一处继续。
2. 工具注册表
const tools = {
search_material: {
schema: { query: "string", grade: "number" },
run: async (args) => searchMaterial(args)
},
create_draft: {
schema: { childId: "number", questions: "array" },
run: async (args) => createDraft(args)
}
}
function execute(toolName, args) {
const tool = tools[toolName]
if (!tool) return { ok: false, error: "UNKNOWN_TOOL" }
return tool.run(args)
}
工具注册表同时承担校验和权限边界。模型只能从这里选择,不能直接访问任意接口。
3. 主循环
async function run(state) {
while (state.status === "running") {
if (state.steps.length >= MAX_STEPS) {
state.status = "failed"
break
}
const decision = await decideNextStep(state)
if (decision.type === "finish") {
state.status = "done"
break
}
if (decision.type === "ask_human") {
state.status = "waiting_human"
break
}
const result = await execute(decision.tool, decision.args)
state.steps.push(decision)
state.observations.push(result)
if (!result.ok) state.retries += 1
}
return state
}
这段代码里最重要的不是 decideNextStep,而是 MAX_STEPS、ask_human 和结构化观察。它们保证系统不会无限跑,也不会在需要人时硬撑。
4. 完成条件要用代码检查
不要只让模型说“我完成了”。完成条件应该是可验证的:题目数量对不对、答案是否齐全、文件是否存在、用户是否确认。
function isDone(state) {
return state.observations.some(o => o.type === "draft_created")
&& state.observations.some(o => o.type === "review_passed")
}
模型负责提出下一步,代码负责判断能不能结束。这是 Agent 可靠性的分界线。
5. 先记录,再优化
第一版一定要把每一步写进轨迹:模型决定了什么、调用了哪个工具、参数是什么、返回了什么、为什么停止。没有轨迹,后面的评估和优化都无从谈起。
最小可用标准
能完成一类真实任务;失败时能看到原因;需要人时能停下来;重启后能恢复。满足这四点,再谈更强的自主性。
回到架构总览 →00 / ARCHITECTURE