Planner

我们将把你的智能体,从一个"单打独斗的码农",升级为一个包含"架构师 (Planner) + 程序员 (Coder) + 测试员 (Tester)"的微服务开发团队!


🧠 阶段五核心思考:为什么我们需要 Planner(规划师)?

在写代码之前,我们先用架构设计的思维来推演一下:

  • 大模型的局限性(Context Window 的迷失): 对于简单的需求(如"写个回文判断"),让 Claude 直接生成代码(Zero-shot)是很轻松的。但如果用户的需求极其复杂,例如: "写一个脚本,先读取本地的 CSV 配置文件,然后并发请求 3 个不同的天气 API 取平均值,最后把结果存入 SQLite 数据库。"
  • "面条代码"灾难: 如果直接把这个复杂需求扔给 Coder,它大概率会把所有逻辑揉在一个巨大的函数里。这会导致两个致命问题:一是极容易写出 Bug,二是 Tester 一旦报错,满屏的 Traceback 会让 Coder 在修复时彻底"晕头转向",最终在重试 3 次后惨淡收场(触发熔断)。
  • 引入架构师(Planner 节点):

在真实的软件开发中,拿到复杂需求的第一步是系统设计与任务拆解 。我们需要在 Graph 的最前端加入一个 planner_node。它的任务绝对不是写代码,而是输出一份**"施工图纸(步骤列表)"**。


🐍 代码实现:升级 State 与加入 Planner 节点

为了实现这个蓝图,我们需要在全局的 State 中新增一个字段来存放这份"施工图纸",并为 Planner 专门定义一个结构化输出。

Step 1: 升级全局状态 (State) 与定义 Planner 契约
复制代码
from typing import TypedDict, Annotated, List, Optional
import operator
from pydantic import BaseModel, Field

# ==========================================
# 1. 升级全局 State
# ==========================================
class SkillsCreatorState(TypedDict):
    user_requirement: str

    # 【新增】:存放 Planner 拆解出的详细步骤列表
    # 这里用覆盖逻辑即可,因为通常只在最开始规划一次
    plan: List[str]  

    current_code: str
    current_test_code: str
    execution_logs: Annotated[List[str], operator.add]
    iteration_count: int
    error_message: Optional[str]

# ==========================================
# 2. 定义 Planner 的输出结构 (Schema)
# ==========================================
class PlannerOutputSchema(BaseModel):
    """规划师节点输出的结构"""
    steps: List[str] = Field(
        description="""将用户的复杂需求拆解为 3-5 个具体的实现步骤。
每个步骤需要清晰说明要做什么,比如'步骤1:定义数据类','步骤2:实现API请求'等。"""
    )
Step 2: 编写 Planner 节点

这个节点就像一个经验丰富的架构师,只动口(做规划),不动手(写代码)。

复制代码
from langchain_core.prompts import ChatPromptTemplate

# 假设 structured_llm 依然是绑定了 Claude 的实例
# 针对 Planner,我们绑定 Planner 的 Schema
planner_llm = llm.with_structured_output(PlannerOutputSchema)

def planner_node(state: SkillsCreatorState) -> dict:
    """负责将复杂需求拆解为具体步骤的节点"""
    print(">>> [节点执行]: 进入 Planner 节点,正在进行系统架构设计...")

    user_req = state.get("user_requirement")

    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个资深的 Python 架构师。你的任务是将用户的复杂需求,拆解为清晰、解耦的开发步骤。不要写代码,只输出步骤。"),
        ("human", f"用户的需求是:{user_req}")
    ])

    # 调用大模型生成规划
    response: PlannerOutputSchema = planner_llm.invoke(prompt)

    print(f"    [Planner] 拆解完成,共 {len(response.steps)} 个步骤。")
    for i, step in enumerate(response.steps, 1):
        print(f"      - 步骤 {i}: {step}")

    # 将生成的步骤列表作为补丁更新到 State 的 'plan' 字段中
    return {
        "plan": response.steps,
        "execution_logs": [f"[Log] Planner 生成了包含 {len(response.steps)} 步的开发计划。"]
    }
Step 3: 修改 Coder 节点的 Prompt (让程序员看图纸)

