☰
隔离内网部署AI Agent实战:MCP协议与Skills编排落地指南
2026/10/2 5:47:37 网站建设 项目流程

1. 项目缘起:为什么要在隔离内网里折腾 AI Agent

先交代一下背景。我在一家做企业级软件的公司负责内部工具链建设,去年下半年开始,团队想把 AI Agent 引入到日常研发流程里——代码审查、日志分析、工单自动分类、内部知识库问答,这些场景都挺适合让 Agent 来分担。但问题来了:我们的开发环境是完全隔离的内网,没有外网出口,不能访问任何公有云的大模型 API,也不能随便装外部依赖。

这就意味着,网上那些“五分钟搭一个 Agent”的教程基本全部作废。你不能pip install一个需要联网拉模型的包,不能调用外部推理服务,甚至连 MCP 协议里默认走远程连接的 Server 都得重新设计。所有东西必须在内网自给自足。

我前后折腾了大概三个月,踩了无数坑,最终跑通了一套可用的方案。这篇文章就是把整个过程拆开讲清楚:隔离内网下 AI Agent 工程到底难在哪、怎么选型、怎么落地、怎么排障。适合两类人看——一类是同样在内网环境里做 AI 落地的工程师,另一类是想理解 Agent 工程化到底涉及哪些环节的开发者。不管你现在用的是哪种框架,这里面的思路都能借鉴。

需要先说明一点:本文涉及的模型部署、MCP 协议实现、Skills 编排等内容,都是基于内网离线环境的通用工程实践,不涉及任何特定网络接入手段。所有方案的前提是——你手里已经有可用的内网算力和模型权重。

2. 隔离内网带来的核心约束与整体设计思路

2.1 先搞清楚“隔离”到底隔离了什么

很多人一听到“内网隔离”就笼统地觉得“啥都干不了”,其实要拆开看。隔离环境通常限制的是这几层:

  • 网络层:没有公网出口,DNS 不解析外部域名,HTTP/HTTPS 出站被防火墙拦截。
  • 依赖层:不能从外部包仓库拉取依赖,npm、pip、maven 这些都得走内网镜像。
  • 模型层:不能调用外部推理 API,模型权重必须提前导入。
  • 工具层:Agent 需要调用的外部工具(浏览器、数据库客户端、API 网关)都得在内网有对应部署。

这四层里,模型层和工具层是最容易被低估的。很多人以为只要把模型部署到内网就完事了,结果发现 Agent 要调用的工具全在外面,一样跑不起来。

我一开始就犯了这个错。模型用内网的推理服务部署好了,MCP Server 也写好了,结果 Agent 要执行一个“查内部工单系统”的动作,发现工单系统的 API 需要走一个外部网关——直接卡死。后来我们把工单系统的查询接口在内网做了一层代理,才解决。

2.2 整体架构:三层分离

最终我们采用的架构是三层分离:

层级职责部署位置
推理层提供 LLM 推理能力内网 GPU 集群
编排层Agent 逻辑、Skills 调度、MCP 通信内网应用服务器
工具层具体执行单元(代码执行、文件操作、API 调用)内网各业务系统

这三层之间全部走内网通信,编排层是核心。它负责把用户的自然语言请求拆解成一系列工具调用,然后通过 MCP 协议分发给工具层执行,最后把结果汇总返回。

为什么这么分?因为隔离环境下最怕的就是耦合。如果 Agent 逻辑和工具执行混在一起,一旦某个工具不可用,整个 Agent 就挂了。分层之后,工具层某个服务挂了,编排层可以降级处理,至少保证核心流程不断。

2.3 为什么选 MCP 而不是自己造协议

这里要解释一下 MCP(Model Context Protocol)。简单说,它是一套让 AI 模型和外部工具之间标准化通信的协议。你可以把它理解成“AI 世界的 USB 接口”——不管什么工具,只要实现了 MCP Server,Agent 就能通过统一的方式调用它。

在内网环境里,自己造一套协议也不是不行,但有几个问题:

  • 维护成本高:每个工具都要单独适配,工具一多就失控。
  • 复用性差:今天给 A 项目写的工具调用逻辑,B 项目用不了。
  • 调试困难:没有标准协议,出问题了很难定位是 Agent 的问题还是工具的问题。

MCP 的好处是,它把“工具描述”和“工具调用”标准化了。Agent 只需要知道有哪些工具可用、每个工具接受什么参数,剩下的通信细节由 MCP 协议处理。在内网里,我们把 MCP Server 全部部署在内网服务器上,走内网 WebSocket 或 stdio 通信,完全不依赖外部网络。

