我们将把你的智能体,从一个"单打独斗的码农",升级为一个包含"架构师 (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 的产生率。