基于WebMCP的AI代理工具调用实践:Ollama与FastAPI搭建指南
2026/9/8 11:27:32 网站建设 项目流程

AI 代理要真正落地产出价值,关键点往往不在模型本身,而在于它如何安全地调用外部工具。WebMCP 这类基于 Web 的模型上下文协议封装层,正是为了解决“模型选择工具、服务执行工具、结果回到模型”这一整条链路而出现。本文用一个本地可运行的最小方案:Ollama 本地模型 + WebMCP 网关 + 工具注册中心,搭建一个 AI 代理助手。学完这个例子,你能理解工具调用的完整流程,也能把它扩展到报价计算、订单汇总、内容生成等实际业务场景。

这里的 WebMCP,指的是一种通过 Web 接口暴露模型上下文能力、让前端和业务系统以统一 HTTP 方式调用 AI 代理的实现方案。它不是某个商业产品的专有名词,不同团队对这类网关可能叫“MCP Server”“Agent Gateway”或“Tool Proxy”。文章示例使用 FastAPI 编写,本地模型通过 Ollama 启动,代码不依赖外部 SDK,方便你按自己的项目结构调整。

1. 先理解 WebMCP 在 AI 代理中扮演什么角色

1.1 模型上下文协议解决的是“工具接入”问题

AI 代理(AI Agent)不能只靠聊天完成业务任务。真正做事需要读取数据、计算金额、查询状态、写入记录,这时就必须让模型调用外部工具。早期做法是把工具说明写在系统提示词里,要求模型输出 JSON,再由代码解析执行。这种方式不稳定,模型容易把 JSON 格式写错,或者参数类型对不上,经常要反复调试。

模型上下文协议(MCP)提供了更标准的方式:客户端把“可用的工具清单”发给模型,模型在回答时如果发现需要工具,就返回结构化的tool_calls结果。服务端拿到这个结果后执行对应的函数,再把执行结果作为一条tool消息继续发给模型,让模型基于真实结果生成最终回答。

WebMCP 可以理解为“跑在 Web 层上的 MCP 网关”。浏览器或业务系统不需要直接访问本地模型、本地文件系统或命令行,只需要通过 HTTP 调用 WebMCP,WebMCP 内部再与本地模型和各个工具完成交互。这样做的价值在于:工具能力可以被统一注册、统一鉴权、统一审计,前端只需关心请求和响应。

1.2 为什么不能靠“提示词”硬编码

如果你曾经在提示词里写过“请以 JSON 格式返回”,一定遇到过这些情况:模型返回了 Markdown 代码块、属性名大小写不对、数字被写成字符串、多了一个逗号。为了让结果可用,你不得不再写一轮正则清洗和异常重试。

工具调用(function calling / tool calling)之所以更稳,是因为模型在训练和使用阶段就知道结构化的tool_calls输出格式。框架会告诉服务端“模型要求调用哪个函数、参数是什么”,服务端执行后把真实结果回填给模型。整个过程不需要从自然语言里猜 JSON,也大大减少了解析错误。

1.3 典型架构:浏览器、WebMCP、本地模型与工具

本文示例的架构分为四层:

角色组件职责
客户端前端页面、curl、脚本发起对话请求,传入 messages
WebMCP 网关FastAPI 服务接收请求、维护工具清单、执行工具、返回最终结果
模型服务Ollama + 本地模型理解对话、判断是否需要调用工具、生成回答
工具层Python 函数执行计算、查询、格式化等具体动作

一次完整请求的链路是:用户说“500 个订单,单价 89 元,总收入多少”,WebMCP 把消息和工具清单发给本地模型,模型返回“调用calculate_revenue,参数是 500 和 89”,WebMCP 执行工具,再把结果“44500 元”交给模型,模型最终生成回答返回给用户。

1.4 适用场景与不适合的场景

