DeepSeek Agent工具描述工程实战:让模型用对工具的提示词艺术与TaoToken统一调用

1. DeepSeek Agent 工具描述工程踩坑现场:为什么模型总选错工具

先说结论:DeepSeek Agent 工具描述工程,本质是给模型写一份"工具使用说明书"。这份说明书的质量,直接决定模型在多工具协作时选对工具的概率。我见过太多团队把工具函数写得漂漂亮亮,docstring 却只有一句"获取天气",然后纳闷为什么 Agent 拿get_weather(city="DeepSeek")去查新闻。

工具描述是模型选择工具的唯一依据。模型看不到你的代码实现,看不到你的数据库表结构,它只能看到工具名、description 和 parameters 这三样东西。你给的信息越模糊,模型就越容易靠"字面联想"去猜。用户说"查一下 DeepSeek 最新消息",模型看到有个工具叫get_weather描述是"获取信息",看到另一个叫web_search描述是"搜索",两个都沾边,它就可能选错。

这个问题的典型表现有三类。第一类是语义漂移:工具描述太宽泛,覆盖了本不该覆盖的场景,模型把"查新闻"映射到"获取信息"上。第二类是参数误解:参数说明只写了类型没写约束,模型传了个城市名给搜索工具,或者把超时单位当成毫秒。第三类是边界缺失:没告诉模型"什么时候不该用这个工具",模型在历史天气、天气预报、实时天气之间反复横跳。

面向多工具协作场景,这个问题会被放大。单工具时选错也就是报个错,多工具时选错会引发连锁反应——模型先用错工具拿到脏数据,再基于脏数据继续推理,最后整个任务链跑偏。所以工具描述工程不是"锦上添花",而是 Agent 工具集开发的地基。

这篇内容适合两类人:一是正在用 DeepSeek 做 Agent 工具集开发的工程师,二是已经把工具跑通但发现模型选工具不稳定、想系统优化提示词的团队。我会拆解描述结构、参数说明、调用边界的写法,给出可复制的工具描述模板,并用 TaoToken 统一 Key/API 通道做实际验证,把"模型选错工具"的复现和修正动作完整走一遍。

先明确一个认知:工具描述不是写给人看的注释,是写给模型看的提示词。人看注释能靠上下文脑补,模型不能。所以判断一段工具描述好不好,标准只有一个——把这段描述单独丢给模型,它能不能准确判断"这个工具该在什么场景用、不该在什么场景用"。

2. TaoToken 前置准备:统一 Key 与 API 通道配置

在动手改工具描述之前,先把调用通道理顺。多工具协作场景下,你会频繁切换模型、对比不同描述版本的效果,如果每个模型都单独配 Key、单独记 Base URL,调试成本会很高。我习惯用 TaoToken 做统一入口,一个 Key 走多个模型,改描述、跑 A/B 对比时不用来回换配置。

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。进控制台创建 API Key,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建后立刻复制保存,页面刷新后不再完整显示。

第二步,确认模型 ID。工具描述工程要对比不同描述下的工具选择准确率,建议固定用同一个模型跑,避免模型差异干扰结论。模型列表和对话调试可以在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里看,先用对话页手动试几轮,确认模型对工具描述的理解符合预期,再写进代码。

第三步,理解三件套。不管用哪种客户端,接入都是三样东西:Base URL 填https://taotoken.net/api,API Key 填刚创建的那串,Model ID 填你选定的模型标识。这三件套在后面的 Claude Code、Cline、Codex 配置里会反复出现,先记牢。

注意:Key 只存在服务端环境变量或本地密钥管理里,别硬编码进仓库。工具描述工程要反复跑实验,Key 泄露的风险比单次调用高得多。

如果你打算长期做 Agent 工具集开发、频繁跑多轮对比实验,可以看下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,按周期计费比按次调用更适合这种高频调试场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,遇到路径或参数问题先查文档再排查。

通道理顺后,把环境变量配好,后面所有实验都基于这套配置跑:

export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_MODEL="你的模型ID"

这样后面写脚本时直接读环境变量,换模型只改一个值,工具描述本身不动,对比实验才干净。

3. 可复制配置:工具描述模板与客户端 settings 片段

这一节给可直接抄的配置。先给工具描述模板,再给客户端 settings 片段,路径和字段名保持原样,你按自己项目改值即可。

3.1 工具描述四要素模板

好的工具描述包含四块:做什么、何时用、何时不用、示例。参数描述再补类型、约束、示例值。下面这个模板可以直接套:

from typing import Literal def get_weather(city: str) -> str: """ 获取指定城市当前(今天)的实时天气信息。 何时使用: - 用户询问某个城市今天或现在的天气 - 用户需要实时天气、当前温度、当前是否下雨 何时不使用: - 用户询问历史天气(昨天、上周、去年) - 用户询问天气预报(明天、下周) - 用户询问非天气信息(新闻、股票、技术文档) 参数: - city: 城市中文名,字符串,如"北京"、"上海"、"深圳"。 不接受英文名、拼音、省份名。 示例: - 用户问"北京今天天气怎么样" -> get_weather(city="北京") - 用户问"上海现在下雨吗" -> get_weather(city="上海") """ ...

