☰
隔离内网AI Agent工程实战:MCP协议与Skills机制落地指南
2026/10/6 10:25:03 网站建设 项目流程

1. 为什么要在隔离内网里折腾 AI Agent

先把场景说清楚。所谓“隔离内网”,就是一台或者一批机器,物理上或者逻辑上跟公网断开,没有外网出口,DNS 解析不了外部域名,pip、npm、apt 这些包管理器全部失效,甚至连系统时间同步都得靠内网 NTP。很多做金融、制造、政企交付的同行对这个环境太熟了——代码不能出网,数据不能出网,模型权重也得走审批拷进来。在这种环境里谈 AI Agent,第一反应往往是“这不是自找麻烦吗”。

但需求是真实存在的。我接触过的几个项目,内网里有大量私有文档、内部知识库、业务系统日志,业务方希望有一个能自动检索、自动总结、自动执行部分操作的智能体,而不是每次都人工翻文档。同时,内网里跑着 RuoYi-Vue-Pro 这类后台管理系统,跑着 Spring 生态的服务,也有前端团队在用 Vue 做内部工具。把这些东西串起来,让 Agent 能调用工具、能读文件、能触发流程,这就是“隔离内网下 AI Agent 工程实战”要解决的核心问题。

这篇文章面向的是有一定工程基础、需要在无外网环境下落地 Agent 的开发者。我会把整个工程拆成几块:整体架构怎么设计、模型和运行时怎么搬进内网、MCP 协议和 Skills 机制怎么落地、并发怎么扛、以及一堆只有真在内网里踩过才知道的坑。所有内容都是基于常见工程实践整理的,具体参数你按自己环境调整。

先给一个整体判断:内网 Agent 的难点从来不是“模型会不会用”,而是“依赖怎么进去、工具怎么连、状态怎么存、并发怎么稳”。把这四件事想明白,剩下的就是体力活。

2. 内网 Agent 的整体架构与选型思路

2.1 三种典型部署形态,先选对路子

内网 Agent 的部署形态,我大致归为三类,选错了后面全是返工。

第一种是单机全离线形态。一台配置还行的服务器,模型、Agent 运行时、工具服务、向量库全塞在一起。优点是简单,缺点是资源争抢严重,模型一推理,工具服务就卡。适合 POC 和低并发场景。

第二种是内网服务化形态。模型推理单独一台(或者一组)GPU 机器,Agent 运行时和工具服务跑在应用服务器上,向量库和缓存各自独立。这是我最推荐的生产形态,扩展性好,故障隔离清晰。

第三种是边缘轻量形态。Agent 跑在离数据最近的机器上,只做检索和简单编排,重推理回中心节点。适合数据量大、网络带宽受限的场景。

选型的时候有个很实际的判断标准:你的并发预期是多少。如果 QPS 个位数,单机全离线就够了;如果几十上百,必须服务化。别一上来就追求架构漂亮,内网环境里每多一个组件,就多一份运维负担。

2.2 模型选型:不是越大越好,是越“听话”越好

内网里选模型,跟公网完全不是一个逻辑。公网你可以随时调 API,内网你得把权重搬进来,显存、量化、推理框架全得自己扛。

我的经验是,内网 Agent 优先选指令遵循能力强、支持工具调用格式的中等规模模型。原因很简单:Agent 的核心是“按格式输出工具调用”,不是“写诗”。一个 7B 到 14B 级别、经过工具调用微调的模型,在内网里的实际表现往往比一个没调好的 70B 更稳。

量化方面,如果显存紧张,用 4bit 量化(比如 GPTQ 或 AWQ)通常能接受,但要注意量化后工具调用的格式稳定性会下降,需要多测几轮。我一般会准备两套:一套高精度用于复杂推理,一套量化版用于高并发简单任务,按请求路由。

推理框架上,vLLM 和 SGLang 是内网里比较常见的选择,前者生态成熟,后者在结构化输出和并发调度上有优势。如果团队对 Rust 有偏好,也可以考虑基于 Rust 的推理服务,启动快、内存占用低,但生态相对小一些,看团队维护能力。

2.3 Agent 运行时:自研还是用框架

