1. 模型如何“看见”你的工具
先说明一点:对模型而言,tool 并不是“你用 TypeScript 写的漂亮函数”,而是一段结构化描述,大致是这样的:
- name:技术名,例如 "search_gifts";
- description:面向人类的文本,解释何时以及为何使用该工具;
- inputSchema:带有字段的 JSON Schema,每个字段也可以有 description、类型与约束。
非常粗略地说,模型会这样做(GPT 脑内伪代码):
1. 读取用户请求(任意语言)。
2. 读取工具列表:name + description + 参数说明。
3. 对每个工具估计是否适合任务。
4. 如需调用工具——根据 schema 生成带参数的 JSON。
5. 否则——直接以文本回答。
这里有两个重要结论。
首先,工具的 description 字段并不是给开发者看的注释,而是模型与你的后端之间的接口。如果工具描述含糊、不完整,或者与用户语言不一致,模型更容易“打偏”:选错工具、填错参数,或在需要工具时直接给出文本答案。
其次,JSON Schema 中各字段的 description 同样重要,与工具本身的描述一样重要。模型确实会阅读每个属性的 description,并据此判断把“年龄”放进哪个字段、把“预算”放进哪个字段,以及哪里应该是 id。
GiftGenius 小示例
以我们的工具 search_gifts 为例。在“仅 EN”版本中,它可能是这样的:
// server/tools/searchGifts.ts
export const searchGiftsTool = {
name: "search_gifts",
description: "Search for gift ideas based on user preferences.",
inputSchema: {
type: "object",
properties: {
recipient_age: {
type: "integer",
description: "Age of the recipient in years.",
},
budget: {
type: "number",
description: "Maximum budget in user's currency.",
},
},
required: ["budget"],
},
};
如果用户写道:“需要给妈妈准备礼物,她 60 岁,预算不超过 3000 卢布”,模型需要:
- 判断 search_gifts 是合适的工具。
- 判断“60 岁”应放入 recipient_age,“3000 卢布”应放入 budget。
在只有英文描述时,GPT 依然能完成,但这会要求它多做一步内部“翻译”。在多语言场景或较弱模型上,这会影响准确度。
2. 多语言 App 中“仅英文描述”的问题
在第 9 模块我们曾略谈过整体“本地化地图”:UI 小部件、目录、错误、交易文案等。现在聚焦到一个点:当工具描述只用英语,而用户用俄语或西班牙语交流时——混乱就开始了。
典型场景:
- 用户:“给一位程序员朋友挑礼物,预算 50 欧元”。
- 模型查看 tools 列表,发现只有英文描述。
- 如果请求也是英文——基本没问题。
- 如果请求是其他语言,它就不得不:
- 先理解用户请求,
- 在脑中把它映射到英文描述,
- 挑选工具,
- 然后还要在 JSON 中提取参数。
在强模型中还能运行,但问题在于:
- 不调用工具的回答占比更高——模型“觉得”自己可以直接答;
- 参数更容易出错(尤其涉及货币、计量单位、地区限制时);
- 多工具之间的路由逻辑更不稳定(选了“错档”的工具)。
一个简单的错误示例:budget 字段期望“使用用户的货币”,而描述里完全没提。模型就默认是 USD,在用户明明想要 50 欧元时,后端却收到 50 美元。
这时就轮到描述的本地化上场了。
3. 本地化方案:独立工具 vs 多语言描述
有两种基本架构思路,二者都可行。
为每种语言提供独立的工具
在这种方案里,你为不同语言创建多个工具,使用不同的 name,每个工具的描述语言各自对应。
在 GiftGenius 中可能是这样:
export const searchGiftsEn = {
name: "search_gifts_en",
description: "Search for gift ideas based on user preferences.",
// ...
};
export const searchGiftsRu = {
name: "search_gifts_ru",
description: "根据用户偏好挑选礼物。",
// ...
};
对 ChatGPT App 而言,关键是可用工具列表要取决于 locale。如果 locale = "ru-RU", 你的 MCP 服务器应仅返回 search_gifts_ru。如果 locale = "en-US", ——只返回 search_gifts_en。
优点在于描述干净、单语化。你甚至可以把 App 当作多个单语版本,每个都有自己的 prompts 和描述。当语言少而市场差异大时,这很舒服。
缺点是逻辑重复和分析复杂。后端代码多半仍是同一套处理,而在 MCP/清单层面却要维护两个不同工具。每次变更都别忘了同步更新所有语言版本的描述。
Insight(数据截至 2025-12-01)
实验上未发现显著优势来自将 description 使用用户语言/locale。无论描述语言,工具被选择的频率几乎相同。如果存在两个描述相似但语言不同的工具,它们会让 ChatGPT 更困惑。
此外,你的应用在注册 Store 时需要通过审核。因此建议干脆把所有工具描述和参数说明都写成英文。
不过,如果未来 ChatGPT 中出现成千上万的应用,“工具选择”的竞争加剧,那么使用与用户 locale 一致的描述或许会获得优势。拭目以待 Tool Search Optimization 的到来。
单个工具 + 多语言描述
第二种方案是保留一个 name(例如 search_gifts),但让它的 description 和 JSON Schema 字段说明变成多语言。
常见几种写法:
- 简短的双语形式:
description: "Search gifts for a recipient. / 根据收礼者偏好搜索礼物。", - 按语言分块标注:
description: "[EN] Search for gifts based on user preferences. [RU] 根据收礼者偏好挑选礼物。", - 分离字段再拼接成字符串(不太方便):
description: `EN: ${enDescription} RU: ${ruDescription}`,
优点:只有一个工具,单一事实来源(single source of truth),更容易部署带 MCP Gateway 的架构:无论用户用哪种语言与 ChatGPT 交互,你始终暴露相同的接口。
缺点:描述会变长。如果不加标识把多种语言“混在一起”,模型可能会有些困惑——尤其当英文与本地文本混排且没有明确标记 [EN]、[RU] 时。
对于像 GiftGenius 这样的教学项目,我们推荐折中方案:描述以英文为主,辅以简短的本地语言说明;而真正的“语义”(使用哪种语言、如何称呼用户)通过参数(locale)和 system‑prompt 传递。
4. JSON Schema 的本地化:字段说明
接下来更深入到工具的参数本身。
在 JSON Schema 中,每个字段都可以(也应该)写 description。模型在生成调用工具的 JSON 时会阅读这段文字。
在 GiftGenius 中,可以这样写:
export const searchGiftsTool = {
name: "search_gifts",
description:
"Search gifts based on user preferences (RU: 根据收礼者偏好挑选礼物)。",
inputSchema: {
type: "object",
properties: {
recipient_age: {
type: "integer",
description:
"Recipient age in years. RU: 收礼者年龄(整数)。",
},
budget: {
type: "number",
description:
"Maximum budget in user's currency. RU: 以用户货币计的最高预算。",
},
locale: {
type: "string",
description:
"User locale (e.g. 'en-US', 'ru-RU'). RU: 界面与回答所用语言。",
},
},
required: ["budget", "locale"],
},
};
若干实践注意点。
首先,字段名(recipient_age、budget、locale)通常保持英文。仅翻译 description。这很重要——这样 JSON 格式不会随语言改变,你也无需维护两套不同的契约。
其次,在 description 中明确货币、计量单位与关键限制非常有用。它能显著降低“歪参”的数量。
第三,如果你已使用 MCP Gateway,可以约定它自动把 locale 透传到工具参数里,这样模型不必自己填。但即便如此,给 locale 写清楚说明仍然有益:模型会更理解这个参数是什么、为何需要它。
5. 如何选择描述语言:面向真实 App 的策略
关键实践问题:描述的主语言选哪个?何时需要完全本地化?
经验与实践显示,GPT 模型在英文上下文中仍表现最佳,许多开发者只保留英文描述。但对多语言 App 来说,这或许只是折中。
来看几种策略。
仅 EN 描述
最简单的方案——全部使用英文。
优点:单一代码基、单一维护语言,更易写出准确表述。模型在“纯英文世界”更舒服。
缺点:对使用其他语言的用户,工具选择与参数质量可能下降,尤其对“懒模型”或参数繁多的复杂工具。
EN + 简短本地化补充
折中方案:主体描述为 EN,结尾附上本地语言的简短块,帮助模型将用户措辞映射到参数。
示例:
description:
"Search for gifts based on user preferences. RU: 工具会依据收礼者描述、年龄与预算来挑选礼物。",
对于 JSON Schema:
description:
"Age of the recipient in years. RU: 收礼者年龄(以年计)。",
优点:模型仍处在“英文世界”,但得到一段用户语言的提示。
缺点:描述会更长,但通常不致命。
按 locale 完全本地化描述
最彻底的方案:根据从 ChatGPT 获得的 locale 动态更换工具与字段的描述。对 en-US 返回纯英文描述,对 ru-RU 返回纯俄文,对 de-DE 返回德文。
这已不再是“一份 JSON Schema 永远通用”,而是一组 schema,MCP/Gateway 按需选择。
在 MCP 层大致是:
function getSearchGiftsToolDescription(locale: string) {
if (locale.startsWith("ru")) {
return {
name: "search_gifts",
description: "根据收礼者偏好挑选礼物。",
// ru‑schema...
};
}
return {
name: "search_gifts",
description: "Search for gifts based on user preferences.",
// en‑schema...
};
}
优点:模型看到的接口语言与用户写作语言一致——体验最佳。
缺点:维护与测试复杂度上升。需要流程保证各语言版本描述在语义上保持同步,避免在英文 schema 新增字段时忘了更新德文/俄文版本。
6. 在我们的 GiftGenius 中实现
进入落地细节。在 GiftGenius 中采用混合方案:一个工具 search_gifts,描述以 EN 为主,附上俄文说明,并增加参数 locale。
假设你的 MCP 服务器使用 TypeScript,并按 MCP SDK 的风格描述工具。
// mcp/tools/searchGifts.ts
import { z } from "zod";
export const searchGiftsInputSchema = z.object({
recipient_age: z
.number()
.int()
.describe(
"Age of the recipient in years. RU: 收礼者年龄(整数)。"
),
budget: z
.number()
.describe(
"Maximum budget in user's currency. RU: 以用户货币计的最高预算。"
),
locale: z
.string()
.describe(
"User locale (e.g. 'en-US', 'ru-RU'). RU: 界面与回答所用语言。"
),
});
export const searchGiftsTool = {
name: "search_gifts",
description:
"Search for gifts based on user preferences (RU: 根据收礼者偏好挑选礼物)。",
inputSchema: searchGiftsInputSchema,
// execute(...) ...
};
要点:
- locale 为必填项。如果小部件已知它(我们可从 _meta["openai/locale"] 获取),它可以在 callTool 时自行填入,或由 MCP Gateway 在其侧自动透传;
- 描述里已有俄语关键词“年龄”“预算”“界面语言”,模型更容易把用户请求中的信息映射到对应字段。
在 Apps SDK 侧,如果开启了 widgetAccessible,你可以写一个函数直接调用该工具,并把小部件中的 locale 一并传入。
// widget/hooks/useSearchGifts.ts
export async function searchGiftsFromWidget(params: {
recipientAge: number;
budget: number;
locale: string;
}) {
const openai = (window as any).openai;
const result = await openai.callTool("search_gifts", {
recipient_age: params.recipientAge,
budget: params.budget,
locale: params.locale,
});
return result;
}
这条链路巩固了架构:locale 来自 ChatGPT → 进入工具 → 工具选择正确的目录与价格格式 → 你在前端优雅呈现。
7. 行为实验:如何衡量本地化的影响
接下来是有趣的部分:如何判断工具与描述的本地化确实改善了模型行为,而不是浪费翻译时间?
可以直接在 GiftGenius 的 Dev Mode 做一个“小实验”。
两个 App 版本:base vs localized
准备两份 App 配置:
- base——工具与 JSON Schema 描述仅 EN;
- localized——描述为 EN+RU(或你已准备好的完整 ru 版本)。
保持其它部分(目录、UI、prompts)一致,避免混入额外因素。
简化操作可以:
- 在 Dev Mode(以及更可能在 Store)里只保留 localized 版本;
- 在本地开一个分支运行 base,并用预设的请求集对比结果。
测量什么
三项关键指标:
第一——工具选择的正确率。对一组俄文(或其它语言)的测试请求,观察模型:
- 在需要时是否调用了工具;
- 是否选择了 search_gifts 而非其他工具。
第二——参数正确性。检查调用 JSON 是否符合预期:字段未混淆、预算货币正确、年龄为显式整数、locale 未丢失。
第三——奇怪/无意义调用的数量。例如对“现在几点了?”这样的问题调用了 search_gifts,或把 recipient_age 填成 3000(本应是预算)。
既可手测,也可通过 MCP/Agents 的日志来测——日志迟早有用,早点建立这类分析流程更好。
如何组织手工测试集
可以准备一个小型“golden prompt set”用于本地化评估:
1. “我需要一个不超过 30 欧元的便宜礼物,送给 10 岁喜欢画画的女孩。”
2. “给 35 岁的同事程序员挑礼物,预算 100$。”
3. “给奶奶的周年纪念挑礼物,70 岁,预算不超过 5000 卢布。”
将它们分别在 base 与 localized 两个版本中跑一遍,观察:
- 模型选择了哪些工具;
- 填入了哪些参数;
- 当未调用工具时,文本回答有什么变化。
一个半专业的小技巧:可以写个简单脚本把这些请求轮流丢给 ChatGPT API,但在本课程范围内,Dev Mode 手工即可。特别建议加入一类请求:混合语言消息与奇怪的 locale 组合。我们在下面单独介绍。
如果你在开发严肃的商业应用、事关数百万美元,请务必针对你的应用验证这些点。第 20 模块专门讨论如何专业化构建“golden prompt set”——务必深入掌握。
8. 混合语言与奇怪的 locale 组合
没有什么比同时使用两种语言的用户更能“娱乐”LLM 开发者了。例如:
“需要礼物 for my friend,他喜欢 Star Wars,budget 100€”
我们知道模型是多语言的,多数情况下可以搞定。但语言混合且描述是英文时,出错概率更高。
有几种常见情况。
第一——用户用俄语,工具描述是英文。模型能理解,但在专业术语上(如类别名、少见字段标签)偶有混淆。
第二——locale = "ru-RU",但用户却写英文。ChatGPT 传达的信号是 UI 应该用俄语,但文本实际是 EN。可以:
- 仍然给出俄语描述,把 locale 视为首要信号;
- 或实现消息语言检测,作为额外信号,让描述适配实际文本语言。
第三——locale = "en",但用户偶尔夹入俄语词汇。通常英文描述在这种情况下表现良好。
实践中只需选定一条清晰策略。例如:
- 若 locale 以 "ru" 开头——就在描述中增加俄语片段;
- 否则——描述保持纯英文。
清晰规则的好处是你能针对每条分支做专项测试,而不是猜今天为何出现了某种语言的描述。
9. 文档、流程与“规范语言”
本地化描述不是一次性的,而是一项持续工作。用户希望不断有新功能,你也希望不破坏旧功能。因此最好事先约定:
- 把哪种语言作为“规范语言”,其它翻译以此为准;
- 把本地化描述存放在哪里;
- 如何检查一致性。
通常英语是规范语言。所有新工具与字段先用 EN 描述、完成评审,再本地化到其它语言。在代码库中可这样表达:
- 文件 tools.en.json 保存完整的 name/description/字段说明;
- 文件 tools.ru.json、tools.de.json 等作为特定语言的“派生”;
- 一个小生成器,基于这些字典组装用于 MCP 的最终 JSON Schema。
简单版本也可以先把字符串放在代码里,但要便于之后抽离到字典。
记住:描述同样是产品文案。它们应像 UI 文案一样严格评审:可理解、无歧义、无冗词。尤其在多语言描述中,我们不希望本地语言的补充与英文部分相互矛盾。
10. 可视化示意:语言如何穿越整个栈
为了整体串起来,看看一张考虑工具本地化的简化请求流转示意。
flowchart TD
U[用户以 RU 撰写] --> C[ChatGPT UI]
C -->|"_meta.openai/locale = 'ru-RU'"| W[GiftGenius 小组件]
W -->|"locale = 'ru-RU'"| T["工具描述(EN+RU)"]
T --> M[GPT 模型]
M -->|callTool search_gifts| MCP[MCP / Gateway]
MCP -->|"locale = 'ru-RU'"| B[Backend / RU 目录]
B --> MCP --> M2["GPT 模型(答复)"]
M2 --> C2[ChatGPT UI + RU 小组件]
这里,用户语言与 locale 决定:
- 小部件 UI 使用的语言;
- 模型看到的工具与字段描述语言;
- 后端选择的目录与货币;
- 答案的格式化方式(模型与小部件)。
11. 本地化工具与描述时的常见错误
错误 №1:把 description 当“技术字段”,完全不做本地化。
这种做法只在你的用户全是英语时勉强可行。一旦出现其他语言,模型更常直接回答不调工具,或按 schema 传“歪参”。你翻译了 UI,但 App 仍然“英语思维”。
错误 №2:按语言更改 JSON 中的字段名。
有人会手痒想把 age → vozrast、budjet 等。这会让后端噩梦连连:不同 schema、不同格式、日志分析也困难。最好保持字段 name 稳定,仅本地化描述。
错误 №3:在描述中混杂语言且无结构。
类似“根据 user 偏好搜索 gifts”的句子既不帮模型,也不帮人。如果要写多语言描述,请明确分隔:[EN] ... [RU] ...。模型能看到结构,而不是“语言粥”。
错误 №4:不向工具传递 locale。
即使你做了描述本地化,但没有把 locale 传给工具(或 MCP Gateway 未透传),后端就不知道应选择哪些目录与格式。结果是模型努力“多语言”,服务器却只返回一个市场的数据。
错误 №5:用机器翻译自动生成描述且不做评审。
看似把描述丢进机器翻译就万事大吉。实际上这种翻译常常不准,尤其在术语与参数上。模型可能误解工具或字段的含义。宁可先打磨出一份高质量 EN,再谨慎本地化,也不要上二十种“自动机翻”的版本。
错误 №6:缺少针对不同 locale 的测试/实验。
如果你不为每种 locale 至少做一套基础请求检查,问题可能“躺着”几个月,直到第一个真实用户踩中。准备一个小型 golden 集合,加上 Dev Mode 的手测,可以显著降低风险。
错误 №7:规范语言与本地化描述之间失去同步。
你在英文 schema 里新增了字段 occasion(礼物“场合”),却忘了更新俄文版本。结果在 RU locale 下,模型根本不知道这个字段,也就不会填它;而后端已经在用它筛选礼物,收到 null 只能给出过于泛化的列表——EN 正常,RU 悄悄“失灵”。因此任何工具描述的变更都应遵循简单而固定的流程:更新 EN → 更新各本地化 → 快速跑一遍测试。
GO TO FULL VERSION