多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

隔离内网AI Agent工程实战:MCP与Skills离线部署及依赖搬运

隔离内网AI Agent工程实战:MCP与Skills离线部署及依赖搬运 1. 为什么隔离内网里的 AI Agent 工程是另一套玩法先把场景说清楚。所谓隔离内网指的是开发机、构建机、运行环境全部处在一个没有公网出口的局域网里可能连外网 DNS 都解析不了pip、npm、docker pull 这些命令全部失效。很多人在公网环境里把 AI Agent 玩得飞起一进内网就懵了——模型拉不下来、依赖装不上、MCP 服务连不通、Skills 加载报错最后只能退回人工复制粘贴的原始状态。我前后在三个不同规模的内网环境里落地过 AI Agent 工程从最早的手动搬包到后来形成一套相对稳定的离线交付流程踩的坑足够写一本小册子。这篇就把这套东西完整拆开讲内网里 AI Agent 到底难在哪、MCP 和 Skills 这两个核心概念在离线环境下怎么落地、依赖和模型怎么搬运、并发怎么扛、以及那些只有真正在内网干过才知道的细节。需要先明确一点内网 AI Agent 工程的核心矛盾不是模型不够强而是供应链断裂。公网环境下你习惯了缺什么装什么内网环境下每一个依赖、每一个模型权重、每一个 MCP Server 的可执行文件都必须提前规划、离线打包、可复现地搬进去。这本质上是一个软件供应链治理问题而不是单纯的 AI 问题。想通这一点后面的所有设计就顺了。这篇文章适合三类人一是要在内网环境部署 AI Agent 的工程师二是正在做 MCP、Skills 相关离线化改造的开发者三是想理解AI Agent 工程化到底包含哪些环节的技术负责人。我会尽量把每一步的为什么讲透而不是只丢一堆命令。2. 内网 AI Agent 的三大断点模型、依赖、服务发现在动手之前得先搞清楚内网环境到底断在哪几个环节。我把它们归纳成三个断点每一个断点对应一套独立的搬运和适配策略。很多人一上来就想着怎么把公网那套原样搬进来结果发现根本搬不动就是因为没分清这三个断点的性质完全不同。2.1 模型权重断点体积大、校验严、不能增量模型权重是内网部署里最重的一块。一个 7B 的量化模型动辄 4-8GB32B 的模型轻松上 20GB。公网环境下你可以用huggingface-cli download断点续传内网里只能靠移动硬盘或者内部文件服务器中转。这里有个很多人忽略的点模型文件的完整性校验。大文件在多次拷贝、压缩、解压过程中极易损坏而损坏的模型往往不会在加载时报错而是在推理时输出乱码或者直接崩溃排查起来极其痛苦。我的做法是搬运前先算好每个文件的 SHA256落地后逐个校验校验脚本随包一起交付。# 打包前生成校验清单 find ./model -type f -exec sha256sum {} \; model.sha256 # 落地后校验 sha256sum -c model.sha256另外模型权重的目录结构要原样保留。有些团队为了省空间只拷贝.safetensors文件把config.json、tokenizer.json、generation_config.json这些小文件漏掉结果加载直接失败。这些配置文件加起来可能就几百 KB但缺一个都跑不起来。2.2 依赖断点Python 生态的隐式依赖最坑Python 依赖是内网部署里最容易被低估的部分。表面上看pip download一下就能把包下全实际上很多包在安装时会动态编译、下载额外的二进制、或者依赖系统级的库比如libpq、libssl、gcc工具链。我的经验是永远不要指望在内网机器上现场编译。正确做法是在一台和內网目标机器操作系统版本、CPU 架构、Python 版本完全一致的构建机上把所有 wheel 包下全包括传递依赖。# 在构建机上针对目标平台下载所有依赖 pip download -r requirements.txt \ --platform manylinux2014_x86_64 \ --python-version 310 \ --only-binary:all: \ -d ./offline_packages--only-binary:all:这个参数很关键它强制只下载预编译的 wheel避免下到源码包后在内网现场编译。如果某个包没有对应平台的 wheel那就要提前评估要么换包要么在构建机上编译好再打包。2.3 服务发现断点MCP Server 找不到彼此MCPModel Context Protocol是 AI Agent 连接外部工具的标准协议一个 Agent 往往要同时挂载多个 MCP Server——文件系统、数据库、内部 API 等等。公网环境下这些 Server 可以通过各种方式动态发现内网里没有服务注册中心全靠配置文件硬编码。这里最容易出问题的是端口冲突和启动顺序。多个 MCP Server 如果都用默认端口启动时就会互相抢占。我的做法是给每个 Server 分配固定的端口段用一个统一的启动脚本按顺序拉起并在启动后做健康检查。断点类型核心难点应对策略模型权重体积大、易损坏SHA256 校验 原样保留目录结构Python 依赖隐式编译依赖构建机预下载 wheel 平台锁定服务发现无注册中心固定端口段 顺序启动 健康检查把这三个断点想清楚内网 AI Agent 的骨架就立起来了。接下来逐个展开。3. MCP 在内网里的落地从协议理解到离线部署MCP 这两年火得很快但很多人在公网用惯了现成的 MCP Server一到内网就不知道怎么搞。要真正在内网落地 MCP得先理解它到底解决什么问题再谈部署。3.1 MCP 到底解决了什么把工具调用标准化在没有 MCP 之前每个 AI Agent 框架都有自己的工具调用格式。LangChain 一套、各家 SDK 又一套你想让 Agent 调用一个内部数据库就得为每个框架写一遍适配代码。MCP 的价值在于把工具抽象成一个标准协议Agent 作为 Client工具作为 Server双方通过统一的 JSON-RPC 消息通信。这个抽象在内网环境里尤其重要。因为内网的内部系统五花八门——有的老系统只有命令行接口有的只有 HTTP API有的甚至是文件交换。如果每个都单独适配维护成本爆炸。用 MCP 统一封装后Agent 侧只需要会说 MCP 协议具体怎么调内部系统是 Server 的事。MCP 的核心通信方式有两种stdio标准输入输出和 SSEServer-Sent Events。内网环境我强烈推荐stdio 模式因为它是进程内通信不涉及网络端口天然规避了防火墙和端口冲突问题。3.2 离线打包一个 MCP Server 的完整流程假设我们要把一个内部知识库查询封装成 MCP Server部署到内网。完整流程是这样的第一步在构建机上用官方 SDK 写好 Server 代码。Python 用mcp包Node 用modelcontextprotocol/sdk。代码本身不复杂核心是实现list_tools和call_tool两个方法。from mcp.server import Server from mcp.server.stdio import stdio_server app Server(internal-kb) app.list_tools() async def list_tools(): return [{ name: query_kb, description: 查询内部知识库, inputSchema: { type: object, properties: {keyword: {type: string}}, required: [keyword] } }] app.call_tool() async def call_tool(name, arguments): if name query_kb: result search_internal_kb(arguments[keyword]) return {content: [{type: text, text: result}]}第二步把 Server 的所有依赖打成离线包。Python 的用pip downloadNode 的用npm pack或者直接把node_modules整个打包。第三步写一个启动脚本把 Server 的启动命令固化下来。这个脚本要处理环境变量、工作目录、日志路径这些细节。第四步在 Agent 侧的 MCP 配置文件里注册这个 Server。以常见的配置格式为例{ mcpServers: { internal-kb: { command: python, args: [/opt/mcp/internal_kb/server.py], env: { KB_ENDPOINT: http://internal-api:8080 } } } }3.3 内网 MCP 的常见故障与排查顺序内网 MCP 出问题排查顺序很重要乱查一通只会浪费时间。我总结的顺序是先看进程起没起再看协议通不通最后看业务逻辑对不对。进程起没起直接ps aux | grep server.py。如果进程都没起来八成是依赖缺失或者 Python 版本不对看启动脚本的 stderr 输出。协议通不通用一个最小的 MCP Client 去 ping 一下。官方 SDK 都带测试工具或者自己写个几行的脚本调用list_tools。如果这一步失败通常是 stdio 的读写被日志污染了——MCP 的 stdio 模式下stdout 只能输出协议消息任何 print 调试都会破坏协议。这是新手最常踩的坑调试信息一定要打到 stderr。业务逻辑对不对就是单独测试 Server 内部调用的那个函数。这一步和 MCP 无关纯粹是业务代码的问题。提示stdio 模式下日志必须走 stderrstdout 留给协议。这一条能帮你省下至少半天的排查时间。4. Skills 的离线化让 Agent 在内网也能长本事Skills 是最近半年 AI Agent 领域最热的概念之一。简单说Skills 就是把完成某类任务的固定套路封装成可复用的模块Agent 在需要时加载对应的 Skill。公网环境下有各种 Skills 市场可以一键安装内网里就得自己搞一套离线分发机制。4.1 Skills 和 MCP 的分工一个管怎么做一个管能调什么很多人分不清 Skills 和 MCP。用一句话概括MCP 解决Agent 能调用哪些外部能力Skills 解决Agent 遇到某类任务该怎么做。举个例子你要让 Agent 处理一份内部报表。MCP 负责提供读取文件查询数据库这些原子能力Skills 则封装如何识别报表格式、如何提取关键指标、如何生成汇总这一整套流程。MCP 是手和脚Skills 是经验和套路。理解这个分工很重要因为它决定了你的离线包该怎么组织。MCP Server 是独立进程Skills 通常是提示词模板 辅助脚本 参考资料的组合。4.2 一个可离线分发的 Skill 目录结构我实践下来一个便于离线分发的 Skill 应该长这样skills/ report_analysis/ SKILL.md # 技能描述和触发条件 prompt.md # 核心提示词模板 scripts/ extract.py # 辅助脚本 references/ format_spec.md # 参考资料 manifest.json # 元信息版本、依赖、校验值SKILL.md是入口描述这个 Skill 什么时候该被激活。manifest.json是离线分发的关键它记录了版本号、依赖的其他 Skill、以及所有文件的校验值。内网分发时只要校验 manifest 就能确认 Skill 完整。{ name: report_analysis, version: 1.2.0, depends_on: [file_reader], files: { SKILL.md: a1b2c3..., prompt.md: d4e5f6... } }4.3 Skill 加载失败的四种典型原因内网里 Skill 加载失败基本逃不出这四种原因按概率从高到低排第一种路径问题。Skill 的加载器通常按固定目录扫描如果打包时目录层级多了一层或少了一层就扫不到。这个用find命令确认一下实际路径和配置里的路径是否一致即可。第二种编码问题。内网机器如果是某些特定语言环境默认编码可能不是 UTF-8导致中文提示词读进来是乱码。解决办法是在加载器里显式指定encodingutf-8。第三种依赖的 Skill 缺失。Skill 之间可以互相依赖如果 A 依赖 B 但 B 没打包进去A 就加载不了。这就是manifest.json里depends_on字段的价值——加载前先做依赖检查。第四种版本不匹配。Agent 框架升级后Skill 的接口可能变了。内网环境升级困难所以 Skill 和框架的版本要严格锁定在 manifest 里记录兼容的框架版本范围。失败原因排查方法修复方式路径问题find 确认实际路径调整目录层级或配置编码问题检查文件编码显式指定 UTF-8依赖缺失检查 manifest补齐依赖 Skill版本不匹配对比框架版本锁定版本或适配5. 依赖与模型的离线搬运一套可复现的打包流程前面讲了断点这一节讲具体的搬运工程。核心目标是可复现——同样的打包产物在任何一台符合条件的内网机器上都能一次装好不需要现场调试。5.1 构建机与目标机的环境对齐搬运失败的头号原因是构建机和目标机环境不一致。要对齐的维度包括操作系统发行版和版本、CPU 架构x86_64 还是 ARM、glibc 版本、Python/Node 版本、以及系统级依赖库。我习惯在构建机上先跑一个环境快照脚本把关键信息记录下来随包交付#!/bin/bash echo OS env_snapshot.txt cat /etc/os-release env_snapshot.txt echo Arch env_snapshot.txt uname -m env_snapshot.txt echo glibc env_snapshot.txt ldd --version | head -1 env_snapshot.txt echo Python env_snapshot.txt python3 --version env_snapshot.txt目标机落地前先跑同样的脚本对比一下。glibc 版本不一致是最隐蔽的坑——wheel 包在构建机上能装到目标机上就报GLIBC_2.xx not found。这种情况只能换更老的构建机或者用 manylinux 标准镜像构建。5.2 用虚拟环境锁定依赖树依赖打包最忌讳的是在全局环境里 pip download。全局环境里可能已经装了一堆无关的包pip download会把它们也带上包体积翻倍不说还可能引入版本冲突。正确做法是建一个干净的虚拟环境只装requirements.txt里的东西然后导出完整的依赖树python3 -m venv build_env source build_env/bin/activate pip install -r requirements.txt pip freeze locked_requirements.txt pip download -r locked_requirements.txt \ --only-binary:all: \ -d ./offline_packagespip freeze导出的locked_requirements.txt包含了所有传递依赖的精确版本这是可复现的关键。内网安装时用这个文件而不是原始的requirements.txt。5.3 模型权重的分卷与校验大模型权重搬运时单个文件超过移动硬盘的文件系统限制比如 FAT32 单文件 4GB就会失败。解决办法是分卷压缩# 分卷压缩每卷 2GB tar czf - ./model | split -b 2G - model.tar.gz.part_ # 内网侧合并解压 cat model.tar.gz.part_* | tar xzf -分卷后一定要重新算校验值因为分卷过程本身可能出错。校验清单要包含每个分卷的哈希和合并后完整文件的哈希双重保险。注意分卷压缩的卷大小要小于目标文件系统的单文件限制留 10% 余量。2GB 的卷在绝大多数文件系统上都安全。6. 并发与稳定性内网 Agent 扛压的工程细节内网 AI Agent 上线后很快会遇到并发问题。公网环境可以靠弹性扩容扛内网资源固定只能靠工程手段优化。这一节讲几个实战中真正有效的做法。6.1 模型推理的并发瓶颈在哪模型推理的并发瓶颈通常不在 CPU而在显存和批处理效率。一个 7B 模型在单张消费级显卡上如果每个请求单独推理显存利用率很低吞吐上不去。解决办法是动态批处理——把短时间内到达的多个请求攒成一个 batch 一起推理。主流推理框架vLLM、TGI 等都内置了动态批处理。内网部署时关键参数是max_batch_size和max_num_seqs。这两个值要根据显存大小调调大了会 OOM调小了吞吐上不去。我的经验是从保守值开始逐步加压测试。参数作用调优方向max_batch_size单批最大请求数显存允许下尽量大max_num_seqs并发序列数影响吞吐和延迟平衡gpu_memory_utilization显存占用比例0.85-0.9 较稳妥6.2 Agent 层的请求队列与超时控制模型层之上是 Agent 层这里要处理的是请求排队和超时。Agent 的一次任务可能包含多轮模型调用和多次工具调用整体耗时可能几十秒甚至几分钟。如果没有队列和超时控制高并发下会雪崩。我的做法是在 Agent 入口加一个带优先级的请求队列同时给每个任务设置总超时和单步超时。总超时防止任务无限挂起单步超时防止某个工具调用卡死拖垮整个任务。import asyncio async def run_agent_task(task, total_timeout300, step_timeout60): try: async with asyncio.timeout(total_timeout): for step in task.steps: async with asyncio.timeout(step_timeout): await execute_step(step) except asyncio.TimeoutError: return {status: timeout, task: task.id}6.3 内网环境下的可观测性建设内网没有公网的监控 SaaS可观测性得自己搭。最小可用的方案是结构化日志 本地指标采集 简单的可视化面板。结构化日志用 JSON 格式每条日志带上task_id、step、duration、status这些字段方便后续用jq或者脚本分析。指标采集可以用 Prometheus 的 Python 客户端把请求数、延迟、错误率这些指标暴露出来。可视化用 Grafana全部离线部署。这套东西搭起来不复杂但没有它内网 Agent 出问题就是黑盒只能靠猜。我踩过的最大一个坑就是早期没做可观测性一个偶发的超时问题查了整整两天最后发现是某个 MCP Server 在特定输入下会卡住。7. 内网 Agent 工程的几条血泪经验最后分享几条只有真正在内网干过才会懂的经验都是踩坑换来的。第一条永远保留一个最小可运行集。内网环境复杂全量部署经常出问题。我的做法是维护一个最小可运行集——一个模型、一个 MCP Server、一个 Skill能在目标机上跑通。全量部署出问题时先用最小集验证基础环境能快速定位是环境问题还是配置问题。第二条所有配置都要有默认值。内网机器千奇百怪环境变量经常缺失。代码里读环境变量时一定要给默认值否则一个缺失的变量就能让整个服务起不来。第三条日志级别要可动态调整。内网排查问题困难如果日志级别写死在代码里出问题时想开 debug 日志就得重新部署。用环境变量或者配置文件控制日志级别出问题时改配置重启即可。第四条交付包里一定要带一份从零到跑通的文档。这份文档不是给写代码的人看的是给半年后接手的人看的。要包含环境要求、安装步骤、启动命令、验证方法、常见问题。我见过太多项目因为文档缺失接手的人花一周才跑起来。第五条版本号要刻进骨子里。模型版本、依赖版本、Skill 版本、MCP Server 版本全部要记录在案。内网升级困难一旦出问题需要回滚没有版本记录就是灾难。我习惯在交付包的根目录放一个VERSIONS.md把所有组件的版本列清楚。这套内网 AI Agent 工程实践从最初的手动搬包到现在相对成熟的离线交付流程前后迭代了三个版本。核心体会是内网工程的难点从来不是技术本身而是把公网环境下习以为常的便利一件件用工程手段补回来。想清楚每一个依赖从哪来、每一个服务怎么起、每一个故障怎么查内网 Agent 就能稳稳跑起来。
返回列表