内网里用框架有个尴尬:很多框架默认要联网拉配置、拉插件、上报遥测。所以选型时第一件事是看它能不能完全离线运行。

我的建议是,核心编排逻辑自己写,别把命脉交给一个联网依赖重的框架。Agent 的本质就是一个循环:接收输入、拼 prompt、调模型、解析工具调用、执行工具、把结果塞回上下文、再调模型。这个循环用几百行代码就能写清楚,可控性远高于套框架。

当然,MCP 协议和 Skills 机制这类标准化能力可以复用。MCP 解决的是“工具怎么标准化暴露给模型”,Skills 解决的是“能力怎么模块化打包”。这两个概念在内网里特别有价值,因为它们把“模型”和“工具”解耦了,工具服务可以独立部署、独立升级。

2.4 一张架构对照表,帮你快速定位

维度单机全离线内网服务化边缘轻量
适用并发个位数 QPS几十到上百 QPS低并发、大数据量
部署复杂度低中高中
故障隔离差好中
扩展性差好中
典型场景POC、演示生产交付数据本地化

这张表不是绝对的,但能帮你快速排除明显不合适的方案。我见过太多项目在 POC 阶段用单机,到了生产直接推倒重来,浪费的时间比一开始就服务化多得多。

3. 离线环境下的依赖搬运与运行时搭建

3.1 依赖搬运的核心原则:一次打包,多次复用

内网最痛的就是装依赖。pip install 一条命令在公网是秒级,在内网就是“找不到包”。我的做法是,在公网机器上把所有依赖完整下载成离线包,然后整体拷进内网。

Python 这边,用pip download把 wheel 全部拉下来:

pip download -r requirements.txt -d ./offline_pkgs --platform manylinux2014_x86_64 --python-version 310 --only-binary=:all:

注意--platform和--python-version必须跟内网目标机器一致,否则下下来的 wheel 装不上。这一步踩坑率极高,很多人忘了指定平台,结果下了一堆源码包,内网编译又缺编译器。

Node 这边,用npm pack或者直接把node_modules打包。前端 Skills 相关的依赖,建议用 pnpm 的离线模式,pnpm fetch配合 store 目录整体拷贝,比 npm 干净。

系统级依赖(apt/yum)更麻烦,建议用apt-get download或者搭建一个内网镜像源。如果内网有物理 DHCP 服务器和交换机,网络配置本身也要提前规划好,别到时候机器都连不上。

3.2 模型权重的搬运与校验

模型权重动辄几个 G 到几十个 G,拷贝过程容易出错。我的习惯是,拷贝前后都做一次 SHA256 校验,并且把校验值写进交付文档。

sha256sum model.safetensors > model.sha256 # 内网侧 sha256sum -c model.sha256

如果模型是分片的,注意所有分片都要校验,别只校验第一个。我遇到过一次,最后一个分片损坏,模型加载时报了个莫名其妙的错,排查了大半天。

另外,模型目录结构要跟推理框架的预期一致。vLLM 对config.json、tokenizer.json这些文件的位置有要求,搬进去之前先在公网跑通一次,确认目录结构没问题再打包。

3.3 运行时环境的固化

内网机器上,我强烈建议用容器化部署。把模型、运行时、依赖全部打进镜像,内网里直接docker load就能跑。这样避免了“这台机器能跑那台跑不了”的经典问题。

镜像构建在公网做,导出成 tar 包:

docker save -o agent_runtime.tar agent-runtime:1.0 # 内网侧 docker load -i agent_runtime.tar

如果内网不允许用 Docker,那就用 conda-pack 把 Python 环境整体打包,解压即用。conda-pack 的好处是连 Python 解释器都打包进去了,内网机器只要有 glibc 就能跑。

提示:打包前把环境里的绝对路径依赖清理一遍,conda-pack 会自动处理大部分,但有些硬编码路径它搞不定,需要手动改。

3.4 内网服务发现的土办法

内网没有 Consul、没有 etcd 的时候,服务发现怎么办?我的土办法是配置文件 + 固定 IP。把所有服务的地址写进一个services.yaml,Agent 启动时加载。简单粗暴,但极其可靠。

