☰
AI Agent Skill Day 3:Tool Use技能:工具使用能力的封装与集成
2026/9/29 3:59:43 网站建设 项目流程

1. 为什么你的 Agent 总是“只会聊天不会干活”

很多人第一次搭 AI Agent,都会遇到同一个尴尬:模型能跟你聊得头头是道,但一旦让它“查一下明天上海天气”“把 100 美元换成人民币”,它就开始编。不是它笨,而是它手里没有工具。大模型的知识停在训练截止那一刻,实时数据、私有接口、数据库、计算器,它一个都碰不到。

Tool Use(工具使用)要解决的就是这件事。你可以把它理解成给模型配了一双手:模型负责“想”,工具负责“做”。Function Calling 是这套机制里的“调用约定”,LangChain 是帮你把约定封装成可复用对象的“装配线”,而 MCP 协议则是让工具能跨平台共享的“通用插座”。三者叠起来,Agent 才真正从聊天框走进业务流。

这篇是 AI Agent Skill 系列 Day 3,聚焦 Tool Use 的落地。我会带你走完一条完整链路:定义工具描述、写参数 Schema、封装调用回环、接上模型、跑一次端到端验证,最后把常见报错一个个拆掉。适合已经写过简单 LangChain Demo、但工具一多就乱、调用一回就崩的开发者。读完你手里会有一套可复制的工具注册骨架,而不是又一篇“概念科普”。

2. 前置准备:把模型接入层先搭稳

工具调用对模型的要求比普通对话高:它必须支持 Function Calling / Tool Use 协议,否则你传过去的 tools 参数会被直接忽略。所以第一步不是写工具,而是先把模型接入层跑通。

我习惯用 TaoToken 做统一接入层,原因是它同时兼容 OpenAI 风格的 function calling 和 Claude 的 tool_use 格式,切换模型时不用重写工具定义。你只需要在控制台拿到 API Key,然后把 base_url 指过去即可。整个流程不涉及任何网络环境改造,就是标准的 HTTPS 调用。

具体操作路径是这样的:先到官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册账号,进入控制台后创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。拿到 Key 之后,接口基址用 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为 OpenAI SDK 的 base_url 使用。

如果你只是想先验证模型能不能正确识别工具描述,可以打开模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 手动贴一段工具 Schema 试试。但真正要跑回环,还是得写代码。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言 SDK 的 base_url 配置示例,照着改一行就行。

注意:API Key 只显示一次,创建后立刻复制到 .env 文件,不要硬编码进源码,更不要提交到 Git。

3. 可复制配置:工具描述、参数 Schema 与调用回环

这一节是全文的核心。我会先给出一套最小可运行的工具封装骨架,再解释每个字段为什么这么写。你直接复制就能跑,改掉业务逻辑即可复用。

3.1 环境依赖与目录结构

先装依赖,Python 3.9 以上:

pip install langchain-core langchain-openai requests python-dotenv jsonschema

目录建议这样分,工具多了也不会乱:

agent_tool_demo/ ├── .env ├── tools/ │ ├── __init__.py │ ├── base.py │ ├── weather.py │ └── currency.py └── run_agent.py

.env 里放两样东西:

OPENAI_API_KEY=你的TaoTokenKey OPENAI_BASE_URL=https://taotoken.net/api

3.2 工具抽象基类:统一输入输出

工具一多,最怕的就是每个工具返回格式不一样,模型看不懂。所以先定一个基类,强制所有工具走同一套run()入口,内部做校验和异常兜底。

# tools/base.py from abc import ABC, abstractmethod from typing import Any, Dict import jsonschema class BaseTool(ABC): name: str = "" description: str = "" parameters: Dict[str, Any] = {} @abstractmethod def _run(self, **kwargs) -> Dict[str, Any]: """真正的业务逻辑,子类实现""" raise NotImplementedError def run(self, input_data: Dict[str, Any]) -> Dict[str, Any]: """统一入口:先校验参数,再执行,异常不抛出""" try: jsonschema.validate(instance=input_data, schema=self.parameters) except jsonschema.ValidationError as e: return {"success": False, "error": f"参数校验失败: {e.message}"} try: data = self._run(**input_data) return {"success": True, "data": data} except Exception as e: return {"success": False, "error": str(e)} def to_openai_schema(self) -> Dict[str, Any]: """转成 OpenAI function calling 需要的格式""" return { "type": "function", "function": { "name": self.name, "description": self.description, "parameters": self.parameters, }, }

