1. 为什么你的第一个 LangGraph Agent 总是跑不通
很多人第一次接触 LangGraph 时,会卡在一个很尴尬的位置:官方文档能看懂,create_agent的示例也能抄下来,但真正把代码跑起来,要么是模型 Key 报 401,要么是工具调用后没有回传,要么是循环停不下来。问题往往不在 LangGraph 本身,而在于三个环节没有对齐:模型接入层、工具定义层、以及图执行的终止条件。
LangGraph 里的 Agent 本质上是一个带循环的 StateGraph。它由三部分组成:模型(Model)、工具(Tools)、以及一个驱动循环的提示(Prompt)。模型负责推理下一步该做什么,工具负责执行具体动作,提示负责约束行为边界。每次迭代,模型会决定是否调用工具、调用哪个工具、传什么参数;工具执行完把结果作为观察(Observation)塞回消息列表;模型再基于新消息决定继续调用还是给出最终回答。这个循环会一直持续,直到模型不再产生tool_calls,或者达到你设置的递归上限。
这套机制听起来简单,但落地时有几个高频坑。第一,模型接入用的是 OpenAI 兼容协议,但很多人把base_url和api_key配错,导致请求直接 401。第二,工具函数的 docstring 写得太随意,模型看不懂参数含义,于是要么不调用,要么传错参数。第三,没有配置 checkpointer 和thread_id,多轮对话时状态丢失,Agent 表现得像失忆。第四,langgraph.json里的 graph 路径写错,langgraph dev启动后找不到 agent,Studio 里一片空白。
这篇内容面向刚接触 LangGraph、想先跑通一个可观测 Agent 的开发者。我会用 TaoToken 作为统一的模型接入层,把 Key 和 Base URL 收敛到一处,然后给出从 StateGraph 定义、节点与边连接、工具调用到循环终止条件的完整可复制配置。最后附一次真实运行日志,确认 Agent 能按预期完成工具调用与结果回传。你不需要先理解 LangGraph 的全部概念,跟着步骤走就能看到第一个 Agent 跑起来。
TaoToken 在这里的角色是统一 Key 网关。它兼容 OpenAI 协议,所以 LangChain 的ChatOpenAI可以直接指向它,不需要改任何 LangGraph 侧的代码。你只需要把OPENAI_API_KEY和OPENAI_BASE_URL换成 TaoToken 的地址,模型名换成你实际要用的 ID,剩下的图定义、工具注册、流式输出全部照常写。这样做的好处是,后面你想换模型、加工具、接 MCP,都不用再动接入层。
2. TaoToken 前置准备与 LangGraph 项目初始化
在写 Agent 之前,先把模型接入层和项目骨架搭好。这一步的目标是:拿到一个可用的 API Key,配好环境变量,装好依赖,并且确认langgraph dev能正常启动。很多人跳过这一步直接写 graph,结果报错时不知道是模型问题还是图的问题,排查成本很高。
2.1 获取 TaoToken API Key 并配置环境变量
先到 TaoToken 控制台创建一个 API Key。地址是https://taotoken.net/api-keys,登录后新建一个 Key,复制出来。这个 Key 就是后面所有模型请求的凭证。注意不要把它硬编码进代码,统一放到.env文件里。
在项目根目录创建.env:
OPENAI_API_KEY=sk-你的TaoToken密钥 OPENAI_BASE_URL=https://taotoken.net/api这里OPENAI_BASE_URL填 TaoToken 的 API 地址,不要带多余的路径。LangChain 的ChatOpenAI会自动拼接/chat/completions。如果你填成https://taotoken.net/api/v1,有些版本会重复拼接导致 404,所以按上面这个写最稳。
2.2 安装依赖
LangGraph 的 Agent 依赖langchain、langgraph、langchain-openai三个核心包。如果你后面要接 MCP 或做流式 SDK 调用,再加langgraph-sdk和langchain-mcp-adapters。先装最小集合:
pip install langchain langgraph langchain-openai python-dotenv如果你打算用 LangGraph Studio 做可视化调试,还需要langgraph-cli:
pip install "langgraph-cli[inmem]"装完后可以用pip show langgraph确认版本。LangGraph 迭代很快,建议用较新的版本,避免create_agent的 API 差异。
2.3 项目目录结构
LangGraph 本身不强制目录结构,但为了后面加工具、加 MCP、加自定义 State 时不乱,建议按下面这个组织:
langgraph-agent-demo/ ├── src/ │ └── agent/ │ ├── __init__.py │ ├── ai_model.py # 模型接入层 │ ├── graph.py # Agent 图定义 │ └── tools/ │ ├── __init__.py # 工具统一导出 │ └── weather_tool.py ├── .env ├── langgraph.json └── requirements.txtai_model.py只负责创建ChatOpenAI实例,所有模型配置集中在这里。graph.py负责定义 Agent。tools/放自定义工具,__init__.py里统一导出all_tools列表。langgraph.json是 LangGraph CLI 的入口配置,告诉它去哪里找 graph。
2.4 模型接入层 ai_model.py
import os import dotenv from langchain_openai import ChatOpenAI dotenv.load_dotenv() llm = ChatOpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), model="Qwen/Qwen2.5-72B-Instruct", temperature=0, )这里model填你在 TaoToken 上可用的模型 ID。temperature=0是为了让工具调用更稳定,减少模型自由发挥导致参数漂移。如果你用的是推理型模型,可以保留默认温度。
2.5 langgraph.json 配置
{ "$schema": "https://langgra.ph/schema.json", "dependencies": ["."], "graphs": { "agent": "./src/agent/graph.py:graph" }, "env": ".env" }graphs里的agent是暴露给 LangGraph API 的 assistant ID,后面 SDK 调用时用这个名字。冒号后面是文件路径:变量名,必须和graph.py里定义的变量名一致。env指向.env,这样langgraph dev启动时会自动加载环境变量。
3. 可复制的 Agent 配置:StateGraph、工具与循环终止
这一节是核心。我会给出一个最小但完整的 Agent 定义,包含工具函数、create_agent调用、以及循环终止的机制说明。你可以直接复制到graph.py里跑。
3.1 定义第一个工具
在src/agent/tools/weather_tool.py里写一个天气查询工具。注意 docstring 要写清楚参数含义,这是模型判断是否调用、怎么传参的唯一依据。
from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市的天气。 Args: city: 城市名称,例如"深圳"、"北京"。 """ return f"在{city},天气总是晴朗的,气温在28摄氏度。"@tool装饰器会把函数名、docstring、参数类型自动转成模型能理解的 JSON Schema。city: str的类型标注不能省,否则模型不知道参数是字符串还是数字。
3.2 统一导出工具
在src/agent/tools/__init__.py:
from .weather_tool import get_weather all_tools = [get_weather]后面加工具只需要在这里追加,graph.py不用改。
3.3 定义 Agent 图
在src/agent/graph.py:
from langchain.agents import create_agent from agent.ai_model import llm from agent.tools import all_tools graph = create_agent( model=llm, tools=all_tools, system_prompt="你是一个智能助手,尽可能使用工具来回答用户的问题。", )create_agent内部会帮你构建一个 StateGraph:一个模型节点、一个工具节点、以及一条条件边。条件边的判断逻辑是:如果模型返回的消息里包含tool_calls,就路由到工具节点;否则路由到结束。工具节点执行完,把ToolMessage追加到消息列表,再回到模型节点。这个循环就是 Agent 的推理-行动闭环。
3.4 循环终止条件
循环终止有两个层面。第一层是模型层面:当模型不再产生tool_calls,条件边直接指向END,图执行结束。第二层是框架层面:LangGraph 有一个recursion_limit,默认 25。如果模型陷入反复调用同一个工具的循环,达到上限后会抛出GraphRecursionError。你可以在调用时通过 config 调整:
config = {"recursion_limit": 10}对于第一个 Agent,建议先保持默认,等跑通后再根据业务调整。如果发现 Agent 反复调用工具,通常是工具返回结果没有让模型满意,或者 system prompt 没有说清楚什么时候该停止。
3.5 带记忆的 Agent 配置
如果你想让 Agent 记住多轮对话,需要加 checkpointer 和thread_id。最小改动是在create_agent里传入checkpointer:
from langgraph.checkpoint.memory import InMemorySaver checkpointer = InMemorySaver() graph = create_agent( model=llm, tools=all_tools, system_prompt="你是一个智能助手,尽可能使用工具来回答用户的问题。", checkpointer=checkpointer, )调用时传入thread_id:
config = {"configurable": {"thread_id": "demo-1"}} result = graph.invoke( {"messages": [{"role": "user", "content": "深圳今天天气怎么样?"}]}, config=config, )同一个thread_id下的多轮对话会共享消息历史。换一个thread_id就是新会话。生产环境可以把InMemorySaver换成PostgresSaver,配置方式类似,只是连接字符串不同。
4. 验证请求:一次完整的运行日志与结果确认
配置写完后,必须验证 Agent 真的能完成工具调用和结果回传。这一节给出两种验证方式:直接用 Python 调用,以及通过langgraph dev+ SDK 流式调用。两种方式都能看到完整的消息流转。
4.1 直接调用验证
在项目根目录创建一个run_agent.py:
from agent.graph import graph config = {"configurable": {"thread_id": "verify-1"}} result = graph.invoke( {"messages": [{"role": "user", "content": "深圳今天天气怎么样?"}]}, config=config, ) for msg in result["messages"]: print(f"[{msg.type}] {msg.content}") if hasattr(msg, "tool_calls") and msg.tool_calls: print(f" tool_calls: {msg.tool_calls}")运行python run_agent.py,你会看到类似下面的输出:
[human] 深圳今天天气怎么样? [ai] tool_calls: [{'name': 'get_weather', 'args': {'city': '深圳'}, 'id': 'call_abc123', 'type': 'tool_call'}] [tool] 在深圳,天气总是晴朗的,气温在28摄氏度。 [ai] 深圳今天天气晴朗,气温大约28摄氏度。这四条消息构成了一个完整的单轮工具调用闭环:HumanMessage 是用户提问,AIMessage 带tool_calls是模型决定调用工具,ToolMessage 是工具执行结果,最后一条 AIMessage 是模型基于工具结果生成的最终回答。如果你看到这四步,说明 Agent 已经跑通。
4.2 通过 langgraph dev 启动服务
在项目根目录执行:
langgraph dev启动成功后会输出一个本地 URL,通常是http://localhost:2024。同时会提示你可以在 LangGraph Studio 里打开。第一次打开 Studio 可能需要登录 LangSmith,按提示操作即可。登录后在 Studio 里选择agent,输入用户消息,就能看到图的可视化执行过程,每个节点的输入输出都能展开查看。
4.3 用 SDK 流式调用验证
如果你想把 Agent 接到自己的前端或服务里,用langgraph-sdk的流式接口更实用。安装:
pip install langgraph-sdk同步调用示例:
from langgraph_sdk import get_sync_client client = get_sync_client(url="http://localhost:2024") for chunk in client.runs.stream( None, "agent", input={ "messages": [ {"role": "human", "content": "深圳今天的天气怎么样?"} ] }, stream_mode="messages-tuple", ): if isinstance(chunk.data, list) and chunk.data and chunk.data[0].get("type") == "AIMessageChunk": print(chunk.data[0]["content"], end="|")stream_mode="messages-tuple"会把消息通道的值以轻量元组形式返回,适合前端直接渲染对话流。你会看到类似深圳|今天|天气|晴朗|,|气温|28|摄氏度|。的流式输出。这说明 Agent 的最终回答是逐 token 生成的,工具调用阶段则不会出现在这个流里,因为工具调用是 AIMessage 的tool_calls字段,不是文本内容。
4.4 验证工具调用确实发生
只看最终回答不够,要确认工具真的被调用了。有两个办法。第一,在工具函数里加一行print,运行时会输出到终端。第二,用stream_mode="updates"查看每一步的状态变化:
for chunk in client.runs.stream( None, "agent", input={"messages": [{"role": "human", "content": "深圳今天天气怎么样?"}]}, stream_mode="updates", ): print(chunk.data)你会看到两个 update:第一个是模型节点产生的带tool_calls的 AIMessage,第二个是工具节点产生的 ToolMessage。这两个 update 就是工具调用发生的证据。
5. 本篇常见错误排查:401、local proxy failed 与 reading choices
跑第一个 Agent 时,报错集中在接入层和配置层。这一节列出高频错误、真实报错文本、以及对应的修复动作。遇到报错先对照这里,能省很多时间。
5.1 401 Unauthorized
报错文本通常是:
openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key', 'type': 'invalid_request_error'}}原因有三种。第一,.env里的OPENAI_API_KEY没填或填错。第二,dotenv.load_dotenv()没有在ai_model.py里调用,环境变量没加载。第三,Key 复制时带了空格或换行。修复方式是打印os.getenv("OPENAI_API_KEY")的前几位确认,并确保load_dotenv()在创建ChatOpenAI之前执行。
5.2 local proxy failed / Connection error
报错文本:
openai.APIConnectionError: Connection error. httpx.ConnectError: [Errno 111] Connection refused这类错误通常是OPENAI_BASE_URL写错,或者本地网络无法访问该地址。检查.env里的OPENAI_BASE_URL是否为https://taotoken.net/api,不要多写/v1或结尾斜杠。如果确认地址正确,用curl直接测一下:
curl -X POST https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"Qwen/Qwen2.5-72B-Instruct","messages":[{"role":"user","content":"hi"}]}'如果 curl 能通而 Python 不通,检查是否有本地代理环境变量干扰,比如HTTP_PROXY、HTTPS_PROXY。这些变量会让 httpx 走代理,导致连接失败。临时清掉再试:
unset HTTP_PROXY HTTPS_PROXY5.3 reading choices / KeyError: 'choices'
报错文本:
KeyError: 'choices'或者:
openai.APIError: Unexpected response format这通常说明请求返回的不是标准 OpenAI 格式。可能原因:base_url指向了一个不兼容 OpenAI 协议的端点,或者模型 ID 写错导致服务端返回了错误页。确认base_url是https://taotoken.net/api,model是 TaoToken 上真实可用的 ID。如果模型 ID 不存在,有些服务会返回 404 页面而不是 JSON,LangChain 解析时就会报choices缺失。
5.4 GraphRecursionError
报错文本:
langgraph.errors.GraphRecursionError: Recursion limit of 25 reached without hitting a stop condition.这说明 Agent 在循环里出不来。常见原因是工具返回的内容让模型认为还需要继续调用,或者 system prompt 没有给出停止条件。修复方式:在 system prompt 里明确「如果已经获得足够信息,直接给出最终回答,不要重复调用工具」。另外检查工具函数是否真的返回了有意义的结果,如果返回空字符串,模型可能会反复重试。
5.5 langgraph dev 启动后 Studio 里看不到 agent
检查langgraph.json里的路径。"./src/agent/graph.py:graph"要求graph.py里有一个名为graph的变量。如果你把变量名写成agent或app,这里必须同步改。另外确认dependencies里的"."指向项目根目录,且src/agent/__init__.py存在。如果路径不对,langgraph dev启动时不会报错,但 Studio 里 assistant 列表是空的。
5.6 工具没有被调用
模型直接回答了问题,没有走工具。原因通常是 docstring 不够清晰,或者 system prompt 没有鼓励使用工具。把工具 docstring 写具体,比如「查询指定城市的实时天气,参数 city 为城市中文名」,并在 system prompt 里加一句「涉及天气、时间、计算等问题时,优先调用工具」。另外确认tools=all_tools传的是列表,不是单个函数。
6. 从第一个 Agent 到可观测的编码工作流
第一个 Agent 跑通后,你手里已经有了一个可观测的最小闭环:StateGraph 定义、节点与边连接、工具调用、循环终止、以及流式输出。接下来可以沿着三个方向扩展。
第一,把InMemorySaver换成PostgresSaver,让多轮对话状态持久化。配置方式和内存版几乎一样,只是连接字符串换成 PostgreSQL 的 DSN,并在首次运行时调用checkpoint.setup()初始化表结构。这样重启服务后,同一个thread_id的历史消息还在。
第二,把工具从单个天气查询扩展成一组业务工具。按tools/目录一个文件一个工具的方式组织,在__init__.py里统一导出。工具多了之后,模型选择工具的准确率会下降,这时候可以在 system prompt 里给出工具选择的原则,或者用更结构化的StructuredTool定义参数。
第三,把 Agent 接到编码工作流里。如果你想让 Agent 长期跑在代码仓库、终端、文件系统上,可以用 TaoToken 的 Coding Plan 作为模型接入层,配合 LangGraph 的 checkpointer 做会话保持。Coding Plan 的接入方式和普通 API 一致,只是模型 ID 和计费方式不同,适合长时间、高频次的 Agent 调用。
如果你在验证模型能力阶段,想先对比不同模型对工具调用的支持程度,可以直接在 TaoToken 的模型对话页面里试。把同一段 system prompt 和工具定义贴进去,看不同模型是否稳定产生tool_calls,再决定用哪个模型跑 LangGraph。模型对话入口在https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite。
接入文档里有一份完整的 OpenAI 兼容调用说明,包括base_url、鉴权头、流式参数。如果你在配置ChatOpenAI时不确定某个参数怎么填,可以先对照文档确认。文档地址是https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。
API Key 管理页面可以创建多个 Key,按项目或环境区分。建议给 LangGraph 项目单独建一个 Key,方便后续排查调用量和权限问题。入口在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。
如果你打算把 Agent 跑在长期编码任务上,比如自动修 bug、跑测试、生成 PR,Coding Plan 比按量计费更划算。它的接入地址和普通 API 相同,只是需要在控制台开通对应套餐。入口在https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。
最后提醒一个实操细节:langgraph dev默认监听2024端口,如果你本地这个端口被占用,可以用--port指定其他端口,同时 SDK 里的url也要同步改。另外 Studio 的可视化依赖 LangSmith 登录,如果公司网络无法访问,可以先用 Python 直接调用验证,不影响 Agent 本身的功能。