services: model: host: 10.0.1.10 port: 8000 vector_db: host: 10.0.1.11 port: 6333 tool_server: host: 10.0.1.12 port: 9000

如果内网 IP 会变,那就上内网 DNS,或者用主机名 + hosts 文件。别在内网里搞太复杂的服务发现,运维成本划不来。

4. MCP 协议与 Skills 机制在内网的落地

4.1 MCP 到底是什么,用一句话说清

MCP(Model Context Protocol)本质上是一套让模型和工具之间用标准格式对话的协议。你可以把它理解成“工具界的 USB 接口”——不管工具是查数据库、读文件还是调 API,只要按 MCP 的格式暴露出来,模型就能用统一的方式调用。

在内网里,MCP 的价值被放大了。因为内网工具五花八门,有 Java 写的、有 Python 写的、有前端直接调的,如果没有统一协议,每接一个工具就要改一次 Agent 代码。有了 MCP,工具服务独立部署,Agent 只认协议不认实现。

MCP 的核心概念就几个:Server(工具提供方)、Client(Agent 侧)、Tool(具体能力)、Resource(可读资源)。Server 把能力注册成 Tool,Client 拿到 Tool 列表后拼进 prompt,模型输出调用请求,Client 转发给 Server 执行,结果回传。

4.2 内网 MCP Server 的部署要点

内网部署 MCP Server,有几个点必须注意。

第一,传输方式选 stdio 还是 SSE。stdio 适合工具和 Agent 在同一台机器,简单但不好扩展。SSE(Server-Sent Events)适合跨机器,但内网里要注意防火墙放行。我一般生产环境用 SSE,POC 用 stdio。

第二,工具描述要写清楚。模型能不能正确调用工具,八成取决于工具描述。描述里要包含:这个工具干什么、参数是什么类型、什么情况下用。别写“查询数据”这种模糊描述,要写“根据用户 ID 查询订单列表,返回订单号、金额、状态”。

第三,超时和重试。内网工具服务可能因为各种原因慢,MCP Client 侧必须设超时,并且对幂等操作做重试。非幂等操作千万别自动重试,会出数据问题。

# MCP Client 调用示例(伪代码) result = mcp_client.call_tool( tool_name="query_order", arguments={"user_id": "12345"}, timeout=10, retry=2 # 仅幂等操作 )

4.3 Skills 机制:把能力模块化打包

Skills 这个概念,可以理解成给 Agent 预置的“技能包”。一个 Skill 通常包含:一段说明(告诉模型这个技能是干嘛的)、一组工具、可能还有示例。模型在需要的时候加载对应 Skill,不需要的时候不占上下文。

内网里用 Skills 有个巨大好处:能力可以按需加载,不用把所有工具都塞进 prompt。上下文窗口是有限的,工具一多,prompt 就爆了。Skills 让 Agent 先选技能,再选工具,两级筛选,效率高很多。

我一般会把 Skills 按业务域划分,比如“文档检索 Skill”“工单处理 Skill”“数据查询 Skill”。每个 Skill 独立目录,独立版本,独立测试。

skills/ doc_search/ skill.md # 技能说明 tools.json # 工具定义 examples/ # 示例 ticket_handle/ skill.md tools.json

skill.md里写清楚这个技能解决什么问题、什么时候用、有什么限制。这份说明会进 prompt,所以要写得像给新人看的操作手册,别写得太技术。

4.4 工具调用的格式稳定性问题

内网模型(尤其是量化后的)在工具调用格式上经常翻车。模型该输出 JSON 的时候输出了一段自然语言,或者 JSON 少了个括号。这个问题没有银弹,只能靠工程手段兜。

我的做法是三层防护:第一层,prompt 里给足格式示例,越具体越好;第二层,解析失败时做一次“格式修复”,用正则把 JSON 抠出来;第三层,修复也失败就返回错误让模型重试,但重试次数限制在 2 次以内,避免死循环。

import json, re def parse_tool_call(text): try: return json.loads(text) except json.JSONDecodeError: # 尝试从文本中抠 JSON match = re.search(r'\{.*\}', text, re.DOTALL) if match: try: return json.loads(match.group()) except json.JSONDecodeError: pass return None

