【大模型】从零构建 LLM Agent(一):用 LangGraph 跑通 ReAct Agent
2026/7/23 7:35:19 网站建设 项目流程

从零构建 LLM Agent(一):用 LangGraph 跑通 ReAct Agent

系列说明

本系列围绕「从用到懂到造」展开,记录基于自部署 LLM 构建 Agent 的实践。

主题阶段
1(本文)create_react_agent跑通一个 ReAct Agent
2手写StateGraph,拆解 ReAct 循环的状态机实现
3脱离预置图,自实现 agent loop
4引入长期记忆:向量检索与 RAG
5多 Agent 协作与人机协同(human-in-the-loop)
6生产化:可观测、容错与部署工程

运行环境:Python 3.11.15 · langgraph 1.2.9 · langchain-openai 1.3.5 · langchain-core 1.4.9
模型:Qwen3-235B-A22B,公司内网自部署,暴露 OpenAI 兼容接口(/v1/chat/completions)。
适用场景:本文不依赖 OpenAI / Anthropic 官方 API,所有调用指向自部署的 Qwen3,对同样使用自部署模型(vLLM / Ollama 部署 Qwen、DeepSeek 等)的场景可直接复用。

1. Agent 与 Chatbot 的边界

从工程视角,二者的差别集中在控制流归属是否具备外部交互能力

维度ChatbotAgent
控制流调用方驱动,单轮请求-响应LLM 驱动,内部循环
工具调用通过 function calling 协议调用外部函数
状态对话历史短期消息链 + (可选)长期记忆
终止条件单次生成结束LLM 不再发起 tool_call

据此可给出工程定义:Agent 是以 LLM 为决策核心、通过 tool calling 协议与外部环境交互、由 LLM 自主控制循环终止的有状态系统。

其最小实现可表述为一个循环:

while True: resp = llm(messages, tools=tools) if not resp.tool_calls: break messages += execute(resp.tool_calls) # 得到 Observation messages += resp return resp.content

LangGraph 的create_react_agent即对该循环的图封装。本文先用它跑通,第 2 篇再拆开。

2. 前置验证:模型是否支持 tool calling

tool calling 是 Agent 的硬依赖:LLM 必须能按 OpenAI function calling 协议返回结构化的tool_calls,框架据此分发到对应函数。Qwen3 走 OpenAI 兼容接口,需实测确认。

验证脚本:

