# 实践案例01：用LangGraph构建空间决策工作流

## 目标

通过本实践，你将：
1. 理解状态驱动的Agent设计
2. 实现一个简单的空间决策工作流
3. 添加Human-in-the-Loop审查点
4. 处理工作流中的错误和重试

---

## 背景知识

### 什么是LangGraph

LangGraph是构建**有状态**的多Agent应用的框架：

```
核心概念：

1. State（状态）: 在节点间传递的数据
2. Node（节点）: 处理状态的函数
3. Edge（边）: 节点之间的连接
4. Graph（图）: 节点和边组成的完整工作流
```

### 为什么用LangGraph

- **状态管理**: 自动管理工作流状态
- **可视化**: 可以绘制和查看工作流图
- **持久化**: 支持中断和恢复
- **条件路由**: 基于状态动态选择路径

---

## 实践步骤

### 步骤1：安装依赖

```bash
pip install langgraph langchain-core langchain-anthropic
```

### 步骤2：定义状态

```python
from typing import TypedDict, Annotated, List, Optional
from operator import add
from typing_extensions import TypedDict

class EcologicalAnalysisState(TypedDict):
    """生态网络分析状态"""

    # 输入
    input_path: str
    parameters: dict

    # 处理过程
    current_step: str
    intermediate_results: dict

    # 人机交互
    review_requested: bool
    human_feedback: Optional[str]

    # 输出
    final_result: Optional[dict]
    errors: Annotated[List[str], add]
```

### 步骤3：定义节点

```python
def load_data_node(state: EcologicalAnalysisState) -> EcologicalAnalysisState:
    """加载数据节点"""
    print("执行: load_data")
    # 实际实现中读取文件
    return {
        **state,
        "current_step": "data_loaded",
        "intermediate_results": {"data": "loaded"}
    }

def identify_sources_node(state: EcologicalAnalysisState) -> EcologicalAnalysisState:
    """识别源地节点"""
    print("执行: identify_sources")
    # 实际实现中运行源地识别算法
    sources = [{"id": 1, "area": 1000}, {"id": 2, "area": 800}]
    return {
        **state,
        "current_step": "sources_identified",
        "intermediate_results": {**state["intermediate_results"], "sources": sources}
    }

def human_review_node(state: EcologicalAnalysisState) -> EcologicalAnalysisState:
    """人类审查节点"""
    print("执行: human_review")
    print(f"待审查: {state['intermediate_results']}")
    # 实际实现中等待人类输入
    return {
        **state,
        "review_requested": False,
        "human_feedback": "approved"
    }

def build_resistance_node(state: EcologicalAnalysisState) -> EcologicalAnalysisState:
    """构建阻力面节点"""
    print("执行: build_resistance")
    return {
        **state,
        "current_step": "resistance_built"
    }
```

### 步骤4：定义路由

```python
def should_review(state: EcologicalAnalysisState) -> str:
    """决定是否需要审查"""
    sources = state["intermediate_results"].get("sources", [])
    if len(sources) > 2:  # 源地数量多时需要审查
        return "review"
    return "continue"
```

### 步骤5：构建图

```python
from langgraph.graph import StateGraph, END

def build_workflow():
    """构建工作流图"""

    # 创建图
    workflow = StateGraph(EcologicalAnalysisState)

    # 添加节点
    workflow.add_node("load_data", load_data_node)
    workflow.add_node("identify_sources", identify_sources_node)
    workflow.add_node("human_review", human_review_node)
    workflow.add_node("build_resistance", build_resistance_node)

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

    # 添加边
    workflow.add_edge("load_data", "identify_sources")

    # 添加条件边
    workflow.add_conditional_edges(
        "identify_sources",
        should_review,
        {
            "review": "human_review",
            "continue": "build_resistance"
        }
    )

    workflow.add_edge("human_review", "build_resistance")
    workflow.add_edge("build_resistance", END)

    # 编译
    return workflow.compile()
```

### 步骤6：运行工作流

```python
def run_workflow():
    """运行工作流"""

    # 初始状态
    initial_state = {
        "input_path": "data.geojson",
        "parameters": {},
        "current_step": "start",
        "intermediate_results": {},
        "review_requested": False,
        "human_feedback": None,
        "final_result": None,
        "errors": []
    }

    # 构建并运行
    app = build_workflow()
    result = app.invoke(initial_state)

    print("\n=== 最终结果 ===")
    print(result)
```

