1. 从聊天机器人到 Agent差的不只是几个工具很多人第一次接触 Agent 这个概念时会觉得它和聊天机器人差不多——不就是问一句答一句吗我一开始也这么想直到真正动手搭了一个才发现两者之间隔着一整套「思考-行动-观察」的循环机制。聊天机器人的工作模式是你给一段文字它回一段文字结束。它不会主动去查资料不会调用计算器更不会在发现自己答错后重新规划。而 Agent 的核心区别在于它能自己决定「下一步该干什么」——是先搜索一下最新数据还是直接调用某个 API或者干脆承认信息不足需要追问。这个决策过程就是 ReActReasoning Acting循环要解决的问题。这篇内容面向的是想从零跑通第一个 Agent 原型的开发者。你不需要有 LangChain 深度使用经验但最好写过 Python、调过至少一个 LLM 的 API。我会用一个统一的 Key 来打通 LLM 推理和工具调用两条通道避免在多个平台之间来回切换 Key 和配置。整个链路拆成四块环境准备、Agent 骨架配置、ReAct 循环实现、一次完整的验证请求。跟着走一遍你能得到一个能自主决定「要不要调工具、调哪个工具」的最小可用 Agent。2. 前置准备用 TaoToken 统一 Key 打通 LLM 与工具通道搭 Agent 最烦的事情之一是 LLM 一个 Key、搜索工具一个 Key、代码执行环境又一个 Key配置散落在四五个地方调试的时候光找 Key 就耗掉一半耐心。我试过把不同供应商的 Key 写在一个.env里结果每次换模型都要改代码里的 base_url 和 model 名非常容易出错。TaoToken 在这里的作用是提供一个统一的接入层LLM 推理请求走同一个 API 地址工具调用通道也通过同一套 Key 体系管理。你只需要在settings.json里维护一份配置Agent 的推理模块和工具模块都从这里读。先拿到 Key。访问控制台创建 API Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite创建完成后你会得到一个以sk-开头的字符串。把它存到环境变量里不要硬编码进代码export TAOTOKEN_API_KEYsk-你的key如果你用的是 Claude Code 或类似的编码 Agent 工具可以直接参考接入文档里的配置方式https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteAPI 的基础地址是https://taotoken.net/api注意这个地址不带 UTM 参数直接用于代码里的base_url配置。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content3. 可复制配置Agent 骨架的 settings.json 片段Agent 的骨架配置需要解决三件事LLM 怎么调、工具怎么注册、ReAct 循环的提示词模板长什么样。下面这份settings.json可以直接复制使用把 Key 的部分替换成你自己的。{ llm: { base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, model: claude-sonnet-4-20250514, max_tokens: 2048, temperature: 0.3 }, tools: { search_web: { enabled: true, description: 搜索互联网获取实时信息输入为搜索关键词字符串, endpoint: https://taotoken.net/api/tools/search, api_key_env: TAOTOKEN_API_KEY }, run_python: { enabled: true, description: 执行 Python 代码片段输入为可运行的代码字符串, endpoint: https://taotoken.net/api/tools/code, api_key_env: TAOTOKEN_API_KEY } }, agent: { max_iterations: 6, stop_sequence: Final Answer:, prompt_template: react_v1 } }几个关键参数说明一下。max_iterations控制 ReAct 循环最多跑几轮设成 6 是防止 Agent 陷入无限思考——我踩过的坑就是没设上限结果模型在「搜索-发现不够-再搜索」之间循环了十几次Token 消耗直接起飞。temperature设 0.3 是因为 Agent 需要稳定的决策太高的随机性会让它频繁选错工具。stop_sequence用来告诉模型什么时候该输出最终答案而不是继续调工具。工具注册部分每个工具需要提供description这段文字会直接拼进 Prompt 里模型靠它来判断该不该调用这个工具。描述写得越清楚工具选择越准确。比如「搜索互联网获取实时信息」就比「搜索工具」好得多。Prompt 模板单独放在一个文件里核心结构是这样的REACT_PROMPT 你是一个可以使用工具的智能助手。请严格按照以下格式回应 Question: 用户的问题 Thought: 你需要思考当前该做什么 Action: 要调用的工具名必须是 [{tool_names}] 中的一个 Action Input: 传给工具的输入 Observation: 工具返回的结果 ...Thought/Action/Action Input/Observation 可以重复多次 Thought: 我现在知道最终答案了 Final Answer: 对用户问题的最终回答 可用工具 {tools} 开始 Question: {input} {agent_scratchpad}agent_scratchpad是循环过程中不断累积的中间步骤每次调用 LLM 时把之前的 Thought-Action-Observation 记录拼进去模型就能基于历史决定下一步。4. 实现 ReAct 循环从 Thought 到 Observation 的完整代码配置就绪后核心逻辑就是一个 while 循环。下面这段代码实现了完整的 ReAct 流程可以直接跑import os import json import re import requests with open(settings.json) as f: config json.load(f) API_KEY os.environ[config[llm][api_key_env]] BASE_URL config[llm][base_url] def call_llm(prompt: str) - str: resp requests.post( f{BASE_URL}/v1/messages, headers{ x-api-key: API_KEY, anthropic-version: 2023-06-01, content-type: application/json }, json{ model: config[llm][model], max_tokens: config[llm][max_tokens], temperature: config[llm][temperature], messages: [{role: user, content: prompt}] } ) resp.raise_for_status() return resp.json()[content][0][text] def execute_tool(tool_name: str, tool_input: str) - str: tool_cfg config[tools].get(tool_name) if not tool_cfg or not tool_cfg[enabled]: return f错误工具 {tool_name} 未注册或未启用 resp requests.post( tool_cfg[endpoint], headers{Authorization: fBearer {API_KEY}}, json{input: tool_input} ) resp.raise_for_status() return resp.json().get(output, 工具返回为空) def run_agent(user_input: str) - str: tool_names [k for k, v in config[tools].items() if v[enabled]] tool_descs \n.join( f- {k}: {v[description]} for k, v in config[tools].items() if v[enabled] ) scratchpad for i in range(config[agent][max_iterations]): prompt REACT_PROMPT.format( toolstool_descs, tool_names, .join(tool_names), inputuser_input, agent_scratchpadscratchpad ) output call_llm(prompt) if Final Answer: in output: return output.split(Final Answer:)[-1].strip() action_match re.search(rAction:\s*(\w), output) input_match re.search(rAction Input:\s*(.), output) if not action_match or not input_match: scratchpad output \nObservation: 格式错误请按 Thought/Action/Action Input 格式输出\n continue tool_name action_match.group(1).strip() tool_input input_match.group(1).strip() observation execute_tool(tool_name, tool_input) scratchpad f{output}\nObservation: {observation}\n return 达到最大迭代次数未能得出最终答案这段代码的关键点在于scratchpad的累积方式。每次循环把模型输出的 Thought 和 Action、以及工具返回的 Observation 拼接到一起下一轮再喂回去。模型看到「我之前搜了什么、得到了什么结果」就能判断是继续调工具还是给出最终答案。execute_tool里做了工具名的校验如果模型输出了一个不存在的工具名会返回错误信息而不是直接崩溃。这个错误信息也会进入 Observation模型看到后通常会修正自己的选择。5. 验证请求一次完整的 Thought-Action-Observation 流程配置和代码都就位后跑一个真实请求来验证整条链路。用下面这个调用result run_agent(帮我查一下 2025 年诺贝尔物理学奖颁给了谁并计算获奖者人数乘以 100 万) print(result)预期会看到类似这样的中间过程实际输出取决于模型和工具返回Thought: 这个问题需要两步先搜索诺贝尔物理学奖信息再做乘法计算。 Action: search_web Action Input: 2025 诺贝尔物理学奖 获奖者 Observation: 2025 年诺贝尔物理学奖授予 John Clarke、Michel Devoret 和 John Martinis... Thought: 我找到了 3 位获奖者现在需要计算 3 * 1000000。 Action: run_python Action Input: print(3 * 1000000) Observation: 3000000 Thought: 我现在知道最终答案了。 Final Answer: 2025 年诺贝尔物理学奖授予 3 位科学家获奖者人数乘以 100 万等于 3000000。这个流程完整展示了 ReAct 的三个阶段Thought 是模型的推理Action 是它选择的工具Observation 是工具返回的结果。模型在第一轮判断需要搜索第二轮判断需要计算第三轮确认信息足够后输出最终答案。如果你想单独验证模型对话通道是否正常可以用模型对话入口快速测一下https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite如果 Agent 需要长期运行、频繁调用工具建议看一下 Coding Plan 的额度方案https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite6. 本篇常见错排查报错一KeyError: TAOTOKEN_API_KEY环境变量没设置或者设置在了错误的 shell 会话里。检查echo $TAOTOKEN_API_KEY是否有输出。如果是在 IDE 里运行需要在运行配置里单独加环境变量而不是只在终端 export。报错二模型一直输出 Action 但不输出 Final Answer通常是 Prompt 模板里的stop_sequence没生效或者max_iterations设得太小。先检查模板里是否明确写了「如果信息足够输出 Final Answer」。另一个可能是工具返回的 Observation 太长把上下文撑满了模型看不到完整历史。可以在execute_tool里对返回结果做截断比如只保留前 500 字符。报错三工具调用返回 401工具通道的 Key 和 LLM 通道的 Key 不一致。检查settings.json里工具的api_key_env是否指向了同一个环境变量。如果工具端点是外部服务确认该服务的鉴权方式是不是 Bearer Token。报错四ReAct 循环卡在同一个工具上反复调用模型没有正确解析 Observation。检查execute_tool返回的内容是否包含换行符或特殊字符这些可能干扰模型对格式的解析。建议在 Observation 前后加明确的分隔标记比如[OBSERVATION_START]...[OBSERVATION_END]。报错五requests.exceptions.SSLError本地 Python 环境的证书链有问题。可以临时用verifyFalse跳过验证来确认是否是证书问题但生产环境不要这么做。正确的做法是更新certifi包pip install --upgrade certifi。排查完这些之后如果还有接入层面的问题可以对照接入文档里的示例请求逐项检查https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite7. 下一步从原型到可用 Agent 的迭代方向跑通上面这个最小原型后你手里已经有一个能自主决策的 Agent 了。接下来可以按需加东西加一个向量数据库做长期记忆让 Agent 记住跨会话的信息把工具从两个扩展到五六个覆盖搜索、计算、文件读写、API 调用优化 Prompt 模板加入 Few-shot 示例来提升工具选择的准确率。但别一上来就堆功能。我的建议是先把当前这个版本跑稳用十几个不同的问题测一遍观察它在哪些情况下会选错工具、哪些情况下会陷入循环。把这些边界情况摸清楚之后再针对性地加工具和改 Prompt比盲目扩展要有效得多。
