AI Agent Python 学习路线
首页
  • Month 1 概览
  • Week 1 · Python 语法速成
  • Week 2-3 · FastAPI 实战
  • Week 4 · AI / LLM 基础
  • Month 2 概览
  • Week 5-6 · LangChain / LangGraph
  • Week 7-8 · RAG 检索增强生成
  • Month 3 概览
  • Week 9-10 · 架构设计
  • Week 11 · 实现 + 调试
  • Week 12 · 部署 + 复盘
📚 资源 & 避坑
首页
  • Month 1 概览
  • Week 1 · Python 语法速成
  • Week 2-3 · FastAPI 实战
  • Week 4 · AI / LLM 基础
  • Month 2 概览
  • Week 5-6 · LangChain / LangGraph
  • Week 7-8 · RAG 检索增强生成
  • Month 3 概览
  • Week 9-10 · 架构设计
  • Week 11 · 实现 + 调试
  • Week 12 · 部署 + 复盘
📚 资源 & 避坑

Week 5-6:LangChain / LangGraph 入门

🎯 本周目标:用 LangGraph 重写 Month 1 的 Function Calling 例子,体会框架带来的便利。

1. 为什么需要框架?

Month 1 Week 4 你手写了 150+ 行代码实现 Function Calling。现在让我们看看 LangChain/LangGraph 能帮你省多少事。

没有框架 vs 有框架

任务手写代码(Month 1)LangGraph
调用 LLM + Function Calling手动拼 messages、tools schema、循环处理 tool_calls框架自动处理 ✨
多步骤决策流程手写 if/else + 状态传递StateGraph 声明式定义 ✨
多 Agent 协作完全自己写编排Graph 支持多节点协作 ✨
流式输出手动处理 chunk框架提供 astream() ✨
记忆管理手动维护 messages 列表Checkpointer 持久化 ✨
不同 LLM 切换改 API URL + schema换一行构造函数 ✨

框架选型:LangChain vs LangGraph

框架定位适用场景
LangChain模块化组件库简单链(Chain)、快速原型
LangGraph图编排引擎(LangChain 生态的核心)有状态、多步骤、可循环的 Agent

💡 现在主流选择是 LangGraph

LangChain 老版本的 Chain(LCEL 之前)基本不再推荐。所有新 Agent 项目直接用 LangGraph。


2. 安装 LangGraph

# 核心依赖
.venv\Scripts\python -m pip install langgraph langchain langchain-core langchain-community

# LLM 适配层(硅基流动用 OpenAI 兼容接口)
.venv\Scripts\python -m pip install langchain-openai

💡 TS 对照:langchain-openai 就像给 axios 写了一个适配层,让你用 LangChain 的统一接口调任何兼容 OpenAI API 的模型。


3. 核心概念对照

LangGraph 组件 → TypeScript 类比

LangGraph 组件功能TS / React 类比
StateGraph定义状态 + 节点 + 边的图Redux reducer + state
State图中流转的数据结构Redux state / React context
Node图中的一个处理步骤Express middleware / Composable function
Edge节点之间的流转逻辑React Router / event bus
Conditional Edge根据状态决定走哪条路Redux saga / if-else 路由
Checkpointer状态持久化localStorage / sessionStorage
ToolAgent 可调用的函数后端 API endpoint

代码结构对照

# ===== LangGraph 伪代码 =====
from langgraph.graph import StateGraph
from typing import TypedDict

class MyState(TypedDict):
    # ... 定义状态字段

graph = StateGraph(MyState)
graph.add_node("node_a", handler_a)
graph.add_node("node_b", handler_b)
graph.set_entry_point("node_a")
graph.add_edge("node_a", "node_b")        # 固定流转
graph.add_conditional_edges(...)          # 条件流转
app = graph.compile()
result = await app.ainvoke(initial_state)
// ===== TS 对照:Redux 类似结构 =====
// const reducer = (state: MyState, action): MyState => { ... }
// const store = createStore(reducer)
// dispatch(actionA) → reducer 处理 → 新 state → 触发 actionB

4. 动手:用 LangGraph 重写交易 Agent

安装 ChromaDB(本周后面 RAG 会用到,先装上)

.venv\Scripts\python -m pip install chromadb langchain-chroma langchain-huggingface

完整代码

# app/agent/trading_agent.py
"""
用 LangGraph 构建的交易 Agent
能力:
1. 对话理解
2. 工具调用(查询价格、下单)
3. 决策(要不要下单)
"""

from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

# ===== 第一步:定义 State =====
class TradingAgentState(TypedDict):
    """Agent 在各节点之间流转的状态"""
    messages: list              # 对话历史
    user_intent: str            # 用户意图分类
    tools_called: list[str]     # 已调用过的工具名
    final_answer: str           # 最终回答

