下面是一个语言无关的骨架。重点不是代码本身,而是四个明确的对象:任务、状态、工具注册表、完成条件。

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