注意:MCP 本身是协议标准,不绑定任何特定网络环境。内网部署时,关键是确保 MCP Server 的传输层走内网可达的地址,不要硬编码外部端点。

3. 核心细节解析:模型、MCP、Skills 三件套怎么落地

3.1 内网模型部署:不是跑起来就行

内网部署模型,很多人以为把权重下载下来、起个推理服务就完事了。实际上有几个关键决策点:

第一,模型选型。隔离环境下你没有试错成本,不能今天用 A 模型明天换 B 模型。选型时要考虑:

  • 模型大小和显存匹配:7B 模型至少需要 16GB 显存(FP16),量化后可以降到 8GB 左右。
  • 推理框架兼容性:vLLM、TGI、Ollama 这些框架在内网的安装难度不同,要提前确认依赖是否齐全。
  • 工具调用能力:不是所有模型都擅长 Function Calling,选型时要专门测试这一点。

我们最后选的是一个 14B 级别的模型,做了 4-bit 量化,跑在两张 A 系列卡上。实测下来,工具调用的准确率比 7B 模型高出一大截,尤其是在多步推理场景下。

第二,推理服务的稳定性。内网环境没有云厂商的自动扩缩容,服务挂了就是挂了。我们的做法是:

  • 推理服务做双实例部署,前面挂一个内网负载均衡。
  • 加健康检查接口,每 30 秒探测一次。
  • 设置请求超时和重试机制,避免单个请求卡死整个队列。

第三,Token 限制和上下文管理。内网模型的上下文窗口通常比云端小,我们用的是 8K 上下文。这意味着 Agent 的对话历史不能无限增长,需要做截断或摘要。我们的策略是:保留最近 5 轮对话原文,更早的对话做摘要压缩。

3.2 MCP Server 的内网适配

MCP 协议本身是标准化的,但在内网部署时,有几个地方需要特别注意:

传输层选择。MCP 支持两种传输方式:stdio(标准输入输出)和 WebSocket。内网环境下:

  • stdio 适合本地工具,比如文件操作、代码执行,直接在同一台机器上起进程。
  • WebSocket 适合远程工具,比如数据库查询、API 调用,需要跨机器通信。

我们大部分工具用的是 stdio,因为部署简单、延迟低。只有少数需要跨机器调用的工具用了 WebSocket,走内网地址。

工具描述的设计。MCP Server 需要向 Agent 描述自己提供哪些工具、每个工具的参数是什么。这个描述直接决定了 Agent 能不能正确调用工具。我们的经验是:

  • 工具名称要语义清晰,比如query_ticket_by_id比get_data好得多。
  • 参数描述要包含类型、是否必填、示例值。
  • 每个工具最好附带一个使用场景说明,帮助模型理解什么时候该调用它。

下面是一个我们实际使用的 MCP 工具描述示例(JSON 格式):

{ "name": "query_internal_ticket", "description": "根据工单ID查询内部工单系统的详细信息,包括状态、处理人、创建时间", "parameters": { "type": "object", "properties": { "ticket_id": { "type": "string", "description": "工单ID,格式为 TK-开头加8位数字,例如 TK-20240101" } }, "required": ["ticket_id"] } }

错误处理。内网工具调用失败是常态——服务可能没启动、网络可能抖动、参数可能不对。MCP Server 必须把错误信息结构化返回,而不是直接抛异常。我们统一了错误返回格式:

{ "success": false, "error_code": "TOOL_TIMEOUT", "error_message": "工单系统查询超时,请稍后重试", "retryable": true }

这样 Agent 可以根据retryable字段决定是否重试,而不是直接崩溃。

3.3 Skills 编排:让 Agent 知道“先干什么再干什么”

Skills 这个概念在不同框架里叫法不一样,有的叫“工作流”,有的叫“任务链”。本质上就是把多个工具调用按逻辑顺序编排起来,完成一个复杂任务。

举个例子:用户说“帮我查一下上周所有未处理的工单,按优先级排序”。这个任务需要:

  1. 调用工单查询工具,获取上周所有工单。
  2. 过滤出状态为“未处理”的工单。
  3. 按优先级字段排序。
  4. 格式化输出。

如果让模型自己一步步推理,很容易漏步骤或者顺序搞错。Skills 的作用就是把这些步骤固化下来,模型只需要判断“用户意图匹配哪个 Skill”,然后按预定义流程执行。

