refactor: 重组项目目录结构
以讲义内容为骨架迁移到标准目录格式: - officefile/ 主内容(12章 + 附录 + CC4SI补充) - dofile/ 代码示例(11个Python脚本) - data/ 图片资源 - output/ 生成输出(忽略) - Archive/ 归档旧目录(忽略) - .claude/skills/ 保留markdown-to-docx工具链 - .pandoc/ 保留CSL和本地化配置 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
# 实践案例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(空间智能)**
|
||||
Reference in New Issue
Block a user