第 5 章:为什么需要框架 ------ 手搓的六道坎
手搓了 ReAct、工具循环、Agentic RAG------够用了吗? 把系统推向生产时会撞上六道坎:
- 状态管理乱:messages、scratchpad、检索结果、计划清单......全是散装变量,流程一复杂就成面条代码;
- 流程不可视:口头说"先规划再执行遇错反思",代码里却是嵌套 if/while,新同事看三天;
- 挂了不能续:跑到第 8 步崩了/用户关了页面,只能从头再来------长任务不可接受;
- 没法暂停等人 :"人工确认"是靠工具返回话术软实现的,无法真正暂停进程等审批;
- 多会话隔离:一百个用户同时用,各自的状态怎么存、怎么取?
- 可观测性:哪一步慢了、贵了、错了?打印大法撑不住生产。
LangGraph 的答案 :把 Agent 建模成状态图(State Graph) ------状态集中定义、流程显式画成节点和边、每步自动存档(Checkpoint)、原生支持暂停/恢复/人工介入、按 thread_id 天然隔离会话、每步可追踪。接下来五章,你手搓过的每样东西都会在图上找到自己的工位。
第 6 章:LangChain 核心件速通 ------ Model / Prompt / Tool / LCEL
定位校准:2026 年的正确姿势是"LangChain 当零件库用,LangGraph 当装配线用"。所以本章只速通四个真正高频的零件------每个都会立刻映射到你手搓过的东西。
6.1 Model:统一的模型接口
python
import os
from langchain_openai import ChatOpenAI # pip install langchain-openai
llm = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com", # 老规矩:换平台改两个字符串
temperature=0.3,
)
resp = llm.invoke("用一句话解释LangGraph") # 简单场景直接传字符串
print(resp.content) # 返回的是 AIMessage 对象
消息对象化 :LangChain 把 {"role": "user", ...} 字典升级成了类------SystemMessage / HumanMessage / AIMessage / ToolMessage,与你熟悉的四种 role 一一对应;AIMessage.tool_calls 是解析好的申请单列表({"name", "args", "id"},args 已经是 dict,不用再 json.loads------框架替你做了)。字典写法在 1.x 里也仍然兼容,两种都要认识。
6.2 Tool:@tool 装饰器
python
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""查询指定城市当前实时天气。用户询问天气/气温/穿衣建议时使用。"""
import requests
r = requests.get(f"https://wttr.in/{city}?format=3", timeout=10)
return r.text.strip()
print(get_weather.name) # get_weather
print(get_weather.args_schema.model_json_schema()) # schema 自动生成了!
@tool 读函数签名(类型注解)+ docstring,自动生成你那坨 JSON Schema------ "docstring 会被喂给大模型",至此完全兑现。绑定到模型:
python
llm_with_tools = llm.bind_tools([get_weather])
ai_msg = llm_with_tools.invoke("上海热不热")
print(ai_msg.tool_calls) # [{'name': 'get_weather', 'args': {'city': '上海'}, 'id': ...}]
6.3 Prompt 模板与 LCEL 管道(看懂即可,重度使用在图里)
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "你是{role},回答不超过50字"),
("user", "{question}"),
])
chain = prompt | llm | StrOutputParser() # LCEL:用 | 把组件接成流水线
print(chain.invoke({"role": "宋朝诗人", "question": "什么是AI"}))
| 管道(LCEL)= "上一个的输出当下一个的输入",并白送 .invoke/.stream/.batch/.ainvoke 四件套。认知边界:LCEL 适合线性流程;有分支、循环、状态的一律上 LangGraph------官方 1.x 也是这么划的地界。
6.4 结构化输出的框架姿势
python
from pydantic import BaseModel, Field
class Plan(BaseModel):
steps: list[str] = Field(description="按顺序执行的步骤清单,每步一句话")
planner = llm.with_structured_output(Plan) # 底层自动走FC或JSON Mode
plan = planner.invoke("帮我调研'2026国产大模型价格战'并写份简报,先列执行计划")
print(plan.steps) # 直接就是校验好的 Plan 对象!
with_structured_output(PydanticModel) = 你手写的"schema 提示 + JSON Mode + 校验 + 重试"一条龙。
第 7 章:LangGraph 起步 ------ State / Node / Edge 三件套
7.1 心智模型:Agent = 一张会流转状态的图
LangGraph 三件套
├── State(状态):一个 TypedDict ------ 全流程共享的"公共白板"
├── Node(节点):普通Python函数 ------ 读白板、干活、返回"要更新白板的哪几格"
└── Edge(边):节点间的连线 ------ 固定边(必经) / 条件边(看白板内容选路)
运行 = 状态沿着边在节点间流动,每个节点改一点白板,直到走到 END
7.2 第一张图:三节点流水线(先不带 AI,纯理解机制)
python
"""first_graph.py"""
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict): # ① 定义白板的格子
topic: str
outline: str
article: str
def make_outline(state: State) -> dict: # ② 节点=函数:入参是整个白板
print(" [节点] 生成大纲")
return {"outline": f"《{state['topic']}》三段式大纲"} # 只返回要更新的格子!
def write_article(state: State) -> dict:
print(" [节点] 依据大纲写正文")
return {"article": f"根据[{state['outline']}]写出的正文..."}
builder = StateGraph(State) # ③ 搭图
builder.add_node("outline", make_outline)
builder.add_node("write", write_article)
builder.add_edge(START, "outline") # START→outline→write→END
builder.add_edge("outline", "write")
builder.add_edge("write", END)
graph = builder.compile() # ④ 编译成可执行对象
result = graph.invoke({"topic": "AI Agent 的一天"}) # ⑤ 给初始状态,开跑
print(result) # 终态白板:三个格子都填上了
四条核心规则(背下):
- 节点函数返回"增量字典",不是整个 State------LangGraph 负责合并进白板;
- 默认合并方式 = 覆盖(后写的盖前写的);
compile()之前是图纸,之后才是机器;graph.get_graph().draw_mermaid()能吐出流程图代码------粘进任何 Mermaid 预览器就是架构图,写文档/答辩神器。
7.3 Reducer:改写"合并规则"------add_messages 的真身
对话场景下,白板里的 messages 每次都被覆盖 就完了------我们要的是追加 。LangGraph 用 Annotated 给格子指定专属合并函数(reducer):
python
from typing import Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
class ChatState(TypedDict):
messages: Annotated[list, add_messages] # 合并规则=追加(且按消息id去重/更新)
def chatbot(state: ChatState) -> dict:
reply = llm.invoke(state["messages"]) # llm 直接吃消息列表
return {"messages": [reply]} # 只返回新消息,reducer 负责append
builder = StateGraph(ChatState)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile()
out = graph.invoke({"messages": [{"role": "user", "content": "你好,我叫王小明"}]})
print(out["messages"][-1].content)
看懂 Annotated[list, add_messages]这一行,你就看懂了 LangGraph 一半的示例代码 ------它宣告:这格是消息列表,更新方式是智能追加。(官方还提供了内置的 MessagesState 类,就是上面两行的预制版,class MyState(MessagesState): 其它字段... 继承着用即可