WebMCP 适合以下场景:

  • 自动化客服:查询订单信息、计算退款金额、生成工单摘要。
  • 业务助理:根据报表数据计算营收、统计库存、生成日报。
  • 内容生产:读取素材、总结摘要、生成结构化草稿。
  • 企业内部知识问答:把本地文档作为工具,让模型按需检索。

不适合直接使用自动执行能力的场景包括:涉及高危操作、资金转账、删除数据、修改权限等。这些场景不是不能用 WebMCP,而是不能由模型直接执行工具,必须插入人工审批环节。

2. 环境准备:本地模型和依赖先跑通

2.1 硬件与软件要求

运行本地模型需要先确认自己的硬件条件。本文示例使用 Ollama 加载模型,推理发生在本机。

软件建议说明
Python3.10 或更高WebMCP 服务运行环境
Ollama当前稳定版管理本地模型和提供 OpenAI 兼容接口
本地模型qwen2.5:7b、llama3.2:3b 等优先选择支持工具调用的模型
FastAPI0.100 或更高HTTP 网关
uvicorn0.30 或更高ASGI 服务进程
requests2.31 或更高调用 Ollama 接口

如果内存 16GB 以上,运行 7B 量级模型比较从容。内存小于 16GB 时,建议先尝试 3B 量级模型,跑通工具调用流程后再考虑升级。

2.2 安装 Ollama 并拉取本地模型

Ollama 支持 Windows、macOS、Linux,可以到官网下载对应安装包。安装完成后先确认命令可用:

ollama --version

启动服务。不同系统下服务启动方式可能不同,如果是手动启动,可以在终端执行:

ollama serve

再打开一个新终端,拉取模型:

ollama pull qwen2.5:7b

拉取结束后查看本机已存在的模型:

ollama list

看到对应模型出现在列表中,说明模型已经就绪。

2.3 验证本地模型接口

Ollama 除了原生接口,还提供了 OpenAI 兼容接口,方便以后切换云端模型。先验证原生接口:

curl http://localhost:11434/api/tags

返回 JSON 数组,里面包含已经拉取的模型名。再验证 OpenAI 兼容接口:

curl http://localhost:11434/v1/models

如果两个请求都返回 HTTP 200,说明模型服务已经可供 WebMCP 使用。

2.4 学习环境与生产环境的差异

学习环境只需要让 Ollama 在本地启动,WebMCP 直接访问localhost即可。生产环境则完全不同:

  • 模型服务应部署在单独主机或容器中,避免被业务代码误影响。
  • WebMCP 与 Ollama 之间的调用需要网络鉴权,不能裸奔在内网。
  • 本地模型需要显存和内存配置,建议先做压测,再设置合理的并发数。
  • 密钥、模型路径、端口等配置必须外置,不能写死在代码里。

如果是个人学习,第 2 节完成就足够了。如果准备接业务,请在第 7 节检查生产化清单。

3. 用最小代码实现一个 WebMCP 网关

3.1 项目结构与依赖

先创建一个目录,比如webmcp-agent,里面的结构保持简单清晰:

webmcp-agent/ ├── app.py ├── ai_proxy.py ├── tool_registry.py ├── demo_tools.py └── requirements.txt

app.py是 FastAPI 入口,接收 HTTP 请求;ai_proxy.py负责和 Ollama 交互并处理工具调用循环;tool_registry.py提供工具注册和分发;demo_tools.py定义具体工具。

requirements.txt内容如下:

fastapi>=0.100 uvicorn[standard]>=0.30 requests>=2.31 pydantic>=2.0

安装依赖:

pip install -r requirements.txt

3.2 工具注册中心:让工具可以被模型发现

工具注册中心解决两个问题:收集工具 schema、根据名称执行对应函数。

