02-Tool Schema 设计规范:好写法 vs 坏写法对照

通过同一货币换算工具的好坏两份 Schema 对比实验,总结四条 Schema 设计黄金法则:description 写「什么时候用」、参数类型正确、明确 required 字段、用 enum 锁死可选値。

2026/6/13
9 分钟阅读
目录

直接看代码注释

"""练习 6:Tool Schema 设计 —— 坏 schema vs 好 schema 对照实验

为什么提前做这个练习?
    因为练习 2(多工具选择)依赖它。模型选不选对工具、传不传对参数,
    90% 取决于 schema 写得好不好,而不是模型笨。

本练习用【同一个货币换算工具】,写两份 schema:
    - BAD_TOOLS :常见的新手写法(毛病全集)
    - GOOD_TOOLS:规范写法
然后把【同样的问题】分别喂给模型,打印它吐出的参数,差异一眼可见。

四条要对比的 schema 设计规范:
    1) description 写【什么时候用】,不是【做什么】   ← 黄金规则,最重要
    2) 参数用对类型(金额是 number,不是 string)
    3) 明确列出 required 必需字段
    4) 用 enum 锁死可选值(货币代码只能是固定几种)
"""

import json
import os

from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.environ["DEEPSEEK_API_KEY"],
    base_url=os.environ["DEEPSEEK_BASE_URL"],
)
MODEL = os.environ["MODEL"]


# ── 坏 schema:把新手常犯的错全踩一遍 ──────────────────────────────────
BAD_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "convert",
            # 毛病①:description 只说“做什么”,没说“什么时候用”。
            #         模型据此很难判断一个问题到底该不该调它。
            "description": "货币转换",
            "parameters": {
                "type": "object",
                "properties": {
                    # 毛病②:金额本该是数字,这里写成 string,模型可能回 "100" 甚至 "一百"。
                    "amount": {"type": "string"},
                    # 毛病③:没有 enum、没有 description,
                    #         模型会随手填“美元/人民币/USD/dollar”各种花样,下游没法用。
                    "from": {"type": "string"},
                    "to": {"type": "string"},
                },
                # 毛病④:没有 required,模型可能漏传参数。
            },
        },
    }
]


# ── 好 schema:四条规范全部做到 ───────────────────────────────────────
GOOD_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "convert_currency",
            # 规范①(黄金规则):description 说清【什么时候用】+【边界】,
            #   让模型自己就能判断该不该调、什么情况不该调。
            "description": (
                "当用户想把一笔【具体金额】从一种货币换算成另一种货币时调用,"
                "例如“100美元是多少人民币”。"
                "仅用于金额换算;如果用户只是闲聊或问汇率以外的问题,不要调用。"
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    # 规范②:用对类型——金额是 number。
                    "amount": {
                        "type": "number",
                        "description": "要换算的金额,纯数字",
                    },
                    # 规范④:用 enum 锁死取值,逼模型输出标准货币代码而非随意文字。
                    "from_currency": {
                        "type": "string",
                        "enum": ["USD", "CNY", "EUR", "JPY"],
                        "description": "源货币代码",
                    },
                    "to_currency": {
                        "type": "string",
                        "enum": ["USD", "CNY", "EUR", "JPY"],
                        "description": "目标货币代码",
                    },
                },
                # 规范③:明确必需字段,缺一不可。
                "required": ["amount", "from_currency", "to_currency"],
            },
        },
    }
]


def ask(tools: list, question: str) -> str:
    """把一个问题 + 一份 schema 发给模型,返回它的“决定”(调用了什么/传了什么)。

    注意:这里【故意不真正执行函数】,因为本练习只关心
    “模型在这份 schema 下做出了什么决定、吐出了什么参数”。
    """
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": question}],
        tools=tools,
    )
    msg = resp.choices[0].message

    if not msg.tool_calls:
        return "(未调用工具)直接回答:" + (msg.content or "")

    call = msg.tool_calls[0]
    # 原样打印参数字符串,方便观察类型/格式问题("100" 还是 100、USD 还是 美元)
    return f"调用 {call.function.name},参数 = {call.function.arguments}"


# 三个测试问题:第 1 个正常换算,第 2 个是边界(只问汇率不换算),第 3 个完全无关。
QUESTIONS = [
    "100美元是多少人民币?",
    "美元和人民币的汇率大概是多少?",
    "你好,帮我写一首关于春天的诗。",
]


if __name__ == "__main__":
    for q in QUESTIONS:
        print("=" * 60)
        print(f"问题:{q}")
        print(f"  ❌ 坏 schema → {ask(BAD_TOOLS, q)}")
        print(f"  ✅ 好 schema → {ask(GOOD_TOOLS, q)}")
    print("=" * 60)
    print(
        "\n观察重点:\n"
        "  · 参数类型:坏 schema 的 amount 常是字符串 \"100\",好 schema 是数字 100。\n"
        "  · 货币格式:坏 schema 可能填“美元/人民币”,好 schema 受 enum 约束输出 USD/CNY。\n"
        "  · 何时调用:好 schema 的 description 写清了边界,边界/无关问题更不容易误触发。"
    )