1. 为什么要在隔离内网里折腾 AI Agent
先把场景说清楚。所谓“隔离内网”,就是那种物理上跟公网断开、或者只允许极少数白名单流量出入的办公网、研发网、生产网。很多做金融、制造、政企项目的团队,代码仓库、数据库、CI 流水线全在这张网里,开发机连不上外网,npm、pip、docker hub 一律不通。在这种环境下谈 AI Agent 工程化,跟在公网环境里完全是两码事。
我在过去一年里,前后在三个不同的隔离内网环境里落地过 AI Agent 相关的工具链,踩的坑足够写一本小册子。核心矛盾其实就一句话:AI Agent 的生态默认你是联网的,而内网默认你是断网的。模型要下载、依赖要拉取、MCP Server 要连外部服务、Skills 要从市场同步——这些在公网里一行命令搞定的事,到了内网全得手动搬砖。
所以这篇东西不是讲“AI Agent 是什么”,而是讲当你被关在一张没有外网的内网里,怎么把一套能用的 AI Agent 工程体系搭起来。涉及的关键词包括 AI Agent、MCP、Skills、内网、工程实战,我会围绕这几个点,把方案选型、依赖搬运、MCP 协议在内网的适配、Skills 的本地化管理、并发与资源控制这些实打实的问题拆开讲。
适合谁看?三类人:一是在内网环境做研发、被网络限制卡住的工程师;二是想把 AI Agent 引入团队但发现处处碰壁的技术负责人;三是单纯好奇“断网了 AI 还能不能干活”的折腾党。不管你是哪种,只要你的机器连不上外网,这篇里的思路和步骤都能直接抄。
先给一个整体判断:内网 AI Agent 工程的核心不是模型多强,而是依赖治理和协议适配。模型可以提前下好放本地,但 MCP 的连接、Skills 的加载、工具链的调用,这些动态行为才是真正难搞的地方。下面我按“整体设计 → 核心细节 → 实操落地 → 问题排查”的顺序展开。
2. 内网 AI Agent 的整体设计与方案选型
2.1 先想清楚:内网 Agent 到底要解决什么问题
很多人一上来就问“内网能用哪个模型”,这其实是把问题想窄了。模型只是 Agent 的一个组件,真正决定你能不能跑起来的是工具调用链路。一个典型的 AI Agent 工作流是这样的:用户输入 → 模型推理 → 决定调用某个工具 → 工具执行 → 结果回传模型 → 生成最终回答。在内网里,这条链路上每一环都可能断。
我一般会先把需求拆成三层来看。第一层是推理层,也就是模型本身,这个相对好解决,提前把模型权重下好、用本地推理框架跑起来就行。第二层是工具层,包括文件操作、代码执行、数据库查询这些,内网里反而比公网更安全,因为不用担心数据外泄。第三层是协议层,也就是 MCP 这类把模型和工具连起来的协议,这一层是内网环境里最容易出问题的地方,因为 MCP 的设计初衷是连接外部服务。
想清楚这三层,方案选型就有方向了。推理层选本地部署,工具层选内网自建,协议层要么改造 MCP 让它走本地,要么干脆用更轻量的自定义协议替代。我见过太多团队一上来就照搬公网那套 MCP 全家桶,结果卡在连接外部 Server 这一步,白白浪费两周。
2.2 模型选型:内网里能跑什么,该跑什么
内网部署模型,第一个约束是硬件。你得先摸清楚手上有多少张卡、显存多大。我一般按显存分档:单卡 24G 以下,老老实实跑 7B 到 14B 的量化模型;单卡 48G 到 80G,可以上 32B 级别的模型;多卡集群才有资格谈 70B 以上。别信那些“消费级显卡跑 70B”的教程,量化到 4bit 之后效果掉得厉害,Agent 场景对指令遵循要求高,掉一点就全乱套。
第二个约束是模型的工具调用能力。不是所有模型都擅长 function calling。我实测下来,Qwen 系列、DeepSeek 系列在工具调用上的表现比较稳,指令遵循也扎实。选模型的时候一定要看它有没有专门的 tool use 微调版本,这个比参数量重要得多。一个 14B 的工具调用专用模型,在 Agent 场景里往往比一个 70B 的通用模型更好用。
第三个约束是推理框架。内网里我推荐用 vLLM 或者 SGLang,这两个对 OpenAI 兼容接口支持好,Agent 框架接起来省事。如果显存实在紧张,llama.cpp 也能凑合,但并发能力差,只适合个人用。这里有个细节:推理框架的版本要和模型格式匹配,我踩过一次坑,模型是 GPTQ 量化的,结果装了个只支持 AWQ 的框架版本,折腾半天才发现。
提示:内网部署模型前,务必在公网环境里把模型跑通、把接口调通,再整体搬到内网。内网里调试模型加载问题,成本是公网的十倍。
2.3 MCP 协议在内网的适配思路
MCP 是这两年 Agent 领域最热的东西,全称是 Model Context Protocol,本质是一套让模型和外部工具、数据源通信的标准协议。它的架构是 Client-Server 模式,Agent 作为 Client,工具作为 Server,通过标准化的消息格式交互。公网环境下,你可以直接连各种现成的 MCP Server,比如连 Figma、连蓝湖、连各种 SaaS 服务。
但内网里这套玩不转,因为大部分 MCP Server 都是要连外网的。我的适配思路有三条。第一条是本地化 MCP Server,把需要的 MCP Server 源码拉下来,改造成连内网服务的版本,比如把连外部数据库的改成连内网数据库。第二条是自建 MCP 网关,在内网里起一个统一的 MCP 服务端,把内网的各种工具封装成 MCP 接口,Agent 只连这一个网关。第三条是降级方案,如果 MCP 实在搞不定,就用最原始的函数调用,把工具定义直接写进 Agent 的 prompt 里。
我个人最推荐第二条,自建 MCP 网关。原因很简单:内网的工具是有限的、可控的,与其让每个 Agent 去连一堆 Server,不如集中管理。网关这一层还能做权限控制、日志审计、限流,这些在内网环境里都是刚需。具体怎么搭,后面实操部分细讲。
2.4 Skills 的本地化管理策略
Skills 这个概念最近很火,Claude 的 Agent Skills、Codex 的 Skills,本质都是把一组相关的工具调用、提示词、知识打包成一个可复用的模块。公网环境下,你可以从官方市场直接装,一行命令的事。内网里没有市场,只能手动管理。
我的做法是建一个内网 Skills 仓库,用 Git 管理。每个 Skill 是一个独立目录,里面放三样东西:一份skill.yaml描述元信息(名称、版本、依赖、触发条件),一份prompt.md放提示词模板,一份tools/目录放这个 Skill 用到的工具定义或脚本。这样做的最大好处是版本可控、可审计、可回滚。内网环境最怕的就是“这个 Skill 是谁改的、改了什么”,用 Git 管理一目了然。
Skills 的加载策略也要设计。我一般分两级:常驻 Skills和按需 Skills。常驻的比如文件操作、代码执行,Agent 启动就加载;按需的比如某个特定业务查询,等用户提到相关关键词再动态加载。这样能控制上下文长度,避免一上来就把所有 Skill 的描述塞进 prompt,把模型的注意力稀释掉。
3. 核心细节解析与实操要点
3.1 依赖搬运:内网工程的第一个拦路虎
内网开发最烦的就是装依赖。pip、npm、maven、docker,全都要手动搬。我总结了一套“三件套”搬运法,实测下来最稳。
第一件套是离线包制作。在公网机器上,用pip download把依赖下成 whl 包,用npm pack把 node 包下成 tgz,用docker save把镜像存成 tar。这里有个关键点:要下全平台的包。比如 pip 依赖,如果内网机器是 ARM 架构,你在 x86 上下的包可能装不上,得加--platform参数指定。我踩过这个坑,搬了一堆包过去发现架构不对,全部重来。
第二件套是私有源搭建。内网里起一个 pip 源(用 devpi 或 pypiserver)、一个 npm 源(用 verdaccio)、一个 docker registry(用 registry:2)。把离线包传上去,内网机器直接从这个源装。这样比一个个手动装包高效得多,而且能解决依赖嵌套的问题——A 依赖 B,B 依赖 C,手动装能把你逼疯。
第三件套是依赖清单固化。用pip freeze、npm ls把最终依赖树导出成文件,跟代码一起进 Git。内网环境里,依赖版本漂移是灾难,今天能跑的代码明天可能就跑不起来。固化清单之后,任何人拿到代码都能复现出一样的环境。
注意:搬运依赖时一定要记录系统级依赖,比如某些 Python 包需要
libpq-dev、gcc这些系统库。光搬 pip 包不够,系统库缺失照样装不上。我一般会写一个setup.sh,把 apt 或 yum 的安装命令也列进去。
3.2 MCP Server 的内网改造实操
前面说了自建 MCP 网关的思路,这里讲具体怎么改。假设你有一个现成的 MCP Server,它原本是连外部 API 的,现在要改成连内网服务。
第一步是读源码找连接点。MCP Server 一般会有一个配置文件或者环境变量,指定它连的目标地址。找到这个点,把它改成内网地址。比如原本连https://api.example.com,改成http://internal-service:8080。
第二步是处理认证。公网服务的认证一般是 API Key 或者 OAuth,内网服务可能是简单的 Token 或者干脆没有认证。这里要注意,不要把公网的认证逻辑硬套到内网,内网的安全边界不一样,过度设计反而增加复杂度。
第三步是测试协议兼容性。MCP 协议本身是标准化的,但不同 Server 的实现可能有差异。改完之后,用 MCP 官方的测试工具或者自己写个 Client 脚本,把每个工具都调一遍,确认返回格式正确。我一般会写一个test_mcp.py,把所有工具列出来逐个测,跑通了再接入 Agent。
第四步是加日志和监控。内网环境出问题不好排查,MCP Server 这一层一定要打详细日志,记录每次调用的入参、出参、耗时。这些日志在排查 Agent 行为异常时是救命稻草。
3.3 Skills 的开发规范与测试方法
Skills 开发最容易犯的错是写得太随意。公网环境下,Skill 写错了顶多不生效,内网里可能引发连锁问题。我定了一套规范,团队里强制执行。
规范一:每个 Skill 必须有明确的触发条件。不能写“当用户需要时触发”,要写“当用户输入包含‘查询订单’且提供了订单号时触发”。触发条件越明确,Agent 的决策越稳定。
规范二:每个 Skill 必须有输入输出契约。输入是什么格式、输出是什么格式,写死在skill.yaml里。Agent 调用时按契约传参,工具返回时按契约解析。这样即使模型抽风,也不会传进来乱七八糟的东西。
规范三:每个 Skill 必须有测试用例。我一般要求至少三个用例:正常输入、边界输入、异常输入。测试用例放在tests/目录,用 pytest 或者 jest 跑。内网里没有 CI 的话,就写个脚本手动跑,但一定要跑。
规范四:Skill 之间不能有隐式依赖。A Skill 不能假设 B Skill 已经执行过。所有依赖都要显式声明在skill.yaml里,由加载器负责按顺序加载。这个规范能避免很多“单独测没问题、一起跑就崩”的问题。
3.4 并发控制:Agent 扛并发的关键
“AI Agent 怎么扛并发”是个高频问题。内网环境里,并发问题比公网更突出,因为资源是固定的,没有弹性扩容这一说。
我的经验是分三层控制。第一层是模型推理层,用推理框架自带的并发控制。vLLM 有--max-num-seqs参数,控制同时处理的请求数。这个值设太大,显存爆;设太小,吞吐上不去。我一般按显存的 70% 来估算,留 30% 余量。
第二层是Agent 调度层,用队列控制。所有请求先进队列,Agent 从队列里取任务执行。队列长度要设上限,超过就拒绝或者排队,避免雪崩。这里可以用 Redis 或者简单的内存队列,内网里我倾向用内存队列,少一个依赖少一个故障点。
第三层是工具调用层,用信号量控制。每个工具能同时处理多少请求,要有限制。比如数据库查询工具,同时开 100 个连接肯定把数据库打挂,得限制到 10 个。这一层用信号量或者连接池实现。
三层控制叠加起来,一个内网 Agent 服务大概能扛住几十到上百的并发,具体看硬件。别指望内网 Agent 能扛几千并发,那不是它的定位。
4. 完整实操流程与核心环节实现
4.1 环境准备:从零搭建内网 Agent 运行环境
假设你拿到一台全新的内网机器,什么都没有,怎么从零搭起来。我按顺序列一遍。
第一步,确认系统信息。uname -a看架构,nvidia-smi看显卡,df -h看磁盘,free -h看内存。这些信息决定了后面所有选型。我见过有人没看架构就下包,结果 x86 的包往 ARM 机器上装,白忙活。
第二步,装基础工具。Python、Node、Git、Docker,这些是标配。内网里装这些也得用离线包,提前在公网下好。Python 建议用 3.10 或 3.11,太新的版本有些库还不支持,太老的版本 Agent 框架不兼容。
第三步,搭私有源。按前面说的,起 pip 源、npm 源、docker registry。这一步做完,后面装依赖就顺了。
第四步,部署模型。把提前下好的模型权重传到内网,用 vLLM 起服务。启动命令大概长这样:
python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --served-model-name local-agent-model \ --max-num-seqs 32 \ --gpu-memory-utilization 0.7 \ --port 8000启动之后用 curl 测一下,确认接口通。
第五步,部署 MCP 网关。把改造好的 MCP Server 起起来,配置好内网工具的连接信息。网关的配置文件我一般放在/etc/mcp-gateway/config.yaml,内容包括工具列表、连接地址、认证信息、限流参数。
第六步,部署 Agent 框架。选一个支持本地模型和 MCP 的框架,配置好模型地址和 MCP 网关地址。启动之后跑一个冒烟测试,问它一个简单问题,看能不能正常回答、能不能调用工具。
这六步走完,一个最小可用的内网 Agent 环境就搭好了。整个过程顺利的话一天,不顺利的话三天,主要时间花在依赖搬运和问题排查上。
4.2 模型部署的参数计算与调优
模型部署这块,参数设置直接决定能不能跑起来、跑得好不好。我拿一个具体例子算一遍。
假设你有一张 48G 显存的卡,要部署一个 32B 的模型,4bit 量化。模型权重大小大概是 32B × 0.5 字节 = 16G。KV Cache 的大小取决于上下文长度和并发数,公式是:KV Cache = 2 × 层数 × 隐藏维度 × 上下文长度 × 并发数 × 精度字节。以 32B 模型为例,层数大概 64,隐藏维度 5120,上下文 8192,并发 16,精度 2 字节(fp16),算下来 KV Cache 大概是 2 × 64 × 5120 × 8192 × 16 × 2 ≈ 17G。加上权重 16G,总共 33G,48G 显存够用,还能留 15G 余量。
基于这个计算,--gpu-memory-utilization设 0.7 比较稳,--max-num-seqs设 16 到 32 之间。如果实际跑起来发现显存不够,优先降并发数,而不是降上下文长度,因为 Agent 场景对上下文长度要求高,截断了会丢信息。
调优的时候关注两个指标:首 token 延迟和吞吐量。首 token 延迟影响用户体验,吞吐量影响并发能力。这两个指标往往互相矛盾,并发开大了首 token 延迟就高。我的经验是,内网 Agent 场景优先保首 token 延迟,因为用户等的是响应速度,不是极限吞吐。
4.3 MCP 网关的配置与工具注册
MCP 网关是整个内网 Agent 的核心枢纽,配置得好不好直接决定 Agent 好不好用。我拿一个实际配置举例。
gateway: listen: 0.0.0.0:9000 auth: type: token tokens: - name: agent-client value: "internal-token-xxx" tools: - name: query_database description: "查询内网业务数据库" endpoint: http://db-service:8080/query method: POST timeout: 30 rate_limit: 10 params: - name: sql type: string required: true description: "SQL 查询语句,仅允许 SELECT" - name: read_file description: "读取内网共享目录文件" endpoint: http://file-service:8080/read method: GET timeout: 10 rate_limit: 50 params: - name: path type: string required: true description: "文件路径,必须在 /shared 目录下"这个配置里,每个工具都定义了名称、描述、端点、超时、限流、参数。描述很重要,模型是根据描述来决定调不调这个工具的,描述写得清楚,模型决策就准。参数里的description也一样,模型靠它来填参数。
工具注册完之后,要写一个工具发现接口,让 Agent 能动态获取工具列表。MCP 协议里有tools/list方法,网关实现这个方法,返回所有工具的元信息。Agent 启动时调一次,把工具列表缓存起来。
提示:工具描述里一定要写清楚使用场景和限制。比如“仅允许 SELECT”这种约束,写在描述里,模型会遵守;不写,模型可能给你来个 DELETE,那就出大事了。
4.4 Skills 的加载与执行流程
Skills 的加载流程我设计成四步:扫描 → 解析 → 注册 → 激活。
扫描阶段,遍历 Skills 仓库目录,找到所有skill.yaml文件。解析阶段,读取每个 yaml,校验格式,提取元信息。注册阶段,把 Skill 的元信息注册到 Agent 的 Skill 注册表里,包括名称、描述、触发条件、依赖。激活阶段,根据当前对话上下文,判断哪些 Skill 应该激活,把激活的 Skill 的提示词和工具定义注入到 Agent 的上下文中。
执行流程是:Agent 决定调用某个 Skill → 从注册表里找到 Skill 定义 → 按契约准备参数 → 调用 Skill 对应的工具 → 解析返回结果 → 把结果回传给模型。这里的关键是参数校验,模型给的参数不一定符合契约,调用前一定要校验,不符合就返回错误让模型重试。
我一般会在 Skill 执行层加一个重试机制。模型第一次调用失败,把错误信息回传,让它重新生成参数。重试最多两次,两次还不行就放弃,返回错误给用户。这个机制能解决大部分参数格式问题。
4.5 端到端联调:从用户输入到工具执行
环境搭好、模型跑起来、MCP 网关配好、Skills 加载完,最后一步是端到端联调。我一般按这个顺序测。
先测纯对话,不涉及工具调用。问模型“你好”,看它能不能正常回复。这一步确认模型推理链路通。
再测单工具调用。问“帮我查一下订单表有多少条记录”,看模型能不能识别出要调query_database工具,能不能生成正确的 SQL,能不能拿到结果并组织成自然语言回答。这一步确认 MCP 链路通。
然后测多工具协作。问“读取 /shared/report.csv,统计一下销售额总和”,看模型能不能先调read_file再调query_database(或者用代码执行工具处理 CSV),能不能把多个工具的结果串起来。这一步确认 Agent 的规划能力。
最后测异常处理。故意给一个不存在的文件路径,看模型能不能识别错误、能不能重试、能不能给用户合理的提示。这一步确认系统的健壮性。
联调过程中,日志是关键。Agent 框架的日志、MCP 网关的日志、模型服务的日志,三份日志对着看,能快速定位问题出在哪一环。我一般会开一个终端专门 tail 日志,边测边看。
5. 常见问题与排查技巧实录
5.1 模型加载失败:显存、格式、版本三大坑
模型加载失败是最常见的问题,原因基本逃不出三个:显存不够、格式不对、版本不匹配。
显存不够的表现是加载到一半 OOM。排查方法是看加载日志,确认是在加载权重阶段还是 KV Cache 阶段挂的。权重阶段挂,说明模型太大,得换更小的量化版本;KV Cache 阶段挂,说明并发或上下文设太大,调小参数。
格式不对的表现是加载时报“unsupported format”。排查方法是确认模型权重的量化格式,是 GPTQ、AWQ 还是 GGUF,然后确认推理框架支持哪种。我踩过一次坑,模型是 AWQ 格式,结果装了个只支持 GPTQ 的 vLLM 版本,报错信息还很隐晦,查了半天。
版本不匹配的表现是加载成功但推理结果乱码。排查方法是确认模型和 tokenizer 的版本是否一致,有时候模型更新了但 tokenizer 没更新,就会出这种问题。解决方法是重新下载配套的 tokenizer。
5.2 MCP 连接超时:网络、认证、协议三层排查
MCP 连接超时也是高频问题。我按三层排查。
网络层:ping一下目标地址,telnet一下端口,确认网络通。内网里经常是防火墙规则没开,或者服务没起在预期端口上。
认证层:确认 Token 或证书是否正确。内网里认证信息经常是手动配的,容易配错。我一般会写个脚本,把认证信息单独测一遍。
协议层:确认 MCP 版本是否匹配。Client 和 Server 的 MCP 版本不一致,会出现“连上了但握手失败”的情况。排查方法是看两边的日志,对比协议版本号。
5.3 Skills 不生效:触发条件、加载顺序、上下文长度
Skills 不生效,先看触发条件。触发条件写得太宽泛,模型可能忽略;写得太严格,模型可能匹配不上。我一般会加一个调试模式,把当前激活的 Skills 列表打出来,看看到底激活了没有。
再看加载顺序。有依赖关系的 Skills,加载顺序错了就会失败。排查方法是看加载日志,确认依赖的 Skill 先加载了。
最后看上下文长度。Skills 太多,提示词太长,超出模型的上下文窗口,后面的 Skill 就被截断了。排查方法是算一下总 token 数,超了就精简 Skill 描述,或者改成按需加载。
5.4 并发场景下的资源竞争与死锁
并发一上来,资源竞争和死锁就来了。我遇到过最典型的是数据库连接池耗尽。多个 Agent 同时调数据库工具,连接池满了,后面的请求全卡住。解决方法是给数据库工具加信号量,限制同时调用的数量。
还有文件锁竞争。多个 Agent 同时读写同一个文件,出现读写冲突。解决方法是给文件操作加锁,或者干脆让每个 Agent 操作独立的文件副本。
死锁的排查比较麻烦,我一般用py-spy或者gdbattach 到进程上,看线程栈,找到卡在哪把锁上。预防死锁的原则是统一加锁顺序,所有地方都按同一个顺序获取锁,就不会出现循环等待。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 模型加载 OOM | 显存不足 | 看加载日志定位阶段 | 换小模型或调小并发 |
| 推理结果乱码 | tokenizer 不匹配 | 对比模型和 tokenizer 版本 | 重新下载配套 tokenizer |
| MCP 连接超时 | 网络/认证/协议问题 | 分层排查 | 对应修复 |
| Skill 不生效 | 触发条件/加载顺序/上下文 | 开调试模式看激活列表 | 调整条件或精简描述 |
| 并发卡死 | 资源竞争/死锁 | py-spy 看线程栈 | 加信号量或统一锁顺序 |
| 工具调用参数错误 | 模型理解偏差 | 看调用日志 | 优化工具描述,加重试 |
这张表我贴在工位上,出问题先对照一遍,能解决八成常见故障。
6. 内网 Agent 工程的几条实战心得
最后分享几条踩坑踩出来的经验,都是文档里不会写的。
第一条,内网环境里,简单方案永远优于复杂方案。公网里你可以随便堆组件,出问题了重启一下、扩容一下。内网里每个组件都是负担,多一个组件多一个故障点。能用脚本解决的别上框架,能用单机解决的别上分布式。
第二条,依赖版本一定要锁死。内网里没有“最新版”这个概念,你装的是什么版本,就一直是什么版本。所以第一次装的时候就要把版本号记下来,写进依赖清单。我见过太多“上周还能跑,这周就不行了”的案例,全是版本漂移导致的。
第三条,日志要打够,但别打太多。内网排查问题全靠日志,但日志太多又会拖慢性能、占满磁盘。我的做法是分级打日志:正常调用打 INFO,异常打 ERROR,调试信息打 DEBUG 且默认关闭。出问题了再开 DEBUG 复现。
第四条,Skills 要小而精,不要大而全。一个 Skill 只干一件事,干好一件事。大而全的 Skill 看起来省事,实际上难维护、难测试、难复用。我一般要求单个 Skill 的代码不超过 200 行,超过就拆。
第五条,定期做灾备演练。内网环境里,机器挂了、服务崩了是常事。定期演练一下“模型服务挂了怎么办”“MCP 网关挂了怎么办”,把恢复流程写成文档,真出事了照着做,不至于手忙脚乱。
这套东西我在三个内网环境里跑下来,最深的体会是:内网 AI Agent 工程的难点不在 AI,在工程。模型能力是现成的,但把它塞进一个受限环境里稳定运行,需要的是扎实的工程功底和对细节的把控。把依赖治理好、把协议适配好、把并发控制好,剩下的就是水到渠成的事。