用 LangGraph 构建企业级售前 Agent:一个生产级架构的设计实录

从需求到可维护的状态机:3423 行代码的治理经验

今年上半年,我开发了一个售前方案生成 Agent。业务需求很直接:用户输入项目需求,Agent 自动生成方案文档,在关键节点让人确认,支持断点续传。

半个个月后,我面临一个问题:**代码已经 3423 行,但这不是因为写得太乱,而是业务逻辑本身就复杂。** 需求解析、多方案生成、人机确认、断点续传、知识库检索、多格式导出......这些功能挤在一起,即使结构清晰,改一个地方也要在多个文件间跳转。

这不是代码质量的问题,而是**架构的问题**。

这篇文章记录了我如何用 LangGraph 重新设计这个 Agent 的架构。如果你也在构建复杂的 AI Agent,希望这些经验能帮到你。

一、业务全景:Agent 到底要做什么?

1.1 核心流程

售前方案生成不是一个"输入→输出"的简单过程,而是一个**多阶段、多确认、可中断**的复杂流程:

Phase 1: 需求分析

用户输入 → 意图识别 → 项目档案补全 → 方案规划

↘ 信息不足 → 追问用户

Phase 2: 需求确认 + 模板选择

必填字段收集 → 功能点匹配 → 需求确认 → 模板检索/选择

Phase 3: 文档生成(逐章推进)

模板检索 → 大纲生成 → 确认 → 输出格式选择 → 逐章生成 → 确认 → 下一章

↘ 用户反馈 → 基于原文修改

Phase 4: 导出交付

所有章节通过 → 组装方案 → docx/pptx/飞书导出 → 完成

1.2 关键挑战

| 挑战 | 具体表现 | 技术难点 |

|------|---------|---------|

| 多阶段状态 | 需求收集、大纲确认、章节生成... 每个阶段状态不同 | 状态管理、持久化 |

| 人机协作 | 每个关键节点都要等用户确认或修改 | 中断恢复、子图路由 |

| 可中断性 | 用户可能聊到一半离开,明天继续 | Checkpoint、恢复机制 |

| 内容质量 | 生成的内容要有据可查,不能凭空捏造 | RAG、引用追踪 |

| 多方案并行 | 一个项目可能有技术方案+实施方案+运维方案 | 跨方案状态共享 |

1.3 技术选型

| 组件 | 选型 | 核心理由 |

|------|------|---------|

| Agent 框架 | LangGraph | 原生支持状态机、人机确认、Checkpoint |

| 向量检索 | Qdrant | 本地部署简单,支持双 Collection |

| 持久化 | PostgreSQL + JSONB | 团队熟悉,LangGraph Checkpointer 官方支持 |

| LLM | Qwen(主)+ DeepSeek(备) | 国内可用,结构化输出稳定 |

二、架构设计:从混乱到有序的三层结构

2.1 整体架构图

2.2 核心设计原则

在设计这个架构时,我遵循了三个原则:

**原则 1:统一状态,单一事实来源**

所有节点通过 `DocumentState` 通信,不共享 `self.xxx`。新增字段只改一个地方。

**原则 2:节点纯函数化**

每个节点只做一件事,输入输出都是 `DocumentState`。易测试、易修改。

**原则 3:交互逻辑子图化**

人机确认有 6+ 种分支,用子图封装,每个 handler 独立文件。

三、核心实现:三个关键设计决策

决策 1:统一状态模型 `DocumentState`

**问题**:最初状态散落在 9 个 Pydantic 模型 + DB 的 JSONB 列中,加一个字段要改 5 个文件。

**解决方案**:设计统一的 `DocumentState`,作为所有节点的唯一数据契约。

python 复制代码
class DocumentState(BaseModel):
    """所有节点共享的统一状态"""
    
    # === 会话标识 ===
    session_id: str
    request_id: str
    
    # === 业务状态 ===
    solution_type: str                    # 方案类型
    profile: ProjectProfile              # 项目画像
    outline_sections: List[str]          # 大纲章节列表
    chapters: Dict[int, str]             # 已生成章节
    current_chapter_idx: int             # 当前章节索引
    
    # === 控制流 ===
    status: SessionStatus                # 会话状态枚举
    need_human_review: bool              # 是否需要人确认
    pending_action: Optional[str]        # 等待的用户动作
    
    # === 持久化 ===
    version: int                         # 乐观锁版本
    updated_at: datetime