# tool_registry.py import functools _TOOL_FUNCS = {} def tool(schema): def decorator(func): name = schema["function"]["name"] _TOOL_FUNCS[name] = (func, schema) @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator def get_schemas(): return [schema for _, schema in _TOOL_FUNCS.values()] def execute(name, **kwargs): if name not in _TOOL_FUNCS: raise ValueError(f"tool not found: {name}") func, _ = _TOOL_FUNCS[name] return func(**kwargs)

这里用装饰器注册的好处是,新增工具时只需要写一个普通函数并标注 schema,后面get_schemas()会自动把工具清单传给模型。

3.3 示例工具:计算收入和查询服务器时间

为了演示工具调用闭环,这里定义两个工具。第一个是calculate_revenue,接收订单数量和单价,返回总收入,贴近业务场景。

第二个是get_server_time,返回当前服务器时间,用来验证无参工具调用。

# demo_tools.py import datetime import tool_registry @tool_registry.tool({ "type": "function", "function": { "name": "calculate_revenue", "description": "根据订单数量和单个订单的单价计算总收入,返回单位是元。", "parameters": { "type": "object", "properties": { "units": { "type": "integer", "description": "订单数量" }, "unit_price": { "type": "number", "description": "单个订单的单价,单位是元" } }, "required": ["units", "unit_price"] } } }) def calculate_revenue(units: int, unit_price: float): if units < 0 or unit_price < 0: raise ValueError("units and unit_price must be >= 0") total = round(units * unit_price, 2) return { "units": units, "unit_price": unit_price, "total_revenue": total } @tool_registry.tool({ "type": "function", "function": { "name": "get_server_time", "description": "获取 WebMCP 服务所在服务器的当前时间,返回 ISO 格式字符串。", "parameters": { "type": "object", "properties": {} } } }) def get_server_time(): return { "time": datetime.datetime.now().isoformat() }

工具有两个细节值得注意:一是参数校验要放在函数内部,避免脏数据进入下游;二是返回值要序列化为可 JSON 化的结构,不能返回 Python 对象。

3.4 后端调用本地模型并处理工具调用

ai_proxy.py是核心文件。它把用户消息和工具清单一起发给 Ollama 的 OpenAI 兼容接口,如果模型返回tool_calls,则执行工具并把结果追加到消息列表,然后再请求一次模型,直到模型不再要求调用工具。

# ai_proxy.py import json import requests import tool_registry OLLAMA_BASE = "http://localhost:11434/v1" DEFAULT_MODEL = "qwen2.5:7b" MAX_ROUNDS = 5 def chat_once(model: str, messages: list): current = list(messages) for _ in range(MAX_ROUNDS): payload = { "model": model, "messages": current, "tools": tool_registry.get_schemas(), "tool_choice": "auto", } resp = requests.post( f"{OLLAMA_BASE}/chat/completions", json=payload, timeout=120, ) resp.raise_for_status() data = resp.json() message = data["choices"][0]["message"] if not message.get("tool_calls"): return {"reply": message.get("content", "")} current.append(message) for idx, call in enumerate(message["tool_calls"]): fn_name = call["function"]["name"] raw_args = call["function"].get("arguments") or "{}" if isinstance(raw_args, str): args = json.loads(raw_args) else: args = raw_args call_id = call.get("id") or f"call_{idx}" try: result = tool_registry.execute(fn_name, **args) except Exception as exc: result = {"error": str(exc)} current.append({ "role": "tool", "content": json.dumps(result, ensure_ascii=False), "tool_call_id": call_id, }) raise TimeoutError("tool call loop exceeds MAX_ROUNDS")

这段代码有几个关键点:

  • tool_choice: "auto"表示允许模型自行判断是否需要调用工具。
  • 工具调用循环必须设置最大轮数,防止模型反复调用工具形成死循环。
  • 工具执行错误不能直接把异常抛出,而是作为tool消息返回给模型,让模型理解失败原因。
  • 消息列表必须保持连贯,assistanttool_callstool消息要成对出现,否则接口可能报错。

