Skip to content

本文聚焦工程视角,给出 Agent 的“规划-执行-记忆”协作思路与可复用代码片段。

🎯 文章目标

  • 把 Agent 拆成“规划(Planner)/工具(Tools)/记忆(Memory)/执行(Executor)”
  • 给出函数调用与工具编排的工程化做法
  • 明确观测、重试、幂等与安全的最小闭环

📚 背景/前置

  • 典型链路:用户意图 → 规划 → 工具选择/调用 → 结果合并 → 反思/重试 → 输出
  • 工程关注:标准化函数接口、可重试/幂等、权限隔离、耗时/失败可观测

🔧 核心内容

1) 设计要点

  • 规划:用结构化计划(steps/goals/constraints),避免“纯自然语言计划”不可执行
  • 工具:输入/输出 JSON Schema;错误语义化(错误码/重试建议)
  • 记忆:短期(会话)+ 长期(外部存储);按“任务/用户/组织”隔离
  • 执行:有状态(长任务)需引入队列与回放,保证幂等

2) 函数调用与工具编排

javascript
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: process.env.OPENAI_API_BASE, apiKey: process.env.OPENAI_API_KEY })

const tools = [{
  type: 'function',
  function: {
    name: 'searchTickets', description: '搜索工单',
    parameters: { type:'object', properties:{ q:{type:'string'} }, required:['q'] }
  }
},{
  type: 'function',
  function: {
    name: 'closeTicket', description: '关闭工单',
    parameters: { type:'object', properties:{ id:{type:'string'} }, required:['id'] }
  }
}]

const plan = [
  { step: 1, action: 'searchTickets', input: { q: '退款' } },
  { step: 2, action: 'closeTicket', input: { id: '\{\{ticketId\}\}' } }
]

const msgs = [
  { role:'system', content:'严格按计划执行。无法执行要说明原因,不可编造。' },
  { role:'user', content:'帮我关闭最近的退款相关工单。' },
  { role:'system', content:`计划: \${JSON.stringify(plan)}`}
]

const rsp = await client.chat.completions.create({ model: process.env.CHAT_MODEL, messages: msgs, tools })
console.log(rsp.choices[0].message)

要点:

  • 用计划结构显式传输“步骤/约束/变量”,避免全靠模型“猜”
  • 工具的参数/返回体对齐 JSON Schema,异常携带“是否可重试/需人工介入”等字段

3) 记忆与状态

  • 短期:会话级(上下文窗口内),用于近期意图与变量
  • 长期:外部存储(如 Redis/DB/向量库),按 key 组织,注意权限与回收
  • 长任务:任务 ID + 状态机(pending/running/succeeded/failed),幂等键与重试策略

4) 观测与评估

  • 指标:成功率、平均步数、平均工具调用次数、平均延迟、失败分布(解析/权限/超时)
  • 记录:请求上下文、计划、工具入参/结果、失败样本;支持回放
  • 回归:固定计划与输入,评估不同模型/提示版本的稳定性

💡 实战示例:最小 Agent 执行器(Node.js)

javascript
async function exec(plan, call){
  const ctx = {}
  for (const p of plan){
    const input = JSON.parse(JSON.stringify(p.input).replace('\{\{ticketId\}\}', ctx.ticketId||''))
    const out = await call(p.action, input)
    if (!out || out.error) throw new Error(out?.error || 'tool failed')
    if (out.ticketId) ctx.ticketId = out.ticketId
  }
  return ctx
}

📊 对比/取舍(速查)

  • 单工具 vs 工具链:工具链表达力更强,但需要状态与回退;简单场景优先单工具
  • 模型驱动 vs 规则驱动规划:可解释性与稳定性优先时,规划要规则化/模板化
  • 内存 vs 外部记忆:会话内方便,跨会话需外部存储与权限隔离

🧪 踩坑与经验

  • 将“工具失败/权限不足/参数缺失”等错误语义化,便于重试与定位
  • 任务超时需“补偿动作/回滚策略”;长任务务必幂等
  • 观测缺失会导致 Agent 难以治理:务必保存计划与工具调用流水

📎 参考与延伸

  • ReAct/Plan-and-Execute/LLM+工具链综述
  • OpenAI 函数调用、JSON Schema、工具异常语义化设计
  • 有状态任务编排与回放

💭 总结

  • 用“规划结构化 + 工具 JSON Schema + 外部记忆 + 有状态执行 + 观测回放”组合,提升 Agent 的可控性与落地性