☰
AI Agent(五):Tool Use 工具调用——用 TaoToken 统一 Key 打通 Function Calling 与 MCP
2026/10/2 20:09:45 网站建设 项目流程

1. 从“只会说”到“能动手”:AI Agent 工具调用到底卡在哪

AI Agent 的 Tool Use(工具调用)能力,说白了就是让大模型从“只会聊天”变成“能真正干活”。Function Calling 负责让 LLM 输出结构化的调用意图,MCP(Model Context Protocol)负责把外部工具标准化地注册进来,两者配合才能跑通一条完整的工具调用链路。这套东西适合谁?适合已经在写 Agent 项目、被“模型说得好听但啥也干不了”折磨过的开发者,也适合刚接触 Function Calling、想搞明白 tool_calls 到底怎么回填结果的新手。

我一开始做 Agent 的时候,最大的困惑不是模型不会调用工具,而是调用链路太碎:LLM 输出一个 JSON,我得手动解析、手动执行、手动把结果塞回 messages,中间任何一环格式对不上,整个循环就断了。更麻烦的是,每接一个新工具就要改一遍代码,工具多了以后维护成本直线上升。后来我把接入点统一到 TaoToken 的 API 通道上,用一套 Key 同时跑 Function Calling 和 MCP,链路才真正稳定下来。

这篇文章不讲空概念,直接给你能复制的东西:一份工具 schema、一段 MCP 配置、一次端到端调用验证。你跟着走一遍,就能在自己的 Agent 项目里把 Tool Use 跑通。核心检索词就三个:AI Agent、Tool Use、Function Calling,外加 MCP 这个绕不开的协议。

先说清楚一个最容易搞混的点:LLM 本身不执行工具。它只负责“决定调用哪个工具、传什么参数”,真正执行的是你的 Agent 代码。这个分工想明白了,后面所有报错你都能定位到是“模型决策层”还是“执行层”的问题。

2. TaoToken 前置:统一 Key 打通 Function Calling 与 MCP 的接入点

在动手写代码之前,先把接入层理清楚。Tool Use 链路里有两个地方要发请求:一是 LLM 的 chat/completions(带 tools 参数),二是 MCP Server 的工具注册与调用。如果这两条链路各用一套鉴权和地址,调试起来会非常痛苦。我的做法是统一走 TaoToken 的 API 通道,Base URL 固定为https://taotoken.net/api,Key 用同一个,模型 ID 也在一处管理。

先拿 Key。打开 API Keys 页面(https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite),创建一个新 Key,复制出来存到环境变量里。注意别把 Key 硬编码进代码,后面配置片段里我都用TAOTOKEN_API_KEY这个变量名。

export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

模型 ID 这块,Function Calling 对模型能力有要求,不是所有模型都能稳定输出 tool_calls。我实测下来,选支持工具调用的模型,在请求里显式带上tools字段,模型才会返回结构化的调用意图。如果你不确定当前模型支不支持,可以先去模型对话页面(https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite)手动发一条带工具定义的请求,看返回里有没有tool_calls字段,这是最快的验证方式。

MCP 这一侧,TaoToken 的接入文档(https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite)里有完整的协议说明和示例配置。我建议你先按文档把 MCP Server 的地址和鉴权方式确认一遍,再回到 Agent 代码里对接。很多人卡在 MCP 连不上,其实不是协议问题,是 Base URL 或 Key 没对齐。

这里有个关键点:Function Calling 和 MCP 虽然协议不同,但都可以复用同一个 API 通道和同一套鉴权。你不需要为 MCP 单独申请一套凭证,也不需要维护两个 Base URL。统一之后,Agent 代码里只需要一个 client 实例,工具注册和模型调用共用,链路清晰很多。

如果你打算长期做编码类 Agent,或者要跑多工具编排的复杂任务,可以考虑 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite),它在长链路调用和并发工具执行上更稳一些。不过对于本文这个最小闭环,普通 API Key 就够了。

3. 可复制配置:工具 schema、MCP 配置与 Agent 接入片段

这一节是全文最核心的部分,所有片段都可以直接复制。我按“工具定义 → MCP 注册 → Agent 接入”的顺序给,你照着改路径和参数就行。

先看工具 schema。Function Calling 要求工具用 JSON Schema 描述参数,描述写得越清楚,模型调用越准。下面这个add_device_model工具是我实际项目里用的,你可以替换成自己的业务工具:

