结合 chao-go 项目实战,讲解 Agent 如何动态生成执行计划
一、核心定义
一句话理解
Planning = 业务流程编排层 ,以前程序员写死
if-else,现在 LLM 动态生成执行计划。
传统后端 vs Agent Planning
传统后端(写死流程):
┌─────────────────────────────────────────────────────┐
│ submitOrder() { │
│ checkStock(); // Step 1 │
│ createOrder(); // Step 2 │
│ deductInventory(); // Step 3 │
│ sendMessage(); // Step 4 │
│ } │
│ │
│ 问题:流程固定,无法根据实际情况调整 │
└─────────────────────────────────────────────────────┘
Agent Planning(动态流程):
┌─────────────────────────────────────────────────────┐
│ 用户:"帮我查订单 ORD001 能否改签" │
│ │
│ LLM 动态生成计划: │
│ Plan: │
│ 1. order_query → 查订单状态 │
│ 2. change_check → 判断改签条件 │
│ 3. recommend → 推荐方案 │
│ │
│ 根据实际结果动态调整: │
│ - 订单不存在 → 提示用户确认,不继续 │
│ - 已起飞 → 直接返回不可改签 │
│ - 正常 → 完整执行三步 │
└─────────────────────────────────────────────────────┘
二、常见 Planning 模式
2.1 ReAct(Reasoning + Acting)
最经典的 Planning 模式,chao-go 项目采用此模式。
循环流程:
┌─────────────────────────────────────────────────────┐
│ │
│ Thought(思考)← LLM 决定下一步做什么 │
│ ↓ │
│ Action(行动)← 执行工具 │
│ ↓ │
│ Observation(观察)← 记录结果 │
│ ↓ │
│ Thought(下一轮思考)← 基于观察继续决策 │
│ ↓ │
│ ... 循环直到完成 ... │
│ │
└─────────────────────────────────────────────────────┘
特点:逐步决策,每步都能根据上一步结果调整。
2.2 Plan-and-Execute
先规划完整计划,再逐步执行。
Phase 1: Plan(规划)
┌─────────────────────────────────────────────────────┐
│ LLM 生成完整计划: │
│ 1. order_query │
│ 2. change_check │
│ 3. recommend │
└─────────────────────────────────────────────────────┘
↓
Phase 2: Execute(执行)
┌─────────────────────────────────────────────────────┐
│ 逐步执行计划中的每一步 │
│ (执行过程中不再询问 LLM) │
└─────────────────────────────────────────────────────┘
特点:稳定、成本低,适合企业级 Agent。
2.3 Tree of Thoughts(ToT)
树状规划,评估后选择最优路径。
根节点
/ \
方案A 方案B
/ \ / \
A1 A2 B1 B2
评估每个分支,选择最优路径
特点:适合复杂推理、多方案决策。
2.4 Graph Planning
DAG 图规划,多工具协作。
order_query
/ \
↓ ↓
user_info flight_info
\ /
↓ ↓
recommend
特点:适合 LangGraph、工作流引擎。
三、chao-go 项目实现:ReAct 模式
3.1 核心数据结构
// internal/react/agent.go
// Thought 思考阶段 - LLM 决定下一步做什么
type Thought struct {
Reasoning string `json:"reasoning"` // 推理过程
ToolName string `json:"tool_name"` // 决定调用的工具
Arguments map[string]interface{} `json:"arguments"` // 工具参数
IsFinished bool `json:"is_finished"` // 是否完成
FinalAnswer string `json:"final_answer"` // 最终答案
}
// Observation 观察阶段 - 工具执行结果
type Observation struct {
Content string `json:"content"` // 结果内容
IsError bool `json:"is_error"` // 是否错误
}
// Step 单次循环步骤
type Step struct {
Thought *Thought `json:"thought"`
Observation *Observation `json:"observation"`
}
// AgentState Agent 状态
type AgentState struct {
UserInput string `json:"user_input"` // 用户原始输入
History []Step `json:"history"` // 执行历史
StartTime int64 `json:"start_time"` // 开始时间
}
3.2 ReAct 循环实现
// Agent ReAct Agent
type Agent struct {
llm LLMClient // LLM 客户端
tools ToolRegistry // 工具注册表
prompt *PromptTemplate // Prompt 模板
config *Config // 配置
}
// Run 执行 ReAct 循环
func (a *Agent) Run(userInput string) (*Result, error) {
// 初始化状态
state := &AgentState{
UserInput: userInput,
History: make([]Step, 0),
}
// ========== Planning 循环 ==========
for i := 0; i < a.config.MaxIterations; i++ {
// ---------- Phase 1: Thought(思考)----------
// 生成 Prompt,包含:可用工具、历史记录、用户输入
promptData := PromptData{
Tools: a.tools.ListTools(), // 告诉 LLM 有哪些工具可用
History: state.History, // 告诉 LLM 之前做了什么
UserInput: userInput, // 用户原始需求
}
prompt := a.prompt.Render(promptData)
// 调用 LLM 生成思考
thought := a.llm.GenerateThought(prompt)
// 检查是否完成
if thought.IsFinished {
return &Result{FinalAnswer: thought.FinalAnswer}, nil
}
// ---------- Phase 2: Action(行动)----------
// 获取工具处理器
handler, ok := a.tools.GetHandler(thought.ToolName)
if !ok {
// 工具不存在,记录错误,继续循环
observation := &Observation{
Content: fmt.Sprintf("工具不存在: %s", thought.ToolName),
IsError: true,
}
state.History = append(state.History, Step{Thought: thought, Observation: observation})
continue
}
// 执行工具
content, err := handler(thought.Arguments)
// ---------- Phase 3: Observation(观察)----------
observation := &Observation{
Content: content,
IsError: err != nil,
}
// 记录历史,下一轮 LLM 会看到这个结果
step := Step{Thought: thought, Observation: observation}
state.History = append(state.History, step)
}
// 达到最大迭代次数
return &Result{Error: "达到最大迭代次数"}, fmt.Errorf("max iterations")
}
3.3 Prompt 模板
// NewPromptTemplate 创建 Prompt 模板
func NewPromptTemplate() *PromptTemplate {
tmpl := "你是一个 ReAct Agent,可以使用以下工具解决问题。\n\n" +
"## 可用工具\n\n" +
"{{range .Tools}}" +
"\n### {{.Name}}\n{{.Description}}\n" +
"参数:\n{{range $k, $v := .Parameters}}" +
" - {{$k}} ({{$v.Type}}): {{$v.Description}}\n{{end}}" +
"{{end}}\n\n" +
"## 输出格式\n\n" +
"每次回复必须是纯 JSON(不要 markdown 代码块):\n\n" +
"{\n" +
" \"reasoning\": \"你的推理过程\",\n" +
" \"tool_name\": \"工具名称(完成时填 finish)\",\n" +
" \"arguments\": {\"参数名\": \"参数值\"},\n" +
" \"is_finished\": false,\n" +
" \"final_answer\": \"最终答案(仅完成时填写)\"\n" +
"}\n\n" +
"## 历史记录\n\n" +
"{{if .History}}\n" +
"{{range $i, $step := .History}}" +
"\n### 第 {{add $i 1}} 轮\n" +
"- **思考**: {{.Thought.Reasoning}}\n" +
"- **行动**: {{.Thought.ToolName}}({{json .Thought.Arguments}})\n" +
"- **观察**: {{if .Observation.IsError}}错误: {{else}}成功: {{end}}{{.Observation.Content}}\n" +
"{{end}}\n" +
"{{else}}\n(无历史记录)\n{{end}}\n\n" +
"## 用户输入\n\n" +
"{{.UserInput}}\n\n"
return &PromptTemplate{template: t}
}
四、Planning 的关键:上下文传递
Planning 之所以能"动态",是因为每一步都能看到之前的结果:
4.1 执行流程示例
用户输入: "查订单 ORD001 能否改签"
═════════════════════════════════════════════════════════════
【第 1 轮循环】
═════════════════════════════════════════════════════════════
LLM 看到:
┌─────────────────────────────────────────────────────┐
│ - 用户输入:"查订单 ORD001 能否改签" │
│ - 可用工具:[order_query, change_check, recommend] │
│ - 历史:(空) │
└─────────────────────────────────────────────────────┘
LLM 决策:
┌─────────────────────────────────────────────────────┐
│ reasoning: "需要先查订单状态" │
│ tool_name: "order_query" │
│ arguments: {"order_id": "ORD001"} │
└─────────────────────────────────────────────────────┘
执行结果:
┌─────────────────────────────────────────────────────┐
│ {"status": "已出票", "flight": "CA1234"} │
└─────────────────────────────────────────────────────┘
═════════════════════════════════════════════════════════════
【第 2 轮循环】
═════════════════════════════════════════════════════════════
LLM 看到:
┌─────────────────────────────────────────────────────┐
│ - 用户输入:"查订单 ORD001 能否改签" │
│ - 可用工具:[order_query, change_check, recommend] │
│ - 历史: │
│ 第 1 轮: │
│ 思考: 需要先查订单状态 │
│ 行动: order_query({"order_id": "ORD001"}) │
│ 观察: {"status": "已出票", "flight": "CA1234"} │
└─────────────────────────────────────────────────────┘
LLM 决策(基于第 1 轮结果):
┌─────────────────────────────────────────────────────┐
│ reasoning: "订单已出票,需要检查改签条件" │
│ tool_name: "change_check" │
│ arguments: {"order_id": "ORD001"} │
└─────────────────────────────────────────────────────┘
执行结果:
┌─────────────────────────────────────────────────────┐
│ {"can_change": true, "fee": 200} │
└─────────────────────────────────────────────────────┘
═════════════════════════════════════════════════════════════
【第 3 轮循环】
═════════════════════════════════════════════════════════════
LLM 看到:
┌─────────────────────────────────────────────────────┐
│ - 历史: │
│ 第 1 轮:order_query → {"status": "已出票"} │
│ 第 2 轮:change_check → {"can_change": true} │
└─────────────────────────────────────────────────────┘
LLM 决策:
┌─────────────────────────────────────────────────────┐
│ reasoning: "可以改签,生成最终答案" │
│ is_finished: true │
│ final_answer: "订单 ORD001 可以改签,手续费 200 元" │
└─────────────────────────────────────────────────────┘
4.2 关键代码:历史传递
// 每轮循环,历史都会追加
state.History = append(state.History, Step{
Thought: thought, // LLM 的思考
Observation: observation, // 工具执行结果
})
// 下一轮生成 Prompt 时,历史会被包含
promptData := PromptData{
History: state.History, // ← 关键:传递历史
}
五、后端类比
5.1 架构映射
|------------|--------------------|
| 传统后端 | Agent Planning |
| Controller | Prompt(接收请求) |
| Service 编排 | Planning(流程控制) |
| Service | Skills(业务能力) |
| DAO | Tool(原子操作) |
5.2 代码对比
传统后端(写死流程):
public Result handleOrder(String orderId) {
// Step 1: 查订单
Order order = orderDao.query(orderId);
// 预判所有分支
if (order == null) {
return Result.error("订单不存在");
}
if (!order.getStatus().equals("已出票")) {
return Result.error("订单状态不允许改签");
}
// Step 2: 检查改签条件
ChangeResult change = changeService.check(orderId);
if (!change.isCanChange()) {
return Result.error(change.getReason());
}
// Step 3: 推荐方案
return recommendService.suggest(order, change);
}
Agent Planning(动态流程):
func (a *Agent) Run(userInput string) (*Result, error) {
for i := 0; i < maxIterations; i++ {
// LLM 根据历史动态决策
thought := a.llm.GenerateThought(prompt)
if thought.IsFinished {
return thought.FinalAnswer
}
// 执行工具
result := execute(thought.ToolName, thought.Arguments)
// 记录历史,下一轮会看到
history.Append(result)
}
}
5.3 核心差异
|--------|---------------|--------------------|
| 维度 | 传统后端 | Agent Planning |
| 流程定义 | 程序员写死 | LLM 动态生成 |
| 分支处理 | 预判所有 if-else | LLM 根据上下文决策 |
| 异常处理 | try-catch 预定义 | LLM 自动调整路径 |
| 扩展性 | 需要改代码 | 只需加工具定义 |
六、运行示例
# 进入项目目录
cd chao-go
# Mock 模式运行(不需要 API Key)
go run examples/react-demo/main.go
# 真实 LLM 模式
go run examples/react-demo/main.go -real -input "格式化 JSON 并计算 MD5"
输出示例:
╔════════════════════════════════════════════════════════════╗
║ ReAct Agent 演示 ║
║ Reasoning + Acting = 思考 + 行动 ║
╚════════════════════════════════════════════════════════════╝
📡 使用阿里百炼 qwen-plus
════════════════════════════════════════════════════════════
🎯 用户输入: 格式化 JSON 并计算 MD5
════════════════════════════════════════════════════════════
【第 1 轮循环】
────────────────────────────────────────────────────────
🤔 [Phase 1: Thought - 思考]
推理: 用户需要格式化 JSON 并计算 MD5。我先调用 json_format 工具格式化 JSON。
决定调用: json_format
参数: map[input:{"name":"test","value":123}]
👉 [Phase 2: Action - 行动]
执行工具: json_format
👀 [Phase 3: Observation - 观察]
✅ 结果: {
"name": "test",
"value": 123
}
【第 2 轮循环】
────────────────────────────────────────────────────────
🤔 [Phase 1: Thought - 思考]
推理: JSON 已格式化成功。现在调用 crypto_hash 计算 MD5 哈希值。
决定调用: crypto_hash
参数: map[input:{"name":"test","value":123} algorithm:md5]
👉 [Phase 2: Action - 行动]
执行工具: crypto_hash
👀 [Phase 3: Observation - 观察]
✅ 结果: md5({"name":"test","value":123}) = simulated_hash_28
【第 3 轮循环】
────────────────────────────────────────────────────────
🤔 [Phase 1: Thought - 思考]
✅ 任务完成!
最终答案: 已完成!
1. JSON 格式化结果:{ "name": "test", "value": 123 }
2. MD5 哈希值:md5(...) = simulated_hash_28
════════════════════════════════════════════════════════════
📊 执行结果
════════════════════════════════════════════════════════════
✅ 成功: true
📝 步骤数: 2
🎯 最终答案: 已完成!...
七、总结
核心观点
|----------|------------------------------------------|
| 维度 | 说明 |
| 本质 | 业务流程编排层,动态生成执行计划 |
| 实现 | ReAct 循环(Thought → Action → Observation) |
| 关键 | 上下文传递,每步决策都能看到历史结果 |
| 价值 | 流程动态调整,无需写死所有分支 |
| 后端类比 | Service 编排层,但由 LLM 动态决策 |
Planning 在五层架构中的位置
Prompt(规则层)
↓
Planning(规划层)← 动态生成执行计划
↓
MCP(连接层)
↓
Skills(能力层)
↓
Harness(质量层)
一句话总结
Planning 让 Agent 从"执行固定流程"进化为"动态规划路径",是 Agent 自主性的核心来源。