隔离内网AI Agent落地:离线部署、Rust架构与Token优化实践

发布时间:2026/10/8 15:42:04
隔离内网AI Agent落地:离线部署、Rust架构与Token优化实践 前几个月接了个不太好啃的活把一个 AI Agent 部署到和生产外网彻底隔离的内网环境里。模型、依赖、工具、数据全部要自己背进去既要让它能回答问题还得让它能调内网的 API、查数据库、按预设流程干活。整个过程中我最大的体会是在隔离内网里做 Agent技术上真正难的已经不是 Agent 本身而是怎么在断网条件下把一套云端依赖很重的系统搬进现实。这篇文章把我从需求拆解、架构选型到落地的完整过程记录下来重点覆盖主流 Agent 架构怎么取舍、为什么会出现基于 Rust 的 AI Agent这种方案、内网里 Token 到底意味着什么以及模型和依赖离线导入的具体做法。适合正在做私有化部署、数据不出域、生产网隔离场景的工程师参考。1. 隔离内网做 AI Agent真正的三道坎很多人一听内网部署 AI Agent第一反应是找一台 GPU 机器装个模型就完事。真做起来才发现隔离内网带来的问题不是一个点而是一整条链路的断供。1.1 第一道坎模型进不来没有大脑Agent 的核心是大模型大模型在开放环境里可以直接调用云厂商的推理 API但隔离内网里不存在这条路。所有推理必须落到本地模型上。这就连带出一串决策模型文件怎么通过安全审查进入内网用什么推理引擎跑Ollama、vLLM、llama.cpp显存不够怎么办是量化还是裁剪上下文CPU 只跑的方案能不能先顶住业务。我见过不少项目在这道坎上反复折腾模型文件倒是拷进去了结果量化和引擎版本不匹配服务起不来或者模型太大GPU 显存装不下只能把上下文窗口从 8K 砍到 2K效果明显变差。1.2 第二道坎依赖装不上生态断供在开放环境里pip install、cargo add 都是顺手敲一下的事。内网完全没外网Python 包下不下来Rust crate 拉不到连基础镜像都可能没有缓存。想用新一点的技术栈就得先把整个依赖树搬到内网。Python 这边需要 pip download 然后带包进去离线安装Rust 这边要用 cargo vendor 把 crate 源码拷贝进去再离线构建容器方案需要 docker save 再 docker load镜像体积分分钟几个 G。这还不是最折磨人的。最折磨的是装到一半发现少了个传递依赖又要重新走一遍导入流程。离线环境的依赖问题本质上是个供应链管理问题必须在进入内网之前就把整棵依赖树完整梳理清楚。1.3 第三道坎没有现成 APIAgent 没有手脚Agent 和普通聊天机器人最大的区别是它会动手——查数据库、调业务接口、操作内部系统。隔离内网的业务系统往往接口不规范、权限体系复杂、文档缺失甚至很多系统只能通过堡垒机跳板访问。这让工具层成为整个项目里工作量最大的部分。模型只负责理解意图真正执行动作的是代码代码要接的是五花八门的内部系统。做 Agent 之前先把工具接口理清楚比选模型还重要。2. Agent 架构选型别急着抄云端那套2.1 主流架构到底长什么样现在网上讨论AI Agent 主流架构基本绕不开这几种范式ReAct 循环模型交替输出 Thought思考和 Action行动行动调用工具后得到 Observation观察结果再进入下一轮循环直到拿到答案。这是最经典、最易于理解的结构适合任务链路不长的场景。Plan-and-Execute模型先拆解计划再把计划里的每个步骤交给执行器依次完成。适合多步骤任务但对计划质量要求高一旦计划有误后面全崩。多 Agent 编排让多个角色化 Agent 分工协作比如一个规划 Agent、一个工具调用 Agent、一个质检 Agent。表现上限高但隔离内网里资源有限跑多个 Agent 的成本和稳定性都是问题。单 Agent 带工具最简单也最实用。一个 Agent 挂一个工具清单按需调用。很多生产环境项目落地用的其实是这种。如果你去搜ai agent 主流架构看到的材料大部分来自开放云环境默认你随时可以调用外部模型和能力。隔离内网做架构选型时这些默认前提全都不成立。2.2 隔离内网给架构加了哪些约束我在内网做过几轮方案对比后发现架构设计必须服从四个硬约束第一模型是有限的本地算力不是无穷的 API。多 Agent 编排意味着多份上下文拷贝Token 消耗成倍增长推理延迟和显存压力都扛不住。所以能单 Agent 解决的就不要上多 Agent。第二工具调用必须是确定性优先。开放环境里工具调用失败可以重试、可以请求模型换个参数内网里很多系统接口不稳定Agent 一旦反复调用失败会陷入循环烧 Token。架构上要给工具调用加超时、重试上限和降级策略。第三上下文窗口是稀缺资源。内网业务往往涉及长文档、大量数据库记录光靠把所有内容塞进 Prompt 显然不行。架构里必须有取舍、摘要和检索机制。第四审计要求高。内网系统对操作留痕有严格要求Agent 的每一步思考、每个工具调用参数、每次返回结果都要能回溯。这个在架构设计阶段就要预留日志埋点。2.3 一个能复用的最小架构我最终落地的是一个偏保守的单 Agent 工具总线结构入口层提供 HTTP 接口接收业务请求可以是 Chat 补全格式也可以封装成内部 RPC。编排层跑 ReAct 循环。维护消息历史调用模型解析输出执行工具回填结果。工具总线统一管理内网工具注册、参数校验、权限校验、调用超时和错误归一化。记忆层短期记忆用上下文窗口内的消息列表长期记忆用向量知识库和会话摘要。推理层本地模型推理服务。这个结构没有花哨的设计但每一层职责清晰出了问题很容易定位。更重要的是它对算力要求低单人维护成本可控符合内网项目稳定优先的调性。3. 技术栈掂量为什么我会考虑 Rust3.1 Python 生态成熟但离线部署很痛做 AI 项目大部分人的第一选择是 Python。推理、数据处理、模型微调这些环节的 Python 生态确实无可替代。但隔离内网环境里Python 的部署问题非常真实目标机器往往没有编译器很多科学计算包需要 wheels 或源码编译Python 解释器和虚拟环境版本差异会带来各种诡异问题生产网不允许随意安装一堆动态库系统干净得像一张白纸。我最早用 Python 搭了一套 Agent 服务流程验证没问题但往内网迁移时光是把环境完整无缺地搬进去就花了快两天。后来换成 Rust 做核心服务才发现这条路有多舒服。3.2 Rust 做 Agent 引擎的真实优势现在基于 Rust 语言 AI Agent能成为热词不是没道理。Rust 的优势在隔离内网场景里体现得特别明显单二进制部署编译成一个静态链接的可执行文件拷贝进去就能跑不需要装解释器、不需要管理依赖包。这对内网部署是降维打击。内存安全与稳定性长驻进程、大量并发请求、工具调用频繁出错的环境下Rust 的内存安全特性让运行期崩溃概率低很多。并发能力强基于 tokio 的异步运行时处理多个会话、多个工具调用同时执行资源占用控制得很好。有现成的 Agent 生态像 rig、llm-chain、genai 这类 crate 已经能覆盖模型调用、工具定义等基础能力虽然不如 Python 丰富但做骨架足够了。当然Rust 也有短板生态毕竟不如 Python想做复杂数据分析或数据处理时很吃亏开发效率也没有 Python 高。所以我不建议全 Rust 化而是建议引擎用 Rust手脚用 Python。3.3 我的组合方案Rust 当骨架Python 当手脚最终我采用的混合方案是Agent 编排服务、HTTP 网关、工具调用调度用 Rust 写。这部分是核心链路要求稳定、高效、易部署。数据预处理、文档切分、向量化、评测脚本用 Python 写。这部分迭代频繁、依赖大量 AI 库但不需要常驻在线都是离线任务可以在有网环境的开发机上做好后再把成果导入内网。推理引擎用 llama.cpp 或 vLLM。前者是 C 核心后者是 Python 服务但都通过 HTTP API 给 Agent 引擎调用语言差异被隔离在网络层。这样各取所长。核心服务的更新迭代通过 Rust 二进制替换Python 侧改动只影响离线数据准备流程不会影响在线稳定。3.4 Token 到底是什么意思内网里它指什么很多刚接触 Agent 的人会问ai agent token 是什么意思。在 Agent 工程里Token 的含义相差很大必须分两层看。第一层Token 是大模型处理文本的最小单位。一个 Token 不是一个字可能是半个词、一个词甚至几个字符。大模型的上下文窗口用 Token 数量表示比如 8K 上下文意味着模型一次能看到约 8000 个 Token 的内容。第二层Token 是 Agent 编排的资源计量单位。Agent 的每一轮工具调用循环都要把历史对话重新发给模型历史越长消耗的 Token 越多。开放 API 场景里 Token 直接对应钱隔离内网本地部署里 Token 不直接收费但它对应的是显存占用、计算时间和吞吐量。我在内网环境里专门做了 Token 监控原因很实际上下文窗口被塞满后Agent 会忘掉前面的指令回答质量断崖式下降Token 消耗过多时推理延迟直线上升用户体验变差多用户并发场景下Token 总消耗决定了是否需要扩容 GPU。所以在内网做 Agent 优化一定程度上就是做Token 成本优化。能用一句话说清楚的事别让模型看三段历史能检索到的内容别全文塞进 Prompt。4. 离线模型与依赖准备前置工作最磨人4.1 把外网的在线安装改成离线搬运隔离内网部署的第一步是把所有依赖提前准备好。这一步没有任何技巧就是细心加耐心。Python 依赖我用的方案是 pip download# 在有网环境的开发机上先锁定版本再下载 pip download -r requirements.txt -d ./offline_packages \ --platform manylinux2014_x86_64 \ --python-version 3.11 \ --only-binary:all: # 目标内网机器上离线安装 pip install --no-index --find-links./offline_packages -r requirements.txtRust 依赖用 cargo vendorcargo vendor ./vendor # 配置 .cargo/config.toml cat .cargo/config.toml EOF [source.crates-io] replace-with vendored-sources [source.vendored-sources] directory vendor EOF cargo build --release --offline容器镜像用 docker save 和 docker load 搬运docker save nginx:latest -o nginx.tar # 内网导入 docker load -i nginx.tar这里我吃了不少亏给出三个提醒依赖版本一定要锁定不锁版本的话离线包清单和实际代码对不上装完必炸Python 的 --only-binary 很关键很多机器没有编译工具链源码包装起来就是灾难Rust 的 vendor 目录体积不小但胜在完整拷贝进去后 target 机器可以完全离线构建。4.2 大模型怎么进内网下载、校验、量化模型文件通常有十几个 G 到几十个 G不能靠日常 U 盘搬运解决。我的流程是先在开放环境的机器上下载原始模型记录 SHA256 哈希值。进入内网前模型文件通过审批流程拷贝进去落盘后校验哈希确保文件完整。然后根据算力选择量化规格GPU 显存充足如 24G 以上可以直接用 16-bit 精度或 GGUF 的 Q6/Q8效果最好GPU 显存有限8G~16G推荐 GGUF 的 Q4_K_M质量和体积比较平衡只有 CPU同样用 Q4_K_M但上下文窗口要控制小一点推理速度快不起来适合内部非实时场景。Ollama 内网导入模型的做法也值得一提# 在有网环境先拉取并导出 ollama pull qwen2.5:14b ollama show qwen2.5:14b --modelfile # 在内网服务器创建 Modelfile 并加载 ollama create qwen2.5-internal -f Modelfile注意 ONNX 格式和 GGUF 格式的区别。不同推理引擎支持的格式不同一定要提前确认目标引擎的格式要求否则模型文件搬进去了才发现跑不了返工成本极高。4.3 在内网建一个软件源而不是发一堆压缩包刚开始我做离线部署是手动发压缩包结果每个同事的机器都装得不一样。后来我意识到隔离内网一样可以建自己的软件源Python 包用 devpi 或 Nexus 搭建内网 PyPI 源把项目需要的包同步上去Rust crate建一个本地的 crate registry 镜像或者干脆用 cargo vendor 配合内网 Git 仓库容器镜像用 Harbor 做内网镜像仓库开发机统一从内网源拉镜像。建源的前期工作量比较大但一旦建成后续部署、扩展、换机器都省心很多。团队协作时统一的依赖来源能避免大量无谓的版本冲突。4.4 第一台机器跑通的最小验证清单我建议在任何模型训练、Agent 编排之前先拿一台机器做最小链路验证按这个清单过一遍[ ] 推理引擎服务能启动模型文件加载成功[ ] 通过 HTTP 请求能正常返回对话结果[ ] 同一个请求连续调用两次结果稳定可用[ ] Agent 编排进程能连上推理服务完成一次带工具调用循环[ ] 查询条件和返回结果的日志能完整落盘[ ] 服务在无外网环境下重启后仍能正常运行。这个清单看着简单却能筛掉七八成部署问题。别一上来就直接搭完整系统先把最小闭环跑通心里才有底。5. 内网 AI Agent 的落地实现从一个问答 Agent 讲起5.1 裸模型并不是 Agent先打通推理服务Agent 的第一步是让业务代码能稳定地调用本地模型。我用 Ollama 起推理服务暴露一个 OpenAI 兼容接口OLLAMA_HOST0.0.0.0 OLLAMA_PORT11434 ollama serve然后 Rust 侧用 HTTP 客户端调用use anyhow::Result; use reqwest::Client; use serde_json::json; pub async fn chat( model: str, messages: [serde_json::Value], ) - ResultString { let client Client::builder() .timeout(std::time::Duration::from_secs(60)) .build()?; let body json!({ model: model, messages: messages, stream: false, temperature: 0.2, }); let resp client .post(http://127.0.0.1:11434/v1/chat/completions) .json(body) .send() .await?; let data: serde_json::Value resp.json().await?; // 根据 OpenAI 兼容格式提取 content let content data[choices][0][message][content] .as_str() .unwrap_or_default(); Ok(content.to_string()) }这一步跑通了后面所有 Agent 功能都建立在它之上。注意超时一定要设置模型推理慢起来能拖几分钟HTTP 客户端默认无超时的话整个服务会被拖死。5.2 让 Agent 学会调用内网工具Function Calling 是怎么一回事很多人以为Function Calling是模型真的去执行函数其实不是。本质是模型根据当前的对话输出一个结构化的调用意图包含函数名和参数 JSON。真正去执行函数的是你自己的代码执行完把结果拼回 Prompt 里模型再根据结果继续推理。Rust 侧我定义了一个工具注册表pub struct Tool { pub name: String, pub description: String, pub parameters: serde_json::Value, // JSON Schema pub handler: fn(serde_json::Value) - ResultString, }Agent 每轮循环的伪逻辑把 messages 发给模型带上所有已注册工具的描述模型要么返回普通文本答案要么返回 tool_calls 数组如果是 tool_calls按名称找到对应 handler执行内部 API 调用获得结果把结果作为一条 user 消息追加到历史里重复直到模型返回普通文本。比如我想让 Agent 查内网工单系统的状态{ function: query_work_order, arguments: { order_id: WO202405001 } }代码里对应 handler 会调用内网工单 API返回一个 JSON 字符串最后模型把结果读一遍再组织答案。隔离内网里工具就是 Agent 的手脚关键是每个工具都要有清晰描述和参数 Schema否则模型不知道什么时候该调用、该传什么参数。顺带提一下 MCPModel Context Protocol。它是把工具、资源、提示的调用方式标准化的协议内网环境可以用它做工具协议层避免每家系统各写一套 adapter。不过在项目初期接口数量少于 20 个时直接写工具函数反而更快。5.3 记忆与知识库内网文档检索不能靠人肉喂Agent 要回答业务问题光靠模型训练时学到的知识远远不够内网里真正的知识都在文档、操作手册和数据库里。我的做法是做一层离线 RAG把内网文档Word、PDF、Markdown统一转成纯文本按固定长度切块做重叠切分避免语义被切断用本地嵌入模型比如 bge-m3 系列的量化版把每块文本向量化向量存进内网部署的向量数据库里Milvus、Qdrant 都行甚至 ES 也可以用户提问时先检索 Top-K 相关片段拼进 Prompt再让模型作答。这个流程的核心是让知识进 Prompt而不是塞进模型参数。本地模型参数体积有限不可能内化多少业务知识RAG 是隔离内网里扩大知识覆盖最实用的一招。我踩过的一个坑是向量化模型在开放环境用的版本和内网部署的版本不一致导致同一份文档向量空间完全对不上。所以嵌入模型的版本也要和模型文件一样严格锁定并校验。5.4 实测效果与调优记录我用一个小业务场景做了压测——让 Agent 根据内网知识库回答 30 个高频业务问题同时偶尔要求它查工单 API。记录了几个关键指标场景模型量化平均首 Token 延迟成功率平均 Token 消耗/次纯知识问答14BQ4_K_M1.8s93%860工具调用问答14BQ4_K_M2.6s87%1450多轮追问7BQ4_K_M0.9s89%2100调试发现最影响成功率的是工具参数描述不准确。模型不知道order_id应该是什么格式多调几次就会给错参数。解决办法是在工具描述里加示例值让模型照着样例填参数。另外本地模型的发挥稳定性不如开放大模型。同一道题跑 10 次偶尔会有一两次答偏甚至直接拒绝回答。我在 Agent 层加了一句兜底 Prompt如果信息不足就明确说根据内网资料无法回答并给出可怎么继续补充信息避免硬编答案误导业务方。6. 部署与运维稳定比功能更考验人6.1 服务化部署与进程守护Agent 编排服务是长驻进程我直接封装成 systemd 服务开机自启、崩溃自动重启[Unit] DescriptionInternal AI Agent Engine Afternetwork.target [Service] Useragent ExecStart/opt/agent/bin/agent-engine --config /etc/agent/config.toml Restartalways RestartSec5 EnvironmentFile/etc/agent/env [Install] WantedBymulti-user.target推理服务也一样做成 service并对 GPU 显存做监控。显存一旦占满GPU 上运行的任务会全部超时所以我在 Agent 引擎里做了并发上限控制——同一时刻只允许 N 个推理请求其他排队等待。6.2 日志与链路追踪Agent 现场还原Agent 系统有很强的随机性一个问题换几次重问结果可能完全不同。排错的时候最怕的就是只有最终答案没有过程。所以我在编排层强制记录每一轮循环完整的请求消息序列包括工具描述模型返回的原始输出不要只记解析后的字段工具调用的参数、开始时间、结束时间、返回内容前 N 个字符每一轮的 Token 消耗和耗时最终答案和引用来源。每轮循环用同一个 request_id 关联起来。这样即使 Agent 胡言乱语我也能还原出它是在哪一步开始跑偏的。6.3 内网迭代模型和依赖怎么升级隔离内网的升级不能像开放环境一样顺手必须有一套离线升级流程新模型文件先进测试内网跑一遍回归评测集对比回答质量和 Token 消耗通过后走审批导入生产内网保留旧版本模型文件方便回滚依赖更新同理先在开发环境锁定新版本打包完整离线包再进内网更新每次升级都要更新版本台账记录模型/依赖版本、校验哈希、导入时间、更新内容。我做过一次最失败的升级只换了模型文件没同步更新推理引擎版本结果新模型格式老引擎不认服务全部挂掉。所以模型和推理引擎要绑定升级不能只换一个。7. 踩坑清单与一句话心得7.1 我踩过的几个典型坑工具调用超时没设某个内网接口平时 3 秒返回某天数据量大拖了 5 分钟。Agent 一直等着连接池被占满其他请求全部排队。后来统一给所有工具 handler 加了 30 秒超时并对超时结果做一个固定兜底文案让模型能继续往下走。上下文窗口被撑爆多轮对话里每轮都带着工具返回的 JSON几轮就把 8K 上下文占满了。模型开始失忆连用户最初问什么都忘了。后来加了上下文压缩策略超过阈值就对旧的工具结果做摘要只保留最新几轮的完整信息。模型幻觉工具名模型有时会不按注册工具名输出而是自己编一个函数名。我在解析 tool_calls 时做了白名单校验认不出来的函数名直接当作一次未知工具反馈给模型让它纠正。这个机制让工具调用成功率提升了不少。嵌入模型版本不一致前面提过文档向量化用的模型和部署时用的版本不一致导致检索结果完全错乱。现在嵌入模型也纳入版本台账管理换模型必须重新跑一遍全部文档向量化。中文路径和编码问题内网机器可能存在老系统路径带中文配置文件编码不是 UTF-8Rust 程序读配置直接报编码错误。所有配置文件、路径规范统一 UTF-8并在启动脚本里加检测。7.2 给同行的一些实话隔离内网做 AI Agent外在限制很多但本质上它逼着你把工程的每个环节想清楚依赖从哪来、模型怎么进、工具怎么接、数据怎么留痕、升级怎么回滚。这些在开放环境里会被在线安装和随时重试掩盖掉但在内网里全都赤裸裸地暴露出来。如果你准备开干我给三条实在的建议先跑通最小闭环再铺量。不要一上来就做十几种工具、多 Agent 协作先让一个 Agent 能稳定回答一种问题、能调一种工具再逐步扩展。把工具层当一等公民对待。内网 Agent 的价值不在模型多聪明而在工具接得多顺手。工具描述、参数校验、权限控制、日志留痕都要专门写清楚。把离线当成一种设计约束而不是临时困难。从架构选型、依赖管理到部署方式每个环节都优先考虑不依赖外网也能活这样交付的系统才是真正能在内网环境长期扎根的。我做完整套下来最大的感慨是隔离内网并没有让 Agent 工程变复杂多少它只是把一个成熟系统里所有差不多能用的东西都逼成了必须彻底做对。如果你能接受这种较真那这个项目会做得比想象中扎实得多。