这个函数看着土,但在内网环境里救过我好几次。

5. 并发扛压:内网 Agent 的性能工程

5.1 并发瓶颈到底在哪

很多人一上来就优化模型推理,其实内网 Agent 的瓶颈往往不在模型,而在工具调用和上下文管理。

我做过一次压测,单模型推理节点能扛 50 QPS,但整个 Agent 链路只能扛 8 QPS。排查下来,瓶颈在工具服务的数据库连接池和上下文拼接的字符串操作上。模型反而闲着。

所以优化顺序应该是:先测工具服务,再测上下文管理,最后才是模型。别本末倒置。

5.2 模型推理侧的并发优化

模型侧优化,核心是批处理和KV Cache 复用。vLLM 的 continuous batching 能把多个请求拼成一个 batch 推理,吞吐提升非常明显。SGLang 的 RadixAttention 在共享前缀场景下效果更好,Agent 场景里系统 prompt 通常是共享的,正好吃这个红利。

参数上,max_num_seqs控制并发序列数,gpu_memory_utilization控制显存占用比例。这两个参数要一起调,显存给太少并发上不去,给太多容易 OOM。

python -m vllm.entrypoints.openai.api_server \ --model /models/agent-model \ --max-num-seqs 64 \ --gpu-memory-utilization 0.9 \ --enable-prefix-caching

--enable-prefix-caching这个开关在 Agent 场景里必开,系统 prompt 和工具定义都是重复前缀,缓存命中率高,延迟能降不少。

5.3 工具服务的并发设计

工具服务要按“无状态 + 连接池”来设计。每个工具调用独立,不依赖上一次调用的状态,这样才能水平扩展。

数据库连接池大小要算。假设工具服务有 4 个实例,每个实例连接池 20,数据库最大连接数至少 80,再加余量。别把连接池开太大,数据库扛不住反而更慢。

对于慢工具(比如调外部系统),要加异步 + 超时。用 asyncio 或者线程池,别让一个慢调用阻塞整个 Agent 循环。

import asyncio async def call_tool_with_timeout(tool, args, timeout=10): try: return await asyncio.wait_for(tool.call(args), timeout=timeout) except asyncio.TimeoutError: return {"error": "tool_timeout"}

5.4 上下文管理的性能陷阱

上下文拼接是隐藏的性能杀手。每次循环都要把历史消息、工具结果、系统 prompt 拼成一个长字符串,如果实现得不好,字符串拷贝的开销会非常大。

我的做法是,用列表存消息,只在最后调模型时拼一次,并且用join而不是+=。Python 里字符串是不可变的,+=在循环里是 O(n²) 的复杂度,消息一多就卡。

另外,历史消息要做截断或摘要。上下文窗口有限,不能无限塞。我的策略是保留最近 N 轮完整消息,更早的做摘要压缩。摘要用模型生成,虽然多一次调用,但省下的上下文空间值这个成本。

5.5 一个并发压测的实操记录

我拿一个内网 Agent 做过压测,配置是:单张 A100 40G,模型 14B 4bit 量化,工具服务 4 实例,向量库单节点。

并发数平均延迟P99 延迟吞吐
11.2s1.8s0.8 QPS
82.5s4.1s3.2 QPS
326.8s12s4.7 QPS
6415s28s4.2 QPS

可以看到,32 并发之后吞吐不升反降,说明系统已经饱和。瓶颈在工具服务的数据库连接池。把连接池从 20 调到 50 后,32 并发的吞吐提升到 6.5 QPS。

这个数据说明一个道理:内网 Agent 的并发优化,八成时间花在非模型部分。别一上来就想着换更大的模型。

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

6.1 模型加载失败:先看显存再看格式

内网里模型加载失败,九成是这两个原因:显存不够,或者权重格式不对。

显存不够的表现是 OOM,日志里能看到CUDA out of memory。解决办法是降量化精度、减max_num_seqs、或者换更小的模型。别硬扛,显存是物理限制。

格式不对的表现是加载到一半报错,或者加载成功但推理结果乱码。这时候检查config.json里的model_type和推理框架是否匹配,检查 tokenizer 文件是否完整。我遇到过一次,tokenizer 的vocab.json和merges.txt版本不匹配,模型输出全是乱码,排查了很久。