3.5 FastAPI 接口:把模型能力暴露成 HTTP 服务

app.py提供三个接口:健康检查、工具列表、对话接口。

# app.py from typing import Any, Dict, List, Optional from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import demo_tools # noqa import tool_registry from ai_proxy import chat_once app = FastAPI(title="WebMCP Agent Gateway") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) class ChatBody(BaseModel): messages: List[Dict[str, Any]] model: Optional[str] = "qwen2.5:7b" @app.get("/health") def health(): return {"status": "ok"} @app.get("/tools") def list_tools(): return {"tools": tool_registry.get_schemas()} @app.post("/v1/chat") def chat(body: ChatBody): return chat_once(body.model, body.messages)

启动服务:

uvicorn app:app --reload --port 8000

启动后访问http://localhost:8000/docs可以看到 Swagger 文档,方便调试。

3.6 一次完整调用的预期输出

用 curl 发起请求:

curl -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "system", "content": "你是业务助理,需要使用工具回答用户问题。"}, {"role": "user", "content": "500 个订单,单价 89 元,总收入是多少?"} ] }'

预期返回结构类似:

{ "reply": "500 个订单,单价 89 元,总收入是 44500.0 元。" }

如果没有得到这个结果,而是模型直接输出一段文本,请检查当前模型是否支持工具调用,或者是否没有把tools参数传给 Ollama。

4. 参数配置与协议细节:为什么工具描述会影响模型行为

4.1 工具 schema 的关键字段

工具描述不是给机器看的,而是给模型看的。模型通过description判断什么时候调用哪个工具,通过parameters了解参数格式。

字段作用编写建议
name工具唯一名称使用小写字母和下划线,避免特殊符号
description说明工具功能和使用条件写清楚输入、输出、单位、边界条件
properties参数定义每个字段都写description,说明类型和含义
required必填参数只把确实必要的参数放进去,减少模型误判

一个常见问题是把 description 写成“计算收入”。模型看到后,不能确定自己该传哪些参数,也不知道返回什么单位。更推荐写成“根据订单数量和单个订单的单价计算总收入,返回单位是元”。

4.2 模型选择与 tool calling 能力

不是所有本地模型都支持工具调用。如果你发现模型始终不返回tool_calls,第一步不是调 WebMCP 代码,而是确认模型本身是否具备该能力。

可以到模型发布页查看是否支持 function calling 或 tool calling。实际使用中,也可以换用支持 OpenAI 兼容工具调用的模型。若模型不支持结构化工具调用,只能退化为“让模型输出 JSON 文本”的兼容方案,但那样稳定性会明显下降。

4.3 temperature、max_tokens 等参数的影响

工具调用链路里的关键参数:

参数含义推荐设置错误设置的影响
temperature采样随机性0 到 0.3太高时模型可能编造工具名或参数
max_tokens单次生成最大 token 数1024 以上太小可能截断tool_calls
tool_choice是否强制调用工具auto强制调用会降低灵活性
timeoutHTTP 请求超时30 到 120 秒太短会让本地模型来不及返回

在编排类任务中,temperature建议调低。因为工具参数是结构化数据,随机性太强容易导致模型“自由发挥”,把不存在的参数名传给工具。

4.4 多轮消息结构

工具调用协议要求消息列表严格区分角色:

system: 系统提示词 user: 用户问题 assistant: 模型返回的 tool_calls tool: 工具执行结果 assistant: 模型基于工具结果生成的最终回答

这里最常犯的错误是:收到tool_calls后,把工具执行结果直接追加到messages,但没有先追加原来的assistant消息。很多兼容接口会因为这个原因拒绝请求。在ai_proxy.py的循环里,就是先把message追加进去,再追加工具消息,顺序不能反。

5. 运行验证与效果分析

5.1 启动服务并确认三个接口

启动uvicorn后,先确认基础接口可用:

curl http://localhost:8000/health

返回:

{"status": "ok"}

再查看工具清单:

curl http://localhost:8000/tools

返回结果里应该包含calculate_revenueget_server_time。如果这里没有工具,说明装饰器注册没有生效,需要检查导入顺序。

5.2 验证 AI 代理是否主动调用工具

发送一个需要计算的问题:

curl -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "system", "content": "你是业务助理,需要使用工具回答用户问题。"}, {"role": "user", "content": "帮我计算 20 个订单,单价 15.5 元,总收入是多少?"} ] }'

正常链路里,模型会先返回一次tool_calls,WebMCP 执行工具后再次调用模型,最终返回“310.0 元”这类结果。为了观察这个过程,可以临时在ai_proxy.py里打印消息列表,看中间是否出现了tool_calls

5.3 验证工具调用链路的检查顺序

如果链路不按预期工作,按以下顺序检查:

  1. Ollama 是否正常启动:curl http://localhost:11434/v1/models
  2. /tools是否返回完整 schema。
  3. 请求里是否带上了tools字段。
  4. 模型响应里是否有tool_calls
  5. 工具函数是否抛异常。
  6. 最终回答是否包含“总收入”。

5.4 失败分支:工具调用异常时的表现

工具函数calculate_revenue如果收到负数参数,会主动抛出ValueError。WebMCP 会把错误转成 JSON 返回给模型。此时模型应该能基于错误信息修正回答,而不是把异常直接抛到前端。

这也是一种安全设计:工具自身要做参数校验,WebMCP 只负责把结果回传模型,不替工具做数据合法性兜底。

6. 常见问题排查:AI 代理“不听话”怎么办

6.1 模型不支持工具调用

现象:WebMCP 代码没问题,但模型返回的全部是普通文本,没有tool_calls

原因:当前模型没有工具调用能力,或 Ollama 版本不支持该模型的工具调用。

检查方式:

ollama list

查看模型名和版本,再到模型文档确认是否支持 tool calling。

解决:换用支持工具调用的模型,或者升级 Ollama 后重新拉取模型。

6.2 工具描述与模型理解不匹配

现象:模型调用了工具,但传的参数语义不对,例如把单价和数量传反。

原因:description写得太模糊,模型无法区分参数含义。

检查方式:查看/tools返回的 schema,判断描述是否足够具体。

解决:在参数描述里补充单位、边界和示例。例如“units 是订单数量,必须为非负整数”“unit_price 是单个订单的单价,单位为元”。

6.3 上下文太长被截断

现象:多轮对话后,模型突然不再调用工具,或回答内容变短。

原因:本地模型上下文窗口有限,历史消息过长后被截断。

检查方式:查看请求中的messages是否包含过多历史内容,观察模型输出是否突然中断。

解决:只保留最近几轮消息,必要时用摘要替换旧对话,或者切换上下文窗口更大的模型。

6.4 本地模型推理慢导致超时

现象:请求偶尔卡住,最终返回 HTTP 500 或请求超时。

原因:本地模型推理耗时过长,requests.posttimeout设置太短。

检查方式:直接在终端用 curl 请求 Ollama,观察第一次返回时间。

解决:把timeout调大到 120 秒,并在生产环境使用异步任务或队列,不让请求线程一直等待。

6.5 工具参数缺失或类型错误

现象:工具执行时报TypeErrormissing required positional argument

原因:模型返回的arguments是空字符串或缺少必填字段。

检查方式:打印raw_args和解析后的args,看模型生成了什么。

解决:在ai_proxy.py中做参数容错,捕获异常后把错误信息作为tool消息返回给模型,让模型重新生成参数。

6.6 工具副作用没有审计日志

现象:工具被调用后,你无法确认它执行过几次、传了什么参数。

原因:没有在工具执行入口增加日志。

检查方式:查看 WebMCP 进程日志,是否记录了工具名和参数。