对比一下坏描述,差距一目了然:

def get_weather(city: str) -> str: """获取天气""" ...

坏描述里模型不知道三件事:获取哪个城市的天气、什么时候该用、什么时候不该用。它只能靠工具名get_weather猜,用户问"DeepSeek 最新消息"时,模型看到"获取天气"里的"获取"二字,就可能误判成"获取信息"。

3.2 参数描述模板

参数描述要写清类型、约束、示例值。以run_bash为例:

def run_bash(command: str, timeout: int = 30) -> str: """ 执行 bash 命令并返回标准输出与标准错误。 何时使用: - 跑测试、装依赖、编译代码、查看目录 何时不使用: - 读取单个文件内容(用 read_file) - 按内容搜索代码(用 grep) 参数: - command: 要执行的 bash 命令,字符串形式,支持管道和重定向。 示例:"ls -l"、"pytest tests/ -v"、"pip install requests" - timeout: 超时秒数,整数,默认 30。防止命令卡死。 大多数命令 30 秒内完成,复杂命令可设 60 到 120。 示例: - 用户问"跑一下测试" -> run_bash(command="pytest tests/ -v") """ ...

参数说明里最容易漏的是"单位"和"格式"。timeout不写单位,模型可能传 30000 以为是毫秒;command不写"字符串形式、支持管道",模型可能传个列表进来。

3.3 客户端 settings 片段

如果你用 Claude Code 做工具调试,settings 里按三件套配:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "你的模型ID" } }

Cline 的 MCP 配置同理,Base URL、Key、Model ID 三件套一个不能少:

{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "your-mcp-server"], "env": { "BASE_URL": "https://taotoken.net/api", "API_KEY": "sk-你的Key", "MODEL_ID": "你的模型ID" } } } }

Codex 的auth.json也是三件套结构:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "你的模型ID" }

提示:三件套里 Base URL 统一填https://taotoken.net/api,不要带末尾斜杠,不要拼 UTM 参数。Key 和 Model ID 按你控制台里的实际值填。

配置写完后,先别急着跑工具实验,用一次最小请求确认通道通。下一节给验证脚本。

4. 验证请求与成功结果:复现选错工具并修正

这一节做两件事:先复现"模型选错工具",再改描述验证修正效果。整个过程用同一套 TaoToken 通道,保证变量只有一个——工具描述。

4.1 复现:坏描述下的错误调用

准备两个工具,描述都写得很糊:

tools_bad = [ { "type": "function", "function": { "name": "get_weather", "description": "获取信息", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"] } } }, { "type": "function", "function": { "name": "web_search", "description": "搜索", "parameters": { "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"] } } } ]

用户输入"查一下 DeepSeek 最新消息",跑一次请求:

import os, json, requests resp = requests.post( f"{os.environ['TAOTOKEN_BASE_URL']}/v1/chat/completions", headers={"Authorization": f"Bearer {os.environ['TAOTOKEN_API_KEY']}"}, json={ "model": os.environ["TAOTOKEN_MODEL"], "messages": [{"role": "user", "content": "查一下 DeepSeek 最新消息"}], "tools": tools_bad, "tool_choice": "auto" }, timeout=60 ) print(json.dumps(resp.json(), ensure_ascii=False, indent=2))

实测下来,坏描述版本里模型有较大概率返回get_weather(city="DeepSeek")。原因很直接:get_weather的描述是"获取信息","查消息"被模型理解成"获取信息",而web_search的描述只有"搜索"两个字,信息量不足以压过前者。这就是典型的语义漂移。

4.2 修正:好描述下的正确调用

把两个工具的描述换成四要素版本:

tools_good = [ { "type": "function", "function": { "name": "get_weather", "description": ( "获取指定城市当前(今天)的实时天气信息。" "何时使用:用户询问某城市今天或现在的天气。" "何时不使用:历史天气、天气预报、非天气信息。" "示例:用户问'北京今天天气' -> get_weather(city='北京')" ), "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市中文名,如'北京'、'上海',不接受英文名或拼音" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "web_search", "description": ( "搜索网页获取最新信息。" "何时使用:最新新闻、实时动态、技术文档、API 用法。" "何时不使用:本地文件内容、代码逻辑、历史天气。" "示例:用户问'DeepSeek 最新消息' -> web_search(query='DeepSeek 最新消息')" ), "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,字符串,保留用户原始表述" } }, "required": ["query"] } } } ]

同样的请求换成tools_good,模型返回web_search(query="DeepSeek 最新消息")的概率显著提升。成功结果的判断标准不是"看起来对",而是返回的tool_calls里function.name等于web_search,且arguments里的 query 保留了用户原始语义。

4.3 批量验证:五个任务对比准确率

单条请求有随机性,要稳定结论得跑批量。准备五个任务,每个任务跑三轮取多数:

