把 LangGraph 的模型通道改到 TaoToken 之后,ReAct 评测能测多模型
把 LangGraph 的 ReAct Agent 从写死ChatOpenAI(model='gpt-4o-mini')改成可切换多模型,真正麻烦的不是图结构,而是模型通道和 Key 管理。TaoToken(官网:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=)在配置里只充当统一模型通道,把base_url指向https://taotoken.net/api,再换model字段,就能在同一套 ReAct 评测代码里跑deepseek-v3、deepseek-r1等模型。下面从原问题、前置准备、可复制配置、验证请求、常见错排查到 CTA 逐步写。
一、原问题与场景:react_agent.py 里写死 ChatOpenAI(model='gpt-4o-mini')
原场景很典型:在react_agent.py里手搓了一个 LangGraph ReAct Agent,工具包括搜索、计算器、摘要、邮件发送等,核心推理节点call_model直接调用:
llm = ChatOpenAI(model='gpt-4o-mini')然后call_model内部用await llm.ainvoke(...)拿模型输出,再解析Thought、Action、Action Input,必要时把工具调用写入response.tool_calls,交给ToolNode执行。图结构没问题,问题出在评测阶段:今天想对比deepseek-v3,明天想测deepseek-r1,后天还想把开源蒸馏版本拉进来,如果每次都改model='...',再重新跑一遍,评测代码很快会变得不可维护。
更关键的是,ReAct Agent 对模型能力很敏感。通用模型和推理模型在任务步骤规划、结构化输出、工具调用参数生成上的表现差异明显。要让评测尽量公平,必须保证同一套图、同一套 Prompt、同一套工具,只切换模型通道。也就是说,应该把“模型来自哪里”和“Agent 怎么推理”拆开。ChatOpenAI里的base_url、api_key、model正是拆开的切入点。TaoToken 在这里承担统一模型通道的角色,不改 LangGraph 的 ReAct 逻辑,只让同一个llm对象可替换。
二、TaoToken 前置:在官网创建 Key 并确认 API 地址
先打开官网创建 Key:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
创建完成后你会拿到类似YOUR_API_KEY的 Key。本文所有示例都用YOUR_API_KEY占位,实际运行时替换成自己的 Key。API 地址使用:
https://taotoken.net/api注意这里不加 UTM,也不要写成首页地址。LangGraph 侧仍然使用ChatOpenAI,只是把base_url从 OpenAI 默认地址改为 TaoToken 的 API 地址,把api_key改为 TaoToken 签发的 Key,把model改为deepseek-v3或deepseek-r1。这样同一套 ReAct 代码不需要为了模型切换而改图结构。
建议把 Key 放进环境变量,不要硬编码到react_agent.py:
# .env TAOTOKEN_API_KEY=YOUR_API_KEY TAOTOKEN_BASE_URL=https://taotoken.net/api REACT_MODEL=deepseek-v3如果你的项目没有自动加载.env,可以用python-dotenv,或者在 shell 里export。这一步做完,后面就只剩配置和验证。
三、可复制配置:在 .env 与 react_agent_taotoken.py 中接入 TaoToken
先安装依赖:
pip install -U langchain-openai langgraph langchain-core python-dotenv openai新建或修改react_agent_taotoken.py,先写一个build_llm工厂函数。原代码里写死的llm = ChatOpenAI(model='gpt-4o-mini')替换成按模型 ID 构建:
import os from langchain_openai import ChatOpenAI def build_llm(model_id: str | None = None) -> ChatOpenAI: return ChatOpenAI( model=model_id or os.getenv("REACT_MODEL", "deepseek-v3"), api_key=os.getenv("TAOTOKEN_API_KEY", "YOUR_API_KEY"), base_url=os.getenv("TAOTOKEN_BASE_URL", "https://taotoken.net/api"), temperature=0, timeout=180, max_retries=2, )原代码中:
# 旧:llm = ChatOpenAI(model='gpt-4o-mini') llm = build_llm("deepseek-v3")评测时直接:
llm = build_llm("deepseek-r1")接下来把 ReAct 图的创建封装成create_react_graph(llm),这样同一个图工厂可以接收不同模型。下面给出一个可运行的核心结构,保留你原来的REACT_PROMPT和工具定义即可:
import json from dataclasses import dataclass, field from typing import Annotated, Dict, List, Literal, Sequence from langchain_core.messages import AIMessage, AnyMessage, HumanMessage, SystemMessage from langchain_core.runnables import RunnableConfig from langchain_core.tools import tool from langgraph.graph import StateGraph from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode REACT_PROMPT = """你是一个 ReAct Agent。 你可以使用以下工具: {tools_desc} 可用工具名:{tool_names} 需要调用工具时,请严格输出: Thought: 你的推理 Action: 工具名 Action Input: {{"参数名": "参数值"}} 可以结束时输出: Thought: 我已经获得足够信息。 Answer: 最终答案 一次只输出一个 Action,Action Input 必须是合法 JSON。""" @tool def network_search(query: str) -> str: """搜索关键词并返回模拟结果。""" return f"模拟搜索结果:{query}" @tool def calculator(expression: str) -> str: """计算简单数学表达式。""" return "模拟计算结果" @tool def send_email(recipient: str, subject: str, body: str) -> str: """发送邮件。""" return f"邮件已发送给 {recipient}" TOOLS = [network_search, calculator, send_email] def create_react_graph(llm: ChatOpenAI): @dataclass class InputState: messages: Annotated[Sequence[AnyMessage], add_messages] = field(default_factory=list) @dataclass class State(InputState): is_last_step: bool = field(default=False) tools_desc = "\n".join([f"- {t.name}: {t.description}" for t in TOOLS]) tool_names = [t.name for t in TOOLS] async def call_model(state: State, config: RunnableConfig) -> Dict[str, List[AIMessage]]: system_prompt = REACT_PROMPT.format( tools_desc=tools_desc, tool_names=tool_names, ) response = await llm.ainvoke( [SystemMessage(content=system_prompt)] + list(state.messages) ) content = response.content or "" if "Action:" in content and "Action Input:" in content: action_lines = [ line for line in content.split("\n") if line.startswith("Action:") or line.startswith("Action Input:") ] if len(action_lines) >= 2: tool_name = action_lines[0].replace("Action:", "").strip() tool_input = action_lines[1].replace("Action Input:", "").strip() try: args = json.loads(tool_input) except json.JSONDecodeError: args = {} response.tool_calls = [{ "id": "call_1", "type": "function", "name": tool_name, "args": args, }] return {"messages": [response]} def route_model_output(state: State) -> Literal["__end__", "tools"]: last_message = state.messages[-1] if not isinstance(last_message, AIMessage): raise ValueError(f"Expected AIMessage, got {type(last_message).__name__}") if not getattr(last_message, "tool_calls", None): return "__end__" return "tools" builder = StateGraph(State, input=InputState) builder.add_node("call_model", call_model) builder.add_node("tools", ToolNode(TOOLS)) builder.add_edge("__start__", "call_model") builder.add_conditional_edges("call_model", route_model_output) builder.add_edge("tools", "call_model") return builder.compile()然后写多模型评测入口:
import asyncio from langchain_core.messages import HumanMessage async def run_eval(): tasks = [ "搜索 DeepSeek-R1 在 Agent 评测中的常见问题", "计算 12*8,并把结果发送到 test@example.com", ] for model_id in ["deepseek-v3", "deepseek-r1"]: llm = build_llm(model_id) graph = create_react_graph(llm) for task in tasks: try: result = await graph.ainvoke({ "messages": [HumanMessage(content=task)] }) print(model_id, "OK", result["messages"][-1].content[:120]) except Exception as exc: print(model_id, "ERROR", repr(exc)) if __name__ == "__main__": asyncio.run(run_eval())这段代码的重点不是工具逻辑,而是build_llm(model_id)和create_react_graph(llm)的组合。只要模型 ID 变,整个 ReAct 图就换模型通道,但 Prompt、工具、路由逻辑不变。这样你才能在同一套评测框架里比较deepseek-v3、deepseek-r1以及其他模型。
四、验证请求与成功结果:先测 chat/completions 再跑 ReAct 图
在跑 LangGraph 之前,先用一个最小请求验证 TaoToken 的 Key 和 API 地址是否可用。用 Python OpenAI SDK 更接近ChatOpenAI底层行为:
import os from openai import OpenAI client = OpenAI( api_key=os.getenv("TAOTOKEN_API_KEY", "YOUR_API_KEY"), base_url="https://taotoken.net/api", ) resp = client.chat.completions.create( model="deepseek-v3", messages=[ {"role": "user", "content": "只回复:taotoken-ok"} ], temperature=0, ) print(resp.choices[0].message.content)也可以直接用 curl:
curl -sS https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v3", "messages": [{"role": "user", "content": "只回复 taotoken-ok"}], "temperature": 0 }'成功时你会看到类似结构:
{ "choices": [ { "message": { "role": "assistant", "content": "taotoken-ok" } } ] }只要这个请求通了,就说明base_url、api_key、model三件套至少有一组可用。然后再跑react_agent_taotoken.py。成功标志有几个:
deepseek-v3和deepseek-r1都能被build_llm创建,不再出现 401 或 404。graph.ainvoke后,result["messages"][-1]是最终AIMessage,内容包含Answer或最终答复。- 如果任务需要工具,日志中能看到
Action、Action Input,并且ToolNode被调用。 - 同一套任务在换
model_id后可以继续复用,不需要改create_react_graph内部代码。
到这里,ReAct 评测的多模型通道基本就打通了。接下来是准确率对比、推理过程对比、工具调用成功率对比,那属于评测设计,不是接入问题。
五、本篇常见错排查:401、404、模型名与 ToolNode 报错
第一类,401 或 403。通常是 Key 不对、Key 没放进环境变量、或者请求头格式不对。检查.env是否真的被加载,检查api_key=os.getenv("TAOTOKEN_API_KEY")是否拿到了值。如果代码里还写着YOUR_API_KEY,那请求必然失败。curl 时检查Authorization: Bearer $TAOTOKEN_API_KEY,不要漏掉Bearer。
第二类,404。常见原因是base_url写错,比如写成首页地址、写成https://taotoken.net/api/v1但 SDK 又自动拼了一层,或者末尾多写斜杠导致路径重复。LangChain 的ChatOpenAI推荐用base_url,不要和旧参数openai_api_base混用。如果出现/chat/completions路径异常,先回到最小 OpenAI SDK 请求,确认base_url="https://taotoken.net/api"能通,再放回 LangGraph。
第三类,模型名不存在。deepseek-r1、deepseek-v3只是本文示例,实际模型 ID 以控制台或接入文档为准。注意大小写、空格、连字符。不要写DeepSeek-R1和deepseek_r1混用,除非文档明确支持。模型名错误时,通常返回 model not found 或 invalid model。
第四类,LangChain 参数报 unexpected keyword。新版langchain-openai支持api_key和base_url,旧版可能要求openai_api_key和openai_api_base。如果你本地版本较旧,要么升级:
pip install -U langchain-openai要么临时改成旧参数名。不要同时写两套,避免覆盖。
第五类,ToolNode 报找不到工具。通常是因为 Prompt 里的tool_names和ToolNode(TOOLS)不一致,或者模型输出的Action带了多余字符,比如反引号、空格、冒号。解析时要先strip(),再和已知工具名比对。如果模型输出多个Action,当前解析逻辑可能只取前两个行,容易错配。ReAct Prompt 里要强调一次只输出一个 Action。
第六类,ReAct 图不结束或反复调用工具。检查route_model_output是否只在tool_calls为空时返回__end__。如果模型输出了 Answer,但还带了一个 Action,就会被继续路由到tools。另外检查最大迭代步数,避免异常情况下无限循环。
第七类,DeepSeek-R1 响应慢导致超时。推理模型本身思考时间长,timeout=180或更大是合理的,max_retries=2也可以减少偶发网络失败。如果并发评测多个模型,建议串行或加并发限制,避免触发限流。还要检查本机HTTP_PROXY、HTTPS_PROXY是否指向了不可用的代理,错误的代理设置会让请求直接失败。
第八类,结构化输出解析失败。R1 可能输出 Markdown 代码块,或者在 JSON 前后加说明文字。解析Action Input时不要直接json.loads整段内容,先提取花括号部分,再做容错。如果解析失败,宁可让 Agent 重新推理一次,也不要用空参数去调用工具。
六、语义一致 CTA:API Keys、接入文档与 Coding Plan
如果你正在把 LangGraph ReAct Agent 接到 TaoToken,建议先把 Key 管理和接入文档两个入口收好。创建和管理 Key 走这里:
https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys
base_url、模型 ID、OpenAI 兼容参数和常见报错说明,优先看接入文档:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc
如果你只是想先验证deepseek-v3或deepseek-r1的返回,不想立刻写代码,可以去模型对话入口发一条最小请求:
https://taotoken.net/console/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat
如果你长期要做 Agent 评测、编码助手接入、多模型对比,Coding Plan 入口在这里:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan
TaoToken 在这个场景里只做统一模型通道,真正决定 ReAct 评测结果的仍然是 Prompt、工具设计、解析容错和评测任务本身。把通道配置一次理顺,后面再换deepseek-v3、deepseek-r1或其他模型时,只需要改一个model_id。