尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从OpenClaw到个人Agent落地:LangChain + LangGraph 的 ReAct 配置骨架与验证

从OpenClaw到个人Agent落地:LangChain + LangGraph 的 ReAct 配置骨架与验证 1. 从 OpenClaw 场景出发个人 Agent 到底难在哪OpenClaw 这类项目最吸引人的地方是它把「聊天」变成了「执行」——你说一句话它真的去查天气、建提醒、调工具。但很多人照着思路自己动手时会卡在同一个地方模型能聊但不会稳定地调工具工具调了但状态接不上状态接上了又不知道中间哪一步断了。LangChain 和 LangGraph 正好补这几个洞。LangChain 负责把模型、工具、提示词组装成可调用的单元LangGraph 负责把这些单元编排成一张有状态、可回溯、能条件跳转的图。ReAct 循环则是这张图里最核心的「思考—行动—观察」骨架模型先想一步决定调哪个工具拿到结果后再想下一步直到能给出最终回答。这篇要做的是一个最小可跑的个人 Agent 骨架。它不追求功能多而是追求三件事能验证一次工具调用能跑通、图节点状态能流转、日志里 ReAct 轨迹完整。适合已经会写 Python、想从「调 API 聊天」进阶到「搭 Agent」的开发者。下面所有配置和代码都可以直接复制改路径使用。2. TaoToken 前置统一 Key 接入与依赖安装在写 Agent 之前先把模型入口统一掉。个人 Agent 最烦的就是换模型要改一堆代码所以这里用 TaoToken 做统一 Key 接入OpenAI 兼容协议LangChain 的ChatOpenAI直接就能接。先去控制台建一个 Key地址是 https://taotoken.net/api-keys 创建后复制保存。然后在项目里用环境变量管理不要硬编码进代码。pip install langchain langchain-openai langgraph python-dotenv安装完成后在项目根目录建一个.env文件TAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api这里有个容易踩的坑base_url结尾不要带/v1LangChain 的 OpenAI 兼容层会自己拼路径。如果你手动加了/v1请求会变成/v1/v1/chat/completions直接 404。模型选择上ReAct 循环对模型的指令遵循能力有要求建议先用一个稳定的对话模型跑通骨架再换更强的模型做规划。TaoToken 的模型列表可以在 https://taotoken.net/models 查看选一个支持 function calling 的即可。3. 可复制配置config.toml 与 settings.json 骨架为了让 Agent 的模型、工具、循环参数可配置我用config.toml管运行时参数用settings.json管工具注册表。这样换模型、加工具都不用动主逻辑。先建config.toml[llm] model gpt-4o-mini temperature 0.2 max_tokens 2048 base_url https://taotoken.net/api [agent] max_iterations 6 verbose true return_intermediate_steps true [graph] checkpoint_backend memory recursion_limit 25max_iterations控制 ReAct 最多循环几轮防止模型陷入死循环。recursion_limit是 LangGraph 的图递归上限比max_iterations略大即可。再建settings.json定义工具注册表{ tools: [ { name: get_weather, description: 查询指定城市的当前天气输入城市名, enabled: true, source: builtin }, { name: get_time, description: 获取当前时间无需参数, enabled: true, source: builtin }, { name: save_note, description: 保存一条笔记输入内容字符串, enabled: true, source: builtin } ], allowlist_mode: true }allowlist_mode为 true 时只有注册表里 enabled 的工具才会暴露给模型。这个设计很关键——工具不是越多越好暴露太多会让模型选择飘移线上排查也麻烦。4. 搭建 ReAct 骨架工具注册与状态图编排配置就绪后开始写核心代码。先定义工具再组装 ReAct Agent最后用 LangGraph 包一层状态图。4.1 工具定义与注册import json from langchain_core.tools import tool tool def get_weather(city: str) - str: 查询指定城市的当前天气输入城市名。 fake_db {武汉: 小雨 18℃, 北京: 晴 22℃, 上海: 多云 20℃} return fake_db.get(city, f{city}暂无数据) tool def get_time() - str: 获取当前时间无需参数。 from datetime import datetime return datetime.now().strftime(%Y-%m-%d %H:%M:%S) tool def save_note(content: str) - str: 保存一条笔记输入内容字符串。 with open(notes.log, a, encodingutf-8) as f: f.write(content \n) return f已保存笔记{content} TOOL_MAP { get_weather: get_weather, get_time: get_time, save_note: save_note, } def load_tools(settings_path: str settings.json): with open(settings_path, r, encodingutf-8) as f: cfg json.load(f) tools [] for item in cfg[tools]: if item.get(enabled) and item[name] in TOOL_MAP: tools.append(TOOL_MAP[item[name]]) return toolsload_tools只返回注册表里启用的工具这就是前面说的 allowlist 治理。想临时摘掉某个工具改settings.json里的enabled即可不用动代码。4.2 组装 ReAct Agentimport os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent load_dotenv() def build_llm(): return ChatOpenAI( modelgpt-4o-mini, temperature0.2, api_keyos.getenv(TAOTOKEN_API_KEY), base_urlos.getenv(TAOTOKEN_BASE_URL), ) def build_agent(): llm build_llm() tools load_tools() agent create_react_agent( modelllm, toolstools, namepersonal_agent, ) return agentcreate_react_agent是 LangGraph 预置的 ReAct 实现内部已经帮你把「模型节点 → 工具节点 → 条件边」串好了。你不需要手写循环但需要理解它的节点结构后面排查问题全靠这个。4.3 用 LangGraph 包一层状态图预置 Agent 能跑但个人 Agent 通常还要加路由、记忆、后处理。所以外面再包一层StateGraphfrom typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages] route: str tool_trace: list def router_node(state: AgentState): last state[messages][-1].content route tool_agent if any(k in last for k in [天气, 时间, 笔记, 提醒]) else chat return {route: route} def tool_agent_node(state: AgentState): agent build_agent() result agent.invoke({messages: state[messages]}) trace [m for m in result[messages] if m.type tool] return {messages: result[messages], tool_trace: trace} def chat_node(state: AgentState): llm build_llm() resp llm.invoke(state[messages]) return {messages: [resp]} def route_decision(state: AgentState): return state[route] def build_graph(): g StateGraph(AgentState) g.add_node(router, router_node) g.add_node(tool_agent, tool_agent_node) g.add_node(chat, chat_node) g.set_entry_point(router) g.add_conditional_edges(router, route_decision, { tool_agent: tool_agent, chat: chat, }) g.add_edge(tool_agent, END) g.add_edge(chat, END) return g.compile()这张图的结构是入口先进router判断走工具还是普通聊天然后分别进tool_agent或chat最后结束。tool_trace字段专门存工具调用记录方便后面验证 ReAct 轨迹。5. 三步验证跑通调用、检查状态、确认轨迹骨架搭完必须验证。下面三步是我每次改完 Agent 都会跑的。5.1 第一步本地跑通一次工具调用from langchain_core.messages import HumanMessage graph build_graph() result graph.invoke({ messages: [HumanMessage(content武汉现在天气怎么样)], route: , tool_trace: [], }) for m in result[messages]: print(f[{m.type}] {m.content})预期输出里应该能看到先是human消息然后ai消息里带tool_calls接着tool消息返回天气结果最后ai给出总结。如果只看到一条ai回复、没有tool消息说明工具没被触发检查settings.json里get_weather是否 enabled。5.2 第二步检查图节点状态流转for event in graph.stream( {messages: [HumanMessage(content现在几点了)], route: , tool_trace: []}, stream_modeupdates, ): for node, payload in event.items(): print(f节点: {node} - 更新字段: {list(payload.keys())})正常应该看到router先输出route然后tool_agent输出messages和tool_trace。如果router之后直接跳到chat说明路由关键词没匹配上检查router_node里的判断逻辑。5.3 第三步确认日志中 ReAct 轨迹完整trace result.get(tool_trace, []) print(f工具调用次数: {len(trace)}) for t in trace: print(f工具: {t.name} | 参数: {t.content[:80]})完整的 ReAct 轨迹应该满足至少一条tool类型消息、工具名和参数可读、最终ai消息引用了工具结果。如果tool_trace为空但模型回答了天气说明模型在「脑补」而不是真调工具这时候要检查工具描述是否清晰、模型是否支持 function calling。6. 本篇常见错排查报错一openai.BadRequestError: Invalid URL八成是base_url写成了https://taotoken.net/api/v1。改成https://taotoken.net/api让 LangChain 自己拼路径。报错二GraphRecursionError: Recursion limit of 25 reachedReAct 循环没退出。先看max_iterations是不是设太大再看工具返回值是不是空字符串导致模型反复重试。把config.toml里recursion_limit调到 25 以上同时给工具加兜底返回。报错三工具没被调用模型直接回答两个原因一是模型不支持 function calling换一个支持的工具调用模型二是工具description写得太模糊模型不知道什么时候用。把描述改成「查询指定城市的当前天气输入城市名」这种明确句式。报错四tool_trace为空但流程正常检查tool_agent_node里过滤条件m.type tool。不同 LangChain 版本消息类型字段可能不同打印result[messages]看实际类型名。报错五多轮对话状态丢失默认MemorySaver只在进程内有效重启就没了。生产环境换成 Redis checkpointer或者把checkpoint_backend改成持久化后端。7. 下一步把骨架接进你的真实场景骨架跑通后扩展方向有三个。一是加记忆把AgentState里的messages换成带摘要的短期记忆加长期记忆抽取参考render_conversation_context的思路分层。二是加工具治理把settings.json的 allowlist 做成运行时热加载不同节点暴露不同工具集。三是加观测把tool_trace写进日志或数据库这样失败时能查到「哪个工具、什么参数、返回了什么」。如果你要长期跑编码类或 Agent 类任务建议直接上 Coding Plan额度和稳定性更适合持续调用入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的完整示例。想先在线验证模型行为可以直接用模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 试几轮 ReAct 提示词确认模型能稳定输出工具调用格式再回到代码里接。
返回列表