我们的 Skills 定义用的是 YAML 格式,大致长这样:

name: query_unhandled_tickets description: 查询指定时间范围内未处理的工单并按优先级排序 trigger: 用户提到"未处理工单"、"待处理工单"等关键词 steps: - tool: query_internal_ticket params: date_range: "{{user.date_range}}" status: "unhandled" output: ticket_list - tool: sort_by_priority params: list: "{{ticket_list}}" output: sorted_list - tool: format_output params: data: "{{sorted_list}}" format: "table"

这里的关键是{{}}模板变量,它把上一步的输出传递给下一步。这种设计让 Skill 变得可组合、可复用。

实操心得:Skills 不要设计得太复杂。我们一开始搞了一个 15 步的 Skill,结果调试了整整两天。后来拆成三个小 Skill,每个不超过 5 步,维护成本直线下降。

4. 实操过程:从零搭建一个内网 Agent 的完整记录

4.1 环境准备清单

在开始之前,你需要确认内网环境里已经具备以下条件:

资源类型具体要求检查方式
GPU 算力至少 1 张 16GB 显存以上的卡nvidia-smi
模型权重已下载到内网存储检查文件是否存在
推理框架vLLM 或同类框架已安装尝试启动服务
Python 环境3.10 以上,依赖已离线安装pip list检查
内网通信各服务之间网络可达curl或telnet测试

我们当时卡在 Python 依赖上最久。内网 pip 源里缺了好几个包,最后是从外部拷贝 whl 文件进去手动安装的。建议提前把所有依赖列出来,一次性解决。

4.2 第一步:启动内网推理服务

我们用 vLLM 部署模型,启动命令大致如下:

python -m vllm.entrypoints.openai.api_server \ --model /path/to/model/weights \ --served-model-name internal-agent-model \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --dtype float16

几个参数解释一下:

  • --max-model-len 8192:限制上下文长度,避免显存溢出。
  • --gpu-memory-utilization 0.85:留 15% 显存给其他进程,防止 OOM。
  • --dtype float16:如果显存不够,可以改成bfloat16或做量化。

启动后,用curl测试一下:

curl http://localhost:8000/v1/models

返回模型列表就说明服务正常。

4.3 第二步:编写第一个 MCP Server

我们以“查询内部知识库”为例,写一个最简单的 MCP Server。用的是 Python 的mcp库(需要提前在内网安装好):