解决:每次调用tool_registry.execute时,统一记录工具名、参数、执行结果和耗时。这既是排查手段,也是生产审计的基本要求。

7. 从跑通到生产:最佳实践与检查清单

7.1 学习环境与生产环境的配置对比

学习和生产环境的差异不是代码量,而是工程保障。

环节学习环境生产环境
模型部署本机 Ollama独立容器或模型服务
鉴权API Key、OAuth、签名
安全localhostHTTPS、网关白名单
日志print结构化日志 + 日志采集
异常直接抛错统一错误码 + 重试策略
监控请求量、延迟、工具失败率
回滚重启代码版本控制 + 模型版本快照

7.2 控制工具权限和副作用

不要把“读工具”和“写工具”混在一起。模型可以自由调用查询类工具,但写入、删除、审批类工具要加人工确认。

推荐做法:

  • 工具注册时增加类别字段,区分readonlywrite
  • WebMCP 网关对写操作校验二次确认令牌。
  • 所有工具调用写入审计日志。
  • 不要在工具函数里硬编码数据库连接串和密钥。

7.3 请求级超时、速率限制和并发控制

本地模型一次请求可能耗时几十秒,直接用同步接口处理容易占满工作线程。

生产设计可以考虑:

客户端请求 -> 任务队列 -> AI 代理执行 -> 结果存储 -> 客户端轮询或回调

同时设置单用户并发限制,避免有人连续发大量请求把本地模型打挂。当前示例是同步处理,能帮助理解原理,但上线前必须加队列和限流。

7.4 模型服务与 WebMCP 分层部署

如果想用容器统一管理,可以按这个思路编写docker-compose.yml,实际版本需要根据你的基础镜像调整:

version: "3" services: webmcp: build: . ports: - "8000:8000" environment: - OLLAMA_BASE=http://ollama:11434/v1 - DEFAULT_MODEL=qwen2.5:7b depends_on: - ollama ollama: image: ollama/ollama:latest volumes: - ollama_data:/root/.ollama ports: - "11434:11434" volumes: ollama_data:

容器部署时,需要把ai_proxy.py中的OLLAMA_BASE改成环境变量,不要写死localhost

7.5 发布前检查清单

上线前至少完成以下检查:

  • 工具清单是否只包含必要工具。
  • 每个工具是否都有参数校验和最大执行耗时。
  • 模型是否支持工具调用。
  • 写操作的二次确认机制是否生效。
  • 日志是否覆盖工具名、参数、执行结果、异常。
  • 请求超时、并发限制、速率限制是否已配置。
  • 模型服务是否做了资源限制和监控。
  • 是否已准备回滚方案,例如切回上一版本模型。
  • HTTPS、鉴权、密钥管理是否到位。
  • 是否对提示词注入做了基本防护,提醒模型只使用可信工具字段。

8. 扩展方向:从 AI 代理助手到业务自动化

当前示例只包含两个工具,但它们已经展现了 WebMCP 的核心能力:模型选择工具、网关执行工具、结果回传模型。沿这个方向扩展,可以接入数据库查询、订单接口、文档解析、消息推送等能力。

如果要走向更完整的业务自动化,建议按这四步推进:

  1. 先补充生产环境要求:队列、日志、鉴权、限流。
  2. 再把常见工具封装成标准函数,例如查询订单、计算金额、生成摘要。
  3. 给写操作增加人工审批环节,让模型只生成建议,不直接执行关键变更。
  4. 最后用可视化看板监控每一次工具调用,形成效果评估和问题回溯能力。

所谓“让 AI 代理为你赚钱”,本质是让代理可靠地完成可重复的增值任务,比如报价计算、订单汇总、内容生成、定时巡检。只有工具可控、结果可审计、异常可回溯,自动化才能真正成为业务资产。本文的最小示例是这一切的起点,接下来你可以在自己熟悉的业务系统里,逐步把更多工具交到 WebMCP 网关中。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询