用與 MCP:格式、并行與安全邊界)
1. 引言大模型LLM本身是“文本生成器”無法直接執(zhí)行外部操作比如查詢數(shù)據(jù)庫、調(diào)用 API、讀寫文件或發(fā)送郵件。工具調(diào)用Function Calling / Tool Use讓模型在對話中聲明“我需要調(diào)用某個工具”由外部系統(tǒng)真正執(zhí)行再把結(jié)果回傳給模型繼續(xù)推理。MCPModel Context Protocol則把“工具、資源、提示詞”統(tǒng)一成一套標(biāo)準(zhǔn)化協(xié)議讓模型可以跨應(yīng)用復(fù)用同一套工具生態(tài)。本文從格式、并行調(diào)用和安全邊界三個維度展開并給出可運行的代碼實戰(zhàn)。2. 工具調(diào)用的核心格式不同廠商對工具調(diào)用的消息格式略有差異但核心思路一致模型輸出一個結(jié)構(gòu)化的“工具調(diào)用請求”而不是直接執(zhí)行代碼。以 OpenAI 風(fēng)格為例工具調(diào)用通常包含工具名稱、參數(shù)和調(diào)用 ID。一個典型的工具調(diào)用請求如下{ role: assistant, content: null, tool_calls: [ { id: call_abc123, type: function, function: { name: get_weather, arguments: {\city\: \北京\, \date\: \2026-08-09\} } } ] }外部系統(tǒng)執(zhí)行后把結(jié)果以“工具消息”回傳給模型{ role: tool, tool_call_id: call_abc123, content: {\temperature\: 32, \condition\: \晴\} }模型拿到工具結(jié)果后繼續(xù)生成面向用戶的最終回答。這個“請求-執(zhí)行-回傳-續(xù)答”的循環(huán)就是工具調(diào)用的基本工作流。3. 工具定義與參數(shù)約束為了讓模型正確調(diào)用工具開發(fā)者需要提供工具的結(jié)構(gòu)化定義包括名稱、描述和參數(shù) JSON Schema。描述越清晰模型選錯工具的概率越低。tools [ { type: function, function: { name: get_weather, description: 查詢指定城市在指定日期的天氣情況, parameters: { type: object, properties: { city: {type: string, description: 城市名稱如北京、上海}, date: {type: string, description: 日期格式 YYYY-MM-DD} }, required: [city, date] } } } ]參數(shù) Schema 中應(yīng)盡量使用 enum、format 等約束字段減少模型生成非法參數(shù)的概率。例如日期字段可以補(bǔ)充 pattern 校驗。4. 并行工具調(diào)用當(dāng)一次回答需要調(diào)用多個相互獨立的工具時模型可以在一次響應(yīng)中返回多個 tool_calls由外部系統(tǒng)并行執(zhí)行從而顯著降低延遲。并行調(diào)用示例{ role: assistant, content: null, tool_calls: [ { id: call_1, function: {name: get_weather, arguments: {\city\: \北京\}} }, { id: call_2, function: {name: get_weather, arguments: {\city\: \上海\}} }, { id: call_3, function: {name: get_stock_price, arguments: {\symbol\: \AAPL\}} } ] }外部系統(tǒng)應(yīng)使用并發(fā)方式執(zhí)行這些調(diào)用例如 Python 的 asyncio.gather 或線程池。需要注意并行調(diào)用只適用于相互之間沒有依賴關(guān)系的工具如果工具 B 的入?yún)⒁蕾嚬ぞ?A 的輸出則必須串行執(zhí)行。5. 代碼實戰(zhàn)完整工具調(diào)用循環(huán)下面給出一個完整的 Python 示例演示“模型聲明調(diào)用-外部執(zhí)行-結(jié)果回傳-模型續(xù)答”的閉環(huán)。示例使用 OpenAI SDK 風(fēng)格但核心邏輯適用于大多數(shù)兼容接口。import json from openai import OpenAI client OpenAI() def get_weather(city: str) - str: 模擬天氣查詢工具 data {北京: 32, 上海: 28, 廣州: 30} return json.dumps({city: city, temperature: data.get(city, 25)}) tools [ { type: function, function: { name: get_weather, description: 查詢指定城市的天氣溫度, parameters: { type: object, properties: { city: {type: string, description: 城市名稱} }, required: [city] } } } ] messages [{role: user, content: 北京和上海今天多少度}] 第一輪模型可能返回工具調(diào)用請求 response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, ) assistant_msg response.choices[0].message messages.append(assistant_msg) 檢查是否有工具調(diào)用 if assistant_msg.tool_calls: for tc in assistant_msg.tool_calls: args json.loads(tc.function.arguments) result get_weather(args[city]) messages.append({ role: tool, tool_call_id: tc.id, content: result, }) 第二輪模型基于工具結(jié)果生成最終回答 final_response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, ) print(final_response.choices[0].message.content)這段代碼的關(guān)鍵點在于assistant 消息必須原樣追加回 messages工具結(jié)果必須通過 tool_call_id 與對應(yīng)的調(diào)用請求關(guān)聯(lián)否則模型無法正確理解哪個結(jié)果對應(yīng)哪個調(diào)用。6. 并行調(diào)用實戰(zhàn)asyncio 實現(xiàn)當(dāng)模型一次返回多個 tool_calls 時可以使用 asyncio 并發(fā)執(zhí)行。下面給出一個可運行的并行示例。import asyncio import json from openai import AsyncOpenAI client AsyncOpenAI() async def call_tool(name: str, arguments: str) - str: 根據(jù)工具名分發(fā)執(zhí)行 args json.loads(arguments) if name get_weather: data {北京: 32, 上海: 28} return json.dumps({city: args[city], temperature: data.get(args[city], 25)}) if name get_stock: return json.dumps({symbol: args[symbol], price: 188.5}) return json.dumps({error: unknown tool}) async def main(): messages [{role: user, content: 查一下北京天氣和 AAPL 股價}] tools [ {type: function, function: {name: get_weather, description: 查天氣, parameters: {type: object, properties: {city: {type: string}}, required: [city]}}}, {type: function, function: {name: get_stock, description: 查股價, parameters: {type: object, properties: {symbol: {type: string}}, required: [symbol]}}}, ] resp await client.chat.completions.create(modelgpt-4o, messagesmessages, toolstools) assistant_msg resp.choices[0].message messages.append(assistant_msg) if assistant_msg.tool_calls: # 并發(fā)執(zhí)行所有工具調(diào)用 results await asyncio.gather(*[ call_tool(tc.function.name, tc.function.arguments) for tc in assistant_msg.tool_calls ]) for tc, result in zip(assistant_msg.tool_calls, results): messages.append({role: tool, tool_call_id: tc.id, content: result}) final await client.chat.completions.create(modelgpt-4o, messagesmessages, toolstools) print(final.choices[0].message.content) asyncio.run(main())并行執(zhí)行時要注意如果某個工具調(diào)用失敗需要決定是整體回滾還是單獨返回錯誤信息給模型。通常建議把錯誤信息作為工具結(jié)果回傳讓模型自行判斷下一步。7. MCP 協(xié)議基礎(chǔ)MCPModel Context Protocol是 Anthropic 于 2024 年底開源的標(biāo)準(zhǔn)協(xié)議旨在解決“每個應(yīng)用都要為模型單獨適配一套工具接口”的問題。MCP 采用客戶端-服務(wù)器架構(gòu)MCP 客戶端如 Claude Desktop、IDE 插件連接 MCP 服務(wù)器服務(wù)器暴露工具、資源和提示詞模型通過統(tǒng)一協(xié)議調(diào)用。MCP 的核心概念包括工具Tools可被模型調(diào)用的函數(shù)與 Function Calling 中的工具概念一致。資源Resources可被讀取的數(shù)據(jù)如文件內(nèi)容、數(shù)據(jù)庫記錄。提示詞Prompts預(yù)定義的提示模板幫助模型理解任務(wù)。傳輸層Transports支持 stdio 和 HTTP/SSE 兩種通信方式。MCP 使用 JSON-RPC 2.0 作為消息協(xié)議所有請求和響應(yīng)都遵循統(tǒng)一格式。一個典型的 MCP 工具調(diào)用流程是客戶端發(fā)送 tools/call 請求服務(wù)器執(zhí)行并返回結(jié)果。8. MCP 實戰(zhàn)構(gòu)建一個最小服務(wù)器下面使用官方 Python SDK 構(gòu)建一個最小 MCP 服務(wù)器暴露一個“獲取當(dāng)前時間”的工具。from mcp.server.fastmcp import FastMCP from datetime import datetime mcp FastMCP(TimeServer) mcp.tool() def get_current_time(timezone: str UTC) - str: 獲取指定時區(qū)的當(dāng)前時間 # 簡化實現(xiàn)實際應(yīng)使用 zoneinfo 處理時區(qū) return datetime.now().isoformat() if name main: mcp.run(transportstdio)啟動后任何支持 MCP 的客戶端都可以連接這個服務(wù)器并調(diào)用 get_current_time 工具。服務(wù)器通過裝飾器自動生成工具定義SDK 負(fù)責(zé)處理 JSON-RPC 通信細(xì)節(jié)。9. MCP 客戶端調(diào)用實戰(zhàn)下面演示如何在 Python 中作為 MCP 客戶端連接上述服務(wù)器并調(diào)用工具。import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params StdioServerParameters( commandpython, args[time_server.py], ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() # 列出可用工具 tools await session.list_tools() print(可用工具:, [t.name for t in tools.tools]) # 調(diào)用工具 result await session.call_tool( get_current_time, arguments{timezone: Asia/Shanghai}, ) print(工具結(jié)果:, result.content) asyncio.run(main())這個示例展示了 MCP 客戶端連接、初始化、列出工具和調(diào)用工具的完整流程。實際項目中MCP 客戶端通常嵌入在 Agent 框架中由模型根據(jù)用戶意圖自動選擇并調(diào)用工具。10. 工具調(diào)用與 MCP 的對比維度Function CallingMCP定位模型接口層的工具調(diào)用能力工具生態(tài)的標(biāo)準(zhǔn)化協(xié)議工具來源由應(yīng)用開發(fā)者硬編碼在請求中由 MCP 服務(wù)器動態(tài)提供跨應(yīng)用復(fù)用困難每個應(yīng)用各自適配容易同一服務(wù)器可被多客戶端復(fù)用傳輸方式HTTP 請求內(nèi)嵌stdio 或 HTTP/SSE典型場景單應(yīng)用內(nèi)快速接入工具多應(yīng)用共享工具生態(tài)、插件市場兩者并非互斥MCP 服務(wù)器內(nèi)部暴露的工具最終仍需要通過模型的 Function Calling 能力被調(diào)用。可以理解為 MCP 是“工具的分發(fā)層”Function Calling 是“模型的調(diào)用層”。11. 安全邊界工具調(diào)用的風(fēng)險工具調(diào)用賦予模型“行動能力”也引入了新的安全風(fēng)險。主要風(fēng)險包括提示注入外部內(nèi)容如網(wǎng)頁、郵件中嵌入惡意指令誘導(dǎo)模型調(diào)用危險工具。權(quán)限濫用模型在用戶未授權(quán)的情況下調(diào)用高權(quán)限工具如刪除文件、轉(zhuǎn)賬。參數(shù)篡改模型生成的參數(shù)超出預(yù)期范圍導(dǎo)致數(shù)據(jù)泄露或系統(tǒng)損壞。過度調(diào)用模型在循環(huán)中反復(fù)調(diào)用工具造成資源消耗或費用失控。安全設(shè)計應(yīng)遵循“最小權(quán)限”原則每個工具只授予完成任務(wù)所需的最小權(quán)限并在調(diào)用前進(jìn)行用戶確認(rèn)。12. 安全邊界MCP 的防護(hù)機(jī)制MCP 協(xié)議本身提供了一些安全機(jī)制但最終安全責(zé)任仍在應(yīng)用層。關(guān)鍵防護(hù)點包括工具白名單客戶端只暴露必要的工具給模型不暴露全部。用戶確認(rèn)高風(fēng)險工具刪除、寫入、支付必須經(jīng)過用戶顯式確認(rèn)。輸入校驗服務(wù)器端對工具參數(shù)做嚴(yán)格校驗拒絕非法輸入。審計日志記錄所有工具調(diào)用便于事后追溯。沙箱隔離在受限環(huán)境中執(zhí)行工具限制網(wǎng)絡(luò)和文件系統(tǒng)訪問。下面給出一個帶用戶確認(rèn)和參數(shù)校驗的工具調(diào)用示例def safe_delete_file(path: str, confirm: bool False) - str: 安全刪除文件必須顯式確認(rèn) if not confirm: return 操作已取消需要用戶確認(rèn) # 校驗路徑防止目錄穿越 if .. in path or not path.startswith(/data/): return 非法路徑 # 實際刪除邏輯 return f已刪除 {path}這個示例體現(xiàn)了兩個關(guān)鍵安全實踐高風(fēng)險操作必須二次確認(rèn)路徑參數(shù)必須校驗防止目錄穿越。13. 實戰(zhàn)帶安全控制的 Agent下面綜合演示一個帶安全控制的 Agent模型可以調(diào)用工具但高風(fēng)險工具需要用戶確認(rèn)且所有調(diào)用都記錄日志。import json import logging from openai import OpenAI logging.basicConfig(levellogging.INFO) logger logging.getLogger(agent) client OpenAI() def send_email(to: str, content: str) - str: 高風(fēng)險工具發(fā)送郵件 # 實際發(fā)送邏輯 return f郵件已發(fā)送至 {to} def read_file(path: str) - str: 低風(fēng)險工具讀取文件 if .. in path: return 非法路徑 return f文件內(nèi)容: {path} tools [ {type: function, function: {name: send_email, description: 發(fā)送郵件, parameters: {type: object, properties: {to: {type: string}, content: {type: string}}, required: [to, content]}}}, {type: function, function: {name: read_file, description: 讀取文件, parameters: {type: object, properties: {path: {type: string}}, required: [path]}}}, ] HIGH_RISK_TOOLS {send_email} def execute_tool(name: str, arguments: str) - str: args json.loads(arguments) logger.info(工具調(diào)用: %s %s, name, arguments) if name in HIGH_RISK_TOOLS: # 高風(fēng)險工具需要用戶確認(rèn) confirm input(f確認(rèn)執(zhí)行 {name}? (y/n): ) if confirm.lower() ! y: return 用戶取消了操作 if name send_email: return send_email(args[to], args[content]) if name read_file: return read_file(args[path]) return 未知工具 messages [{role: user, content: 讀取 config.txt 并發(fā)送郵件給 adminexample.com}] for _ in range(5): # 限制最大循環(huán)次數(shù)防止無限調(diào)用 resp client.chat.completions.create(modelgpt-4o, messagesmessages, toolstools) assistant_msg resp.choices[0].message messages.append(assistant_msg) if not assistant_msg.tool_calls: print(最終回答:, assistant_msg.content) break for tc in assistant_msg.tool_calls: result execute_tool(tc.function.name, tc.function.arguments) messages.append({role: tool, tool_call_id: tc.id, content: result})/code/pre 這個示例實現(xiàn)了三個關(guān)鍵安全控制高風(fēng)險工具的用戶確認(rèn)、工具調(diào)用日志審計、最大循環(huán)次數(shù)限制。這些機(jī)制共同構(gòu)成了工具調(diào)用的安全邊界。 14. 常見陷阱與最佳實踐 在實際開發(fā)中工具調(diào)用和 MCP 集成有幾個常見陷阱需要規(guī)避 忘記追加 assistant 消息工具調(diào)用后必須把 assistant 消息原樣追加回對話否則模型丟失上下文。 tool_call_id 不匹配工具結(jié)果必須通過 tool_call_id 與調(diào)用請求關(guān)聯(lián)否則模型無法理解結(jié)果歸屬。 參數(shù) Schema 過于寬松缺少 enum、format 約束會導(dǎo)致模型生成非法參數(shù)。 無限循環(huán)調(diào)用必須設(shè)置最大迭代次數(shù)防止模型反復(fù)調(diào)用工具。 忽略錯誤處理工具執(zhí)行失敗時應(yīng)把錯誤信息回傳給模型而不是直接中斷。 最佳實踐總結(jié)工具定義要精確、參數(shù)校驗要嚴(yán)格、高風(fēng)險操作要確認(rèn)、所有調(diào)用要審計、循環(huán)要有上限。 15. 總結(jié) 工具調(diào)用讓大模型從“會說話”進(jìn)化為“能做事”MCP 則讓工具生態(tài)標(biāo)準(zhǔn)化、可復(fù)用。本文從格式、并行和安全三個維度展開給出了完整的代碼實戰(zhàn)。核心要點是工具定義要結(jié)構(gòu)化、并行調(diào)用要處理依賴、安全邊界要貫穿始終。建議讀者在真實項目中從最小工具集開始逐步擴(kuò)展并始終把安全控制放在首位。