---

## 扩展练习

### 1. 添加错误处理

```python
def with_error_handling(node_func):
    """装饰器：添加错误处理"""
    def wrapper(state):
        try:
            return node_func(state)
        except Exception as e:
            return {
                **state,
                "errors": [str(e)]
            }
    return wrapper

# 使用
@with_error_handling
def risky_node(state):
    # 可能出错的节点
    ...
```

### 2. 添加检查点（持久化）

```python
from langgraph.checkpoint.memory import MemorySaver

# 创建检查点保存器
memory = MemorySaver()

# 编译时添加检查点
app = workflow.compile(checkpointer=memory, interrupt_before=["human_review"])

# 运行时可以指定thread_id
config = {"configurable": {"thread_id": "conversation-1"}}
result = app.invoke(initial_state, config=config)
```

### 3. 可视化工作流

```python
from IPython.display import Image, display

# 生成图
app = build_workflow()
display(Image(app.get_graph().draw_mermaid_png()))
```

---

## 完整代码示例

```python
"""
完整的LangGraph空间决策工作流示例
"""
from typing import TypedDict, Annotated, List, Optional, Literal
from operator import add
from langgraph.graph import StateGraph, END

class State(TypedDict):
    """工作流状态"""
    step: int
    data: Optional[dict]
    sources: Optional[list]
    reviewed: bool
    result: Optional[str]
    errors: Annotated[List[str], add]

# 节点函数
def load_node(state: State) -> State:
    """加载数据"""
    print(f"[节点: load] 步骤 {state['step']}")
    return {**state, "step": state["step"] + 1, "data": {"loaded": True}}

def analyze_node(state: State) -> State:
    """分析数据"""
    print(f"[节点: analyze] 步骤 {state['step']}")
    return {
        **state,
        "step": state["step"] + 1,
        "sources": [{"id": 1, "value": 100}]
    }

def review_node(state: State) -> State:
    """人类审查"""
    print(f"[节点: review] 步骤 {state['step']}")
    print("等待人类审查...")
    # 实际实现中等待输入
    return {**state, "step": state["step"] + 1, "reviewed": True}

def finalize_node(state: State) -> State:
    """完成"""
    print(f"[节点: finalize] 步骤 {state['step']}")
    return {**state, "result": "completed"}

# 路由函数
def route_after_analyze(state: State) -> Literal["review", "finalize"]:
    """分析后的路由"""
    if state.get("sources") and len(state["sources"]) > 0:
        return "review"
    return "finalize"

# 构建图
def build_graph():
    """构建工作流图"""
    graph = StateGraph(State)

    # 添加节点
    graph.add_node("load", load_node)
    graph.add_node("analyze", analyze_node)
    graph.add_node("review", review_node)
    graph.add_node("finalize", finalize_node)

    # 添加边
    graph.set_entry_point("load")
    graph.add_edge("load", "analyze")

    # 条件边
    graph.add_conditional_edges(
        "analyze",
        route_after_analyze,
        {"review": "review", "finalize": "finalize"}
    )

    graph.add_edge("review", "finalize")
    graph.add_edge("finalize", END)

    return graph.compile()

# 运行
if __name__ == "__main__":
    print("=== LangGraph 空间决策工作流 ===\n")

    app = build_graph()

    initial_state: State = {
        "step": 1,
        "data": None,
        "sources": None,
        "reviewed": False,
        "result": None,
        "errors": []
    }

    result = app.invoke(initial_state)

    print(f"\n最终状态: {result['step']}")
    print(f"结果: {result['result']}")
```

---

## 反思问题

1. **状态设计**：你的状态中哪些信息是必需的？哪些可以省略？

2. **节点粒度**：节点应该多大？如何平衡？

3. **错误处理**：当节点失败时，工作流应该如何处理？

4. **审查点**：你的工作流中哪些地方需要人类介入？

---

## 下一步

完成这个实践后，你已经：
- ✅ 理解了状态驱动的Agent设计
- ✅ 实现了一个简单的LangGraph工作流
- ✅ 掌握了条件路由的基本方法
- ✅ 了解了如何添加HITL审查点

准备好进入下一章：**02-spatial-intelligence（空间智能）**