**关键设计**:

  • 所有字段都是可序列化的(Pydantic 自动处理)

  • `status` 枚举明确定义了所有可能的状态

  • `need_human_review` + `pending_action` 实现了"中断等待用户输入"模式

决策 2:人机确认子图化

**问题**:用户交互有 6+ 种类型(确认、修改章节、修改大纲、重新生成、选择模板......),全部写在一个 `_handle_waiting_user_input` 方法里,新增一种类型要改多个分支。

**解决方案**:将人机确认封装成独立子图,每个处理器独立文件。

python 复制代码
# subgraphs/human_feedback/graph.py
def build_human_feedback_graph():
    builder = StateGraph(DocumentState)
    
    # 路由节点:判断用户意图
    builder.add_node("router", intent_router_node)
    
    # 处理器节点
    builder.add_node("confirm_continue", confirm_continue_handler)
    builder.add_node("modify_chapter", modify_chapter_handler)
    builder.add_node("modify_outline", modify_outline_handler)
    builder.add_node("regenerate_outline", regenerate_outline_handler)
    builder.add_node("select_template", select_template_handler)
    builder.add_node("fallback", fallback_handler)
    
    # 条件路由
    builder.add_conditional_edges(
        "router",
        lambda state: state.pending_action,  # 根据动作类型路由
        {
            "confirm_continue": "confirm_continue",
            "modify_chapter": "modify_chapter",
            "modify_outline": "modify_outline",
            "regenerate_outline": "regenerate_outline",
            "select_template": "select_template",
            "other": "fallback",
        }
    )
    
    return builder.compile()

**效果**:

  • 新增一种确认类型:新建一个 handler 文件 + 在 router 中加一条映射

  • 修改某种确认逻辑:只改对应的 handler,不影响其他

  • 每个 handler 可以独立测试

决策 3:双 Graph 协作

**问题**:

需求分析和文档生成是两种不同性质的流程。前者是多轮对话式的信息收集,后者是线性的章节生成。放在一个 Graph 里会让状态爆炸。

**解决方案**:

拆成两个独立的 Graph,由适配器层协调。

python 复制代码
```python
# RequirementGraph:需求分析专用
# 节点:intent → profile_completion → planner
# 特点:多轮对话、信息补全、追问

# DocumentGraph:文档生成专用
# 节点:plan_outline → generate_chapter → export_doc
# 特点:线性推进、逐章生成、支持修改
```

**协作流程**:

python 复制代码
# 适配器中的状态转换逻辑
class SalesPreAgentService:
    async def process(self, user_input: str, request_id: str):
        state = self._load_state(request_id)
        
        if state.status == "collecting_requirement":
            # 需求分析阶段:使用 RequirementGraph
            state = await self.requirement_graph.ainvoke(state)
            
        elif state.status == "generating_document":
            # 文档生成阶段:使用 DocumentGraph
            state = await self.document_graph.ainvoke(state)
            
        elif state.status == "waiting_confirmation":
            # 人机确认阶段:使用 HumanFeedbackSubgraph
            state = await self.human_feedback_graph.ainvoke(state)
        
        self._save_state(request_id, state)
        return state

四、断点续传:让用户可以随时离开

这是 LangGraph + PostgreSQL Checkpointer 最实用的特性。

4.1 实现原理

python 复制代码
from langgraph.checkpoint.postgres import PostgresSaver

# 初始化 Checkpointer
checkpointer = PostgresSaver(conn_string=DATABASE_URL)

# 编译 Graph 时传入
graph = builder.compile(checkpointer=checkpointer)

# 调用时传入 thread_id(会话标识)
config = {"configurable": {"thread_id": request_id}}
result = await graph.ainvoke(state, config=config)

