1. 为什么内置 Agent 类型选型会卡住你
LangChain 里的 Agent 不是「一个东西」,而是一族按输出协议区分的执行器。XMLAgent、JSONAgent、AgentExecutor 这三个名字经常被混着用,但它们在代码里承担的角色完全不同:XMLAgent 和 JSONAgent 是「怎么把 LLM 的输出解析成工具调用」的协议层,AgentExecutor 是「拿到工具调用后怎么循环执行、怎么把观察结果塞回上下文」的运行时层。
我见过太多项目卡在第一步:模型明明返回了工具名,AgentExecutor 却报Could not parse LLM output。原因往往不是模型不行,而是 prompt 里的格式约定和 Agent 的解析器对不上。XMLAgent 期望<tool>search</tool><tool_input>...</tool_input>,JSONAgent 期望一个 markdown 代码块包着的{"action": ..., "action_input": ...},你把 JSON 格式的 prompt 喂给 XMLAgent,解析器当然找不到标签。
这篇面向需要快速搭建多工具调用链的开发者,给出三类 Agent 的可复制初始化配置、工具注册示例,以及一次完整的端到端调用验证。适合已经跑通过一次create_react_agent、想搞清楚「换 Agent 到底换的是什么」的人。读完之后你应该能按场景选型:模型擅长 XML 就用 XMLAgent,需要严格 JSON 结构就用 JSONAgent,而 AgentExecutor 是所有类型共用的执行外壳,参数调优直接决定你的链路稳不稳。
先说结论性的选型逻辑,后面再展开代码。XMLAgent 适合 Claude 系列这类对标签结构敏感的模型,输出天然带<tool>标签,解析容错高;JSONAgent 适合 GPT 系列和大部分国产模型,因为它们在指令遵循上对 JSON schema 更熟;AgentExecutor 不挑模型,它只负责max_iterations、handle_parsing_errors、return_intermediate_steps这些运行时行为。三者不是三选一,而是「协议 + 运行时」的组合。
2. TaoToken 前置:把模型接入和 Key 管理先理顺
在写 Agent 之前,得先有一个稳定的模型调用入口。LangChain 的ChatOpenAI默认打 OpenAI 官方地址,但你可以通过base_url指向兼容 OpenAI 协议的服务。TaoToken 提供的就是这样一个兼容入口,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 路径不带 UTM 参数。
接入方式很直接:在环境变量里配好 Key 和 Base URL,LangChain 侧只改base_url一个参数。我习惯把配置写进.env,避免 Key 硬编码进代码。
# .env OPENAI_API_KEY=sk-你的TaoToken密钥 OPENAI_BASE_URL=https://taotoken.net/api然后在 Python 里这样初始化模型:
import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", base_url=os.getenv("OPENAI_BASE_URL"), api_key=os.getenv("OPENAI_API_KEY"), temperature=0, )temperature=0对 Agent 场景很重要。Agent 需要模型稳定输出工具调用格式,温度高了它会开始「自由发挥」,把<tool>标签写成自然语言描述,解析器直接崩。这一点在 XMLAgent 上尤其明显。
Key 的获取和模型列表可以在控制台里看,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。如果你要长期跑编码类 Agent,Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 里有针对性的额度方案,比按量调用更适合高频迭代。API Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,建议给 Agent 项目单独建一个 Key,方便按项目排查用量。
这里有个容易踩的坑:base_url到底要不要带/v1。TaoToken 的 API 根路径是https://taotoken.net/api,LangChain 的ChatOpenAI会自动在末尾拼/chat/completions,所以你不要手动加/v1,否则会变成/api/v1/chat/completions导致 404。实测下来,直接写https://taotoken.net/api就能通。
模型选型上,XMLAgent 建议配 Claude 系列(对标签结构天然友好),JSONAgent 配 GPT-4o-mini 或同级别模型即可。如果你想先验证模型连通性,可以用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 发一条消息确认 Key 有效,再去写 Agent 代码,能省掉一半排障时间。
3. 可复制配置:XMLAgent、JSONAgent 与 AgentExecutor 三件套
这一节给出完整可跑的配置。先定义工具,再分别建 XMLAgent 和 JSONAgent,最后用 AgentExecutor 包起来。工具用两个最简单的:一个加法计算器,一个模拟天气查询,避免依赖外部搜索 API 导致排障复杂化。
先装依赖:
pip install langchain langchain-openai langchain-community python-dotenv工具定义:
from langchain_core.tools import tool @tool def add(a: int, b: int) -> int: """计算两个整数之和。输入必须是两个整数。""" return a + b @tool def get_weather(city: str) -> str: """查询指定城市的天气。输入是城市名。""" fake = {"beijing": "晴,25度", "shanghai": "多云,28度"} return fake.get(city.lower(), f"{city} 暂无数据") tools = [add, get_weather]XMLAgent 的 prompt 必须包含{tools}、{tool_names}、{input}、{agent_scratchpad}这几个变量,缺一个都会在运行时抛 KeyError。下面这份是我实测能稳定解析的版本:
from langchain_core.prompts import ChatPromptTemplate from langchain.agents import create_xml_agent xml_prompt = ChatPromptTemplate.from_messages([ ("human", """You are a helpful assistant. Answer the question using tools when needed. You have access to these tools: {tools} Use this format: <tool>tool_name</tool><tool_input>input here</tool_input> Then you will receive <observation>result</observation>. When done, respond with <final_answer>your answer</final_answer>. Available tool names: {tool_names} Question: {input} {agent_scratchpad}"""), ]) xml_agent = create_xml_agent(llm=llm, tools=tools, prompt=xml_prompt)JSONAgent 的 prompt 要求模型输出 markdown 代码块包裹的 JSON,解析器会去抓 ```json 块。注意{tool_names}在 JSONAgent 里通常写在 action 的约束说明中:
from langchain.agents import create_json_agent json_prompt = ChatPromptTemplate.from_messages([ ("system", "You are an assistant that calls tools by emitting JSON."), ("human", """TOOLS: {tools} Respond with a markdown json code block in one of two formats. To call a tool: ```json {{"action": "tool_name", "action_input": "input"}}To finish:
{{"action": "Final Answer", "action_input": "your answer"}}action must be one of: {tool_names}
Question: {input} {agent_scratchpad}"""), ])
json_agent = create_json_agent(llm=llm, tools=tools, prompt=json_prompt)
两个 Agent 建好后,统一交给 AgentExecutor。这里的参数是稳定性的关键: ```python from langchain.agents import AgentExecutor def build_executor(agent): return AgentExecutor( agent=agent, tools=tools, verbose=True, max_iterations=5, handle_parsing_errors=True, return_intermediate_steps=True, ) xml_executor = build_executor(xml_agent) json_executor = build_executor(json_agent)handle_parsing_errors=True让解析失败时把错误信息回灌给模型重试,而不是直接抛异常终止。max_iterations=5防止模型陷入工具调用死循环。return_intermediate_steps=True让你能看到每一步的 tool 和 observation,排障时非常有用。
如果你用 Claude Code 或 Cline 这类工具做本地开发,配置里同样要写全三件套:Base URL 填https://taotoken.net/api,Key 填你的 TaoToken 密钥,Model ID 填具体模型名如claude-3-5-sonnet。Cline 的 MCP 配置里如果引用模型,也要保证这三项一致,否则会出现local proxy failed或 OAuth 相关报错。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 有各客户端的完整字段说明。
4. 验证请求:一次完整调用与成功结果对照
配置写完必须跑一次端到端验证。用同一个问题分别打 XMLAgent 和 JSONAgent,观察中间步骤和最终输出。
question = {"input": "北京天气怎么样?顺便算一下 12 加 30 等于多少"} print("=== XMLAgent ===") xml_result = xml_executor.invoke(question) print(xml_result["output"]) print("=== JSONAgent ===") json_result = json_executor.invoke(question) print(json_result["output"])XMLAgent 的 verbose 输出大致长这样:
> Entering new AgentExecutor chain... <tool>get_weather</tool><tool_input>beijing</tool_input> <observation>晴,25度</observation> <tool>add</tool><tool_input>{"a": 12, "b": 30}</tool_input> <observation>42</observation> <final_answer>北京今天晴,25度;12 加 30 等于 42。</final_answer> > Finished chain.JSONAgent 的输出则是:
> Entering new AgentExecutor chain... ```json {"action": "get_weather", "action_input": "beijing"}晴,25度
{"action": "add", "action_input": "{\"a\": 12, \"b\": 30}"}42
{"action": "Final Answer", "action_input": "北京晴,25度;12+30=42"}Finished chain.
两个都成功返回了 `output` 字段。你可以通过 `xml_result["intermediate_steps"]` 拿到每一步的 `(AgentAction, observation)` 元组,用来做日志或前端展示。 验证成功的判断标准有三个:第一,`output` 字段非空且语义正确;第二,`intermediate_steps` 里能看到至少一次工具调用;第三,verbose 日志里没有出现 `Could not parse LLM output` 或 `Invalid or incomplete response`。三个都满足,说明协议层和运行时层都通了。 如果只想快速验证模型本身能不能按格式输出,可以先用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 手动发一条带格式要求的 prompt,看模型返回是否符合预期,再回到代码里调 Agent。这样能把「模型问题」和「Agent 配置问题」分开定位。 ## 5. 本篇常见错排查:401、解析失败与 OAuth 报错 排障按报错信息对号入座,下面这几个是我实际遇到频率最高的。 **401 Unauthorized**:Key 没读到或 Base URL 拼错。先确认 `.env` 被 `load_dotenv()` 加载,再打印 `os.getenv("OPENAI_API_KEY")` 看是否为空。如果 Key 正常,检查 `base_url` 是不是误加了 `/v1`。TaoToken 的地址是 `https://taotoken.net/api`,不要写成 `https://taotoken.net/api/v1`。401 还有一种情况是 Key 被禁用或额度耗尽,去控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 看用量。 **Could not parse LLM output**:这是 Agent 场景最典型的错。XMLAgent 报这个,通常是模型没输出 `<tool>` 标签,而是用自然语言说「我将调用 get_weather」。解决办法是把 `temperature` 降到 0,并在 prompt 里加一句「Do not explain, output the tag directly」。JSONAgent 报这个,多半是模型输出的 JSON 没被 ```json 包裹,或者 `action` 值不在 `{tool_names}` 里。把 `handle_parsing_errors=True` 打开,让错误回灌重试,能自动救回大部分情况。 **local proxy failed**:出现在 Cline、Claude Code 这类客户端里,通常是 Base URL 或网络配置问题。确认客户端里填的是 `https://taotoken.net/api`,Key 和 Model ID 三项齐全。如果客户端有代理设置,关掉再试。这个报错和 Agent 代码无关,是客户端到服务端的链路问题。 **reading 'choices' of undefined**:说明返回体结构不对,通常是 Base URL 指向了一个不兼容 OpenAI 协议的端点。检查你的 `base_url` 是否指向 `https://taotoken.net/api`,以及模型名是否在服务端存在。模型名写错有时不会返回 404,而是返回一个空结构,LangChain 去读 `choices` 就报 undefined。 **OAuth 相关报错**:在 Claude Code 或 Codex 的 `auth.json` 里,如果同时存在 OAuth token 和 API Key,可能冲突。建议只用 API Key 方式,把 `auth.json` 里的 OAuth 字段清掉,Base URL 填 `https://taotoken.net/api`,Model ID 填对应模型。Codex 的 `auth.json` 三件套是 `base_url`、`api_key`、`model`,缺一不可。 **AgentExecutor 无限循环**:模型反复调用同一个工具不收敛。把 `max_iterations` 设成 5 以内,并在 prompt 里强调「如果已有足够信息,直接输出 final_answer」。`return_intermediate_steps=True` 能帮你看到它到底卡在哪一步。 排障时优先看 verbose 日志的最后一次 LLM 原始输出,90% 的解析错误都能从那里看出格式偏差。接入层面的问题可以对照接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 逐字段核对。 ## 6. 按场景选型与后续接入 选型其实就三条判断。模型是 Claude 系列、对 XML 标签敏感,用 XMLAgent,解析容错高,prompt 里标签写清楚就行。模型是 GPT 系列或国产模型、指令遵循强,用 JSONAgent,结构严格,方便你做二次校验。两者都跑不通时,先别急着换 Agent 类型,把 `temperature` 降到 0、把 prompt 里的格式示例补全,往往就好了。 AgentExecutor 是共用外壳,不管里面是 XML 还是 JSON,`max_iterations`、`handle_parsing_errors`、`return_intermediate_steps` 这三个参数都建议显式设置。默认值在生产环境里不够稳。 如果你要把这套链路接到本地编码工具里,Claude Code 的接入配置在 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 有完整字段说明,Base URL、Key、Model ID 三件套照填即可。长期跑 Agent 任务的话,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 比按量更适合高频调用。Key 统一在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 管理,给每个 Agent 项目分一个 Key,出问题时能快速定位是哪个项目打爆了额度。 最后留一个实操建议:把 XMLAgent 和 JSONAgent 的 prompt 都存成独立的 `.py` 或 `.txt` 文件,用 `load_prompt` 加载,而不是硬编码在业务逻辑里。这样换模型、调格式时只改 prompt 文件,不用动 Agent 构建代码。我试过在同一个项目里同时保留两套 prompt,用环境变量切换 Agent 类型,A/B 对比不同模型下的工具调用成功率,比拍脑袋选型靠谱得多。