1.任务目标

构建一个基于状态机的多步骤智能体,使其能够执行循环、分支和暂停操作,系统不该依赖脆弱的 while True 循环。

2.任务目的

在生产环境中,简单的“聊天循环”极易崩溃。实际业务工作流需要确定性的状态管理和故障恢复机制。

3.demo设计

构建一个“智能内容生成与审查”工作流,包含以下几个核心状态节点:

  1. 分类节点 (classifier):根据输入任务类型进行分支路由。

  2. 生成节点 (generator):撰写或优化初稿。

  3. 评审节点 (critic):检查初稿质量。如果质量不合格且未达到最大循环次数,则循环回生成节点重试。

  4. 人工审批节点 (human_approval):利用 LangGraph 的 Checkpointer 实现持久化与暂停,等待人工确认后才继续。

4.python代码实现

基本的deepseek api配置下就行,主要看节点与状态的跳转

import os
from pathlib import Path
from typing import Literal, TypedDict

from dotenv import load_dotenv
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, StateGraph
from langgraph.types import interrupt
from pydantic import BaseModel, Field

load_dotenv(Path(__file__).resolve().parent / ".env")


def _require_env(name: str) -> str:
    value = os.getenv(name, "").strip()
    if not value:
        raise RuntimeError(f"缺少环境变量 {name},请在 PythonProjects/.env 中配置")
    return value


def build_llm() -> ChatOpenAI:
    return ChatOpenAI(
        model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat").strip() or "deepseek-chat",
        api_key=_require_env("DEEPSEEK_API_KEY"),
        base_url=_require_env("DEEPSEEK_BASE_URL"),
        temperature=0.2,
        timeout=60,
        max_retries=2,
        max_completion_tokens=1200,
    )


llm = build_llm()


class CategoryResult(BaseModel):
    category: Literal["code", "text"] = Field(description="code 表示代码任务,text 表示文本任务")


class CritiqueResult(BaseModel):
    approved: bool = Field(description="草稿是否达到可提交人工终审的质量")
    feedback: str = Field(description="未通过时给出具体修改意见;通过时简要说明理由")


classifier_llm = llm.with_structured_output(CategoryResult, method="function_calling")
critic_llm = llm.with_structured_output(CritiqueResult, method="function_calling")


# 1. 定义状态结构 (State)
class AgentState(TypedDict):
    task: str
    category: str              # 分支类型
    draft: str                 # 当前草稿
    feedback: str              # 评审意见
    iteration: int             # 循环计数器
    approved: bool             # 审批状态


# 2. 定义各个节点函数 (Nodes)
def classifier_node(state: AgentState) -> dict:
    print(f"--- [节点: 分类] 正在分析任务: {state['task']} ---")
    result = classifier_llm.invoke([
        SystemMessage(content=(
            "你是任务分类器。根据任务内容只返回一个类别:"
            "code 表示需要编写、修改或解释代码;text 表示文章、说明、总结等非代码内容。"
        )),
        HumanMessage(content=state["task"]),
    ])
    print(f"--- [节点: 分类] 结果: {result.category} ---")
    return {"category": result.category, "iteration": 0}


def generator_node(state: AgentState) -> dict:
    iter_count = state.get("iteration", 0) + 1
    print(f"--- [节点: 生成] 第 {iter_count} 次生成内容 ---")
    feedback = state.get("feedback") or "无"
    result = llm.invoke([
        SystemMessage(content=(
            "你是内容生成器。根据任务类别生成一份简洁草稿。"
            "category 为 code 时输出可运行的代码;category 为 text 时输出正文。"
            "如果已有评审反馈,按反馈修改。只输出草稿正文。"
        )),
        HumanMessage(content=(
            f"任务: {state['task']}\n"
            f"类别: {state.get('category', 'text')}\n"
            f"上一版草稿: {state.get('draft') or '无'}\n"
            f"评审反馈: {feedback}"
        )),
    ])
    return {"draft": result.content, "iteration": iter_count}


def critic_node(state: AgentState) -> dict:
    print("--- [节点: 评审] 正在评估草稿质量 ---")
    result = critic_llm.invoke([
        SystemMessage(content=(
            "你是评审员。判断草稿是否完成任务、结构清楚且没有明显错误。"
            "达到标准则 approved 为 true;否则为 false,并在 feedback 中指出需要修改的点。"
        )),
        HumanMessage(content=(
            f"任务: {state['task']}\n"
            f"类别: {state.get('category', 'text')}\n"
            f"草稿:\n{state.get('draft', '')}"
        )),
    ])
    print(f"--- [节点: 评审] approved={result.approved} feedback={result.feedback} ---")
    return {"feedback": result.feedback, "approved": result.approved}