6.2 工具调用不触发:prompt 和描述的问题

模型不调工具,通常是两个原因:prompt 里没告诉它可以用工具,或者工具描述太模糊。

排查方法很简单:把完整的 prompt 打出来看。如果 prompt 里工具定义是空的,那是 MCP Client 没拉到工具列表;如果工具定义在但模型不调,那是描述问题。

我的经验是,在系统 prompt 里明确写一句“你可以使用以下工具来完成任务,当需要外部信息时优先调用工具”。这句话看着废话,但实测能显著提升工具调用率。

6.3 内网时间不同步导致的诡异问题

内网机器时间不同步,会导致 JWT 过期判断错误、日志时间错乱、缓存失效异常。这个问题极其隐蔽,因为报错信息往往跟时间没关系。

排查方法是,在所有机器上跑date,对比时间差。如果超过几秒,就要配内网 NTP。内网 NTP 服务器可以自己搭,用 chrony 就行。

# 内网 NTP 服务端 chronyd -d # 客户端 chronyc sources

6.4 常见问题速查表

现象可能原因排查方向
模型加载 OOM显存不足降量化、减并发
输出乱码tokenizer 不匹配检查 vocab 文件
工具不触发prompt 或描述问题打印完整 prompt
延迟突然升高工具服务慢查连接池、超时
缓存不生效时间不同步检查 NTP
请求偶发失败连接池耗尽调大连接池

6.5 几个只有踩过才知道的坑

第一个坑:内网 DNS 解析慢。有些内网 DNS 配置不当,解析一个域名要几秒。解决办法是在 hosts 文件里写死,或者用 IP 直连。

第二个坑:文件句柄泄漏。Agent 长时间运行,如果工具调用里打开文件没关,句柄会耗尽。用with语句,别偷懒。

第三个坑:日志把磁盘写满。内网机器磁盘通常不大,Agent 日志又特别多。一定要配日志轮转,按大小或按天切分。

第四个坑:模型输出不稳定。同样的输入,模型有时调工具有时不调。这是模型本身的随机性,可以通过降低 temperature 缓解,但没法完全消除。工程上要做好兜底。

7. 内网 Agent 的工程化收尾

7.1 配置管理:别把参数写死在代码里

内网环境多变,配置一定要外置。模型地址、工具地址、超时时间、并发数,全部放配置文件或者环境变量。这样换环境不用改代码。

我一般用config.yaml+ 环境变量覆盖的方式。环境变量优先级高于配置文件,方便临时调整。

model: base_url: ${MODEL_URL:-http://10.0.1.10:8000} timeout: 30 agent: max_tool_rounds: 5 temperature: 0.1

7.2 可观测性:内网也得有监控

内网没有云监控,但可观测性不能少。我的做法是,Agent 侧暴露 Prometheus 指标,内网搭一个 Prometheus + Grafana。关键指标包括:请求数、延迟分布、工具调用成功率、模型调用失败率。

日志用结构化日志(JSON 格式),方便检索。别用 print,用 logging 库,配好级别和输出。

7.3 版本管理与回滚

内网交付最怕的就是“改坏了回不去”。所以每次变更都要有版本,模型版本、Agent 版本、工具版本分开管理。回滚方案要提前演练,别等出事才想。

我的习惯是,每次交付打一个 tag,附上变更说明和回滚步骤。内网里没有 CI/CD,这些就得靠人工纪律。

7.4 安全边界:内网不等于安全

最后说个容易被忽略的点。内网不等于安全,Agent 能调工具,就意味着它能执行操作。所以工具权限要最小化,危险操作要二次确认,审计日志要留全。

比如删除类操作,Agent 不能直接执行,必须走人工确认流程。这不是技术问题,是工程纪律问题。

我在实际项目里的体会是,内网 Agent 的成败,技术只占三成,剩下七成是工程规范和交付纪律。模型选得好不好、并发调得高不高,都是次要的;能不能稳定跑三个月不出事,才是真本事。踩过的坑多了,就会明白“简单可靠”这四个字在内网环境里的分量。

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

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

立即咨询