隔离内网下做 AI Agent,最容易被低估的恰恰不是 Agent 框架本身,而是整个运行环境的适配问题。模型怎么进去、依赖怎么装、工具链怎么拨号、推理服务怎么暴露给 Agent,这些环节任何一个断了,后面全是白搭。我去年帮一家单位落地过一个内网工单处理 Agent,前后折腾了大半年,踩了不少坑,也沉淀了一套比较完整的打法。这篇文章把我实际用过的方案、踩过的坑、验证过的数据都整理出来,希望能给准备在隔离内网里搞 Agent 的同学一些参考。
1. 隔离内网部署 AI Agent 的真实痛点:不是模型跑不动,而是链路全断
先说结论:在内网部署 Agent,最核心的矛盾不是算力不够,而是"依赖外网生态的习惯全都要改"。平时我们开发 Agent,随手 pip install 一个库、调一个云端 API、拉一个开源模型权重,都是默认网络通畅的前提。隔离内网里这些动作全部失效,得换一套思路。
我归纳下来,隔离内网落地 Agent 有五个层面的断点:
- 模型获取断。主流开源模型(比如 Llama、Qwen 系列)都在 HuggingFace 或 ModelScope 上,隔离内网访问不了。模型的权重文件动辄几个 GB 到几十个 GB,怎么合法合规地送进内网,是第一关。
- 依赖库断。Agent 项目通常依赖 LangChain、LlamaIndex、FastAPI、pydantic 等一堆 Python 包。内网没有 PyPI 源,pip install 直接失败。需要准备离线 wheel 包或搭建内网镜像源。
- 推理服务断。不能调云端 API,就得在内网自己部署推理服务。无论是 vLLM、llama.cpp 还是 Ollama,都要考虑硬件适配、量化精度、并发性能。
- 工具调用断。Agent 的核心能力是调用工具,但很多现成的工具链(搜索 API、地图 API、代码执行沙箱)都是外网服务。内网里工具必须换成内网已有的系统,比如内部工单系统、内部知识库、内网数据库。
- 数据回流断。Agent 在运行中会产生日志、轨迹、评估数据,这些数据如果要用于后续微调或优化,必须实现内网闭环。
这五个断点里,第 1 和第 2 个是"搬东西"的问题,第 3 是"跑起来"的问题,第 4 是"有用"的问题,第 5 是"越用越好"的问题。很多人栽在第 1 个就放弃了,实际上只要把流程理顺,每一条都有成熟的内网解法。
提示:在做任何技术选型之前,先花两周把"断点清单"列清楚。不要急着写代码,先确认模型、依赖、推理、工具、数据五条链路各自的准入方案,这决定了整个项目的可行边界。
2. 模型与依赖的离线化准备:权重搬运、镜像搭建、版本锁死
2.1 模型权重的合规搬运方案
隔离内网拿不到 HuggingFace 权重,最常见的官方路径是"外网下载—介质拷贝—内网导入"。具体操作上,我建议直接使用 HuggingFace CLI 或 ModelScope 的 SDK 在有外网的机器上先把模型拉下来,注意要带上完整的文件,不仅仅是 safetensors 权重,还包括 tokenizer、config.json、生成配置等。
以 Qwen2.5-7B-Instruct 为例,下载命令大致是这样的:
# 外网机器上执行 pip install modelscope modelscope download --model Qwen/Qwen2.5-7B-Instruct --local_dir ./qwen2.5-7b-instruct下载完成后,整个目录打包,然后通过 U 盘、光盘或者审批过的摆渡通道拷进内网。这里有一个关键技术点:模型的目录结构不要改动。我见过有人只把 safetensors 拷进去,结果加载时报错找不到 tokenizer,排查半天。最稳妥的做法是:整个模型目录原封不动地拷贝,解压后路径中不要有中文和空格。
如果是更大的模型(比如 70B 级别),物理拷盘不现实,可以考虑用内网的磁带库或大容量存储阵列做中转。这一步的核心原则就一条:完整性校验。拷完以后一定要核对 SHA256 哈希值,一个字节都不能差。
2.2 离线 Python 依赖库的三种打法
依赖库的离线安装,我试过三种方案,从低效到高效排列:
方案一:逐个下载 wheel 包拷入内网。这是最原始的方式。在能联网的机器上,用 pip download 把项目依赖全部拉成 wheel 文件,然后拷进内网 pip install。缺点是非常容易漏包,尤其是传递依赖,经常装到一半发现缺了某个依赖。
方案二:搭建内网 PyPI 镜像源。在隔离内网里找一台 Linux 服务器,用 devpi 或者 nexus 搭建一个私有的 PyPI 源。外网定期同步一次包到移动介质,再导入内网源。项目里统一配置 pip 指向内网源,这样整个团队都能受益。这个方案前期搭一次要半天,但长期最省事。
方案三:用 Docker 镜像整体导入。如果内网允许使用容器运行时(比如 Docker 或 containerd),最优雅的方案是:在外网把整个 Python 环境和依赖打进镜像,然后 docker save 成 tar 包,拷入内网后 docker load。这样依赖、系统库、CUDA 驱动层面的问题全都一次解决。隔离内网多半是有私有镜像仓库的,推到内网仓库即可。
从我的实战经验看,如果你的内网管理比较严格,申请流程复杂,优先方案二或方案三,别用方案一自虐。
2.3 版本锁死是内网 Agent 的隐形炸弹
隔离环境下最难受的一点是:出问题了你没法临时 pip install 修补丁。外网环境下,报了个错,pip install 一个新版本分分钟解决。内网里每一个依赖的版本都要提前锁死,并且要锁得非常保守。
我推荐的策略是:在外网搭建一个与内网完全一致的虚拟环境,先在外网把整个 Agent 项目跑通、跑稳,把所有依赖用 requirements.txt 锁死。然后把这个"验证过的环境"原封不动搬到内网。不要相信"大概兼容"这种话,pip 的依赖解析在离线环境里会让人怀疑人生。
这里我提供一个虚拟环境导出的经验值:
| 依赖层级 | 建议策略 | 说明 |
|---|---|---|
| Python 解释器 | 锁死 3.10 或 3.11 | 很多库对 3.12 支持还不完善,别赌 |
| 核心框架(LangChain/LlamaIndex) | 锁到小版本 | 大版本升级 API 变动频繁 |
| 推理框架(vLLM/llama.cpp) | 锁到 commit 级别 | vLLM 的版本差异对算子优化影响巨大 |
| 传递依赖 | 用 pip freeze 全量记录 | 缺一个都装不起来 |
这个表是我实际受苦后总结的,尤其最后一条,pip freeze 全量记录,能救命的。
3. 内网推理服务选型与性能调优:从 7B 到 70B 的取舍
3.1 三个主流推理引擎的实际对比
隔离内网里做 Agent 的推理,我实际用过 vLLM、llama.cpp、Ollama 三种方案,各有各的适用场景。
vLLM是我最推荐的正向选择,尤其是在 GPU 资源相对充足的情况下。vLLM 的 PagedAttention 机制能显著提升吞吐量,连续批处理做得很好,尤其适合 Agent 这种高频、多轮、并发的调用模式。而且 vLLM 提供了 OpenAI 兼容的 API Server,这意味着 Agent 框架里原来写"调 OpenAI"的代码,几乎零改动就能切到内网服务。
llama.cpp的优势是极致的资源利用,纯 CPU 也能跑,支持各种量化等级(GGUF 格式)。如果内网只有 CPU 机器或者 GPU 显存很小(比如 8GB 以下),llama.cpp 是唯一可行的选择。缺点是并发能力弱,吞吐量比 vLLM 差一个数量级,而且 API 层需要自己做一层兼容封装。
Ollama是三者里最省心的,安装即用,自带模型管理和 OpenAI 兼容接口,model 拉取支持从本地文件导入。它适合"快速验证 Agent 流程"的场景,但到了生产级高并发,性能和可控性都弱一些。
我最终给客户落地的方案是:生产环境用 vLLM,开发调试用 Ollama,纯 CPU 边缘节点用 llama.cpp。三个引擎共用一套权重导出逻辑,切换时只需要改一个 base_url。
3.2 量化精度与显存预算的计算方法
模型量化是内网部署回避不了的话题。我的经验公式是:显存占用约等于权重体积乘以一个系数。以 7B 模型为例:
- FP16(半精度):权重约 14GB,推理时显存建议 20GB 以上
- INT8 量化:权重约 7GB,推理时显存建议 12GB 以上
- INT4 量化(GGUF Q4_K_M):权重约 4GB,推理时显存建议 8GB 以上
这个系数是给 KV Cache 和中间激活值留的余量,实际并发越高,需要的余量越大。
关于精度损失,我做过一组实验对比,用 Qwen2.5-7B-Instruct 在 GSM8K 数学题上跑:
| 精度格式 | 推理速度(token/s) | GSM8K 准确率(相对 FP16 的下降) |
|---|---|---|
| FP16 | 28 | 基线 |
| INT8 | 32 | -0.8% |
| INT4(Q4_K_M) | 41 | -3.5% |
结论很清晰:Agent 这种场景,只要不是做高精度数学推理,INT4 完全够用,速度还能提升接近 50%。但如果 Agent 要频繁做结构化输出(比如 JSON 提取),INT4 偶尔会出现格式错乱,需要在 Agent 层加一层格式校验和重试。
3.3 vLLM 服务启动的坑与配置建议
vLLM 启动服务有一个我在文档里没看到、但实际必须处理的坑:模型加载期间会一次性把所有层都加载到 GPU,显存不够直接就崩。解决方法是调整--gpu-memory-utilization参数,让它不要贪心:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/qwen2.5-7b-instruct \ --served-model-name qwen-agent \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 16 \ --port 8000另外强烈建议加--served-model-name,这相当于给模型起一个稳定的别名。Agent 配置里的 model 字段写死这个别名,以后换了底层模型(比如从 7B 升到 14B),Agent 代码一行都不用改。
还有一点:一定不要省略--max-model-len的显式设置。Agent 的多轮对话会累积 context,尤其当工具返回结果很长时,context 很容易爆炸。如果不限制,VLLM 会根据权重自动算一个最大长度,但那个值往往是 32768,非常吃显存。实际 Agent 场景 8192 足够,除非你的工具动辄拼上千字的检索结果。
4. Agent 框架的内网改造:工具调用、检索增强与 API 适配
4.1 工具调用从"外网服务"迁移到"内网系统"
外网环境下的 Agent 工具,大多是调用云端 API。到了内网,思路要立刻切换:所有工具必须是内网已有的系统或数据源。
我在内网工单 Agent 里实际挂的工具是:
- 工单系统查询(读内网 OA 数据库)
- 知识库检索(向量化后存入内网 Milvus)
- 设备状态查询(通过内网运维平台提供的 HTTP 接口)
- 内部邮件草稿生成(仅生成文本,发送走审批流)
这套工具的本质变化是:从"Agent 调别人"变成了"Agent 接内部系统"。这里有三个工程层面的挑战:
第一,接口鉴权。内网系统自带的鉴权五花八门,有的是 token,有的是签名,有的是内网 IP 白名单。Agent 调用工具时,要设计一个统一的鉴权抽象层,不能让每个 Tool 的实现都去处理各自的鉴权细节。我用的是一个简单的装饰器,统一注入内网服务账号的凭据,这样换工具的时候不会到处埋雷。
第二,响应超时。内网系统如果存在慢 SQL 或者旧接口,响应可能高达几十秒。Agent 的推理循环里,工具调用的超时必须设置合理。我建议默认超时 30 秒,超过直接返回"工具超时"让 Agent 换个策略,而不是一直傻等。
第三,返回内容裁剪。内网接口通常返回大量字段,Agent 的 token 预算有限。每个 Tool 的返回结果应做一层截断,只保留 Agent 决策所需的最小信息。我踩过坑:某个工具返回了一个 5000 字的 JSON,Agent 的 context 立刻膨胀,后续推理质量直线下降。后来规定所有工具返回结果限制在 1200 字以内,超长内容走检索而非直接拼接。
4.2 检索增强:本地知识库的落地路径
隔离内网里做 RAG(检索增强生成),一个重要前提是:embedding 模型也得内网化。这一步很多人会忘。外网项目里,embedding 经常直接用一个 API 就解决了;内网里没有这个 API,需要自己部署一个 embedding 服务。
我推荐的组合是:bge-m3 模型 + Milvus 或 Qdrant 向量库。bge-m3 是 BAAI 开源的 embedding 模型,支持中文效果不错,权重约 2GB,单张消费级显卡就能跑起来。部署方式我是用 FastAPI 包了一层encode接口,统一给知识库入库和查询用。
向量库的选型,内网我偏好 Milvus,理由很简单:它有完整的权限管理、数据持久化、备份恢复机制,符合内网对数据管控的要求。Qdrant 也很轻量,但遇到大规模数据(千万级向量)时,Milvus 更稳。
一个容易忽略的细节:知识库的切分策略要针对 Agent 场景调整。普通的 RAG 文档切分,512 字一段就行。但 Agent 在回答问题时,往往需要"一段完整的操作步骤"或者"一整个配置项说明",切得太碎会导致关键信息散落多个 chunk,检索召回率很低。我把切分策略改为:标题层级优先,按 Markdown 标题或文档章节切块,每块最大 2000 字,块与块之间重叠 200 字。实测召回率提升约 15%。
4.3 Agent 框架的 API 适配层设计
内网 Agent 还要解决一个看似简单但实际很麻烦的问题:框架怎么连上内网推理服务。
主流的 Agent 框架(LangChain、LlamaIndex、自研框架)默认都是连 OpenAI 的接口。内网部署后,只要推理服务开的是 OpenAI 兼容接口,框架层面的适配其实非常简单:
from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="qwen-agent", # 对应 vLLM 的 served-model-name base_url="http://10.x.x.x:8000/v1", # 内网 vLLM 服务地址 api_key="EMPTY", # vLLM 不校验 key,但字段必须保留 temperature=0.3, )唯一的坑是api_key字段,即使内网服务不校验,也必须传一个非空值,否则框架的 HTTP 客户端会在 header 组装时报错。我第一次对接时就卡在这个"EMPTY"字符串上,排查了很久才发现是空字符串导致请求头异常。
如果你用的不是 LangChain 而是自研 Agent,同理,直接把请求发到/v1/chat/completions即可,OpenAI 兼容协议是内网 Agent 与外网生态之间最平滑的桥梁。
5. 打通 Agent 的完整运行时:一个内网工单 Agent 的实战拆解
5.1 整体架构与数据流
我在隔离内网落地的第一个生产级 Agent,是工单处理助手。它的完整数据流如下:
- 用户通过内网 Web 页面提交工单描述
- Agent 接收工单文本,进行意图识别和实体抽取
- Agent 调用工单系统查询接口,拉取关联的历史工单和处理记录
- Agent 调用内网知识库进行 RAG 检索,找到匹配的标准处理方案
- Agent 调用大模型进行推理,生成处理建议(含操作步骤、风险提示)
- 处理建议返回 Web 页面,由人工确认后转化为正式的工单处理动作
整个链路在隔离内网内闭环,全程没有外网依赖。部署形态是:2 台 GPU 服务器(一台跑 vLLM 提供推理,一台跑 embedding + 向量库)+ 若干 CPU 应用服务器(跑 Agent 服务和 Web 前端)。
5.2 每个环节的关键参数
这个 Agent 从上线到现在,我又反复调优,最终比较稳定的参数如下:
| 环节 | 参数项 | 实际取值 | 调优逻辑 |
|---|---|---|---|
| 模型 | 基座模型 | Qwen2.5-14B-Instruct(INT8 量化) | 7B 在复杂工单上逻辑不够严谨,14B 是性价比平衡点 |
| 推理 | 温度 | 0.2 | Agent 是任务型工具,不是创作型,温度要低 |
| 推理 | max_tokens | 1024 | 工单处理建议一般不需要超长输出 |
| 检索 | top_k | 6 | 6 个知识块足够覆盖,太多反而引入噪声 |
| 检索 | 重排 | 加一层 bge-reranker | 重排后准确率提升约 10% |
| 工具 | 超时 | 30 秒 | 内网接口最慢 30 秒内必须有响应 |
| 工具 | 返回截断 | 1200 字 | 防止 context 被工具输出撑爆 |
| Agent | 最大迭代 | 5 轮 | 超过 5 轮还没闭环就提示人工介入 |
这些参数不是一次性调出来的,是在一轮一轮的压测和线上反馈中磨出来的。比如温度从最初的 0.7 一路降到 0.2,是因为发现工单建议里经常出现"可能""或许"这类不确定措辞,这在运维场景里是大忌。降温度之后,输出明显更果断、更可执行。
5.3 实测效果与失败模式分析
上线后我统计了前三个月的运行数据,原文摘录一段内部报告里的核心数据:
- 工单处理建议的采纳率(人工直接确认的比例):67%
- 平均响应耗时:约 4.5 秒(含检索和推理)
- 失败率(Agent 未能生成可执行建议):3.2%
- 高峰期(单日 1000+ 工单)P95 响应时间:7.8 秒
这组数据说明:隔离内网的 Agent 完全是可以达到生产可用级别的。但有一个数字值得注意:3.2% 的失败率。我逐一分析了失败样本,发现主要分三类:
- 工单描述信息不足。用户只写了"服务器有问题",没有任何 IP、报错信息。Agent 反复询问后仍无法定位,最终判定为信息不足。这个占了失败样本的六成。
- 知识库未覆盖的冷门问题。某些特殊设备型号的处理流程,内网知识库没有收录,RAG 检索不到有效内容。占了约三成。
- 工具接口异常。内网某个老系统的接口偶尔返回 500,Agent 拿到异常后重试也还是失败。占比一成左右。
针对这三类失败,我的对策是:对第 1 类,在 Web 前端的工单表单里强制校验关键字段,让用户提交前必须填 IP 和问题类型;对第 2 类,建立"知识库盲区"标记,每次失败的 Agent 轨迹自动归档,定期找人补充知识内容;对第 3 类,工具层加了自动重试和降级策略。
这组失败数据其实说明一个真实规律:内网 Agent 的瓶颈往往不在模型智力,而在数据质量和系统稳定性。把这两件事做好,Agent 的效果会超出预期。
6. 可观测性与持续迭代:内网闭环里的 Agent 优化方法
6.1 全链路追踪:Agent 轨迹的落盘与复盘
隔离内网和外网的最大区别之一,就是没有商业化的可观测性平台可用。Sentry、LangSmith 这类工具基本都连不上。所以我在内网自己搭了一套轻量级追踪方案,核心是把 Agent 的每一步轨迹记录成 JSON 日志。
每条轨迹包含:
- 用户原始请求(脱敏后)
- Agent 的规划信息(ReAct 里的 thought)
- 工具调用的输入输出(截断后的)与耗时
- 最终回复文本
- 用户是否采纳
这些日志落盘以后,我用一个简单的 Python 脚本做离线统计:哪些工具调用最多、哪些工具错误率最高、哪类请求最常失败。这个反馈闭环很重要,没有它,Agent 就是"放养"状态,永远不知道哪里出了问题。
6.2 基于真实轨迹的 Prompt 迭代
我的一个深刻体会是:隔离内网里的 Agent 优化,主要手段不是微调模型,而是迭代 Prompt 和工具设计。
我每两周会挑 30 条"高价值轨迹"(包括成功和失败的),人工复盘。重点看两类内容:
一是 Agent 的"思路跑偏"。比如明明是一个网络配置问题,Agent 却一直在查硬件设备。这时候我会在 System Prompt 里加一句"优先根据报错信息中的关键词判断问题类别",相当于给 Agent 一个先验。
二是工具的"使用率失衡"。有个日志查询工具使用率极低,看轨迹发现是 Agent 压根不知道怎么调用它。我在工具描述里加了一个典型的调用示例,频率立刻上来了。这条经验很值钱:工具描述一定要给例子,别写干巴巴的参数说明。
这种迭代方式不需要重新部署模型,只改 Prompt 和工具配置,在隔离内网环境下特别现实。
6.3 模型升级的灰度策略
隔离内网里还要考虑一个问题:模型版本升级怎么平稳过渡。
我在 vLLM 上实践了一个可行的灰度方案:同时启动两个 vLLM 服务,一个跑旧模型(比如 7B),一个跑新模型(比如 14B),通过 Nginx 按比例分发流量。先让 10% 的流量走新模型,观察一周的采纳率和失败率,确认无退化后逐步放开比例,直到 100% 切换。
这个过程中有个小细节:新旧模型的服务名不能相同。我给每个模型起不同的--served-model-name,Nginx 按路径或 header 分流,避免模型管线混串。
我个人在实际操作中的一个建议是:升级模型前,准备一份固定的"回归测试集"(二三十条真实历史工单),切流量前先用这份测试集在新模型上跑一遍,对比输出质量和格式符合度。过了这关再上灰度,能少踩很多坑。
7. 资源有限时的降级路线:没有 GPU 一样可以先跑通
最后聊一个很多人会遇到的现实约束:内网里申请 GPU 机器周期很长,甚至根本申请不到。如果没有 GPU,这套方案是不是就完全没戏了?我的答案是:没有 GPU 也能先跑通,只是体验和效果要打折扣,但比什么都不做要强得多。
纯 CPU 环境下,我验证过两条可走的路线:
路线一:llama.cpp + GGUF 量化模型跑 7B。在 16 核 CPU 的服务器上,Q4 量化的 7B 模型,推理速度大约在 6~10 token/s。这个速度对复杂的多轮 Agent 来说确实有点着急,但对"单轮问答 + 单次工具调用"这类轻量任务完全够用。我做了一个内部知识问答 Agent,就是跑在这条路线上,用户反馈能接受,只是回答前要等十几秒。
路线二:更小参数的模型,比如 Qwen2.5-3B 或 1.5B。这类模型在 CPU 推理很快,速度能到 20~30 token/s,体验好了很多。代价是复杂推理能力下降明显,更适合只做摘要、分类、简单格式转换等子任务。我把它封装成了一个"文本增强服务",供大一点的 Agent 做前置处理。
一个值得强调的观点是:先跑通再优化,远远好过等资源等半年。CPU 方案虽然性能弱,但能让整个链路先走起来,同时积累数据、验证交互逻辑。等 GPU 批下来,只需要替换推理服务那一层,上层 Agent 几乎不用动。
这个小节的最后,我建议 CPU 环境下务必开启 KV Cache 量化和线程优化。llama.cpp 启动时加--threads参数,指定与 CPU 核数一致,能提升 20% 左右的吞吐。另外记得开启--mlock,把模型常驻内存,避免每次推理都重新加载文件,实测这个开关能把首次请求延迟从 30 秒降到 5 秒以内——对于纯 CPU 内网环境来说,这几乎是性价比最高的一项优化。