# 3. 定义条件边与路由逻辑 (Branching & Looping)
def route_after_critic(state: AgentState) -> Literal["generator", "human_review"]:
    """
    循环与分支决策逻辑:
    - 如果未通过评审,且循环次数小于 3,则循环回 generator
    - 否则进入人工审批暂停环节
    """
    if not state["approved"] and state["iteration"] < 3:
        print(">>> [路由决策] 评估未通过,触发循环:返回生成节点修改。")
        return "generator"
    else:
        print(">>> [路由决策] 评估通过或达到上限,进入人工审批环节。")
        return "human_review"


def human_review_node(state: AgentState) -> dict:
    """暂停并等待人工确认。通过后发布,驳回则带着修改意见回到生成节点。"""
    print("--- [节点: 人工审核] 等待确认 ---")
    decision = interrupt({
        "task": state["task"],
        "draft": state.get("draft", ""),
        "feedback": state.get("feedback", ""),
        "iteration": state.get("iteration", 0),
    })
    approved = bool(decision.get("approved"))
    feedback = (decision.get("feedback") or "").strip() or state.get("feedback", "")
    print(f"--- [节点: 人工审核] approved={approved} feedback={feedback} ---")
    return {"approved": approved, "feedback": feedback}


def route_after_human(state: AgentState) -> Literal["generator", "final_polish"]:
    if state["approved"]:
        print(">>> [路由决策] 人工审核通过,进入终审发布。")
        return "final_polish"
    print(">>> [路由决策] 人工审核驳回,返回生成节点修改。")
    return "generator"


def final_polish(state: AgentState) -> dict:
    print(f"--- [节点: 终审发布] 内容已成功发布: {state['draft']} ---")
    return {"approved": True}


# 4. 构建状态图 (StateGraph)
workflow = StateGraph(AgentState)

# 添加节点
workflow.add_node("classifier", classifier_node)
workflow.add_node("generator", generator_node)
workflow.add_node("critic", critic_node)
workflow.add_node("human_review", human_review_node)
workflow.add_node("final_polish", final_polish)

# 设置入口
workflow.set_entry_point("classifier")

# 添加普通边
workflow.add_edge("classifier", "generator")
workflow.add_edge("generator", "critic")

# 添加条件边(实现循环与分支跳转)
workflow.add_conditional_edges(
    "critic",
    route_after_critic,
    {
        "generator": "generator",
        "human_review": "human_review",
    }
)
workflow.add_conditional_edges(
    "human_review",
    route_after_human,
    {
        "generator": "generator",
        "final_polish": "final_polish",
    }
)

workflow.add_edge("final_polish", END)

# 5. 配置持久化。人工审核节点内部调用 interrupt() 暂停,等待确认后再继续。
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)

测试脚本

from langgraph.types import Command

from main import app

# 初始化线程配置(用于状态持久化追踪)
config = {"configurable": {"thread_id": "production_task_001"}}

# 初始输入
initial_input = {"task": "编写一个Python快速排序脚本"}


def run_until_pause(graph_input):
    """执行到人工审核暂停点,或直到流程结束。"""
    interrupted = False
    for event in app.stream(graph_input, config):
        if "__interrupt__" in event:
            interrupted = True
            continue
        print(event)
    return interrupted


def ask_human(snapshot) -> dict | None:
    values = snapshot.values
    print("\n========== 人工审核 ==========")
    print(f"任务: {values.get('task')}")
    print(f"当前轮次: {values.get('iteration')}")
    print(f"模型评审: {values.get('feedback')}")
    print("---------- 草稿 ----------")
    print(values.get("draft"))
    print("--------------------------")
    while True:
        choice = input("请确认:通过输入 y,驳回输入 n,退出输入 q: ").strip().lower()
        if choice in {"y", "yes", "通过"}:
            return {"approved": True, "feedback": values.get("feedback") or "人工审核通过"}
        if choice in {"n", "no", "驳回"}:
            feedback = input("请输入修改意见: ").strip()
            if not feedback:
                print("驳回时需要填写修改意见。")
                continue
            return {"approved": False, "feedback": feedback}
        if choice in {"q", "quit", "退出"}:
            return None
        print("请输入 y、n 或 q。")


print("=== 工作流开始运行 ===")
pending = run_until_pause(initial_input)

while pending:
    decision = ask_human(app.get_state(config))
    if decision is None:
        print("\n已退出。当前草稿未发布,本次进程结束后暂停状态不会保留。")
        break
    print("\n=== 已收到人工审核结果,恢复工作流 ===")
    pending = run_until_pause(Command(resume=decision))
else:
    print("\n=== 工作流结束 ===")