1. 为什么要在隔离内网里折腾 AI Agent
先把场景说清楚。所谓隔离内网,就是那种物理上跟公网断开、或者只允许单向数据摆渡的环境,常见于金融、制造、能源、科研院所这类对数据外流极度敏感的机构。你在这种环境里想跑一个 AI Agent,第一反应通常是:模型怎么进来?依赖怎么装?工具怎么调?这三个问题任何一个卡住,整个工程就停在原地。
我前后在三个不同规模的内网环境里落地过 Agent 项目,从最初级的"单机跑个问答机器人"到后来"多 Agent 协作处理工单流",踩的坑足够写一本小册子。这篇就把整套方法论摊开讲,围绕AI Agent、MCP、内网、工程实战、Skills这几个核心词,把从零到跑通的完整路径拆给你看。
适合谁看?如果你手上有内网服务器、有一块能用的 GPU 或者哪怕只有 CPU、并且被要求"数据不出内网",那这篇就是写给你的。不需要你事先懂 MCP 协议细节,也不需要你玩过多 Agent 框架,我会把每个决策背后的理由讲透,让你能直接抄作业。
核心结论先放这儿:内网 Agent 工程的难点从来不是模型本身,而是依赖闭环、工具协议、以及可观测性这三件事。把这三件事解决,剩下的都是体力活。
2. 内网 Agent 的整体架构设计与选型逻辑
2.1 三种典型部署形态与适用边界
内网环境千差万别,我把它归成三类,你对号入座。
| 形态 | 网络条件 | 典型硬件 | 适合的 Agent 规模 | 主要痛点 |
|---|---|---|---|---|
| 单机离线 | 完全物理隔离 | 单台带 GPU 工作站 | 单 Agent、单轮任务 | 模型与依赖全靠摆渡 |
| 内网集群 | 内网互通、无外网 | 多台服务器 + 共享存储 | 多 Agent、并发任务 | 服务发现、资源调度 |
| 半隔离 | 有受控出口或镜像源 | 混合 | 中等规模 | 出口策略、审计 |
我最早做的是单机离线形态,一台 4090 工作站,模型权重用移动硬盘拷进去,Python 依赖提前在外网机器上pip download打成离线包。这种形态最纯粹,也最能暴露"依赖闭环"这个核心问题。后来做内网集群,才引入 MCP 做工具标准化,因为多 Agent 之间要共享工具能力,硬编码调用根本维护不动。
选型的第一原则:能单机就别集群。很多人一上来就想搞分布式 Agent 编排,结果连一个模型服务都没跑稳。内网环境的调试成本极高,每改一次配置可能要重新走一遍摆渡流程,所以架构越简单越好。
2.2 模型层:本地推理服务怎么选
模型这块,内网里你基本只有两条路:本地跑开源权重,或者内网自建的推理网关。后者通常是单位统一部署的,你只管调 API。如果是前者,选型要考虑三件事——显存、量化、推理框架。
显存是硬约束。一个 7B 模型 FP16 大概要 14GB 显存,INT4 量化后压到 4GB 左右。我实测下来,如果只有单张 24GB 卡,跑 14B 的 INT4 量化模型是比较舒服的平衡点,留出余量给 KV Cache 和并发。32B 以上就得上多卡或者更激进的量化,质量损失开始明显。
推理框架我推荐vLLM或llama.cpp二选一。vLLM 吞吐高、支持连续批处理,适合有并发需求的场景;llama.cpp 部署简单、CPU 也能跑、量化格式丰富,适合单机轻量场景。内网里 vLLM 的坑在于它依赖一堆 CUDA 相关的 wheel,离线安装时版本对齐很折磨,我后面会专门讲。
提示:内网选模型不要盲目追大。我见过有人硬上 70B,结果单次推理 30 秒,Agent 的多轮工具调用直接超时。7B 到 14B 的量化模型,配合好的提示词工程,在多数企业任务上够用。
2.3 工具层:为什么 MCP 是内网 Agent 的关键拼图
MCP 全称 Model Context Protocol,你可以把它理解成"Agent 和外部工具之间的 USB 接口"。在没有 MCP 之前,每个 Agent 要调数据库、调文件系统、调内部 API,都得写一套专属的适配代码,工具一多就是灾难。MCP 把这些能力抽象成标准协议,Agent 只要会说 MCP,就能接任何实现了 MCP 的工具服务。
内网场景下 MCP 的价值被放大了。因为内网工具往往是异构的——有老掉牙的 SOAP 接口、有只能命令行调用的脚本、有需要特定认证的内部系统。如果每个都硬编码进 Agent,代码会烂成一团。用 MCP 把它们统一封装成 Server,Agent 侧只维护一份客户端逻辑,扩展性完全不一样。
MCP 的核心概念就三个:Resources(资源,只读数据)、Tools(工具,可执行动作)、Prompts(提示模板)。Agent 通过标准化的 JSON-RPC 跟 MCP Server 通信,Server 负责把请求翻译成对内部系统的实际操作。这个抽象层是内网 Agent 工程能规模化的前提。
2.4 Skills:把领域知识沉淀成可复用资产
Skills 这个词最近很热,本质上是把特定任务的提示词、工具组合、执行流程打包成一个可复用的能力单元。比如"生成周报"是一个 Skill,"查询库存并生成补货建议"是另一个 Skill。Agent 面对任务时,先匹配 Skill,再按 Skill 定义的流程执行。
内网里 Skills 的意义在于知识沉淀。一个老师傅知道怎么处理某类工单,你把这套经验写成 Skill,Agent 就能复现。Skills 通常以文件形式存在(Markdown 或 YAML 描述 + 配套脚本),天然适合内网的版本管理和摆渡分发。
我一般把 Skills 分成三层:原子 Skill(单个工具调用)、组合 Skill(多工具编排)、领域 Skill(带业务规则的完整流程)。分层的好处是复用,原子 Skill 能被多个组合 Skill 引用,改一处全局生效。
3. 离线依赖闭环:内网工程的第一道生死关
3.1 依赖摆渡的完整流程
内网装不了包,这是所有痛苦的根源。我的标准做法是"外网准备、离线打包、内网还原"三步走。
外网准备阶段,在一台跟内网目标机器操作系统版本、Python 版本、架构完全一致的机器上操作。这一步极其关键,我踩过最惨的坑就是外网用 Ubuntu 22.04 打包,内网是 CentOS 7,glibc 版本不兼容,一堆 wheel 装不上。所以第一件事是确认内网机器的uname -a、python --version、pip --version。
打包命令的核心是pip download:
# 在外网机器上执行,下载所有依赖到本地目录 pip download -r requirements.txt -d ./offline_packages \ --platform manylinux2014_x86_64 \ --python-version 310 \ --only-binary=:all:这里--platform和--python-version必须跟内网目标严格匹配。--only-binary=:all:强制只下载预编译 wheel,避免下载源码包导致内网编译失败。如果某些包没有对应平台的 wheel,就得单独处理,要么找替代包,要么在外网交叉编译好再拷进去。
内网还原时:
pip install --no-index --find-links=./offline_packages -r requirements.txt--no-index禁止访问任何在线源,--find-links指向本地包目录。这样即使内网有残留的 pip 配置指向外网,也不会触发网络请求。
3.2 CUDA 与深度学习框架的版本对齐
这是内网 Agent 工程里最容易翻车的部分。PyTorch、CUDA、cuDNN、驱动四者版本必须严格对齐,错一个就是CUDA error: no kernel image is available。
我的对齐策略是以驱动版本为锚点倒推。先在内网机器上跑nvidia-smi,看驱动版本和它支持的最高 CUDA 版本。然后选一个不超过这个上限的 CUDA 版本,再选对应这个 CUDA 编译的 PyTorch wheel。
| 驱动版本 | 支持最高 CUDA | 推荐 PyTorch |
|---|---|---|
| 535.x | 12.2 | 2.1.x / 2.2.x |
| 525.x | 12.0 | 2.0.x / 2.1.x |
| 470.x | 11.4 | 1.13.x |
下载 PyTorch 时一定要用官方指定的 index URL,比如https://download.pytorch.org/whl/cu121,这样拿到的 wheel 是预编译好对应 CUDA 的。离线打包时把这个 index 下的包全下下来。
注意:vLLM 对 PyTorch 和 CUDA 版本极其敏感,它的 wheel 通常绑定特定 torch 版本。装 vLLM 之前先把 torch 版本定死,然后找匹配的 vLLM wheel,不要反过来。
3.3 模型权重的摆渡与校验
模型权重动辄几个 GB 到几十 GB,摆渡是个体力活。我的做法是分片压缩 + 校验和。
# 外网侧:分片压缩 tar -czf model.tar.gz -C ./model_dir . split -b 2G model.tar.gz model_part_ # 生成校验文件 sha256sum model_part_* > checksums.txt内网侧先校验再合并:
sha256sum -c checksums.txt cat model_part_* > model.tar.gz tar -xzf model.tar.gz -C ./model_dir校验这一步千万别省。我有一次摆渡 40GB 权重,中间一个分片损坏,加载时报了个莫名其妙的 shape mismatch,排查了大半天才发现是文件坏了。从那以后校验和成了我的肌肉记忆。
4. MCP 服务在内网的落地实操
4.1 MCP Server 的最小实现
MCP Server 本质是一个实现了标准协议的进程,对外暴露 Resources、Tools、Prompts。用 Python 写一个最小 Server 大概长这样:
from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app = Server("internal-tools") @app.list_tools() async def list_tools(): return [ Tool( name="query_inventory", description="查询内部库存系统", inputSchema={ "type": "object", "properties": { "sku": {"type": "string", "description": "商品编码"} }, "required": ["sku"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "query_inventory": result = do_internal_query(arguments["sku"]) return [TextContent(type="text", text=result)] async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options())这个 Server 通过 stdio 跟 Agent 通信,Agent 启动时把它作为子进程拉起。内网里 stdio 传输最省事,不涉及端口和网络配置。
4.2 把异构内部系统封装成 MCP Tool
内网工具千奇百怪,封装的核心思路是在 MCP Server 内部做适配,对外只暴露干净的接口。
比如一个只能命令行调用的老系统,Server 内部用subprocess调它,解析输出,转成结构化文本返回。一个需要认证的内部 API,Server 内部维护 token 刷新逻辑,Agent 完全无感。一个数据库查询,Server 内部管理连接池,避免每次调用都重连。
我封装过一个特别恶心的场景:内部系统只提供 Windows 客户端,没有 API。最后的方案是在一台 Windows 机器上跑一个 MCP Server,用 UI 自动化脚本操作客户端,Server 通过内网 HTTP 暴露给 Linux 上的 Agent。虽然丑,但能跑通,这就是内网工程的现实。
提示:MCP Server 的粒度要适中。太细会导致 Agent 要调几十次工具才能完成一个任务,太粗则失去灵活性。我的经验是一个 Tool 对应一个业务动作,比如"查询订单"是一个 Tool,"取消订单"是另一个,而不是把整个订单系统塞进一个 Tool。
4.3 MCP 通信方式的选择:stdio 还是 SSE
MCP 支持多种传输方式,内网里主要用两种:stdio和SSE(Server-Sent Events)。
stdio 适合 Agent 和 Server 在同一台机器、Server 生命周期跟 Agent 绑定的场景。优点是零网络配置、启动简单。缺点是 Server 崩溃会拖垮 Agent,且无法跨机器共享。
SSE 适合 Server 独立部署、多个 Agent 共享的场景。Server 跑成一个 HTTP 服务,Agent 通过 URL 连接。内网里 SSE 要注意端口规划和防火墙策略,但一旦跑通,扩展性远好于 stdio。
我的选择逻辑:单机单 Agent 用 stdio,集群多 Agent 用 SSE。混合场景可以两者并存,核心工具用 SSE 共享,私有工具用 stdio 本地拉起。
5. Skills 工程化:从提示词到可复用能力
5.1 Skill 的文件结构设计
一个规范的 Skill 应该包含描述文件、提示词模板、以及可选的配套脚本。我用的结构是这样:
skills/ inventory_replenish/ SKILL.md # 描述、触发条件、执行流程 prompt.md # 提示词模板 scripts/ calc_safety_stock.py config.yaml # 参数配置SKILL.md是核心,用自然语言描述这个 Skill 干什么、什么时候触发、需要哪些工具、执行步骤是什么。Agent 加载 Skills 时先读这个文件做匹配。
# 库存补货建议 ## 触发条件 用户询问某商品是否需要补货,或要求生成补货计划。 ## 所需工具 - query_inventory - query_sales_history - calc_safety_stock ## 执行流程 1. 调用 query_inventory 获取当前库存 2. 调用 query_sales_history 获取近 30 天销量 3. 调用 calc_safety_stock 计算安全库存 4. 对比当前库存与安全库存,生成建议这种结构的好处是人可读、机可解析。新同事看一遍就知道这个 Skill 怎么工作,Agent 也能从中提取执行逻辑。
5.2 Skill 的匹配与调度机制
Agent 面对一个任务,怎么知道该用哪个 Skill?我实践下来有两套机制。
第一套是关键词匹配,简单粗暴但有效。Skill 的SKILL.md里定义触发关键词,Agent 收到任务后做字符串匹配,命中就加载。适合 Skill 数量少(几十个以内)的场景。
第二套是向量检索,把所有 Skill 的描述向量化,任务来了先做语义检索,取 Top-K 候选再让模型选。适合 Skill 上百个的场景。内网里做向量检索需要本地 embedding 模型,我一般用 BGE 系列的小模型,几百 MB,CPU 也能跑。
调度上要注意Skill 的优先级和互斥。有些 Skill 不能同时激活,比如"查询模式"和"修改模式"的 Skill 同时加载会让 Agent 行为混乱。我在 Skill 配置里加了exclusive_group字段,同组的 Skill 只能激活一个。
5.3 用 Skills 沉淀领域知识的实战案例
讲个真实案例。某制造企业内部有个"设备故障诊断"流程,老师傅凭经验听声音、看参数就能判断故障类型。我们把这套经验拆成了 5 个原子 Skill 和 1 个组合 Skill。
原子 Skill 包括:读取设备实时参数、查询历史故障记录、匹配故障特征库、生成诊断报告、推送工单。组合 Skill 定义诊断流程:先读参数,再查历史,匹配特征库,如果置信度高就生成报告,否则推送人工工单。
上线后,Agent 处理常见故障的准确率到了 80% 以上,老师傅只需要处理疑难杂症。关键是这套 Skill 是可迭代的——每次老师傅纠正了 Agent 的判断,我们就更新特征库,Agent 越来越准。
提示:Skill 的迭代要有版本管理。内网里用 Git 做本地仓库就行,每次修改打 tag,出问题能回滚。我见过有人直接改线上 Skill 文件,改崩了没法恢复,血的教训。
6. 并发与性能:内网 Agent 扛并发的实战调优
6.1 并发瓶颈到底在哪
很多人一上来就优化模型推理,其实内网 Agent 的并发瓶颈往往不在模型,而在工具调用的串行等待。一个任务要调 5 个工具,每个工具平均 2 秒,串行就是 10 秒,模型推理可能只占 1 秒。
所以优化的第一优先级是工具调用并行化。MCP 协议本身支持并发调用,Agent 框架要能把无依赖的工具调用并发发出去。比如"查询库存"和"查询销量"没有依赖关系,可以同时发,等两个都回来再算安全库存。
第二优先级是连接复用。MCP Server 到内部系统的连接要池化,别每次调用都新建连接。数据库连接池、HTTP 连接池都是标配。
第三优先级才是模型推理优化。vLLM 的连续批处理能把并发吞吐拉高好几倍,但前提是你的请求能批起来。如果 Agent 是串行发请求,批处理也救不了。
6.2 实测并发参数与压测方法
我在一台 4090 上做过压测,模型是 14B INT4,vLLM 部署。测试不同并发下的表现:
| 并发数 | 平均延迟 | 吞吐 (tokens/s) | 显存占用 |
|---|---|---|---|
| 1 | 1.2s | 45 | 12GB |
| 4 | 1.8s | 140 | 16GB |
| 8 | 3.5s | 210 | 20GB |
| 16 | 8.2s | 230 | 23GB |
可以看到并发到 8 以后延迟上升明显,吞吐增长放缓。这就是显存和计算资源的边界。生产环境我一般把并发控制在 4 到 8 之间,留出余量应对突发。
压测工具用locust或自己写脚本都行,关键是模拟真实任务分布。别只压单轮问答,要压带工具调用的多轮任务,否则测出来的数字没意义。
6.3 限流、降级与超时策略
内网资源有限,必须有保护机制。
限流:Agent 入口做令牌桶限流,超过阈值的请求排队或拒绝。我一般按 GPU 显存反推最大并发,再打个 7 折作为限流阈值。
降级:模型服务挂了怎么办?我的做法是准备一个规则引擎兜底,简单任务走规则,复杂任务才走模型。模型不可用时自动切到规则模式,保证核心功能不中断。
超时:每个工具调用都要设超时,别让一个卡死的工具拖垮整个 Agent。我一般设 10 秒,超时后返回错误让 Agent 决定重试还是放弃。模型推理超时设 30 秒,超过就中断。
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", "tool": tool.name}这套组合拳下来,Agent 在资源受限的内网里也能稳定扛住日常负载。
7. 常见问题与排查技巧实录
7.1 依赖与模型加载类问题
问题:ImportError: libcudart.so.12: cannot open shared object file
这是 CUDA 运行时库没找到。排查顺序:先ldconfig -p | grep cudart看系统里有没有,没有就说明 CUDA toolkit 没装或路径没配。内网里最省事的做法是把 CUDA 库路径加到LD_LIBRARY_PATH,或者干脆用 conda 装一个自带 CUDA 运行时的环境。
问题:模型加载报shape mismatch或unexpected key
九成是权重文件损坏或版本不匹配。先校验文件 sha256,再确认模型权重和推理框架版本匹配。我遇到过用新版 vLLM 加载旧版权重的情况,报错信息完全看不出是版本问题,折腾很久。
问题:pip install报Could not find a version that satisfies the requirement
离线安装时这个错误通常是 wheel 的平台标签不匹配。用pip debug --verbose看当前环境支持的平台标签,再对比下载的 wheel 文件名。不匹配就重新下载对应平台的。
7.2 MCP 通信类问题
问题:Agent 启动 MCP Server 后无响应
先确认 Server 进程是否真的起来了,ps aux | grep mcp看一眼。如果进程在但无响应,多半是 stdio 缓冲问题。Python 的 stdout 默认带缓冲,MCP 通信要强制刷新。在 Server 入口加sys.stdout.reconfigure(line_buffering=True)或启动时加-u参数。
问题:SSE 模式下 Agent 连不上 Server
排查三步:Server 是否监听正确端口(netstat -tlnp)、防火墙是否放行、Agent 配置的 URL 是否正确。内网里经常是防火墙问题,尤其是跨网段访问。
问题:工具调用返回乱码
编码问题。MCP 默认 UTF-8,但内部系统可能返回 GBK。在 Server 里统一做编码转换,别指望 Agent 侧处理。
7.3 性能与稳定性类问题
问题:Agent 跑一段时间后变慢
先看显存是不是泄漏了。vLLM 长时间运行可能有 KV Cache 碎片,定期重启服务能缓解。再看工具连接池是不是满了,连接没释放会耗尽池子。
问题:并发高了以后任务失败率上升
大概率是超时设置太紧。并发高时每个请求的排队时间变长,原来的超时阈值不够用。动态调整超时,或者加排队机制。
问题:Skill 匹配错误,Agent 用了不该用的 Skill
检查 Skill 的触发关键词是否过于宽泛。我遇到过"查询"这个词被三个 Skill 同时命中,Agent 随机选了一个。解决办法是给关键词加权重,或者用更具体的触发条件。
7.4 问题速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 模型加载失败 | 权重损坏/版本不匹配 | 校验 sha256,核对版本 |
| MCP 无响应 | stdio 缓冲/进程未起 | 检查进程,加 -u 参数 |
| 工具调用超时 | 内部系统慢/连接池满 | 看连接池,调超时 |
| 并发失败率高 | 超时太紧/资源不足 | 动态超时,限流 |
| Skill 匹配错 | 关键词冲突 | 加权重,细化触发条件 |
| 显存溢出 | 并发过高/泄漏 | 降并发,定期重启 |
8. 内网 Agent 工程的可观测性建设
内网里没有现成的 APM 工具,可观测性得自己搭。我的最小方案是结构化日志 + 本地指标 + 简单看板。
日志用 JSON 格式,每条记录包含时间戳、任务 ID、Agent 状态、工具调用、耗时、结果。这样出问题能快速定位是哪个环节慢。日志写到本地文件,按天轮转,别写数据库,内网数据库资源紧张。
指标用 Prometheus 的 Python 客户端暴露,本地起一个 Prometheus 抓取,Grafana 做看板。核心指标包括:任务成功率、平均延迟、工具调用分布、模型推理耗时、显存占用。这套东西在内网里跑完全没问题,都是本地服务。
from prometheus_client import Counter, Histogram task_total = Counter('agent_task_total', 'Total tasks', ['status']) task_duration = Histogram('agent_task_duration_seconds', 'Task duration') tool_calls = Counter('agent_tool_calls_total', 'Tool calls', ['tool_name'])看板上我最关注三个图:任务成功率趋势、P95 延迟趋势、工具调用热力图。成功率掉了说明有系统性问题,延迟涨了说明资源紧张,工具热力图能看出哪些工具是瓶颈。
提示:内网日志要注意脱敏。Agent 处理的可能是敏感业务数据,日志里别记原始内容,记哈希或摘要就行。这是合规底线。
9. 我踩过的几个大坑与经验总结
第一个坑是过早引入多 Agent。我一开始就搞了 Planner、Executor、Reviewer 三个 Agent 协作,结果调试成本爆炸,一个任务出问题要追三个 Agent 的日志。后来退回单 Agent + Skills 架构,反而稳定高效。多 Agent 不是不能用,但要在单 Agent 确实扛不住复杂任务时才上。
第二个坑是忽视 Skill 的版本管理。早期直接改线上 Skill 文件,有次改错了一个计算逻辑,Agent 连续给出错误建议,等发现时已经影响了一批工单。现在所有 Skill 改动都走 Git,改完先在测试环境验证,再合并到生产。
第三个坑是模型选型贪大。为了追求效果上了 32B 模型,结果单次推理 5 秒,多轮任务直接超时。换成 14B 量化模型后,配合更好的提示词,效果没差多少,速度翻了三倍。内网场景下,响应速度往往比模型能力更重要。
第四个坑是没做工具调用的幂等。Agent 重试机制触发后,同一个"创建工单"工具被调了两次,产生了重复工单。后来所有写操作工具都加了幂等键,重试时先查再写。
这些坑的共同点是:都是工程问题,不是 AI 问题。内网 Agent 工程的本质是把 AI 能力塞进一个受限的工程环境里,工程功底比 AI 知识更决定成败。
最后分享一个实用技巧:内网里准备一个"应急 Skill 包",包含最基础的几个工具(文件读写、简单查询、日志查看),当复杂 Skill 出问题时能快速降级到基础能力,保证 Agent 不完全瘫痪。这个包我建议单独维护,别跟业务 Skill 混在一起,出问题时能快速定位和恢复。