☰
一文讲清楚Agent、MCP、Function Call:用TaoToken统一Key跑通实操代码示例
2026/9/28 3:58:36 网站建设 项目流程

1. 先把三个概念摆到同一张桌子上

Agent、MCP、Function Call 这三个词经常被混着用,但它们在系统里站的位置完全不同。你可以把一次完整的 AI 任务想象成装修房子:Function Call 是电钻,模型自己决定什么时候按下开关;MCP 是标准化的插座面板,让不同品牌的电器都能插上去用;Agent 是那个拿着图纸、会自己判断先刷墙还是先铺砖的工头。三者不是替代关系,而是从「单点能力」到「标准接口」再到「自主调度」的递进。

Function Call 的本质是模型的原生能力扩展。你在请求里声明一组函数签名,模型在推理时如果判断需要外部数据,就会返回一个结构化的调用意图,由你的代码去执行真正的函数,再把结果塞回对话。它跟具体模型绑定,换一个模型可能就要改声明格式。

MCP(Model Context Protocol)解决的是「接口不统一」的问题。它把外部服务抽象成 MCP Server,用一套标准协议描述工具、资源和提示模板。任何支持 MCP 的客户端都能发现并调用这些服务,不用为每个模型单独写适配层。它不绑定模型,是应用层和工具层之间的通用插座。

Agent 则是站在最上层的调度者。它维护任务状态、做多步规划、决定什么时候调 Function Call、什么时候走 MCP、什么时候直接回答。Agent 本身不提供工具能力,它依赖下面两层把活干完。

这篇要做的,是用 TaoToken 的统一 Key 和 API 通道,把这三层在本地串起来跑一次闭环。你会拿到可复制的settings.json、config.toml骨架,一个 MCP 服务注册片段,以及一段能实际触发的 Function Call 示例代码。目标不是讲透理论,而是让你在本地看到「模型决定调工具 → 工具返回 → 模型总结」这条链路真的跑通。

2. 为什么用 TaoToken 做统一入口

本地实验最容易卡住的地方不是代码,是 Key 管理。Function Call 要调模型,MCP 客户端要调模型,Agent 循环里每一步还要调模型,如果每个环节用不同的供应商和 Key,排查问题时你根本分不清是模型返回格式变了还是网络抖了。

TaoToken 在这里的角色是一个统一的 API 通道。你申请一个 Key,就能在模型对话、编码工具、Agent 框架里用同一套鉴权和计费。对做实验来说,最大的好处是变量少了:模型侧的行为一致,出问题只可能是你的配置或代码。

接入前先做两件事。第一,到控制台创建一个 API Key,记下它以sk-开头的完整字符串,后面所有配置都用它。第二,确认你要用的模型名,TaoToken 的模型列表在文档里有,选一个支持 Function Call 的,比如带工具调用能力的通用模型。

注意:Key 只显示一次,创建后立刻复制到本地密码管理器或环境变量里,不要写进会提交到 Git 的配置文件。

环境变量方式最省事,后面所有配置都引用它:

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

如果你用的是 Windows PowerShell,换成$env:TAOTOKEN_API_KEY="sk-..."。这一步做完,后面的settings.json和config.toml就不用硬编码密钥了。

3. 可复制的配置骨架

3.1 settings.json:给支持 MCP 的客户端用

很多本地 AI 工具用settings.json管理模型和 MCP 服务。下面这份骨架把模型通道指向 TaoToken,同时注册一个本地 MCP 服务。字段名按你实际用的工具微调,结构是通用的。

{ "model": { "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "model": "你的模型名", "temperature": 0.2 }, "mcpServers": { "local-tools": { "command": "python", "args": ["-m", "mcp_server_demo"], "env": { "TAOTOKEN_API_KEY": "${TAOTOKEN_API_KEY}" } } } }

这里${TAOTOKEN_API_KEY}是占位引用,工具启动时会从环境变量读取。temperature设低一点,Function Call 场景下模型更倾向稳定输出结构化参数,减少瞎编字段的概率。

3.2 config.toml:给编码类工具用

编码工具通常用 TOML。下面这份把模型通道和 MCP 服务都写进去,注意base_url不要带末尾斜杠。

[model] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "${TAOTOKEN_API_KEY}" name = "你的模型名" max_tokens = 4096 [mcp_servers.local-tools] command = "python" args = ["-m", "mcp_server_demo"] [mcp_servers.local-tools.env] TAOTOKEN_API_KEY = "${TAOTOKEN_API_KEY}"

两份配置的核心逻辑一样:模型走 TaoToken 统一通道,MCP 服务作为子进程启动,通过标准输入输出通信。你不需要在 MCP 服务里再写一遍模型鉴权,它只负责提供工具。

3.3 MCP 服务注册片段

MCP Server 的最小实现是暴露一个工具列表和一个调用入口。下面用 Python 写一个只做加法的服务,方便验证链路,不涉及任何外部依赖。

import json import sys TOOLS = [ { "name": "add_numbers", "description": "计算两个数字之和", "inputSchema": { "type": "object", "properties": { "a": {"type": "number", "description": "第一个加数"}, "b": {"type": "number", "description": "第二个加数"} }, "required": ["a", "b"] } } ] def handle(request): method = request.get("method") if method == "tools/list": return {"tools": TOOLS} if method == "tools/call": params = request.get("params", {}) args = params.get("arguments", {}) result = args.get("a", 0) + args.get("b", 0) return {"content": [{"type": "text", "text": str(result)}]} return {"error": "unknown method"} for line in sys.stdin: line = line.strip() if not line: continue req = json.loads(line) resp = handle(req) resp["id"] = req.get("id") sys.stdout.write(json.dumps(resp) + "\n") sys.stdout.flush()

把它保存为mcp_server_demo.py,放在配置里args能找到的路径。这个服务只响应tools/list和tools/call两个方法,足够验证 MCP 注册是否生效。

4. 跑通一次 Function Call 闭环

4.1 准备请求

Function Call 的关键是请求体里带tools字段。下面这段用requests直接打 TaoToken 的 API,模型名换成你实际用的。

import json import os import requests API_KEY = os.environ["TAOTOKEN_API_KEY"] BASE_URL = "https://taotoken.net/api" tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的当前天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } } ] payload = { "model": "你的模型名", "messages": [ {"role": "user", "content": "北京今天天气怎么样?"} ], "tools": tools, "tool_choice": "auto" } resp = requests.post( f"{BASE_URL}/v1/chat/completions", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json=payload, timeout=60 ) print(resp.status_code) print(json.dumps(resp.json(), ensure_ascii=False, indent=2))