from mcp.server import Server from mcp.types import Tool, TextContent import json app = Server("knowledge-base-server") @app.list_tools() async def list_tools(): return [ Tool( name="search_knowledge", description="在内网知识库中搜索相关文档", inputSchema={ "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"}, "limit": {"type": "integer", "description": "返回结果数量", "default": 5} }, "required": ["query"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "search_knowledge": query = arguments["query"] limit = arguments.get("limit", 5) # 这里调用内网知识库的搜索接口 results = internal_search(query, limit) return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False))]

这个 Server 启动后,Agent 就能通过 MCP 协议调用search_knowledge工具了。

4.4 第三步:Agent 编排逻辑

Agent 的核心逻辑是:接收用户输入 → 判断意图 → 选择 Skill 或直接调用工具 → 执行 → 返回结果。

我们用 Python 写了一个简单的编排器:

class InternalAgent: def __init__(self, llm_client, mcp_clients, skills): self.llm = llm_client self.mcp = mcp_clients self.skills = skills async def run(self, user_input: str): # 第一步:意图识别 intent = await self.llm.classify(user_input, self.skills.keys()) # 第二步:如果匹配到 Skill,按 Skill 流程执行 if intent in self.skills: return await self.execute_skill(self.skills[intent], user_input) # 第三步:没有匹配 Skill,走通用工具调用 return await self.general_tool_call(user_input) async def execute_skill(self, skill, user_input): context = {"user_input": user_input} for step in skill["steps"]: tool_name = step["tool"] params = self.resolve_params(step["params"], context) result = await self.mcp.call(tool_name, params) context[step["output"]] = result return context[skill["steps"][-1]["output"]]

这段代码的关键是resolve_params,它负责把模板变量替换成实际值。比如{{user.date_range}}会被替换成用户输入里提取的时间范围。

4.5 第四步:联调与验证

联调阶段最容易出问题。我们的经验是从最简单的场景开始,逐步增加复杂度:

  1. 先测试单个工具调用:直接让 Agent 调用search_knowledge,看能不能返回结果。
  2. 再测试 Skill 编排:用一个两步的 Skill,确认参数传递正确。
  3. 最后测试多 Skill 切换:给 Agent 多个 Skill,看意图识别准不准。

每一步都要记录日志,包括:用户输入、意图识别结果、工具调用参数、工具返回结果、最终输出。这样出问题了才能快速定位。

5. 常见问题与排查技巧实录

5.1 工具调用失败排查表

现象可能原因排查方法解决方案
Agent 不调用工具工具描述不清晰检查 MCP 工具描述补充使用场景说明
调用参数错误参数类型不匹配打印实际调用参数在描述中明确类型和示例
工具返回超时内网服务响应慢检查工具服务日志增加超时时间或重试
结果解析失败返回格式不标准检查返回 JSON 结构统一返回格式
Skill 执行中断某一步骤失败查看步骤执行日志增加错误处理和降级

5.2 几个我踩过的坑

坑一:模型不认识工具名称。我们一开始用了一些缩写工具名,比如qry_tkt,结果模型完全不知道这是干什么的。后来改成query_ticket,调用准确率立刻上来了。工具名称一定要用完整的英文单词,不要缩写。

坑二:MCP Server 启动顺序问题。Agent 启动时如果 MCP Server 还没起来,会直接报错退出。我们的解决方案是:Agent 启动时先做一次工具发现,如果某个 Server 不可用,记录警告但不退出,等实际调用时再重试。

坑三:上下文溢出。有一次用户连续问了十几个问题,对话历史把 8K 上下文占满了,模型开始胡言乱语。后来加了上下文管理逻辑:超过 6K token 就自动截断最早的对话。

坑四:并发调用冲突。多个用户同时调用同一个工具时,如果工具内部有状态,会出现数据混乱。我们的做法是:所有工具设计成无状态的,每次调用传入完整参数,不依赖上一次调用的结果。

独家技巧:在内网环境里,建议给每个 MCP Server 加一个/health接口,Agent 定期探测。这样可以在工具不可用时提前降级,而不是等到用户请求失败了才发现。

5.3 性能优化建议

内网环境的算力通常有限,性能优化很重要。我们做了这几件事:

  • 批量推理:把多个小请求合并成一个批次,提高 GPU 利用率。
  • 缓存工具结果:对于查询类工具,相同参数的请求在 5 分钟内直接返回缓存。
  • 异步调用:多个不相关的工具调用并行执行,减少总耗时。
  • 模型量化:从 FP16 降到 4-bit,显存占用减少 60%,推理速度提升约 30%。

实测下来,优化前一个复杂 Skill 平均耗时 12 秒,优化后降到 4 秒左右。

6. 内网 Agent 工程的扩展方向

跑通基础流程之后,我们陆续做了一些扩展,这里简单提几个方向,给有类似需求的同学参考。

第一个方向是多 Agent 协作。单个 Agent 处理复杂任务时容易顾此失彼,我们尝试了“规划 Agent + 执行 Agent”的模式:规划 Agent 负责拆解任务,执行 Agent 负责具体工具调用。两个 Agent 通过内网消息队列通信。实测下来,复杂任务的完成率提升了大概 20%。

第二个方向是 Skills 的动态加载。一开始 Skills 是硬编码在配置文件里的,每次新增都要重启服务。后来改成了从内网配置中心动态拉取,支持热更新。这样业务方可以自己定义 Skill,不需要我们介入。

第三个方向是调用链追踪。内网环境出问题了很难排查,我们加了一套轻量级的追踪机制:每次 Agent 调用生成一个 trace_id,所有工具调用日志都带上这个 ID。出问题时,通过 trace_id 就能把整个调用链串起来。

第四个方向是权限控制。不同用户能调用的工具应该不一样。我们在 MCP 层加了权限校验,每个工具调用前先检查用户是否有权限。这个在内网环境里尤其重要,因为内网系统往往涉及敏感数据。

这些扩展不是必须的,但如果你打算把 Agent 真正用到生产环境,迟早会遇到这些问题。我的建议是:先把核心流程跑通,再根据实际需求逐步扩展,不要一开始就追求大而全。

最后分享一个我在内网部署时总结的小经验:所有配置都要有默认值,所有外部依赖都要有降级方案。内网环境的不确定性比外网高得多,一个服务挂了可能半天没人发现。把容错做在前面,后面能省很多事。

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

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

立即咨询