# 下次调用时,用同样的 thread_id 自动从上次中断处恢复
result = await graph.ainvoke(user_input, config=config)

4.2 关键设计:显式的中断点

不是所有地方都需要断点续传。我只在两个场景设置中断:

  1. **等待用户输入**(`WAITING_USER_INPUT`):需求信息不足时

  2. **等待用户确认**(`WAITING_USER_CONFIRMATION`):大纲/章节生成后

python 复制代码
# 需要中断时的处理
def plan_outline_node(state: DocumentState):
    outline = generate_outline(state)
    state.outline_sections = outline
    state.status = "WAITING_USER_CONFIRMATION"
    state.pending_action = "confirm_outline"
    # 返回时自动触发中断,状态被持久化
    return state

五、效果与经验

5.1 重构后的量化指标

| 指标 | 重构前 | 重构后 |

|------|--------|--------|

| 最长文件行数 | 3423 行 | ~400 行 |

| 新增确认类型耗时 | 2 小时(改 4 个分支) | 20 分钟(新增 1 个 handler) |

| 修改生成逻辑影响范围 | 可能波及路由 | 只改对应节点 |

| 单元测试覆盖 | ~10% | ~60% |

| 新人理解代码时间 | 1 周 | 2 天 |

5.2 最重要的 3 个经验

**经验 1:先统一状态,再谈拆分**

没有清晰的状态定义,拆出来的节点会互相污染。花 1 天设计 `DocumentState`,后续省 10 天。

**经验 2:子图是管理复杂度的核武器**

6+ 种人机确认分支,一个子图全部封装。主 Graph 只看得到 `human_feedback` 这个节点,细节全在内部。

**经验 3:Checkpointer 不仅仅是断点续传**

有了 Checkpointer,你可以:

  • 调试:重放任意一次执行过程

  • 回滚:回到上一个稳定状态

  • 审计:查看完整的执行历史

六、下一步:从可用到可靠

虽然架构已经稳定,但还有一些工作在进行中:

  • **可观测性**:接入 LangSmith,追踪每次调用的 token 消耗和延迟

  • **评估体系**:用 RAGAS 建立 RAG 效果评估,量化每次优化

  • **容器化**:补全 Dockerfile 和 docker-compose,降低部署门槛

  • **成本控制**:实现 token 预算熔断,防止意外超支

## 写在最后

回顾这个项目,最大的感悟是:**AI Agent 的复杂性不在 AI,在 Agent。**

LLM 调用很简单,难的是状态管理、人机协作、断点续传、多流程编排。LangGraph 提供了很好的基础设施,但如何组织代码、如何分层、如何让系统可维护,这些问题框架不会替你回答。

如果你也在做类似的 Agent,希望这篇文章能给你一些启发。欢迎交流讨论。

相关推荐
野生技术架构师2 小时前
2026最新Java面试1200题全解析:从基础到架构,覆盖所有技术栈(含答案)
java·面试·架构
老码观察2 小时前
如何画好架构图——从五大场景拆解架构表达的方法论
架构
信息安全失业大专人员2 小时前
工业控制系统(ICS/OT)网络安全架构
安全·web安全·架构
渣渣苏2 小时前
LangChain 的 Deep Agents:生产级智能体引擎的架构
架构·langchain·deep agents·harness
小小编程路2 小时前
架构与性能优化
性能优化·架构
heimeiyingwang2 小时前
【架构实战】订单系统架构设计:电商核心系统的演进
unity·架构·系统架构
段一凡-华北理工大学2 小时前
工业领域的Hadoop架构学习~系列文章03:MapReduce编程模型深度解读
大数据·人工智能·hadoop·学习·架构·高炉炼铁·高炉智能化
蓝速科技2 小时前
3D 数字人全息舱算力部署方案对比:本地 X86 独显架构与云端 RK 架构怎么选才好
数据结构·人工智能·算法·架构·排序算法
Regentsoft丽晶软件2 小时前
传统单体架构拖垮分销效率:2026品牌分销系统微服务化升级的价值拆解
微服务·云原生·架构