现在,工单(State)流转到 Coder 手里时,里面多了一份 plan。我们需要把这份图纸喂给 Claude,让它照着写。

复制代码
# 在 real_coder_node 内部,我们需要做如下修改:

def real_coder_node(state: SkillsCreatorState) -> dict:
    # ... (前面的获取状态代码保持不变) ...
    plan = state.get("plan", []) # 获取施工图纸

    # 将列表拼接成易读的字符串
    plan_str = "\n".join([f"步骤 {i+1}: {step}" for i, step in enumerate(plan)])

    # 在首次生成代码时,把计划加入 Prompt
    if not error_message:
        human_prompt = f"""用户的需求是:{user_req}
        
【架构师提供的开发计划】:
请你严格按照以下步骤来实现代码:
{plan_str}
"""
    # ... (其余逻辑与阶段四完全相同) ...
Step 4: 调整图的拓扑结构 (Graph Topology)

最后,我们需要改变流水线的连接方式。起点不再是 Coder,而是 Planner。

复制代码
from langgraph.graph import StateGraph, START, END

builder = StateGraph(SkillsCreatorState)

builder.add_node("planner", planner_node)
builder.add_node("coder", real_coder_node)
builder.add_node("tester", tester_node)

# ==========================================
# 全新的有向图边连接 (Edges)
# ==========================================
# 1. 启动 -> planner (架构师先上)
builder.add_edge(START, "planner")

# 2. planner -> coder (架构师画完图,交给程序员)
builder.add_edge("planner", "coder")

# 3. coder -> tester (程序员写完,交给 QA)
builder.add_edge("coder", "tester")

# 4. tester -> 【条件分支】 (QA 测试完,决定是结束还是打回给程序员)
builder.add_conditional_edges(
    "tester", 
    route_after_test,
    {
        "success": END,
        "max_retries_reached": END,
        "retry": "coder"  # 注意:如果出错,是打回给 coder 修 Bug,而不是打回给 planner 重新规划!
    }
)

💡 逻辑深度解析

  • 关注"退回逻辑(Retry Path)"的设计:

在 add_conditional_edges 中,当代码测试失败时,我们选择路由回 coder,而不是 planner。

为什么这么设计? 这符合真实的软件工程逻辑。需求拆解和架构(Planner)通常是没问题的,跑不通大概率是因为代码(Coder)的具体实现写错了。如果每次报错都让架构师重新推翻重来,不仅浪费 API Token,还会导致整个系统的上下文极其不稳定。

  • 分治法(Divide and Conquer)的体现:

通过把大问题变成有序号的小任务(plan_str),Claude 在编写代码时会更有逻辑性。它会在代码中写出结构更清晰的函数,甚至主动把不同的步骤封装成不同的子函数,极大降低了 Bug 的产生率。


相关推荐
云和数据.ChenGuang5 小时前
光哥说Python RAG 系统需要哪些评估指标:从检索到生成的全链路评测指南
网络·人工智能·python·算法·langchain
染指111013 小时前
162.Agent-LangGraph-检查点的作用
人工智能·python·langchain·agents
叼烟扛炮13 小时前
[LangChain] 学习笔记(02):LCEL 链式调用与动态模型配置实战
服务器·开发语言·网络·笔记·学习·langchain
YIAN14 小时前
RAG 学到最后,我真正学会的不是“检索”,而是让系统知道自己缺什么
langchain·node.js·llm
lee_curry17 小时前
第十周完整学习笔记:继续升级为“检索更准、权限安全、索引可维护、失败可恢复、长期可运行”的企业级 RAG 系统
python·ai·langchain·agent·langgraph
破烂三角裤1 天前
AI 没有记忆?我给儿童故事 Agent 搭了一套“长期记忆”系统
langchain
云和数据.ChenGuang1 天前
光哥说Python 异常深度解析:从异常模型到工程化实践
java·前端·人工智能·python·langchain
Maiko Star2 天前
【HM-LC】LangChain RAG查询优化详解:查询重写、混合检索与重排序实战
langchain·rag
2501_913061342 天前
MCP核心能力
langchain
小林ixn2 天前
从线性 RAG 到 Agentic RAG:用 LangGraph 搭一个会判断、会纠错、会兜底的检索闭环
langchain·llm·agent