这里有个关键点:parameters本身就是一份 JSON Schema,直接拿来做输入校验,一份定义两处用,既喂给模型又校验自己,避免“模型传了错参数、工具直接崩”的连锁反应。

3.3 两个真实工具:天气与汇率

工具描述写得好不好,直接决定模型选不选得对。描述里要写清楚“做什么、什么时候用、参数什么含义”,别只写一句“查询天气”。

# tools/weather.py import os import requests from .base import BaseTool class WeatherTool(BaseTool): name = "get_weather" description = "查询指定城市的当前天气。当用户询问某地天气、气温、是否下雨时使用。" parameters = { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,例如 北京、上海、Tokyo", } }, "required": ["city"], } def _run(self, city: str): # 示例用公开接口,生产请替换为你的数据源 url = f"https://wttr.in/{city}?format=j1" resp = requests.get(url, timeout=8) resp.raise_for_status() current = resp.json()["current_condition"][0] return { "city": city, "temp_c": current["temp_C"], "desc": current["weatherDesc"][0]["value"], "humidity": current["humidity"], }
# tools/currency.py import requests from .base import BaseTool class CurrencyTool(BaseTool): name = "convert_currency" description = "把一种货币金额换算成另一种货币。用户提到汇率、换算、多少钱时使用。" parameters = { "type": "object", "properties": { "amount": {"type": "number", "description": "金额,例如 100"}, "from_currency": {"type": "string", "description": "源货币代码,如 USD"}, "to_currency": {"type": "string", "description": "目标货币代码,如 CNY"}, }, "required": ["amount", "from_currency", "to_currency"], } def _run(self, amount: float, from_currency: str, to_currency: str): url = f"https://open.er-api.com/v6/latest/{from_currency.upper()}" resp = requests.get(url, timeout=8) resp.raise_for_status() rates = resp.json().get("rates", {}) if to_currency.upper() not in rates: raise ValueError(f"不支持的货币: {to_currency}") converted = amount * rates[to_currency.upper()] return { "amount": amount, "from": from_currency.upper(), "to": to_currency.upper(), "result": round(converted, 2), }

3.4 调用回环:模型决策 → 执行 → 回填

这是 Tool Use 最容易写错的地方。回环的本质是:模型返回 tool_calls,你执行工具,把结果作为 ToolMessage 塞回消息列表,再调一次模型让它总结。少任何一步,模型都拿不到工具结果。

# run_agent.py import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, ToolMessage from tools.weather import WeatherTool from tools.currency import CurrencyTool load_dotenv() TOOLS = [WeatherTool(), CurrencyTool()] TOOL_MAP = {t.name: t for t in TOOLS} def build_llm(): return ChatOpenAI( model="gpt-4o-mini", temperature=0, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://taotoken.net/api"), ) def run_agent(query: str, max_rounds: int = 3) -> str: llm = build_llm() llm_with_tools = llm.bind_tools([t.to_openai_schema() for t in TOOLS]) messages = [HumanMessage(content=query)] for _ in range(max_rounds): ai_msg = llm_with_tools.invoke(messages) messages.append(ai_msg) if not ai_msg.tool_calls: return ai_msg.content for call in ai_msg.tool_calls: tool = TOOL_MAP.get(call["name"]) if tool is None: result = {"success": False, "error": f"未知工具 {call['name']}"} else: result = tool.run(call["args"]) messages.append( ToolMessage(content=str(result), tool_call_id=call["id"]) ) return "达到最大回环次数,未能完成。" if __name__ == "__main__": print(run_agent("上海现在天气怎么样?")) print(run_agent("100美元等于多少人民币?"))