4.2 预期返回与二次请求

如果模型判断需要调工具,返回的choices[0].message里会带tool_calls数组,里面有函数名和参数。你拿到后执行本地函数,再把结果作为role: "tool"的消息追加回去,发起第二次请求。

data = resp.json() msg = data["choices"][0]["message"] if msg.get("tool_calls"): call = msg["tool_calls"][0] args = json.loads(call["function"]["arguments"]) # 本地执行 weather_result = {"city": args["city"], "temp": "22C", "condition": "晴"} follow_up = { "model": "你的模型名", "messages": [ {"role": "user", "content": "北京今天天气怎么样?"}, msg, { "role": "tool", "tool_call_id": call["id"], "content": json.dumps(weather_result, ensure_ascii=False) } ] } resp2 = requests.post( f"{BASE_URL}/v1/chat/completions", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json=follow_up, timeout=60 ) print(resp2.json()["choices"][0]["message"]["content"])

第二次返回应该是模型用自然语言总结的天气,比如「北京今天晴,气温 22 摄氏度」。看到这句话,说明 Function Call 闭环成立。

4.3 把 MCP 接进 Agent 循环

Agent 的调度逻辑可以很简单:先让模型规划,如果规划里提到需要外部工具,就分别走 Function Call 或 MCP。下面是一个最小 Agent 骨架,把前面两个能力串起来。

class MiniAgent: def __init__(self, api_key, base_url, model): self.api_key = api_key self.base_url = base_url self.model = model self.history = [] def plan(self, user_input): self.history.append({"role": "user", "content": user_input}) resp = requests.post( f"{self.base_url}/v1/chat/completions", headers={"Authorization": f"Bearer {self.api_key}"}, json={ "model": self.model, "messages": self.history + [ {"role": "system", "content": "判断是否需要调用工具,只回答 need_tool 或 direct"} ] }, timeout=60 ) return resp.json()["choices"][0]["message"]["content"] def run(self, user_input): decision = self.plan(user_input) if "need_tool" in decision: # 这里接 Function Call 或 MCP 调用 return "已触发工具调用分支" return "直接回答分支"

这个骨架不完整,但足够让你看到 Agent 的决策点在哪:它不直接执行工具,而是先判断,再分派。真正的工具执行逻辑复用第 4.1 和 4.2 的代码。

5. 本篇常见错排查

报 401 或鉴权失败:先确认Authorization头是Bearer sk-...格式,中间有空格。再检查环境变量是否在当前 shell 生效,echo $TAOTOKEN_API_KEY看有没有输出。如果 Key 是从文件读的,注意有没有多余换行。

模型不返回 tool_calls:两个原因最常见。一是模型本身不支持 Function Call,换一个支持工具调用的模型名。二是tool_choice设成了none,改成auto。另外提示词太模糊也可能让模型选择直接回答,把用户问题写具体一点。

MCP 服务启动后没反应:MCP 走标准输入输出,服务端必须逐行读取并 flush。检查你的服务有没有在每行处理后sys.stdout.flush(),否则客户端会一直等。另外确认command和args拼起来能在终端直接跑通,路径问题占这类故障的大半。

第二次请求报 tool_call_id 不匹配:追加role: "tool"消息时,tool_call_id必须和模型返回的call["id"]完全一致,不能自己编。同时msg对象要原样放回 messages,不能只放 content。

返回内容被截断:Function Call 的返回可能较长,检查max_tokens是否设得太小。编码类工具里如果max_tokens默认 1024,多轮工具调用容易撞上限,调到 4096 更稳。

6. 把 Key 和通道固定下来

跑通一次之后,建议把配置固化:模型通道统一走 TaoToken,Key 只放环境变量,MCP 服务用版本管理,Function Call 的声明抽成独立模块。这样下次换模型或加工具,改动面很小。

如果你主要在做工具接入和排障,先去 API Keys 页面确认 Key 状态,再对照接入文档检查base_url和路径拼接。路径是https://taotoken.net/api加/v1/chat/completions,不要重复写/v1。

想先验证模型对工具调用的支持情况,可以直接在模型对话里发一句带函数声明的请求,看返回结构里有没有tool_calls。这一步能省掉很多在代码里反复调试的时间。

长期做编码和 Agent 循环的话,Coding Plan 更适合高频调用场景,额度和通道都更稳定。把这三层跑顺之后,你会发现 Agent 的复杂度主要不在模型,而在工具注册和状态管理,而这正是 MCP 想帮你标准化掉的部分。

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

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

立即咨询