白银价格返回CPI:MCP工具词匹配的致命陷阱

AI Agent工具「银价查询返回CPI」案例揭示:静默返回错误答案比报错更危险,解析器必须具备拒绝能力。
canli-markets-mcp 的作者通过一个真实 bug 揭示了 AI Agent 工具开发中的深层陷阱:当用户查询"silver price"时,工具因词频匹配逻辑缺陷,将单个通用词"price"命中 CPI 描述,静默返回了美国消费者物价指数数据。问题的根源在于旧版评分机制用"匹配词占比"而非"区分性词汇全命中"来判断相关性,导致最能说明查询意图的特定词("silver")反而被忽视。修复方案将词汇分为填充词、类别词和特定词三类,要求所有特定词必须全部出现在候选条目中,否则明确拒绝并给出重定向指引。测试策略同步调整为"先写拒绝测试",因为正向测试从第一天就能通过,真正暴露问题的恰恰是那些本不应匹配却被硬凑答案的场景。这一案例的工程价值在于:静默返回错误答案的工具,比直接报错的工具对 Agent 管道危险得多。
AI Agent 调用数据源时很少传递精确的 ID,它们传递的是词语:"10 year treasury yield"、"core inflation"、"silver price"。因此大多数包装数据源的工具都会有一个小小的解析器(resolver),把词语转成 ID。而这个解析器里的一个 bug,可能永远不会抛出错误——它只会默默返回一个错误答案。
MCP 工具 canli-markets-mcp 的作者分享了一个颇具代表性的工程教训:当用户问"silver price(白银价格)"时,工具竟然返回了美国 CPI(消费者物价指数)的数据。这个看似荒谬的结果背后,藏着所有"自然语言转 ID"类工具都可能踩中的陷阱。

