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