{ "type": "function", "function": { "name": "add_device_model", "description": "添加设备机型到数据库。当用户查询未解锁的机型时,可以使用此工具将机型添加到数据库中。机型代码应该去除后缀(如 IN、CN 等),例如 CPH2223IN 应该使用 CPH2223。", "parameters": { "type": "object", "properties": { "model": { "type": "string", "description": "机型代码,例如 CPH2223(去除后缀如 IN、CN 等)" }, "marketName": { "type": "string", "description": "中文营销名,例如 OPPO Find N6" } }, "required": ["model"] } } }

注意description里我特意写了“去除后缀”的规则,因为模型很容易把CPH2223IN原样传进来。工具描述就是给模型的说明书,你写得越具体,它犯错越少。

接下来是 MCP 配置片段。MCP Server 的注册一般放在一个配置文件里,路径按你的项目结构来。我用的是mcp/servers.json:

{ "mcpServers": { "device-tools": { "command": "python", "args": ["-m", "mcp_server.device"], "env": { "TAOTOKEN_API_KEY": "${TAOTOKEN_API_KEY}", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }

这里command和args是启动 MCP Server 的方式,env里把 Key 和 Base URL 透传进去。如果你的 MCP Server 是远程服务,把command换成url字段即可。配置里的${TAOTOKEN_API_KEY}会从环境变量读取,避免明文写 Key。

然后是 Agent 接入片段。我用 Python 写一个最小闭环,核心是三步:注册工具、发起带 tools 的请求、处理 tool_calls 并回填结果。

import os import json import requests BASE_URL = os.environ["TAOTOKEN_BASE_URL"] API_KEY = os.environ["TAOTOKEN_API_KEY"] MODEL_ID = "你的模型ID" def call_llm(messages, tools): resp = requests.post( f"{BASE_URL}/v1/chat/completions", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }, json={ "model": MODEL_ID, "messages": messages, "tools": tools, "tool_choice": "auto", }, timeout=60, ) resp.raise_for_status() return resp.json() def execute_tool(name, arguments): args = json.loads(arguments) if name == "add_device_model": # 这里替换成你的真实业务逻辑 return json.dumps({"success": True, "message": f"已添加 {args['model']}"}) return json.dumps({"success": False, "message": "unknown tool"})

这段代码里,tool_choice: "auto"让模型自己决定要不要调用工具。execute_tool是执行层,模型返回的arguments是字符串,要先json.loads再处理。执行结果再序列化成字符串回填,这是 Function Calling 的硬性要求。

Agent Loop 的核心逻辑是这样:

def run_agent(user_input, tools): messages = [{"role": "user", "content": user_input}] for _ in range(10): data = call_llm(messages, tools) choice = data["choices"][0]["message"] messages.append(choice) tool_calls = choice.get("tool_calls") if not tool_calls: return choice.get("content", "") for tc in tool_calls: result = execute_tool( tc["function"]["name"], tc["function"]["arguments"], ) messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result, }) return "达到最大迭代次数"

注意messages.append(choice)这一步,必须把模型返回的 assistant 消息(含 tool_calls)原样加进历史,否则下一轮模型不知道自己在等工具结果。role: "tool"的消息要带上tool_call_id,和请求里的id对应,这是回填的关键。

4. 验证请求:一次端到端调用与成功结果

配置写完了,现在跑一次端到端验证。这一步的目的是确认整条链路通了:模型输出 tool_calls → Agent 执行工具 → 结果回填 → 模型生成最终回复。

先构造请求。把第 3 节的工具 schema 放进tools数组,用户输入用一句会触发工具调用的话:

tools = [{ "type": "function", "function": { "name": "add_device_model", "description": "添加设备机型到数据库。机型代码应去除后缀,例如 CPH2223IN 使用 CPH2223。", "parameters": { "type": "object", "properties": { "model": {"type": "string", "description": "机型代码"}, "marketName": {"type": "string", "description": "中文营销名"} }, "required": ["model"] } } }] print(run_agent("帮我把机型 CPH2223 添加到数据库", tools))

跑起来之后,你会看到模型第一轮返回的不是文本,而是tool_calls。结构大概是这样:

{ "choices": [{ "message": { "role": "assistant", "content": null, "tool_calls": [{ "id": "call_abc123", "type": "function", "function": { "name": "add_device_model", "arguments": "{\"model\": \"CPH2223\"}" } }] } }] }

看到content是null、tool_calls有内容,说明模型正确识别了工具调用意图。这时候 Agent 执行execute_tool,把结果回填,再发第二轮请求。第二轮模型拿到工具结果后,会生成最终文本回复,类似“已成功将机型 CPH2223 添加到数据库”。

如果你用的是 MCP 方式,验证步骤多一层:先确认 MCP Server 启动成功,再确认 Agent 能列出工具。可以用下面这段代码检查工具注册:

import requests resp = requests.post( f"{BASE_URL}/v1/mcp/tools/list", headers={"Authorization": f"Bearer {API_KEY}"}, json={"server": "device-tools"}, timeout=30, ) print(resp.json())

返回里应该能看到add_device_model这个工具的定义。如果列表是空的,说明 MCP Server 没注册成功,回到第 3 节的servers.json检查command和args路径。

实测下来,整条链路最容易出问题的地方是arguments的解析。模型返回的是 JSON 字符串,不是对象,直接当 dict 用会报错。一定要先json.loads。另外tool_call_id必须一一对应,多个工具调用时不能串。

成功跑通后,你会看到类似这样的完整输出:

[第一轮] 模型返回 tool_calls: add_device_model({"model": "CPH2223"}) [执行] 工具返回: {"success": true, "message": "已添加 CPH2223"} [第二轮] 模型最终回复: 已成功将机型 CPH2223 添加到数据库。

这就是 Tool Use 的最小闭环。MCP 只是把工具注册从“代码里硬编码”变成“配置里声明”,执行链路是一样的。

5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth

这一节按真实报错来,都是我踩过的坑。你对照自己的日志找对应项。

401 Unauthorized:最常见,Key 没传对或者过期了。检查Authorization头是不是Bearer开头,中间有空格。如果你用的是环境变量,确认TAOTOKEN_API_KEY真的被 export 了,Python 里os.environ读不到会直接报 KeyError 或者传空。还有一种情况是 Key 复制时带了换行或空格,肉眼看不出来,重新复制一遍。

local proxy failed / connection refused:这个报错通常出现在 MCP Server 启动阶段。servers.json里的command路径不对,或者 Python 环境里没装 MCP 依赖。先手动在终端跑一遍python -m mcp_server.device,看能不能启动。如果报模块找不到,装依赖;如果报端口占用,换端口。注意别把本地代理配置和这个混在一起,MCP 走的是标准输入输出或 HTTP,不需要额外网络层。

reading choices 报错 / KeyError: 'choices':模型返回体里没有choices字段,说明请求本身失败了,但你的代码直接去取data["choices"]。先打印完整resp.text看错误信息。常见原因是模型 ID 写错,或者请求体里tools格式不对导致服务端拒绝。还有一种情况是模型不支持 Function Calling,返回了普通文本但没有choices结构,换一个支持工具调用的模型 ID 即可。

OAuth / 鉴权失败:如果你接的是需要 OAuth 的 MCP Server,servers.json里要配auth字段,不能只靠 API Key。检查 token 有没有过期,scope 对不对。TaoToken 的接入文档里有 OAuth 流程的完整说明,按文档走一遍。如果报invalid_grant,多半是回调地址和注册时不一致。

tool_calls 为空但模型有回复:模型没决定调用工具,直接给了文本。原因通常是工具描述不够清楚,或者用户输入没触发调用意图。把description写具体,加上“当用户……时使用此工具”的引导。也可以在 system message 里明确告诉模型“你有工具可用,合适时请调用”。

arguments 解析失败:json.loads报错,说明模型返回的参数字符串格式不对。先打印原始字符串看,有时候模型会多包一层引号或者带 markdown 代码块标记。可以在解析前做一次清洗,去掉json 和标记。

排查顺序建议:先看 HTTP 状态码,再看返回体结构,最后看业务逻辑。401 和连接类错误在接入层,choices 和 tool_calls 在协议层,arguments 在执行层。分层定位,别一上来就改代码。

6. 语义一致 CTA:把 Tool Use 链路固化到你的项目里

跑通一次不代表稳定。我的经验是,把工具 schema、MCP 配置、Agent Loop 这三样东西版本化管理,每次改工具都走一遍端到端验证。工具描述和参数 schema 的改动最容易引入回归,改完一定要重新跑第 4 节的验证请求。

如果你在接入过程中遇到鉴权或协议问题,直接去 API Keys 页面(https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite)重新生成一个 Key 试试,再对照接入文档(https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite)检查 Base URL 和请求格式。想先验证模型对工具调用的支持程度,用模型对话(https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite)手动发一条带 tools 的请求最快。长期做编码类 Agent 或多工具编排,Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite)在长链路和并发场景下更省心。

最后留一个实用技巧:在 Agent Loop 里加日志,把每一轮的tool_calls和工具执行结果都打出来。出问题时不用猜,直接看日志就知道是模型没调用、参数传错,还是执行层报错。这个习惯能帮你省掉大量调试时间。

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

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

立即咨询