# ===== 第二步:定义 Tools(Agent 可调用的函数)=====

@tool
def get_stock_price(symbol: str) -> dict:
    """获取交易标的(股票/加密货币)的当前价格"""
    mock_prices = {"BTC": 99999.99, "ETH": 3500.0, "AAPL": 210.5, "特斯拉": 245.0}
    price = mock_prices.get(symbol.upper(), mock_prices.get(symbol, 0))
    return {"symbol": symbol.upper(), "price": price, "currency": "USD"}

@tool
def place_order(symbol: str, side: str, quantity: int) -> dict:
    """
    下单交易。注意:这是 Mock 实现,不会真的下单。
    
    Args:
        symbol: 交易标的代码,如 BTC, ETH, AAPL
        side: buy 或 sell
        quantity: 交易数量
    """
    return {
        "order_id": f"ORD{123456:06d}",
        "symbol": symbol.upper(),
        "side": side,
        "quantity": quantity,
        "status": "pending",
        "timestamp": "2024-01-15T10:30:00Z",
    }

@tool
def get_market_news(symbol: str) -> list[str]:
    """获取某标的的近期市场新闻摘要"""
    news_map = {
        "BTC": ["SEC 批准比特币 ETF", "贝莱德宣布增持 BTC"],
        "ETH": ["以太坊坎昆升级完成", "Layer2 TVL 创新高"],
    }
    return news_map.get(symbol.upper(), [f"暂无 {symbol} 的相关新闻"])

TOOLS = [get_stock_price, place_order, get_market_news]

# ===== 第三步:初始化 LLM =====
# 硅基流动是 OpenAI 兼容接口,所以用 ChatOpenAI
llm = ChatOpenAI(
    model="Qwen/Qwen2.5-7B-Instruct",
    base_url="https://api.siliconflow.cn/v1",
    api_key="your-api-key-here",
    temperature=0.7,
)

# 让 LLM 知道有哪些工具可以用
llm_with_tools = llm.bind_tools(TOOLS)

# ===== 第四步:定义图节点 =====

def analyze_intent(state: TradingAgentState) -> TradingAgentState:
    """
    节点1:分析用户意图,决定要不要调工具、调哪些工具
    这是一个"路由器"节点
    """
    # 取最后一条用户消息
    last_msg = state["messages"][-1].content if state["messages"] else ""
    
    # 简单的意图分类(实际项目用 LLM 分类)
    intent_keywords = {
        "get_price": ["价格", "多少钱", "行情", "现在多少", "price"],
        "place_order": ["买", "卖", "下单", "交易", "买入", "卖出"],
        "get_news": ["新闻", "消息", "分析", "资讯"],
        "chat": ["你好", "help", "帮助", "能做什么"],
    }
    
    detected_intents = []
    for intent, keywords in intent_keywords.items():
        if any(kw.lower() in last_msg.lower() for kw in keywords):
            detected_intents.append(intent)
    
    # 用 LLM 做最终判断(比关键词匹配更准)
    analysis_prompt = f"""
    用户消息:"{last_msg}"
    检测到的意图关键词:{detected_intents}
    
    请判断用户的主要意图是以下哪一种(只返回关键词):
    - query_price: 查询价格
    - place_order: 下单交易
    - query_news: 查询新闻
    - general_chat: 闲聊或帮助
    
    只返回意图关键词,不要其他内容。
    """
    response = llm.invoke([{"role": "user", "content": analysis_prompt}])
    user_intent = response.content.strip()
    
    return {**state, "user_intent": user_intent, "tools_called": []}


def llm_with_tool_calling(state: TradingAgentState) -> TradingAgentState:
    """
    节点2:LLM + Function Calling 主逻辑
    调用 LLM,如果要调工具就让 LangGraph 自动路由到 ToolNode
    """
    messages = state["messages"]
    response = llm_with_tools.invoke(messages)
    return {**state, "messages": [*messages, response]}


def generate_final_response(state: TradingAgentState) -> TradingAgentState:
    """
    节点3:生成最终回答(对工具返回结果做总结)
    """
    messages = state["messages"]
    
    summary_prompt = f"""
    请根据以下对话历史和工具返回结果,给用户一个清晰、自然的回答。
    不要提到内部工具调用过程,用面向用户的语言。
    
    对话历史:
    {messages}
    """
    response = llm.invoke([{"role": "user", "content": summary_prompt}])
    return {**state, "final_answer": response.content}


def direct_chat_response(state: TradingAgentState) -> TradingAgentState:
    """
    简化模式:不需要工具的闲聊,直接让 LLM 回答
    """
    messages = state["messages"]
    response = llm.invoke(messages)
    return {**state, "final_answer": response.content}


# ===== 第五步:构建 StateGraph =====

