1. 为什么隔离内网里的 AI Agent 工程是另一套玩法
先把场景说清楚。所谓隔离内网,就是开发机、构建机、制品库、模型服务全部跑在一张与公网物理断开的网络里,没有外网出口,没有在线包管理源,没有云端大模型 API,甚至连时间同步都得靠内网 NTP。很多人第一次接到这种活儿,脑子里第一反应是"把公网那套搬进去不就行了",结果一进去就发现:pip 装不上、npm 拉不到、Docker 镜像拉不下来、模型权重下不了、MCP 服务连不上外部工具。公网上那些"三行命令跑通一个 Agent"的教程,在这里全部失效。
这就是"隔离内网下 AI Agent 工程实战"这个标题真正的分量所在。它不是教你怎么调一个 API,而是教你在一个资源受限、网络封闭、依赖必须自给自足的环境里,把一套能跑、能维护、能交付的 AI Agent 系统从零搭起来。核心关键词里出现的 MCP、Skills、内网、工程实战,其实指向的是同一件事:把 Agent 的能力来源(工具、技能、模型、依赖)全部本地化、可离线、可复现。
我先把适合读这篇的人圈一下:一是在金融、能源、制造、科研院所这类有强隔离要求的环境里做 AI 落地的工程师;二是需要把 Agent 交付到客户内网、现场无法联网的交付团队;三是想搞清楚 MCP 和 Skills 到底怎么在离线环境里组织起来的技术负责人。如果你只是在自己笔记本上玩在线大模型,这篇的很多约束你用不上,但里面关于依赖治理和工程结构的思路,照样能帮你把项目做得更稳。
需要先建立一个认知:隔离内网不是"公网环境减掉网络",而是"公网环境换了一套约束条件"。约束变了,最优解就变了。公网上你可以随手pip install,内网里你必须提前把所有 wheel 包按平台和 Python 版本对齐好;公网上你可以直接调云端模型,内网里你要么本地部署推理服务,要么走内网已有的模型网关;公网上 MCP 工具可以随便连外部 SaaS,内网里每个工具都得自己实现或者本地化部署。理解了这个前提,后面的所有工程决策才站得住脚。
2. 内网 Agent 的能力来源拆解:模型、MCP、Skills 三件套
2.1 模型层:本地推理服务是地基
隔离内网里,模型层只有两条路:一是内网已经有机房级的推理集群,提供 OpenAI 兼容接口;二是自己在带 GPU 的机器上部署推理框架。前者你只需要拿到 base_url 和鉴权方式,后者你要处理权重文件、推理引擎、显存分配这一整套。
我实际做过的项目里,绝大多数隔离内网都已经有统一的模型网关,因为安全团队不会允许每个业务自己起一套推理服务。这种情况下,Agent 工程的重点就变成适配网关的接口协议。很多内网网关只实现了/v1/chat/completions,不支持 function calling 的原生字段,也不支持流式的 tool_calls 增量返回。这时候你的 Agent 框架如果强依赖原生 tool calling,就会直接卡死。
应对办法是准备一层协议适配层:把 Agent 框架发出的工具调用请求,转换成网关能理解的纯文本 prompt 格式,再把模型返回的文本解析回结构化的工具调用。这层适配看起来土,但在内网里极其常见,而且稳定性反而比依赖原生字段更高,因为你不受网关实现细节的绑架。
提示:进内网前一定要先确认网关支持哪些字段。我见过团队花了三天调 Agent,最后发现是网关把
tools参数直接丢弃了,模型根本没收到工具定义。
2.2 MCP 层:把工具调用标准化,但要在内网重新落地
MCP(Model Context Protocol)的价值在于把"模型怎么调用外部工具"这件事标准化了。公网上大家用现成的 MCP server 连数据库、连文件系统、连各种 SaaS。到了内网,MCP 的协议本身照样能用,但所有 MCP server 必须本地化。
内网里 MCP 的典型落地形态是 stdio 模式:Agent 进程通过标准输入输出和 MCP server 子进程通信,不涉及任何网络端口,天然适配隔离环境。相比 SSE 或 HTTP 模式,stdio 模式在内网里优势明显——不需要开端口、不需要处理跨机通信、不需要担心防火墙策略。代价是 MCP server 必须和 Agent 跑在同一台机器上,资源要一起规划。
内网 MCP server 一般分三类:文件与代码类(读写工作区、执行脚本)、数据类(连内网数据库、查知识库)、业务类(调内网已有系统的接口)。第三类最麻烦,因为内网系统往往没有标准 API,你得写适配器把老系统的调用方式包装成 MCP 工具。
2.3 Skills 层:把领域经验固化成可复用能力
Skills 这个概念最近很热,本质上是把一段可复用的领域知识或操作流程,封装成 Agent 可以按需加载的能力单元。它和 MCP 的区别在于:MCP 偏"工具",是模型可以调用的函数;Skills 偏"知识+流程",是告诉模型"遇到这类任务应该怎么做"的说明书加脚本集合。
内网里 Skills 特别有价值,因为内网业务往往有大量隐性规则——某个字段的取值规范、某类工单的处理流程、某套系统的操作禁忌。这些规则没法靠模型自己猜,必须显式写进 Skills。一个设计良好的 Skill 通常包含:触发条件描述、操作步骤、注意事项、配套脚本。Agent 在处理任务时先匹配 Skill,再按 Skill 指引调用 MCP 工具,形成"知识指导行动"的闭环。
| 能力层 | 公网常见形态 | 内网落地形态 | 核心约束 |
|---|---|---|---|
| 模型 | 云端 API | 内网推理网关 / 本地部署 | 接口字段可能不全 |
| MCP | 远程 server | stdio 本地子进程 | 无网络端口 |
| Skills | 在线市场下载 | 内网仓库自建 | 需自研与审核 |
3. 离线依赖治理:把公网能装的东西提前搬进去
3.1 依赖清单要先冻结,再打包
内网工程最容易翻车的地方就是依赖。公网上pip install -r requirements.txt一句话的事,内网里你要保证每一个包、每一个版本、每一个平台 wheel 都提前准备好。我的做法是先在公网环境用和目标内网完全一致的 Python 版本、操作系统、CPU 架构,把依赖装一遍,然后导出精确清单。
导出的时候不要用pip freeze直接导,因为它会把间接依赖也列出来但顺序混乱。更稳的方式是用pip download把整个依赖树下载成 wheel 包目录:
pip download -r requirements.txt \ --dest ./offline_packages \ --platform manylinux2014_x86_64 \ --python-version 310 \ --only-binary=:all:这里几个参数很关键。--platform和--python-version必须和目标内网机器一致,否则下下来的 wheel 装不上。--only-binary=:all:强制只下二进制包,避免下到源码包后在内网编译——内网机器往往没有编译工具链,源码包会直接卡死。
3.2 内网安装要建本地源,不要一个个装
把 wheel 包拷进内网后,别急着pip install ./xxx.whl一个个装。正确做法是在内网搭一个本地 PyPI 源,最简单的是用pip download出来的目录配合--find-links:
pip install -r requirements.txt \ --no-index \ --find-links=/opt/offline_packages--no-index表示完全不访问任何在线源,--find-links指向本地目录。这样 pip 会只在本地目录里找包,找不到就报错,不会偷偷去连外网然后超时卡住。这个组合在内网里是标配,能省掉大量"为什么装到一半卡住"的排查时间。
如果内网规模大、团队多,建议直接搭一个内网的制品库(比如 Nexus 或 Artifactory 的离线部署版),把 Python、npm、Maven、Docker 镜像统一管起来。一次性投入,长期省事。
3.3 模型权重和镜像的分发
模型权重动辄几十 GB,靠 U 盘拷效率太低。内网里通常有文件共享或者对象存储,权重提前放进去,Agent 启动时从内网地址拉。Docker 镜像同理,公网上docker pull拉下来的镜像,要用docker save导出成 tar 包,拷进内网后docker load导入。
# 公网侧导出 docker save my-agent:1.0 -o my-agent-1.0.tar # 内网侧导入 docker load -i my-agent-1.0.tar注意:导出镜像前确认基础镜像也是内网能拿到的,否则
docker load后启动会因为找不到 base image 失败。最稳的做法是把整个镜像链都导出。
4. Agent 工程骨架:在内网里怎么组织代码和配置
4.1 目录结构要按"能力"而不是按"技术"划分
公网项目很多人习惯按技术分层:controllers、services、utils。内网 Agent 项目我更推荐按能力划分,因为 Agent 的核心是"能做什么",而不是"用了什么技术"。一个我实际用过的结构:
agent-project/ core/ # Agent 主循环、协议适配 mcp_servers/ # 各个 MCP server 实现 skills/ # 技能定义与配套脚本 configs/ # 内网地址、模型参数、工具开关 offline_deps/ # 离线依赖包 scripts/ # 部署、启动、健康检查脚本这样划分的好处是,当你要新增一个能力时,改动集中在mcp_servers或skills里,不会牵动核心逻辑。内网项目迭代慢、评审严,改动面小意味着上线风险低。
4.2 配置与代码分离,且配置要能热切换
内网环境经常有多套:开发内网、测试内网、生产内网,模型地址、数据库地址、工具开关都不一样。配置必须外置,用 YAML 或环境变量都行,关键是同一份代码能在不同内网里跑起来,只换配置。
model: base_url: "http://10.0.0.10:8000/v1" model_name: "internal-llm" supports_tool_calling: false mcp: servers: - name: "file-ops" transport: "stdio" command: "python -m mcp_servers.file_ops" skills: path: "./skills" auto_load: truesupports_tool_calling这个开关特别重要。前面说过内网网关可能不支持原生工具调用,这个开关让适配层知道该走原生路径还是走文本解析路径。一个开关省掉一套代码分支。
4.3 主循环要能"降级"
内网环境不稳定是常态——模型服务可能临时重启、MCP server 可能崩、数据库可能超时。Agent 主循环必须能优雅降级:模型不可用时返回明确错误而不是挂死,MCP server 崩溃时能重启子进程,工具调用超时时有兜底。我一般会给每个外部依赖加超时和重试,重试次数不要多,两次足够,多了会把整个请求拖死。
5. 内网 MCP 与 Skills 的落地细节
5.1 stdio MCP server 的生命周期管理
stdio 模式下,MCP server 是 Agent 的子进程。这里有个坑:如果 Agent 频繁启停,子进程可能变成僵尸进程,时间长了把机器资源吃光。解决办法是在 Agent 启动时统一拉起所有 MCP server,运行期间保持长连接,退出时统一清理。
import subprocess import atexit class MCPServerManager: def __init__(self): self.processes = [] def start(self, command): proc = subprocess.Popen( command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) self.processes.append(proc) return proc def shutdown(self): for proc in self.processes: proc.terminate() try: proc.wait(timeout=5) except subprocess.TimeoutExpired: proc.kill() manager = MCPServerManager() atexit.register(manager.shutdown)atexit注册清理函数是关键,保证即使 Agent 异常退出,子进程也能被回收。这个细节公网项目里经常被忽略,因为公网环境重启频繁、资源充足,内网里机器往往一跑就是几个月,僵尸进程会累积成大问题。
5.2 Skills 的加载与匹配策略
Skills 多了以后,不能全塞进 prompt,会撑爆上下文。我的做法是两级加载:第一级只加载所有 Skill 的名称和一句话描述,让模型判断当前任务该用哪个 Skill;第二级在确定 Skill 后,再加载该 Skill 的完整内容。这样上下文占用可控,匹配准确率也高。
Skill 的匹配可以纯靠模型判断,也可以加一层关键词预筛。内网里我倾向加预筛,因为内网模型往往比公网旗舰模型弱,纯靠模型判断容易选错。预筛逻辑很简单:Skill 定义里写几个触发关键词,用户输入命中关键词就优先候选。
5.3 工具调用的参数校验不能省
内网业务系统对参数极其敏感,一个字段传错可能触发业务异常。MCP 工具在真正执行前,必须做参数校验。校验分两层:一层是类型和必填校验,用 JSON Schema 就能做;另一层是业务规则校验,比如某个 ID 必须存在于内网数据库里。第二层校验很多人偷懒不做,结果工具把脏数据写进业务系统,排查起来非常痛苦。
6. 踩坑实录:内网 Agent 上线前必须过的几道坎
6.1 时间不同步导致的鉴权失败
内网机器如果没配 NTP,时间可能和模型网关差几分钟。很多网关的鉴权 token 带时间戳,时间偏差超过阈值就直接拒绝。我遇到过一次,Agent 所有请求都返回 401,排查了半天以为是密钥错了,最后发现是内网机器时间慢了 7 分钟。进内网第一件事,确认所有机器时间同步。
6.2 字符编码引发的工具调用解析失败
内网老系统经常用 GBK 编码,而 Agent 框架默认 UTF-8。工具返回的中文内容如果编码不对,解析时会乱码甚至抛异常。处理办法是在 MCP server 和业务系统之间加一层编码转换,统一转成 UTF-8 再返回给 Agent。这个坑不踩一次很难想到,但踩过之后就会在每个数据入口都加编码检查。
6.3 长任务被网关超时切断
内网网关通常有请求超时限制,比如 60 秒。Agent 处理复杂任务时,一次模型调用加多次工具调用,很容易超过这个时间。解决办法是把长任务拆成多轮短请求,每轮控制在超时阈值内,中间状态存到本地。或者和网关管理员协商,给 Agent 的请求单独放宽超时。前者更可控,后者依赖别人,我一般优先做前者。
6.4 日志里泄露敏感信息
内网环境对数据安全要求高,Agent 日志如果原样打印用户输入和工具返回,可能把敏感数据写进日志文件。上线前一定要做日志脱敏,对身份证、手机号、内部编号这类字段做掩码。这个不是技术难点,是意识问题,但恰恰最容易出事。
| 坑点 | 表象 | 根因 | 处理方式 |
|---|---|---|---|
| 时间不同步 | 全部请求 401 | 机器时间偏差 | 配内网 NTP |
| 编码不一致 | 中文乱码、解析异常 | GBK 与 UTF-8 混用 | 入口统一转码 |
| 网关超时 | 长任务中途断开 | 请求超时限制 | 任务拆分+状态持久化 |
| 日志泄露 | 敏感数据落盘 | 未做脱敏 | 日志字段掩码 |
7. 交付与运维:让内网 Agent 能长期活下去
7.1 交付物要包含"可复现"的部署脚本
内网交付最忌讳给一堆散装文件让现场自己拼。交付物应该是一个完整的部署包:离线依赖、模型配置模板、MCP server、Skills、启动脚本、健康检查脚本,外加一份部署文档。部署脚本要能一键跑通,从解压到服务起来不超过几步。现场工程师水平参差不齐,脚本越傻瓜越好。
7.2 健康检查要覆盖每一层依赖
Agent 能不能正常工作,取决于模型、MCP server、数据库、文件系统每一层都正常。健康检查脚本要逐层探测:模型接口能不能通、每个 MCP server 能不能启动、数据库能不能连、工作目录能不能读写。任何一层失败都要给出明确提示,而不是笼统报"服务异常"。
7.3 版本升级要能回滚
内网升级不像公网可以灰度、可以快速回滚。一次升级出问题,可能影响整个内网用户。所以升级前必须备份当前版本,升级脚本要支持一键回滚。我的习惯是每次升级把旧版本整个目录打个包留着,出问题直接切回去,比现场调试快得多。
7.4 监控指标要精简但有效
内网监控不用搞太复杂,几个核心指标就够:请求成功率、平均响应时间、模型调用失败率、MCP server 存活状态、工具调用错误率。这几个指标能覆盖 90% 的问题场景。指标采集用内网已有的监控系统,别自己造轮子。
8. 一些实际做下来觉得值得说的经验
内网 Agent 工程做久了,最大的体会是:技术选型要往"少依赖、可离线、易排查"的方向靠。公网上那些花哨的框架、需要联网的组件、依赖云服务的方案,在内网里全是负担。反而是那些看起来朴素、纯本地、日志清晰的方案,能活得最久。
另一个体会是,内网项目里文档和脚本的价值被严重低估。公网项目文档写得烂,用户自己搜一搜就解决了;内网项目文档写得烂,现场工程师只能打电话找你,而你可能也进不去那个内网。所以每做一个内网项目,我都会把部署、排错、升级的每一步写成脚本和文档,这部分的投入回报比写业务代码高得多。
最后说一个具体的技巧:内网里调试 Agent,最好准备一个"最小可复现"的测试用例集,覆盖模型调用、MCP 工具调用、Skill 匹配、异常处理这几条主路径。每次改动后先跑这个用例集,比直接上真实业务场景试错快得多,也安全得多。这个用例集本身也是交付物的一部分,现场工程师遇到问题可以先跑一遍,快速定位是哪一层出的问题。