生成DeepSeek工具定義:告別手寫50個(gè)函數(shù))
1. 從手寫 50 個(gè)工具函數(shù)說(shuō)起這個(gè)項(xiàng)目到底在解決什么問(wèn)題如果你最近在折騰大模型應(yīng)用尤其是想讓模型調(diào)用外部 REST API 來(lái)完成實(shí)際任務(wù)那你大概率經(jīng)歷過(guò)這樣一個(gè)階段打開(kāi)編輯器新建一個(gè)tools.py然后開(kāi)始一個(gè)接口一個(gè)接口地手寫工具定義。每個(gè)工具都要寫名稱、描述、參數(shù) schema、必填項(xiàng)、類型約束寫完還要跟模型反復(fù)調(diào)試描述文案確保它能在正確的場(chǎng)景下選中正確的工具。50 個(gè)接口是什么概念假設(shè)每個(gè)工具平均 30 行定義代碼那就是 1500 行純手工維護(hù)的樣板代碼。更麻煩的是后端接口一改字段你得手動(dòng)同步參數(shù)類型從 string 改成 integer你得挨個(gè)檢查某個(gè)接口新增了可選參數(shù)你還得回去補(bǔ) schema。這種活干一次兩次還行接口數(shù)量上到幾十個(gè)之后維護(hù)成本會(huì)迅速失控。這個(gè)項(xiàng)目的核心思路很直接既然 REST API 本身已經(jīng)有 OpenAPISwagger規(guī)范描述為什么還要人工再翻譯一遍成工具定義讓程序讀取 OpenAPI 文檔自動(dòng)生成符合 DeepSeek 工具調(diào)用格式的 Tools 定義把人力從重復(fù)勞動(dòng)里解放出來(lái)。一句話概括輸入一份 OpenAPI 規(guī)范文件輸出一組可以直接喂給 DeepSeek 的工具定義中間不需要你手寫任何 schema。它適合誰(shuí)三類人最受益。第一類是做 AI Agent 應(yīng)用的開(kāi)發(fā)者手里有一堆內(nèi)部或第三方 REST 接口需要接入模型第二類是做企業(yè)級(jí)集成的工程師公司內(nèi)部服務(wù)都帶 Swagger 文檔想快速搭一個(gè)能調(diào)這些服務(wù)的智能助手第三類是想學(xué)習(xí)工具調(diào)用機(jī)制的技術(shù)愛(ài)好者通過(guò)自動(dòng)生成的過(guò)程反過(guò)來(lái)理解工具定義的結(jié)構(gòu)和約束。我自己的場(chǎng)景是手頭有一個(gè)包含 50 多個(gè)接口的后端服務(wù)全部有完整的 OpenAPI 3.0 文檔之前手工寫了 20 多個(gè)工具就已經(jīng)快崩潰了字段對(duì)不上、描述寫不準(zhǔn)、模型選錯(cuò)工具的問(wèn)題層出不窮。后來(lái)把這套自動(dòng)生成流程跑通維護(hù)成本直接降了一個(gè)數(shù)量級(jí)。下面把這套東西的完整思路和實(shí)現(xiàn)細(xì)節(jié)拆開(kāi)講。2. 整體設(shè)計(jì)思路為什么是 OpenAPI 到 Tools 的自動(dòng)映射2.1 手寫工具定義的三個(gè)致命痛點(diǎn)在講方案之前先把問(wèn)題說(shuō)透這樣你才能理解后面每個(gè)設(shè)計(jì)決策的合理性。痛點(diǎn)一schema 與接口實(shí)際定義脫節(jié)。手寫工具定義時(shí)參數(shù)類型、枚舉值、必填項(xiàng)這些信息全靠人肉從接口文檔里抄。接口文檔更新了工具定義不會(huì)自動(dòng)跟著變。我遇到過(guò)最離譜的一次是后端把某個(gè)參數(shù)從必填改成了可選但工具定義里還標(biāo)著 required導(dǎo)致模型每次調(diào)用都硬塞一個(gè)空值進(jìn)去接口直接報(bào)參數(shù)校驗(yàn)失敗。痛點(diǎn)二描述文案質(zhì)量參差不齊。工具能不能被模型正確選中很大程度上取決于 description 寫得好不好。50 個(gè)工具每個(gè)都要寫一段既準(zhǔn)確又能讓模型理解使用場(chǎng)景的描述這本身就是個(gè)文案活。不同人寫的風(fēng)格不統(tǒng)一模型在相似工具之間就容易選錯(cuò)。痛點(diǎn)三維護(hù)成本隨接口數(shù)量線性增長(zhǎng)。10 個(gè)接口還能忍50 個(gè)接口就是災(zāi)難。每次接口變更都要人工比對(duì)、修改、測(cè)試稍不留神就漏掉一個(gè)。2.2 為什么選 OpenAPI 作為中間層OpenAPI 規(guī)范本身就是對(duì) REST API 的結(jié)構(gòu)化描述它天然包含了工具定義需要的幾乎所有信息工具定義需要的字段OpenAPI 中的對(duì)應(yīng)位置工具名稱operationId 或 path method 組合工具描述summary / description參數(shù)列表parameterspath、query、header請(qǐng)求體結(jié)構(gòu)requestBody 的 schema參數(shù)類型schema.type必填項(xiàng)required 數(shù)組枚舉約束schema.enum參數(shù)說(shuō)明每個(gè)參數(shù)的 description也就是說(shuō)OpenAPI 文檔里已經(jīng)有一份完整的、機(jī)器可讀的接口描述工具定義本質(zhì)上就是這份描述的另一種表達(dá)形式。中間做一次格式轉(zhuǎn)換比人工重新寫一遍可靠得多也快得多。2.3 為什么目標(biāo)格式是 DeepSeek 的 ToolsDeepSeek 的工具調(diào)用格式遵循主流大模型的 function calling 規(guī)范工具定義是一個(gè) JSON 數(shù)組每個(gè)元素包含type: function和function對(duì)象function 里有name、description、parametersJSON Schema 格式。這個(gè)結(jié)構(gòu)和 OpenAPI 的 schema 高度相似轉(zhuǎn)換起來(lái)映射關(guān)系清晰不需要做太多語(yǔ)義層面的猜測(cè)。選 DeepSeek 還有一個(gè)實(shí)際考慮它的工具調(diào)用能力在國(guó)產(chǎn)模型里比較扎實(shí)對(duì) JSON Schema 的理解到位參數(shù)提取準(zhǔn)確率不錯(cuò)而且接口兼容性好本地部署和云端調(diào)用都能用。你把這套生成邏輯跑通后換成其他遵循相同 function calling 規(guī)范的模型改一下輸出格式就行核心轉(zhuǎn)換邏輯是通用的。2.4 整體架構(gòu)三步走整個(gè)流程拆成三步每步職責(zé)單一方便調(diào)試和替換解析層讀取 OpenAPI 文件JSON 或 YAML解析成 Python 字典結(jié)構(gòu)提取所有 paths 和對(duì)應(yīng)的操作。轉(zhuǎn)換層把每個(gè)操作映射成一個(gè)工具定義處理名稱生成、描述拼接、參數(shù)合并、schema 轉(zhuǎn)換、引用解析。輸出層把工具定義列表序列化成 DeepSeek 需要的格式寫入文件或直接傳給模型接口。這個(gè)分層的好處是如果哪天 OpenAPI 版本升級(jí)了或者目標(biāo)模型換了工具格式你只需要改對(duì)應(yīng)那一層不用推倒重來(lái)。3. 核心轉(zhuǎn)換邏輯拆解從接口到工具的每一步3.1 工具名稱的生成策略工具名稱是模型選擇工具的第一道線索必須滿足兩個(gè)條件唯一、可讀。OpenAPI 里的operationId通常是最好的來(lái)源比如getUserById、createOrder語(yǔ)義清晰。但現(xiàn)實(shí)情況是很多接口文檔的 operationId 要么缺失要么是自動(dòng)生成的亂碼比如getUsingGET_1這時(shí)候就得有兜底方案。我的策略是三級(jí)優(yōu)先優(yōu)先用operationId如果它存在且符合命名規(guī)范字母數(shù)字下劃線不以數(shù)字開(kāi)頭。其次用method path拼接比如GET /users/{id}轉(zhuǎn)成get_users_by_id把路徑參數(shù)也體現(xiàn)進(jìn)去。最后做去重處理如果生成的名稱重復(fù)追加數(shù)字后綴。這里有個(gè)細(xì)節(jié)要注意DeepSeek 對(duì)工具名稱的字符集有要求通常只接受字母、數(shù)字、下劃線和短橫線長(zhǎng)度也有限制。所以生成后要做一次清洗把斜杠、花括號(hào)、點(diǎn)號(hào)這些替換掉超長(zhǎng)的截?cái)?。import re def sanitize_tool_name(name, max_length64): # 把非字母數(shù)字下劃線短橫線的字符替換成下劃線 name re.sub(r[^a-zA-Z0-9_-], _, name) # 合并連續(xù)下劃線 name re.sub(r_, _, name) # 去掉首尾下劃線 name name.strip(_) # 截?cái)?if len(name) max_length: name name[:max_length] return name3.2 描述文案的自動(dòng)拼接描述決定了模型在什么場(chǎng)景下會(huì)選這個(gè)工具重要性不亞于名稱。OpenAPI 里的summary和description是主要素材但往往不夠完整。我的做法是把多個(gè)來(lái)源的信息拼起來(lái)summary一句話概括放最前面。description詳細(xì)說(shuō)明補(bǔ)充在后面。HTTP 方法和路徑讓模型知道這是個(gè)什么類型的操作。參數(shù)摘要把關(guān)鍵參數(shù)列一下幫助模型判斷調(diào)用時(shí)機(jī)。拼接時(shí)要注意控制長(zhǎng)度太長(zhǎng)的描述會(huì)占用上下文而且模型對(duì)超長(zhǎng)描述的注意力會(huì)分散。我的經(jīng)驗(yàn)是控制在 200 到 500 字符之間比較合適重點(diǎn)信息前置。def build_description(operation, method, path): parts [] if operation.get(summary): parts.append(operation[summary].strip()) if operation.get(description): parts.append(operation[description].strip()) parts.append(fHTTP {method.upper()} {path}) return .join(parts)[:500]3.3 參數(shù)合并path、query、header 一個(gè)都不能漏OpenAPI 里參數(shù)分散在三個(gè)地方path 參數(shù)、query 參數(shù)、header 參數(shù)都在parameters數(shù)組里通過(guò)in字段區(qū)分。請(qǐng)求體requestBody則是單獨(dú)的一塊。轉(zhuǎn)換時(shí)要把這些全部合并到一個(gè) JSON Schema 的properties里。合并時(shí)有個(gè)坑不同位置的參數(shù)可能重名。比如 path 里有個(gè)idquery 里也有個(gè)id直接合并會(huì)覆蓋。我的處理是給重名參數(shù)加前綴比如path_id、query_id同時(shí)在描述里說(shuō)明來(lái)源。雖然這種情況不常見(jiàn)但一旦出現(xiàn)不處理就會(huì)導(dǎo)致模型傳錯(cuò)參數(shù)。另一個(gè)細(xì)節(jié)是 header 參數(shù)。大部分 header 是認(rèn)證、內(nèi)容類型這類基礎(chǔ)設(shè)施性質(zhì)的不應(yīng)該暴露給模型。我的做法是維護(hù)一個(gè)黑名單把Authorization、Content-Type、Accept、User-Agent這些過(guò)濾掉只保留業(yè)務(wù)相關(guān)的自定義 header。3.4 schema 轉(zhuǎn)換OpenAPI 到 JSON Schema 的映射OpenAPI 的 schema 和 JSON Schema 大部分是兼容的但有幾個(gè)地方需要處理$ref引用OpenAPI 里大量使用$ref指向components/schemas下的定義必須遞歸解析展開(kāi)否則模型看不懂引用。nullableOpenAPI 3.0 用nullable: true表示可空J(rèn)SON Schema 里沒(méi)有這個(gè)關(guān)鍵字要轉(zhuǎn)成type: [string, null]這種形式。allOf/oneOf/anyOf組合 schema 要遞歸處理DeepSeek 對(duì)anyOf的支持較好allOf可以嘗試合并。example和default保留下來(lái)對(duì)模型理解參數(shù)有幫助。$ref解析是這里最容易出問(wèn)題的地方因?yàn)橐每赡芮短滓蒙踔裂h(huán)引用。必須加一個(gè)已訪問(wèn)集合來(lái)防止無(wú)限遞歸。def resolve_ref(schema, components, visitedNone): if visited is None: visited set() if not isinstance(schema, dict): return schema if $ref in schema: ref_path schema[$ref] if ref_path in visited: return {type: object} # 循環(huán)引用兜底 visited.add(ref_path) # 解析 #/components/schemas/Xxx 這種路徑 ref_name ref_path.split(/)[-1] target components.get(schemas, {}).get(ref_name, {}) return resolve_ref(target, components, visited) # 遞歸處理 properties、items 等 result {} for key, value in schema.items(): if key properties and isinstance(value, dict): result[key] {k: resolve_ref(v, components, visited) for k, v in value.items()} elif key items: result[key] resolve_ref(value, components, visited) else: result[key] value return result3.5 必填項(xiàng)的準(zhǔn)確提取必填項(xiàng)判斷錯(cuò)模型就會(huì)漏傳參數(shù)或者多傳空值。OpenAPI 里必填信息有兩個(gè)來(lái)源參數(shù)自身的required: true以及 requestBody schema 里的required數(shù)組。這兩處都要處理合并到最終的required列表里。有個(gè)容易忽略的點(diǎn)path 參數(shù)在 OpenAPI 規(guī)范里是強(qiáng)制 required 的但有些文檔寫得不規(guī)范漏標(biāo)了。我的做法是只要是in: path的參數(shù)一律視為必填不管文檔里寫沒(méi)寫。這是符合 REST 語(yǔ)義的路徑參數(shù)不傳請(qǐng)求根本發(fā)不出去。4. 完整實(shí)操?gòu)牧闩芡ㄗ詣?dòng)生成流程4.1 環(huán)境準(zhǔn)備與依賴安裝這套東西用 Python 寫最順手因?yàn)?OpenAPI 解析和 JSON 處理在 Python 生態(tài)里工具最全?;A(chǔ)依賴就兩個(gè)pip install pyyaml requestspyyaml用來(lái)解析 YAML 格式的 OpenAPI 文件很多項(xiàng)目用 YAML 寫文檔requests用來(lái)在需要時(shí)從遠(yuǎn)程 URL 拉取 OpenAPI 文檔。如果你只用 JSON 格式的文檔pyyaml都可以省掉標(biāo)準(zhǔn)庫(kù)的json就夠了。Python 版本建議 3.8 以上我用的是 3.10類型提示和字典操作都比較舒服。不需要額外的框架純標(biāo)準(zhǔn)庫(kù)加這兩個(gè)依賴就能跑。4.2 讀取和解析 OpenAPI 文檔先寫一個(gè)加載函數(shù)同時(shí)支持本地文件和遠(yuǎn)程 URL自動(dòng)識(shí)別 JSON 和 YAMLimport json import yaml import requests def load_openapi(source): if source.startswith(http://) or source.startswith(https://): resp requests.get(source, timeout30) resp.raise_for_status() content resp.text else: with open(source, r, encodingutf-8) as f: content f.read() # 先嘗試 JSON失敗再試 YAML try: return json.loads(content) except json.JSONDecodeError: return yaml.safe_load(content)這里有個(gè)實(shí)操經(jīng)驗(yàn)有些 OpenAPI 文檔是 JSON 但擴(kuò)展名是.yaml或者反過(guò)來(lái)。不要靠擴(kuò)展名判斷直接按內(nèi)容試解析最穩(wěn)。先試 JSON 是因?yàn)?JSON 解析更快失敗再走 YAML。4.3 遍歷所有接口操作OpenAPI 的paths是個(gè)字典key 是路徑value 是各 HTTP 方法的操作定義。遍歷時(shí)要過(guò)濾掉非標(biāo)準(zhǔn)方法的 key比如parameters、summary這些路徑級(jí)別的字段HTTP_METHODS {get, post, put, delete, patch, head, options} def iter_operations(spec): paths spec.get(paths, {}) for path, path_item in paths.items(): for method, operation in path_item.items(): if method.lower() not in HTTP_METHODS: continue if not isinstance(operation, dict): continue yield method.lower(), path, operation4.4 單個(gè)接口轉(zhuǎn)工具定義的完整實(shí)現(xiàn)把前面幾節(jié)的邏輯串起來(lái)寫一個(gè)完整的轉(zhuǎn)換函數(shù)def convert_operation(method, path, operation, components, used_names): # 1. 生成工具名稱 raw_name operation.get(operationId) if not raw_name: raw_name f{method}_{path} name sanitize_tool_name(raw_name) # 去重 base_name name counter 1 while name in used_names: name f{base_name}_{counter} counter 1 used_names.add(name) # 2. 構(gòu)建描述 description build_description(operation, method, path) # 3. 合并參數(shù) properties {} required [] for param in operation.get(parameters, []): if $ref in param: param resolve_ref(param, components) param_name param.get(name) param_in param.get(in) if not param_name or param_in header: # 跳過(guò) header 參數(shù) continue schema resolve_ref(param.get(schema, {}), components) if param.get(description): schema[description] param[description] properties[param_name] schema if param.get(required) or param_in path: required.append(param_name) # 4. 處理 requestBody body operation.get(requestBody, {}) if $ref in body: body resolve_ref(body, components) content body.get(content, {}) json_content content.get(application/json, {}) if json_content: body_schema resolve_ref(json_content.get(schema, {}), components) if body_schema.get(type) object and properties in body_schema: for prop_name, prop_schema in body_schema[properties].items(): properties[prop_name] prop_schema for req in body_schema.get(required, []): if req not in required: required.append(req) else: # 非對(duì)象類型的 body整體作為一個(gè)參數(shù) properties[body] body_schema if body.get(required): required.append(body) # 5. 組裝工具定義 parameters_schema { type: object, properties: properties, } if required: parameters_schema[required] required return { type: function, function: { name: name, description: description, parameters: parameters_schema } }4.5 批量生成與輸出最后把整個(gè)流程串起來(lái)批量處理所有接口輸出成 DeepSeek 需要的格式def generate_tools(spec): components spec.get(components, {}) used_names set() tools [] for method, path, operation in iter_operations(spec): tool convert_operation(method, path, operation, components, used_names) tools.append(tool) return tools def main(source, output): spec load_openapi(source) tools generate_tools(spec) with open(output, w, encodingutf-8) as f: json.dump(tools, f, ensure_asciiFalse, indent2) print(f生成了 {len(tools)} 個(gè)工具定義已寫入 {output}) if __name__ __main__: main(openapi.json, tools.json)跑一遍50 個(gè)接口幾秒鐘就全部轉(zhuǎn)完了。生成的tools.json可以直接作為tools參數(shù)傳給 DeepSeek 的對(duì)話接口。4.6 接入 DeepSeek 調(diào)用生成工具定義只是第一步真正用起來(lái)還要把它傳給模型。DeepSeek 的接口兼容 OpenAI 的格式調(diào)用方式很直接import requests def chat_with_tools(user_message, tools): resp requests.post( https://api.deepseek.com/chat/completions, headers{ Authorization: Bearer YOUR_API_KEY, Content-Type: application/json }, json{ model: deepseek-chat, messages: [{role: user, content: user_message}], tools: tools, tool_choice: auto }, timeout60 ) return resp.json()模型返回的tool_calls里會(huì)包含它選擇的工具名稱和參數(shù)你解析出來(lái)映射回實(shí)際的 HTTP 請(qǐng)求調(diào)用真實(shí)接口再把結(jié)果喂回模型就完成了一輪工具調(diào)用閉環(huán)。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 工具數(shù)量太多導(dǎo)致模型選擇困難50 個(gè)工具一次性全塞給模型會(huì)出現(xiàn)兩個(gè)問(wèn)題一是上下文占用大二是模型在相似工具之間容易選錯(cuò)。我的經(jīng)驗(yàn)是工具數(shù)量超過(guò) 20 個(gè)之后選擇準(zhǔn)確率會(huì)明顯下降。解決辦法有兩個(gè)方向。一是按場(chǎng)景分組把工具分成幾組根據(jù)用戶意圖先判斷屬于哪組只把該組的工具傳給模型。二是精簡(jiǎn)描述把不必要的信息砍掉讓每個(gè)工具的描述更聚焦。實(shí)測(cè)下來(lái)分組方案效果更好但需要額外做一層意圖路由。5.2 $ref 解析失敗或循環(huán)引用這是最常見(jiàn)的報(bào)錯(cuò)來(lái)源。表現(xiàn)是生成的 schema 里還殘留著$ref字段或者程序直接棧溢出。排查步驟檢查components.schemas里是否真的存在被引用的定義有時(shí)候文檔寫得不完整引用了不存在的 schema。檢查是否有循環(huán)引用比如 A 引用 BB 又引用 A。這種情況必須用 visited 集合兜底。檢查$ref路徑格式有些文檔用的是完整 URL 而不是#/components/schemas/Xxx這種內(nèi)部引用。5.3 參數(shù)類型轉(zhuǎn)換錯(cuò)誤OpenAPI 里有些類型 JSON Schema 不直接支持比如type: file、type: binary。這些參數(shù)在工具調(diào)用場(chǎng)景下沒(méi)法直接傳我的做法是統(tǒng)一轉(zhuǎn)成type: string并在描述里注明是文件內(nèi)容或路徑。模型傳字符串過(guò)來(lái)你在實(shí)際調(diào)用接口時(shí)再做轉(zhuǎn)換。5.4 常見(jiàn)問(wèn)題速查表問(wèn)題現(xiàn)象可能原因解決方向生成的 schema 里有 $ref引用未解析檢查 resolve_ref 遞歸邏輯模型選錯(cuò)工具描述太相似或太模糊優(yōu)化 description增加區(qū)分度參數(shù)漏傳required 提取不全檢查 path 參數(shù)和 body required參數(shù)類型不對(duì)schema 轉(zhuǎn)換遺漏檢查 nullable、allOf 處理工具名重復(fù)operationId 缺失或重復(fù)啟用去重邏輯加后綴上下文超限工具太多或描述太長(zhǎng)分組加載精簡(jiǎn)描述5.5 幾個(gè)踩過(guò)的坑坑一不要相信 operationId 的唯一性。我遇到過(guò)一份文檔里有兩個(gè)接口的 operationId 完全一樣直接導(dǎo)致工具名沖突。去重邏輯必須要有不能省??佣escription 里的換行和特殊字符要清理。有些文檔的 description 里帶 markdown 格式、換行符、甚至 HTML 標(biāo)簽直接塞給模型會(huì)干擾理解。我一般會(huì)做一次清洗去掉多余空白和格式符號(hào)。坑三枚舉值要保留。有些參數(shù)是枚舉類型OpenAPI 里用enum列出可選值。這個(gè)信息對(duì)模型非常重要能大幅降低傳錯(cuò)值的概率。轉(zhuǎn)換時(shí)一定要保留enum字段??铀姆猪?yè)參數(shù)要特別處理。列表類接口通常有page、pageSize、offset、limit這類分頁(yè)參數(shù)。這些參數(shù)模型不一定知道該傳多少可以在描述里給個(gè)默認(rèn)值建議比如默認(rèn)每頁(yè) 20 條。6. 進(jìn)階優(yōu)化讓自動(dòng)生成的工具更好用6.1 描述文案的二次加工自動(dòng)拼接的描述雖然準(zhǔn)確但讀起來(lái)往往比較生硬。如果對(duì)效果要求高可以在自動(dòng)生成的基礎(chǔ)上做一層模板化加工。比如把HTTP GET /users/{id}這種技術(shù)描述轉(zhuǎn)成根據(jù)用戶 ID 查詢用戶詳細(xì)信息這種業(yè)務(wù)語(yǔ)言。我的做法是維護(hù)一個(gè)動(dòng)詞映射表把 HTTP 方法和路徑模式映射成業(yè)務(wù)動(dòng)作ACTION_MAP { (get, list): 查詢列表, (get, detail): 查詢?cè)斍? (post, create): 創(chuàng)建, (put, update): 更新, (delete, delete): 刪除, }結(jié)合路徑里的關(guān)鍵詞做匹配生成更自然的描述。這一步不是必須的但做了之后模型的選擇準(zhǔn)確率會(huì)有可見(jiàn)提升。6.2 工具分組與動(dòng)態(tài)加載前面提到工具太多會(huì)影響選擇準(zhǔn)確率分組是有效的應(yīng)對(duì)。具體做法是給每個(gè)工具打上分類標(biāo)簽可以基于路徑前綴自動(dòng)分類比如/user/*歸到用戶組/order/*歸到訂單組。調(diào)用時(shí)先讓模型判斷意圖屬于哪組再加載對(duì)應(yīng)組的工具。這個(gè)方案的關(guān)鍵是分類要合理組內(nèi)工具數(shù)量控制在 10 到 15 個(gè)比較合適。組太多會(huì)增加路由復(fù)雜度組太少又起不到精簡(jiǎn)作用。6.3 參數(shù)默認(rèn)值與示例注入模型對(duì)參數(shù)的取值經(jīng)常拿不準(zhǔn)尤其是可選參數(shù)。在 schema 里注入default和example能顯著改善這個(gè)問(wèn)題。OpenAPI 文檔里如果已經(jīng)有這些信息直接保留如果沒(méi)有可以根據(jù)參數(shù)名和類型推斷一個(gè)合理的默認(rèn)值。比如pageSize默認(rèn) 20page默認(rèn) 1status如果有枚舉就選第一個(gè)。這些默認(rèn)值不一定要精確主要是給模型一個(gè)參考錨點(diǎn)。6.4 生成結(jié)果的校驗(yàn)自動(dòng)生成的東西不能盲信上線前一定要做校驗(yàn)。我一般會(huì)檢查這幾項(xiàng)工具名稱是否唯一、是否符合字符集要求。每個(gè)工具的parameters是否是合法的 JSON Schema。required里的字段是否都在properties里存在。描述是否為空、是否超長(zhǎng)。寫一個(gè)校驗(yàn)函數(shù)生成后跑一遍有問(wèn)題直接報(bào)出來(lái)比等到模型調(diào)用失敗再排查高效得多。def validate_tools(tools): errors [] names set() for tool in tools: fn tool[function] name fn[name] if name in names: errors.append(f工具名重復(fù): {name}) names.add(name) params fn[parameters] props params.get(properties, {}) for req in params.get(required, []): if req not in props: errors.append(f{name}: required 字段 {req} 不在 properties 中) if not fn.get(description): errors.append(f{name}: 描述為空) return errors這套自動(dòng)生成流程跑通之后我維護(hù)那 50 多個(gè)接口的工具定義從原來(lái)每次改動(dòng)要花大半天變成了改完 OpenAPI 文檔重新跑一遍腳本幾分鐘搞定。真正花時(shí)間的部分轉(zhuǎn)移到了描述優(yōu)化和分組策略上而這些是一次性投入、長(zhǎng)期受益的工作。如果你手頭也有帶 OpenAPI 文檔的接口需要接入模型強(qiáng)烈建議先把這套自動(dòng)化跑起來(lái)別在手工寫 schema 上浪費(fèi)生命。