萬物互聯的開始: Funcation Call 及 MCP 協議解析筆記 -一-
MCP(Model Context Protocol,模型上下文協議) ,2024 年 11 月底,由 Anthropic 推出的一種開放標準,旨在統一大型語言模型(LLM)與外部數據源和工具之間的通信協議。MCP 的主要目的在於解決當前 AI 模型因數據孤島限制而無法充分發揮潛力的難題,MCP 使得 AI 應用能夠安全地訪問和操作本地及遠程數據,爲 AI 應用提供了連接萬物的接口。
MCP 和 function call 的區別
Function Call 指的是 AI 模型根據上下文自動執行函數的機制,充當了 AI 模型與外部系統之間的橋樑,不同的模型有不同的 Function Calling 實現,代碼集成的方式也不一樣。由不同的 AI 模型平臺來定義和實現。
目前支持 function 的模型有 openai 的 GPT 系列,阿里的 qwen,智源的 GLM 系列
Model Context Protocol (MCP)
MCP 是一個標準協議,如同電子設備的 Type C 協議 (可以充電也可以傳輸數據),使 AI 模型能夠與不同的 API 和數據源無縫交互。
MCP 旨在替換碎片化的 Agent 代碼集成,從而使 AI 系統更可靠,更有效。通過建立通用標準,服務商可以基於協議來推出它們自己服務的 AI 能力,從而支持開發者更快的構建更強大的 AI 應用。開發者也不需要重複造輪子,通過開源項目可以建立強大的 AI Agent 生態。
MCP 可以在不同的應用 / 服務之間保持上下文,從而增強整體自主執行任務的能力。
由於 MCP 是一種協議,各個模型在使用這套協議下都能接入
MCP 終極指南: https://guangzhengli.com/blog/zh/model-context-protocol/
function call 使用方法
function call 一般是和 langchain 搭配着使用
llm 調用可以參考下面這個流程圖
這邊使用一個 demo 來講述 ollama 調用方式:
from ollama import ChatResponse, chat
import ollama
import logging
import requests
defget_current_weather(location, unit="celsius"):
"""
Fetches the current weather for a given location.
Args:
location (str): The city and country, e.g., "San Francisco, USA"
unit (str): Temperature unit, either "celsius" or "fahrenheit"
Returns:
str: A string describing the current weather, or an error message
"""
logging.info(f"Getting weather for {location}")
base_url = "https://api.open-meteo.com/v1/forecast"
# Set up parameters for the weather API
params = {
"latitude": 0,
"longitude": 0,
"current_weather": "true",
"temperature_unit": unit
}
# Set up geocoding to convert location name to coordinates
geocoding_url = "https://geocoding-api.open-meteo.com/v1/search"
location_parts = location.split(',')
city = location_parts[0].strip()
country = location_parts[1].strip() iflen(location_parts) > 1else""
geo_params = {
"name": city,
"count": 1,
"language": "en",
"format": "json"
}
try:
# First attempt to get coordinates
logging.info(f"Fetching coordinates for {location}")
geo_response = requests.get(geocoding_url, params=geo_params)
geo_response.raise_for_status()
geo_data = geo_response.json()
logging.debug(f"Geocoding response: {geo_data}")
# If first attempt fails, try with full location string
if"results"notin geo_data ornot geo_data["results"]:
geo_params["name"] = location
geo_response = requests.get(geocoding_url, params=geo_params)
geo_response.raise_for_status()
geo_data = geo_response.json()
logging.debug(f"Second geocoding attempt response: {geo_data}")
# Extract coordinates if found
if"results"in geo_data and geo_data["results"]:
params["latitude"] = geo_data["results"][0]["latitude"]
params["longitude"] = geo_data["results"][0]["longitude"]
logging.info(
f"Coordinates found: {params['latitude']}, {params['longitude']}")
else:
logging.warning(f"No results found for location: {location}")
returnf"Sorry, I couldn't find the location: {location}"
# Fetch weather data using coordinates
logging.info("Fetching weather data")
response = requests.get(base_url, params=params)
response.raise_for_status()
weather_data = response.json()
logging.debug(f"Weather data response: {weather_data}")
# Extract and format weather information
if"current_weather"in weather_data:
current_weather = weather_data["current_weather"]
temp = current_weather["temperature"]
wind_speed = current_weather["windspeed"]
result = f"The current weather in {location} is {temp}°{unit.upper()} with a wind speed of {wind_speed} km/h."
logging.info(f"Weather result: {result}")
return result
else:
logging.warning(f"No current weather data found for {location}")
returnf"Sorry, I couldn't retrieve weather data for {location}"
except requests.exceptions.RequestException as e:
logging.error(f"Error occurred while fetching weather data: {str(e)}")
returnf"An error occurred while fetching weather data: {str(e)}"
weather_tool = {
'type': 'function',
'function': {
'name' : 'get_current_weather',
'description' : 'Get the current weather in a given location"',
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
},
},
'required': ['location'],
}
}
}
messages = [{'role': 'user', 'content': 'What’s the weather like in Nanjing?'}]
print('Prompt:', messages[0]['content'])
available_functions = {
'get_current_weather': get_current_weather
}
client= ollama.Client(host=f"http://127.0.0.1:11434")
response: ChatResponse = client.chat(
'qwen2.5:14b',
messages=messages,
tools=[weather_tool],
)
if response.message.tool_calls:
# There may be multiple tool calls in the response
for tool in response.message.tool_calls:
# Ensure the function is available, and then call it
if function_to_call := available_functions.get(tool.function.name):
print('Calling function:', tool.function.name)
print('Arguments:', tool.function.arguments)
output = function_to_call(**tool.function.arguments)
print('Function output:', output)
else:
print('Function', tool.function.name, 'not found')
demo 的整體邏輯如下圖
demo 分爲幾部分
-
- tool
get_current_weather
爲我們提供給大模型的工具內容爲根據提供的地址 通過接口查到對應地點的溫度信息
-
- 工具描述
weather_tool = {
'type': 'function',
'function': {
'name' : 'get_current_weather',
'description' : 'Get the current weather in a given location"',
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
},
},
'required': ['location'],
}
}
}
這個是對於工具的描述 主要包含了 方法的類型,方法的名字描述,以及出入參數的描述,這個文件是大模型能否更好的調用模型的關鍵,比如 去掉 工具需要的參數
weather_tool = {
'type': 'function',
'function': {
'name' : 'get_current_weather',
'description' : 'The city and state, e.g. San Francisco, CA'
}
}
會發現 雖然模型也解析出了我們 我們需要一個 地點 但工具需要的入參的 key 並不是工具所需要,需要更加精準的提示
所以這個也可以成爲 我們寫工具提示比較好的模板 後面寫其他函數也可以按照這個
更加近一步 可以把工具類代輸入給大模型 然後模型 給我們按我們給的提示模板自動生成 tool 留個坑 之後來試一下 , 估計這種適合 qwen2.5-coder 模型效果會更加好一些
-
- ollama 請求
response: ChatResponse = client.chat(
'qwen2.5:14b',
messages=messages,
tools=[weather_tool],
)
ollama 去年 7 月後的版本開始支持 tool 的調用:https://ollama.com/blog/tool-support
通過將工具的描述傳給工具的方式使用描述傳給 ollama 我們可以通過抓包獲取 ollama 的請求方式和結果
可以看到在傳給 ollama 時帶上了工具類的描述,然後再 在模型的結果中給出了調用的 tool 然後也給分離出了入參數
4. 模型調用工具的方式
這部分就是代碼上的能力了找到對應的 tool 的入口然後將調用函數
引用:
LangChain 函數調用: https://wangwei1237.github.io/LLM_in_Action/langchain_function_call.html
MCP 使用方法
mcp 是 claude 公司提出的模型調用工具的統一協議,旨在統一大型語言模型(LLM)與外部數據源和工具之間的通信協議,當下各個工具
mcp 的架構
從官方給出的架構圖看主要分爲幾部分
MCP Hosts: 像 Claude 桌面、IDE 或希望通過 MCP 訪問數據的 AI 工具
MCP Clients: 與服務器保持 1:1 連接的協議客戶端
MCP Servers: 每個通過標準化的模型上下文協議公開特定功能的輕量級程序
Local Data Sources:MCP 服務器可以安全訪問 您的計算機文件、數據庫和服務,
Remote Services: MCP 服務器可通過互聯網(例如,通過 API)訪問的外部系統
可以看到 MCP 的分層架構很清晰
官方文檔地址: https://modelcontextprotocol.io/introduction#general-architecture
由於官方文檔給出的例子都是基於 claude desktop 的 但 claude 的價格實在高 再加上賬號還被封了,其他也有比如可以使用 vscode+cline+deepseek+mcp 來實現 mcp 調用 但這終究時工具使用上,還是想看一下正常 mcp 交互的整個流程 比如使用 client 使用 ollama
後續比如能通過代碼這個最直接的方式來使用這套規則
ollama+mcp 實戰
這邊找到了一個比較基礎的 mcphost 名字就是這麼直接 ,地址 https://github.com/mark3labs/mcphost
這個老哥是使用 go 實現了 mcphost
先得有基礎的環境 這邊建議使用 Ubuntu+go1.23+nodejs+ollama
通過老哥給的步驟:
安裝go
go install github.com/mark3labs/mcphost@latest
ollama拉模型
ollama pull qwen2.5:7b
然後加上老哥給的一個配置文件
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": [
"mcp-server-sqlite",
"--db-path",
"/tmp/foo.db"
]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp"
]
}
}
}
這個配置主要描述了 模型可以使用的 mcp 服務其中 uvx 和 npx 分別是 python 的 uv 管理方式 npx 爲快速執行 nodejs 腳本的
然後
mcp-server-sqlite 地址: https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite
server-filesystem:https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem
mcphost.exe -m ollama:qwen2.5:7b --config t2.json
然後運行一下就可以看到 qwen 執行了本地的提示的操作 nice
如果你是 windows 系統可以看之前的內容,安裝一個 wsl 環境
再也不用切環境了,wsl2 安裝運行 vllm 指南:https://mp.weixin.qq.com/s/GsfSJdchgBcOvHsQkrl5xA
本章主要講了 funcation call ollama 的調用方式 以及一個 ollama 調 mcp 的方法,下一章將詳細介紹 mcphost 這個 demo 以及 mcp 協議
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vGJ2_hHeL-AILhwROiIvZA