def should_use_tools(state: TradingAgentState) -> str:
    """条件函数:根据意图决定走哪条路"""
    intent = state["user_intent"]
    if intent in ("query_price", "place_order", "query_news"):
        return "use_tools"
    return "direct_chat"


# 构建图
graph = StateGraph(TradingAgentState)

# 添加节点
graph.add_node("analyze_intent", analyze_intent)
graph.add_node("use_tools", llm_with_tool_calling)
graph.add_node("tools", ToolNode(TOOLS))              # LangGraph 内置的工具执行节点
graph.add_node("summarize", generate_final_response)
graph.add_node("direct_chat", direct_chat_response)

# 边定义
graph.add_edge(START, "analyze_intent")                          # 入口 → 意图分析
graph.add_conditional_edges(                                      # 意图分析 → 分支
    "analyze_intent",
    should_use_tools,
    {
        "use_tools": "use_tools",
        "direct_chat": "direct_chat",
    }
)
graph.add_edge("use_tools", "tools")                              # LLM 调用 → 执行工具
graph.add_edge("tools", "summarize")                              # 工具执行完 → 总结
graph.add_edge("direct_chat", END)                                # 闲聊直接结束

# 编译成可运行的应用
app = graph.compile()


# ===== 第六步:暴露给 FastAPI 用的接口 =====

async def run_agent(user_input: str, history: list = None) -> str:
    """运行 Agent,返回最终回答"""
    initial_state: TradingAgentState = {
        "messages": history or [],
        "user_intent": "",
        "tools_called": [],
        "final_answer": "",
    }
    initial_state["messages"].append({"role": "user", "content": user_input})
    
    result = await app.ainvoke(initial_state)
    return result["final_answer"]


# 测试
if __name__ == "__main__":
    import asyncio

    async def test():
        # 测试 1:查询价格(触发工具)
        r1 = await run_agent("BTC 现在多少钱?")
        print(f"Q: BTC 现在多少钱?\nA: {r1}\n")

        # 测试 2:下单(触发工具)
        r2 = await run_agent("帮我买 0.5 个 ETH")
        print(f"Q: 帮我买 0.5 个 ETH\nA: {r2}\n")

        # 测试 3:闲聊(不触发工具)
        r3 = await run_agent("你好,你能做什么?")
        print(f"Q: 你好,你能做什么?\nA: {r3}\n")

    asyncio.run(test())

运行

.venv\Scripts\python app\agent\trading_agent.py

预期输出

Q: BTC 现在多少钱?
A: BTC 当前价格为 99999.99 USD。

Q: 帮我买 0.5 个 ETH
A: 已为您提交买单:买入 0.5 个 ETH,订单号 ORD123456,状态 pending。

Q: 你好,你能做什么?
A: 你好!我是一个交易助手,可以帮你查询行情、下单交易、获取市场新闻等。

这个图长什么样?

flowchart LR
    A[START] --> B[analyze_intent<br/>意图分析]
    B --> C{需要工具?}
    C -->|是| D[use_tools<br/>LLM Function Calling]
    D --> E[tools<br/>执行真实函数]
    E --> F[summarize<br/>生成最终回答]
    F --> G[END]
    C -->|否| H[direct_chat<br/>LLM 直接回答]
    H --> G

TS 对照

这个图编排的思路和你写 React 状态机 + 异步流 的思维是一模一样的:

LangGraphReact / TS
StateGraphuseReducer + useState 状态流转
每个 Node一个 async function 处理数据
Conditional Edgeif (condition) { dispatch(actionA) } else { dispatch(actionB) }
await app.ainvoke()await fetchData().then(processResult)

5. 本周作业

#任务验收标准
1跑通上面的 LangGraph Agent 代码三个测试用例都能正确返回
2新增一个 Tool:get_trading_history(symbol, days),返回历史 K 线数据调用时能返回 Mock 数据
3给 Agent 加一个 记忆功能(用 LangGraph 的 checkpointer)多轮对话能记住上下文
4把 Agent 集成到 Week 2-3 的 FastAPI 后端里POST /api/agent/chat 能调用 Agent
5读 LangGraph 官方 Tutorial 的前 3 个示例理解图的基本概念

6. 常见问题排查

问题原因解决
AttributeError: 'ChatOpenAI' object has no attribute 'bind_tools'langchain-core 版本太低pip install --upgrade langchain-core langchain-openai
工具没被调用,LLM 直接回答了1. 意图分类太严 2. 工具描述写得不好放宽条件 + 优化 @tool 的 docstring
tools_condition 报错LangGraph 版本问题确认 from langgraph.prebuilt import tools_condition 能正常导入
异步混同步同 Month 1 的问题全部用 ainvoke / astream,不要混用 invoke
方法名写错LangChain/LangGraph API 更新快先看 .venv\Lib\site-packages\langgraph\__init__.py 确认导出了什么