Bug 是如何产生的
在 canli-markets-mcp 0.1.0 版本中,economic_series 函数既接受 FRED 系列 ID,也接受自然语言。它的匹配逻辑是:对 47 个常见系列逐一打分,分数是"请求中的词在该系列描述中出现的比例",只要最高分达到 50% 就返回该系列。
问题出在这个阈值的计算方式上。一个两词的请求,只需要有一个词匹配上就能达到 50%。CPI 的描述是 "cpi consumer price index inflation all items",于是 "silver price" 里的 "price" 单独一个词就足够触发匹配——白银被解析成了美国 CPI。
作者给出的对照表触目惊心:
| 请求 | 0.1.0 返回 | 现在 |
|---|---|---|
| silver price | CPIAUCSL (美国CPI) | 拒绝,指向 price_history |
| copper price | CPIAUCSL | 拒绝 |
| bitcoin price | CPIAUCSL | 拒绝 |
| japan inflation | CPIAUCSL | 拒绝,指向 world_economy |
| uk unemployment rate | UNRATE (美国失业率) | 拒绝 |
| wage growth | 实际GDP增速 | 平均时薪 |
每一个错误答案都返回了真实的系列、真实的数字。结果里确实带有系列标题,错误其实就摆在那里——但没有任何机制逼迫 Agent 去读它。
这里暴露出一个清晰的模式:决定问题本质的词("silver"、"japan"、"uk")恰恰是什么都没匹配上的词;而匹配上的词("price"、"inflation"、"rate")只说明你想要哪类数字,却没说明你要哪一个。
FRED(Federal Reserve Economic Data)是美国圣路易斯联邦储备银行维护的一个公开经济数据库,收录了超过 80 万条来自全球数百个来源的经济时间序列。每条序列都有一个唯一的字母数字 ID,例如 CPIAUCSL 代表"All Urban Consumers: All Items in U.S. City Average"(美国城市消费者物价指数),UNRATE 代表美国失业率。这些 ID 对机器精确,对人类却毫无直觉性——这正是需要自然语言解析层的原因,也是解析层出错时错误极难被察觉的原因。在 AI Agent 的场景下,Agent 收到的是带有系列标题和真实数字的 JSON 响应,如果它不主动核对标题与问题的匹配程度,错误就会无声地传入后续推理。
替代它的新规则
修复的核心思路是重新定义"哪些词真正重要"。现在一个统一的函数同时服务三个解析器:FRED 系列、World Bank 指标和 OECD 展望指标。值得一提的是,两个较新的解析器在发布前就犯了同样的毛病——"wage growth" 给出 GDP 增速,"house prices" 给出 CPI,OECD 的 "exchange rate" 甚至给出失业率。
新规则把请求中的每个词归入三类:
- 填充词(Filler):直接忽略,如 "what is"、"show me"、"since"、"this year" 以及年份(如 2020)。
- 类别词(Kind words):说明数字的种类,如 rate、growth、index、level、daily、monthly。它们会参与最佳匹配打分,但永远不是必需的。
- 特定词(Specific words):除上述之外的所有词,而每一个特定词都必须出现在候选条目中。
这个"特定词必须全部命中"的硬约束,正是修复的关键。matchWords 函数先过滤掉填充词,再从中剥离类别词得到特定词;若候选条目没有包含全部特定词,直接跳过;在满足条件的条目里,再按共享词数量择优。split 函数会小写化、保留字母数字和 &,并做单复数折叠("wages" 变 "wage")。平局时按列表顺序取第一个,让更常见的系列排在前面。
每个解析器还能定制自己的词表。由于 FRED 系列全是美国数据,"us" 和 "fred" 在这里就是填充词,而 "interest" 则被当作类别词。
当没有任何条目合格时,工具会明确说"没找到",并指明去哪里找:
No common series matches "silver price". These are US series: for another country's economy use world_economy or economic_outlook, and for the price of a commodity, currency, crypto or stock use price_history. Or pass a FRED series id.
这个差异对 Agent 至关重要。拿到这条拒绝信息的 Agent 可以接着去调用 price_history;而拿到 CPI 的 Agent,没有任何线索告诉它该这么做。
先测试"拒绝"的能力
旧测试全都在检查"正确的请求能找到对应系列",而且全部通过。但没有一条测试去问:当请求的是我们没有的数据时,工具会不会拒绝?
新的测试文件开头就补上了这一环——用 "silver price"、"bitcoin price"、"japan inflation"、"uk unemployment rate" 等一批请求断言:必须抛出 NotFound,且错误信息中要包含 price_history 和 world_economy 这样的指引。之后才是 42 个必须解析到唯一精确系列的正向请求,以及对 World Bank 和 OECD 解析器的同类测试。
一个有意思的设计是:"interest rates" 被故意拒绝。因为利率种类繁多,强行挑一个返回,不过是给同一个 bug 换了件好看的外衣罢了。
这里触及软件测试中一个经典的盲区:测试覆盖率衡量的是代码路径,而非语义正确性。一套只包含正向用例的测试套件,本质上是在问"系统能否对它认识的输入给出正确答案",而不是"系统能否识别自己不认识的输入"。在传统 CRUD 应用里,这个盲区的代价相对可控——用户能看到错误结果并反馈。但在 Agent 管道中,工具的输出是中间状态,不经过人眼,一个错误答案可能在被用于生成最终报告之前已经参与了多步推理。这使得"拒绝能力"成为 Agent 工具可靠性的一阶属性,而不是锦上添花的防御性编程。
可复用的工程启示
作者总结的三条经验,适用于所有做 Agent 工具和自然语言检索的开发者:
- 词到 ID 的解析器,本质上是一个只返回一个结果的搜索引擎。它必须有返回"零结果"的能力。 没有拒绝机制的匹配,等于强行把任何输入都塞进某个答案。
- 打分应该基于能区分条目的词,而不是匹配词的占比。 通用词最容易匹配,却最不重要——这正是 "price" 匹配上 CPI 的根源。
- 先写拒绝测试,再写正向测试。 正向测试第一天就能通过,真正暴露问题的是那些你没有数据却被硬凑答案的场景。
开源可试用
canli-mcp 免费、采用 MIT 协议,在本地运行,使用 FRED、World Bank 和 OECD 公共数据无需 API key:
npx -y canli-mcp
# Claude Code
claude mcp add canli -- npx -y canli-mcp
代码托管在 GitHub 的 arhancanli/canlicapital(含每个 MCP 服务器及 words.mjs 与测试文件)和 arhancanli/alphac(策略研究引擎)。作者也欢迎提交解析错误的 issue——那正是他想要听到的 bug。
对于正在构建 Agent 工具的开发者,这个案例的价值远超一个具体 bug 本身:它提醒我们,静默返回错误答案的工具,比直接报错的工具危险得多。
相关推荐

Workbuddy零基础入门:桌面AI智能体如何落地工作流
Workbuddy桌面AI智能体零基础教程解读:从安装、积分领取到文件管理与自动化工作流搭建,对比CodeX的适用场景与成本差异,帮助职场人完成从AI提问者到AI管理者的思维转变。

OpenAI智能体新栈:计算机操作、决策API与超速工作流
OpenAI Dev Day发布智能体新栈:Computer Use操作速度超越普通人,Decisions API基于Luna实现极低延迟分类,Agents API开放同源计算机操作能力,GPT-6.1 Sol成本仅为Astra七分之一。本文梳理核心更新、实测数据与开发者落地要点。

梁文峰开源推理框架DeepInfo,硬刚美国禁令
DeepSeek在开源模型后再开源推理框架DeepInfo,包含GPU/CPU交叉流水线、小模型预判大模型复核、华为昇腾与英伟达芯片全面适配等技术,直面美国联邦禁令。本文解析其对AI产业落地的意义与开源策略。