
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启动后找不到 agentStudio 里一片空白。这篇内容面向刚接触 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文件里。在项目根目录创建.envOPENAI_API_KEYsk-你的TaoToken密钥 OPENAI_BASE_URLhttps://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-clipip 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.pyimport os import dotenv from langchain_openai import ChatOpenAI dotenv.load_dotenv() llm ChatOpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), modelQwen/Qwen2.5-72B-Instruct, temperature0, )这里model填你在 TaoToken 上可用的模型 ID。temperature0是为了让工具调用更稳定减少模型自由发挥导致参数漂移。如果你用的是推理型模型可以保留默认温度。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__.pyfrom .weather_tool import get_weather all_tools [get_weather]后面加工具只需要在这里追加graph.py不用改。3.3 定义 Agent 图在src/agent/graph.pyfrom langchain.agents import create_agent from agent.ai_model import llm from agent.tools import all_tools graph create_agent( modelllm, toolsall_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里传入checkpointerfrom langgraph.checkpoint.memory import InMemorySaver checkpointer InMemorySaver() graph create_agent( modelllm, toolsall_tools, system_prompt你是一个智能助手尽可能使用工具来回答用户的问题。, checkpointercheckpointer, )调用时传入thread_idconfig {configurable: {thread_id: demo-1}} result graph.invoke( {messages: [{role: user, content: 深圳今天天气怎么样}]}, configconfig, )同一个thread_id下的多轮对话会共享消息历史。换一个thread_id就是新会话。生产环境可以把InMemorySaver换成PostgresSaver配置方式类似只是连接字符串不同。4. 验证请求一次完整的运行日志与结果确认配置写完后必须验证 Agent 真的能完成工具调用和结果回传。这一节给出两种验证方式直接用 Python 调用以及通过langgraph dev SDK 流式调用。两种方式都能看到完整的消息流转。4.1 直接调用验证在项目根目录创建一个run_agent.pyfrom agent.graph import graph config {configurable: {thread_id: verify-1}} result graph.invoke( {messages: [{role: user, content: 深圳今天天气怎么样}]}, configconfig, ) 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(urlhttp://localhost:2024) for chunk in client.runs.stream( None, agent, input{ messages: [ {role: human, content: 深圳今天的天气怎么样} ] }, stream_modemessages-tuple, ): if isinstance(chunk.data, list) and chunk.data and chunk.data[0].get(type) AIMessageChunk: print(chunk.data[0][content], end|)stream_modemessages-tuple会把消息通道的值以轻量元组形式返回适合前端直接渲染对话流。你会看到类似深圳|今天|天气|晴朗||气温|28|摄氏度|。的流式输出。这说明 Agent 的最终回答是逐 token 生成的工具调用阶段则不会出现在这个流里因为工具调用是 AIMessage 的tool_calls字段不是文本内容。4.4 验证工具调用确实发生只看最终回答不够要确认工具真的被调用了。有两个办法。第一在工具函数里加一行print运行时会输出到终端。第二用stream_modeupdates查看每一步的状态变化for chunk in client.runs.stream( None, agent, input{messages: [{role: human, content: 深圳今天天气怎么样}]}, stream_modeupdates, ): 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/apimodel是 TaoToken 上真实可用的 ID。如果模型 ID 不存在有些服务会返回 404 页面而不是 JSONLangChain 解析时就会报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 里加一句「涉及天气、时间、计算等问题时优先调用工具」。另外确认toolsall_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_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。接入文档里有一份完整的 OpenAI 兼容调用说明包括base_url、鉴权头、流式参数。如果你在配置ChatOpenAI时不确定某个参数怎么填可以先对照文档确认。文档地址是https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite。API Key 管理页面可以创建多个 Key按项目或环境区分。建议给 LangGraph 项目单独建一个 Key方便后续排查调用量和权限问题。入口在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。如果你打算把 Agent 跑在长期编码任务上比如自动修 bug、跑测试、生成 PRCoding Plan 比按量计费更划算。它的接入地址和普通 API 相同只是需要在控制台开通对应套餐。入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。最后提醒一个实操细节langgraph dev默认监听2024端口如果你本地这个端口被占用可以用--port指定其他端口同时 SDK 里的url也要同步改。另外 Studio 的可视化依赖 LangSmith 登录如果公司网络无法访问可以先用 Python 直接调用验证不影响 Agent 本身的功能。