fromopenaiimportOpenAI client=OpenAI(base_url="http://10.138.17.22:3000/v1",api_key="sk-xxx")tools=[{"type":"function","function":{"name":"get_weather","description":"查询某城市实时天气","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]},}}]resp=client.chat.completions.create(model="Qwen3-235B-A22B",messages=[{"role":"user","content":"北京今天天气怎么样?请用工具查询"}],tools=tools,)print(resp.choices[0].message.tool_calls)# -> [ChatCompletionMessageToolCall(name='get_weather', arguments='{"city": "北京"}')]

模型正确返回了结构化工具调用,未编造天气内容。该协议链路可用,后续基于langchain-openaiChatOpenAI.bind_tools()可直接复用。

备注:Qwen3 默认开启 thinking 模式,会在content中输出推理过程。在 tool calling 场景下其content为空、仅返回tool_calls,不影响框架解析。

3. 环境与依赖

独立 conda 环境隔离依赖:

conda create-nagentpython=3.11-yconda activate agent pipinstalllanggraph langchain-openai openai requests

选型说明:

  • Python 3.11:Python 3.9 已于 2025-10 EOL,LangGraph 1.x 主流支持 3.10/3.11。
  • langchain-openai:因 Qwen 暴露 OpenAI 兼容接口,ChatOpenAI可零适配接入,无需额外 Provider。
  • 版本:langgraph 1.2.9 / langchain-openai 1.3.5 / langchain-core 1.4.9。框架迭代快,复现请对齐版本。

已知问题:另有 embedding 服务(bge-m3,/v1/embeddings)当前网络不通,本篇不涉及,留待第 4 篇 RAG 部分处理。

4. 实现:三文件结构

config.py 配置集中管理(endpoint / api_key / model) tools.py 工具集合,@tool 声明 agent.py 构建 agent、运行与 trace 输出

4.1 工具定义

工具是普通 Python 函数,@tool装饰器基于函数签名 + docstring自动生成 JSON Schema,作为 tool calling 协议中的function.parameters下发给 LLM。因此 docstring 既是文档也是契约,质量直接影响模型选 tool 的准确率。

fromlangchain_core.toolsimporttool@tooldefget_weather(city:str)->str:"""查询指定城市的实时天气。"""fake_db={"北京":"晴,25°C","上海":"多云,28°C",...}returnfake_db.get(city,f"暂无{city}天气数据")

本篇共定义四个工具:get_weatherget_current_timecalculateget_stock_price,均为 mock 数据。其中calculate对表达式做了字符白名单校验并以禁用__builtins__eval执行,避免注入。

4.2 构建与运行

fromlangchain_openaiimportChatOpenAIfromlanggraph.prebuiltimportcreate_react_agent llm=ChatOpenAI(model="Qwen3-235B-A22B",base_url="...",api_key="...",temperature=0.7)agent=create_react_agent(llm,[get_weather,get_current_time,calculate,get_stock_price],prompt="你是一个个人助手,优先调用工具获取信息,不要编造。用中文回答。",)result=agent.invoke({"messages":[{"role":"user","content":"..."}]})

create_react_agent内部构建的状态图:

┌──────────┐ tool_calls 非空 ┌──────────┐ │ agent │ ────────────────▶│ tools │ │ (调 LLM) │◀──────────────── │ (执行函数) │ └──────────┘ ToolMessage 回填 └──────────┘ │ tool_calls 为空 ▼ END (输出 AIMessage)

状态以消息列表为载体(message-based state):agent 节点产出AIMessage(含tool_calls),tools 节点执行后产出ToolMessage(含结果),二者均 append 到messages,再回流 agent 节点,直至tool_calls为空终止。第 2 篇会用StateGraph显式重建此结构。

5. 行为验证与 trace 解读

5.1 工具链式协作(串行)

输入:「现在几点了?腾讯股票多少钱?持有 100 股市值多少港元?」

[1] user: 现在几点了?腾讯股票多少钱?持有100股市值多少港元? [2] AIMessage tool_calls: get_current_time({}) [3] ToolMessage: 2026年07月15日 16:11:59 Wednesday [4] AIMessage tool_calls: get_stock_price({'symbol': '腾讯'}) [5] ToolMessage: 腾讯控股 当前 385.6 港元,+1.2% [6] AIMessage tool_calls: calculate({'expression': '100 * 385.6'}) [7] ToolMessage: 100 * 385.6 = 38560.0 [8] AIMessage: 市值约为 38,560 港元。

第 [6] 步中calculate的入参385.6来源于第 [5] 步get_stock_price的返回。LLM 在第 [4]→[5] 取得股价前无法发起计算,故 [2][4][6] 呈串行依赖。这条 trace 体现了 agent 对跨步骤数据依赖的隐式规划能力。

5.2 无依赖调用(并行)

输入:「北京和上海今天哪个更暖和?」

[1] user: 北京和上海今天哪个更暖和? [2] AIMessage tool_calls: get_weather({'city': '北京'}) tool_calls: get_weather({'city': '上海'}) # 同一 AIMessage 内并行 [3] ToolMessage: 北京 晴,25°C [4] ToolMessage: 上海 多云,28°C [5] AIMessage: 上海更暖和。

两个查询无数据依赖,模型在单条AIMessage中发起多个tool_calls,框架并行执行。对比 5.1,可见串行/并行由任务的数据依赖决定,由模型在生成 tool_calls 时隐式调度

5.3 工具边界判断

输入「介绍一下你自己」时,模型不发起任何tool_calls,直接返回AIMessage。agent 循环在首轮即终止。这说明工具是否调用由模型基于 prompt 与 tool schema 自主判断,而非强制。

6. 小结

  • ReAct 的工程实现是一个以消息列表为状态、以tool_calls是否为空为终止条件的循环;
  • @tool基于签名与 docstring 生成 schema,是工具契约的来源;
  • create_react_agent将上述循环封装为 agent↔tools 两节点的状态图;
  • agent 的自主性体现在:工具选择、跨步数据依赖规划、串并行调度、终止判断,均由 LLM 隐式完成。

7. 下一阶段

create_react_agent隐藏了状态结构与转移逻辑。下一篇改用langgraph.graph.StateGraph显式定义状态、节点与边,重建等价的 ReAct 图,拆解状态如何在节点间流转、循环如何被tool_calls条件边控制。

对应代码见 series 仓库v1-first-agenttag。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询