注意max_rounds这个护栏。没有它,模型偶尔会陷入“调工具→不满意→再调”的死循环,加上次数上限能兜住。

4. 验证请求:跑一次端到端调用

配置写完,必须验证。分两步:先确认模型能识别工具,再确认回环能跑通。

第一步,单独测工具本身,不经过模型:

python -c "from tools.weather import WeatherTool; print(WeatherTool().run({'city': '上海'}))"

正常输出应该是{'success': True, 'data': {'city': '上海', 'temp_c': '...', ...}}。如果这里是 False,问题在工具内部,跟模型无关。

第二步,跑完整回环:

python run_agent.py

预期看到两段输出。第一段类似“上海当前气温 22°C,多云,湿度 65%”,第二段类似“100 美元约等于 720 元人民币”。如果你在日志里打印 messages,会看到清晰的四段结构:HumanMessage → AIMessage(带 tool_calls) → ToolMessage → AIMessage(最终回答)。

实测下来,一次工具调用的耗时主要在网络请求,天气和汇率接口各在 300–800ms 之间,模型决策本身很快。如果超过 3 秒还没返回,先查工具接口的 timeout,再查模型侧的网络。

提示:想快速验证模型对工具描述的理解,可以到模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 手动贴 Schema 问它“什么情况下你会调用这个工具”,能提前发现描述歧义。

5. 本篇常见错排查

工具调用报错大多集中在下面几类,我按出现频率排了序。

第一类:模型根本不调工具。现象是直接返回一段文字,没有 tool_calls。原因通常是工具描述太模糊,或者bind_tools传的格式不对。检查to_openai_schema()返回的type是不是"function",parameters是不是合法 JSON Schema。描述里补上“当用户……时使用”这类触发条件,命中率会明显上升。

第二类:参数校验失败。报错参数校验失败: 'city' is a required property。这是模型传了空参数或字段名拼错。解决办法是在 description 里给参数加示例,比如“城市名称,例如 北京”,模型对示例的敏感度高于纯类型说明。

第三类:ToolMessage 的 tool_call_id 对不上。报错类似tool_call_id not found。这是回环里最常见的坑:一次返回多个 tool_calls 时,必须为每个 call 生成一条对应的 ToolMessage,id 一一对应,不能合并成一条。

第四类:工具内部异常没被兜住。如果_run里直接抛异常且没被run()捕获,整个 Agent 会中断。基类里的 try/except 就是干这个的,确保任何工具失败都返回结构化错误,让模型有机会换工具或告知用户。

第五类:base_url 配错导致 404。如果你用的是 TaoToken 接入,确认 base_url 是https://taotoken.net/api,不要多加/v1或斜杠。SDK 会自己拼路径,多写反而 404。接入细节可对照接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

第六类:MCP 集成时的工具命名冲突。当你通过 MCP 协议挂载外部工具服务器时,不同 server 可能暴露同名工具。建议在注册层加命名空间前缀,比如weather.get_weather,避免路由时选错。

6. 从单机工具到 MCP 集成,以及下一步

单机跑通之后,你迟早会遇到“工具散落在各个项目里、每个 Agent 都要重新注册一遍”的问题。MCP 协议就是冲这个来的:它把工具的描述和执行拆成 client 和 server 两端,工具提供方按协议暴露能力,Agent 侧只负责发现和调用。落到代码上,你现在的BaseTool骨架几乎不用改,只需要在注册层多一个“从 MCP server 拉取工具列表并转成 schema”的适配器,to_openai_schema()那一步复用即可。

如果你打算把工具调用能力长期用在编码或 Agent 工作流里,建议直接上 Coding Plan,省去每次手动配 Key 和额度的麻烦,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。Claude Code 相关的工具调用配置可以参考 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite ,里面有针对 tool_use 格式的适配说明。

最后留一个我踩过的坑:工具描述不是写完就完事,它是要迭代的。上线后把每次“模型选错工具”的 case 记下来,回头改 description,比调 temperature 有用得多。工具注册表保持精简,低频工具定期下线,模型的选择准确率会跟着涨。

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

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

立即咨询