tasks = [ "北京今天天气怎么样", "查一下 DeepSeek 最新消息", "读一下 test_math.py 的内容", "跑一下 pytest", "找一下所有用到 get_weather 的地方" ] def run_task(task, tools): resp = requests.post( f"{os.environ['TAOTOKEN_BASE_URL']}/v1/chat/completions", headers={"Authorization": f"Bearer {os.environ['TAOTOKEN_API_KEY']}"}, json={ "model": os.environ["TAOTOKEN_MODEL"], "messages": [{"role": "user", "content": task}], "tools": tools, "tool_choice": "auto" }, timeout=60 ) data = resp.json() return data["choices"][0]["message"].get("tool_calls", []) for task in tasks: calls = run_task(task, tools_good) names = [c["function"]["name"] for c in calls] print(task, "->", names)

预期结果:五个任务分别命中get_weather、web_search、read_file、run_bash、grep。坏描述版本里,第二个任务容易错到get_weather,第五个任务容易错到read_file(一个个文件读,效率低)。好描述版本把这两个坑填上,准确率从 3/5 提到 5/5。

注意:跑批量验证时固定temperature为较低值,减少随机性干扰。工具描述工程比的是描述质量,不是采样运气。

验证通过后,把好描述模板固化进工具集,后面新增工具都按四要素写,别退回"获取信息"这种糊描述。

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

工具描述改完,通道配置也写了,实际跑起来还是会撞几类报错。这一节按真实报错对照排查,每个都给定位动作。

5.1 401 Unauthorized

报错长这样:

{"error": {"message": "Unauthorized", "type": "invalid_request_error"}}

先查三处:Key 是否完整复制(创建后只显示一次,复制漏字符很常见)、请求头是否写成Authorization: Bearer sk-xxx(少 Bearer 或多了空格都会 401)、Key 是否被误删或过期。用 curl 最小化验证:

curl -s https://taotoken.net/api/v1/models \ -H "Authorization: Bearer $TAOTOKEN_API_KEY"

返回模型列表说明 Key 有效,401 就出在请求构造上,不在 Key 本身。

5.2 local proxy failed

这个报错通常出现在客户端侧,提示本地代理连接失败。先确认 Base URL 填的是https://taotoken.net/api,没有多余路径、没有末尾斜杠、没有拼 UTM 参数。再确认客户端没有额外配置本地转发端口。工具描述工程本身不涉及网络层,这类报错九成是配置串了。

5.3 reading choices 报错

报错类似Cannot read properties of undefined (reading 'choices'),意思是响应体里没有choices字段。常见原因有两个:一是请求根本没成功,返回的是错误 JSON,代码却直接取data["choices"];二是流式响应没处理完就解析。排查动作是先打印完整响应体:

data = resp.json() print(json.dumps(data, ensure_ascii=False, indent=2))

看到error字段就按错误信息处理,别硬取choices。工具调用场景下,还要确认tools参数格式正确,格式错了部分服务端会返回 400 而非标准结构。

5.4 OAuth 相关报错

如果客户端提示 OAuth 认证失败或 token 无效,说明它走的是 OAuth 流程而不是 API Key 流程。工具描述实验用 API Key 通道即可,把客户端的认证方式切到 API Key,三件套填 Base URL、Key、Model ID。Claude Code 场景下确认ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL都配了,缺一个都会走到默认认证路径。

5.5 工具选错的排查清单

除了通道报错,工具选错本身也要有排查路径。按这个顺序查:第一,工具描述里有没有"何时不使用";第二,参数描述有没有写类型和示例值;第三,多个工具的描述是否语义重叠(比如两个都写"获取信息");第四,工具名是否过于抽象(do_stuff这种);第五,模型是否固定(换模型对比会引入新变量)。五条里前三条命中,基本就是描述工程问题,回去改模板即可。

6. 语义一致 CTA:把工具描述工程沉淀成可复用资产

工具描述工程做完一轮,别停在"这次跑通了"。把它沉淀成团队资产,后面每加一个工具都按模板走,选错工具的概率会持续下降。

具体做法:建一个tool_descriptions/目录,每个工具一个文件,固定四要素结构,参数描述带类型、约束、示例值。新增工具时先写描述再写实现,写完用 TaoToken 通道跑五个典型任务验证选择准确率,准确率不达标就回去改描述,别改模型。

需要拿 Key 和查接入细节的,走这两个入口:API Keys 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。想先在对话页手动试工具描述效果的,用模型对话 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,改一段描述发一轮请求,看模型选哪个工具,比写脚本快。长期做 Agent 工具集开发、要反复跑对比实验的,Coding Plan 在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,按周期走比按次调用省心。

最后留一个我踩过的坑:工具描述不是越长越好。四要素写全即可,堆太多同义词反而让模型抓不住重点。一段描述控制在 150 字以内,参数说明每个不超过两行,示例给一到两个就够。描述工程的目标是让模型"一眼判断该不该用",不是写论文。