观语AI2026
24.3MB · 2026-04-03
大模型的能力边界往往受限于训练数据,想要让模型获取实时数据、操作外部系统,就需要让它具备“调用工具”的能力。传统做法是使用 function calling,但各模型实现方式不统一,维护成本高。而 MCP(Model Context Protocol) 提供了一套标准化的协议,让 AI 应用可以统一地接入各种外部工具和数据源。
本文将展示一个完整的本地闭环:
Ollama 是一个开源的本地大模型运行框架,支持 macOS、Linux 和 Windows。
# macOS/Linux
curl -fsSL | sh
# 或直接下载安装包
安装完成后,验证:
ollama --version
这是阿里巴巴通义千问系列中非常轻量的指令微调模型,仅有 0.5B 参数,在普通 CPU 上也能流畅运行。
ollama pull qwen2.5:0.5b-instruct
测试模型是否可用:
ollama run qwen2.5:0.5b-instruct "你好,请介绍一下自己"
如果看到回复,说明模型部署成功。
为了演示,我们开发一个简单的 Flask API,提供用户数据查询功能(你可以替换成自己的业务 API)。
创建文件 my_api.py:
from flask import Flask, request, jsonify
app = Flask(__name__)
# 模拟数据库
users = {
1: {"name": "张三", "age": 28, "city": "北京"},
2: {"name": "李四", "age": 35, "city": "上海"},
3: {"name": "王五", "age": 22, "city": "深圳"},
}
@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = users.get(user_id)
if user:
return jsonify(user)
else:
return jsonify({"error": "用户不存在"}), 404
@app.route('/api/search', methods=['GET'])
def search_users():
name = request.args.get('name')
result = [u for u in users.values() if name in u['name']] if name else []
return jsonify(result)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
启动 API:
python my_api.py
测试接口:
curl
# 返回 {"age":28,"city":"北京","name":"张三"}
curl "http://localhost:5000/api/search?name=李"
# 返回 [{"age":35,"city":"上海","name":"李四"}]
MCP(Model Context Protocol)是 Anthropic 推出的开源协议,旨在让 AI 模型能够安全、标准地访问外部工具和数据源。其核心概念:
MCP 使用 JSON-RPC 2.0 进行通信,支持多种传输方式(stdio, SSE 等)。
我们将使用 Python 的 mcp 官方 SDK 来编写 Server。首先安装依赖:
pip install mcp flask requests
创建 mcp_server.py:
import asyncio
import json
import requests
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
# 创建 MCP Server 实例
server = Server("my-api-server")
# 定义工具列表
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
return [
types.Tool(
name="get_user_by_id",
description="根据用户ID查询用户信息",
inputSchema={
"type": "object",
"properties": {
"user_id": {
"type": "integer",
"description": "用户ID,例如 1,2,3"
}
},
"required": ["user_id"]
}
),
types.Tool(
name="search_users_by_name",
description="根据姓名关键词搜索用户",
inputSchema={
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "姓名关键词"
}
},
"required": ["name"]
}
)
]
# 实现工具的调用逻辑
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
if not arguments:
arguments = {}
if name == "get_user_by_id":
user_id = arguments.get("user_id")
if not user_id:
return [types.TextContent(type="text", text="错误:缺少 user_id")]
try:
# 调用自定义 API
resp = requests.get(f"http://localhost:5000/api/user/{user_id}")
if resp.status_code == 200:
data = resp.json()
text = f"用户信息:{json.dumps(data, ensure_ascii=False)}"
else:
text = f"查询失败:{resp.status_code} - {resp.text}"
return [types.TextContent(type="text", text=text)]
except Exception as e:
return [types.TextContent(type="text", text=f"调用API出错:{str(e)}")]
elif name == "search_users_by_name":
name_keyword = arguments.get("name")
if not name_keyword:
return [types.TextContent(type="text", text="错误:缺少 name")]
try:
resp = requests.get(f"http://localhost:5000/api/search", params={"name": name_keyword})
if resp.status_code == 200:
data = resp.json()
if data:
text = f"搜索结果:{json.dumps(data, ensure_ascii=False)}"
else:
text = "未找到匹配的用户"
else:
text = f"搜索失败:{resp.status_code} - {resp.text}"
return [types.TextContent(type="text", text=text)]
except Exception as e:
return [types.TextContent(type="text", text=f"调用API出错:{str(e)}")]
else:
raise ValueError(f"未知工具: {name}")
async def main():
# 使用 stdio 传输运行 server
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="my-api-server",
server_version="1.0.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
if __name__ == "__main__":
asyncio.run(main())
这个 MCP Server 暴露了两个工具,分别对应我们 API 的两个端点。当模型需要查询用户时,会调用相应的工具,Server 会转发请求到 Flask API 并返回结果。
现在我们要让模型能够识别用户意图并调用这些工具。有多种方式实现:
mcp-client 库,将工具描述嵌入到系统提示中,让模型决定调用。由于 qwen2.5:0.5b 没有内置 function calling,我们采用一种简单的“意图识别 + 工具调用”流程:
我们用一个 Python 脚本整合所有环节。安装额外依赖:
pip install ollama
创建 main.py:
import json
import subprocess
import sys
import ollama
import requests
# MCP Server 通信(这里简化:直接通过 HTTP 调用 MCP Server?不,我们上面 MCP Server 是用 stdio 的)
# 为了简化演示,我们不使用 MCP Client 库,而是直接让模型输出结构化指令,然后手动调用 API。
# 这样虽然不符合 MCP 标准流程,但能让你看到核心思想。你也可以使用 MCP 的 Python 客户端库来完整实现。
# 定义工具描述(给模型看的)
TOOLS_DESCRIPTION = """
你可以使用以下工具来帮助用户:
1. get_user_by_id: 根据用户ID查询用户信息。参数 user_id (整数)
2. search_users_by_name: 根据姓名关键词搜索用户。参数 name (字符串)
当用户询问关于用户信息时,你需要决定是否使用工具。如果需要使用,请以 JSON 格式输出你的调用,格式为:
{"tool": "工具名称", "arguments": {"参数名": 值}}
然后我会执行工具并返回结果。如果不需要工具,直接回答即可。
"""
def call_mcp_tool(tool_name, arguments):
"""模拟 MCP 调用:这里直接调用我们的 Flask API"""
if tool_name == "get_user_by_id":
user_id = arguments.get("user_id")
resp = requests.get(f"http://localhost:5000/api/user/{user_id}")
if resp.status_code == 200:
return resp.json()
else:
return {"error": resp.text}
elif tool_name == "search_users_by_name":
name = arguments.get("name")
resp = requests.get(f"http://localhost:5000/api/search", params={"name": name})
if resp.status_code == 200:
return resp.json()
else:
return {"error": resp.text}
else:
return {"error": f"未知工具: {tool_name}"}
def chat_with_model(user_input):
messages = [
{"role": "system", "content": TOOLS_DESCRIPTION},
{"role": "user", "content": user_input}
]
# 调用 Ollama 生成
response = ollama.chat(model='qwen2.5:0.5b-instruct', messages=messages)
assistant_reply = response['message']['content']
print("模型原始回复:", assistant_reply)
# 尝试解析 JSON 调用
try:
# 查找 JSON 块
import re
json_match = re.search(r'{.*?}', assistant_reply, re.DOTALL)
if json_match:
tool_call = json.loads(json_match.group())
if "tool" in tool_call and "arguments" in tool_call:
tool_name = tool_call["tool"]
args = tool_call["arguments"]
print(f"调用工具 {tool_name},参数 {args}")
tool_result = call_mcp_tool(tool_name, args)
# 将工具结果返回给模型生成最终答案
messages.append({"role": "assistant", "content": assistant_reply})
messages.append({"role": "user", "content": f"工具返回结果:{json.dumps(tool_result, ensure_ascii=False)},请根据这个结果回答用户问题。"})
final_response = ollama.chat(model='qwen2.5:0.5b-instruct', messages=messages)
return final_response['message']['content']
except Exception as e:
print("解析工具调用失败:", e)
return assistant_reply
if __name__ == "__main__":
print("AI 助手已启动,输入 quit 退出")
while True:
user_input = input("你: ")
if user_input.lower() == 'quit':
break
response = chat_with_model(user_input)
print("AI:", response)
这个脚本做了:
注意:这只是一个演示级实现,生产环境建议使用官方 MCP Client 库来规范通信。
启动 Flask API:
python my_api.py
启动 MCP Server(可选,我们上面直接调用了 API,但为了演示 MCP 概念,你也可以尝试使用 MCP Client 来连接 Server,这里简化了):
python mcp_server.py
但我们的 main.py 并未与 MCP Server 通信,而是直接调 API。如果你想体验完整的 MCP 流程,可以使用 mcp 命令行工具或官方 Python 客户端,这里不再展开。
运行主程序:
python main.py
测试对话:
你: 帮我查一下用户ID为2的信息
AI: 模型原始回复:{"tool": "get_user_by_id", "arguments": {"user_id": 2}}
调用工具 get_user_by_id,参数 {'user_id': 2}
AI: 用户ID为2的用户是李四,年龄35岁,来自上海。
你: 搜索名字中包含“王”的用户
AI: 模型原始回复:{"tool": "search_users_by_name", "arguments": {"name": "王"}}
调用工具 search_users_by_name,参数 {'name': '王'}
AI: 找到了用户王五,年龄22岁,在深圳。
你: 你好,请介绍一下自己
AI: 模型原始回复:你好!我是阿里云研发的超大规模语言模型,我叫通义千问。
AI: 你好!我是阿里云研发的超大规模语言模型,我叫通义千问。
可以看到,模型在需要查询数据时,正确地输出了工具调用指令,我们通过解析并调用真实 API 后,得到了最终回答。
本文实现了一个本地大模型调用自定义 API 的完整流程:
这种架构可以轻松扩展到更多工具和更复杂的业务场景。你可以将 MCP Server 部署为独立服务,让任意支持 MCP 的客户端(如 Claude Desktop)也能调用你的私有 API,实现跨应用的智能交互。
源码仓库:你可以在 GitHub 示例 中找到本文全部代码。
参考资料:
希望这篇文章对你有所帮助,欢迎留言交流!