ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

隔离内网部署AI Agent实战:从模型选型到工具调用全记录

隔离内网部署AI Agent实战:从模型选型到工具调用全记录 开局先交代一个背景我去年接手了一个“隔离内网部署 AI Agent”的活儿环境非常典型——服务器所在的网段不接互联网数据进出全靠离线拷贝生产区连 DNS 都受限。当时团队里讨论最久的不是模型选哪家而是“Agent 在断网环境里到底还跑不跑得起来”。折腾了小半年之后我可以负责任地说隔离内网不仅跑得起来而且跑完后你会发现很多在公网环境下被掩盖的工程问题全都浮出水面了。这篇文章就把那次实战的思路、选型、步骤和踩坑记录完整写出来希望能给同样被困在内网环境里做 AI Agent 的同学一点参考。AI Agent 听起来很玄核心其实就三件事让模型理解任务、让模型调用工具、让模型记住上下文。放到隔离内网里难点变成了“模型从哪来、工具如何暴露、依赖怎么装上、效果怎么调试”。这四件事如果不在动手前想清楚后面每一步都是在填坑。1. 整体设计与思路拆解1.1 为什么要在隔离内网跑 AI Agent先说动机。很多团队做 Agent 第一反应是接云端大模型 API反正联网就行。但实际生产场景里研发或办公内网往往是隔离的原因无外乎三种一是数据敏感业务数据、代码库、用户资料不允许出网二是合规要求金融、能源、政务行业的监管明确规定了核心系统不得连接公共网络三是稳定优先生产网络要保证全年可用率不能把关键链路建立在一家云厂商的 API 上。这三种场景下AI Agent 的价值反而更大。断网不等于断智能内网里有海量日志、知识库、运维脚本、数据库这些传统上要靠人去翻的东西正是 Agent 最擅长处理的。比如说内网工单系统每天几百条重复咨询完全可以让 Agent 先检索本地知识库生成草稿再由人工复核既省人力又不碰外网。这个阶段最忌讳的事情是“先跑起来再说”。我见过不少团队在内网服务器上装个框架模型文件用 U 盘拷进去代码一股脑丢上去结果 GPU 显存不够、依赖缺包、Agent 一回车就报错。先花半天把需求边界和资源清单列清楚比什么都重要。1.2 架构选型推理、编排、工具三层分离隔离内网里做 Agent我最终把系统分成三层每一层都可以独立替换推理层负责跑大模型对外提供 OpenAI 兼容的接口。选型时只考虑能离线运行的推理引擎比如 Ollama、vLLM、llama.cpp我实测下来都可行。编排层负责 Agent 的主循环——接收用户问题、调用模型、解析模型输出的工具调用意图、触发工具执行、把结果送回模型继续生成。这一层是 Agent 的灵魂可以自研也可以基于 LangGraph、Dify 这类框架。工具层把内网里的实际能力包成接口比如查数据库、调内部 API、读取文件系统、执行运维脚本。每个工具都必须有清晰的入参和出参定义。为什么要拆三层而不是搞一个“全家桶”一体化系统因为隔离内网最大的问题是排障困难。一旦把推理、编排、工具混在一起出了问题你根本不知道是模型卡了、代码 bug还是内网服务没响应。分层之后每一层都有独立的日志和监控点问题可以迅速定位到具体环节。还有一点经验不要把编排层和推理引擎耦合死。推理引擎今天用 Ollama明天可能因为性能换 vLLM只要它还兼容 OpenAI 的 Chat Completions 协议编排层就不用大改。所以我在编排层写的调用代码都是基于标准接口的不绑定任何一家厂商的 SDK。1.3 为什么我用 Rust 写编排层这次实战我用了 Rust 来写编排层这个选择很关键。主流方案里Python 生态最丰富写起来最快但我最终选了 Rust原因很现实隔离内网部署环境里能装的东西有限Rust 编译出来的单二进制文件几乎不需要运行时依赖拷到目标机器上直接就能跑Agent 编排本质是大量 I/O 密集型任务模型推理要等待、工具调用要等待Rust 的异步运行时在并发处理上很稳不夸张地说几百个并发会话同时挂着问题不大内网环境通常会碰到各种格式的文本、协议解析Rust 对内存安全性控制严格长期跑在服务器上不容易漏内存模型输出的工具调用是 JSONRust 生态里 serde_json 的解析速度非常快Agent 一次决策循环里可能要 parse 好几轮效率不是问题。当然 Rust 的上手成本确实高生命周期、所有权这些概念会把不少人卡住。我的建议是如果团队已经有 Python 基础可以先在 Python 里把 Agent 逻辑用 FastAPI 写通再将性能敏感的核心模块用 Rust 替换。这次项目我是一开始就定了 Rust因为目标是把它作为一个长期服务跑在内网基线环境里省得后面再迁一次。2. 核心细节解析与实操要点2.1 离线大模型部署与量化选型隔离内网部署 Agent第一关是模型文件怎么进去。我在实际操作中的流程是在可以联网的办公机器上下载模型校验 SHA256 哈希然后通过审批流程拷入内网中转机再分发到 GPU 服务器。这里特别提醒模型文件动辄几个 GB 到几十 GB一定要先做哈希校验再搬运。我遇到过一次 U 盘拷贝的文件损坏模型加载到一半就报非法指令排查了一整天才发现是文件坏了。模型选型上我给了团队一个优先级通用对话用 Qwen 系列或者 DeepSeek 系列这两个系列对中文支持好且都有开源授权可以内部商用代码生成场景用 CodeLlama 或者 Qwen-Coder代码能力明显强于通用模型如果只是做简单意图识别和工具调用7B 到 14B 的量化模型足够不需要盲目上 70B。量化这一步特别重要。我的测试机器是一张 24GB 显存的卡如果跑 FP16 的 14B 模型显存直接爆掉。后来把模型量化成 GGUF 格式的 Q4_K_M文件体积缩到原来三分之一单卡能稳定运行生成速度反而因为显存占用降低而提升。要注意的是量化不是越高越好Q4_K_M日常使用的均衡点质量损失可接受文件小Q5_K_M质量要求高、显存还够的时候用Q8_0几乎无损但文件大适合小模型或显存很宽裕的场景。如果模型要跑 RAG 或者 Agent 工具调用我建议保底用 Q5_K_M因为工具调用对格式的稳定性要求很高过度的量化会导致输出 JSON 不合法。2.2 Skill 机制与函数调用怎么写Agent 能干活的核心是“工具调用”业界也叫 function calling有的框架里把一组工具封装成“Skill”。在隔离内网环境里Skill 不是锦上添花而是 Agent 为数不多能触达真实业务的途径。我见过不少失败案例模型本身很聪明但工具定义写得一塌糊涂模型根本不知道该调用哪个、参数该怎么填。写好一个 Skill 的关键是把函数的描述和参数 Schema 写得极其详细。举个例子我写了一个“查询内网服务器状态”的工具参数定义是{ name: query_machine_status, description: 根据主机名或内网 IP 查询服务器的在线状态、CPU 和内存占用。只有拿到明确的主机名参数时才调用没有主机名直接告诉用户需要补充。, parameters: { type: object, properties: { hostname: { type: string, description: 服务器主机名例如 ops-01必填 }, ip: { type: string, description: 内网 IP 地址例如 10.10.1.5可选 } }, required: [hostname] } }这里我特意写了一句“没有主机名直接告诉用户需要补充”这个看似啰嗦的说明实际测试里极大降低了模型瞎猜参数的概率。模型不是真“懂”你业务它是在根据描述做模式匹配描述越接近人类的表达习惯匹配越准。Skill 的执行权限也要打磨。隔离内网里数据金贵Agent 每次调用工具其实都在“动手操作”。我做了一个简单的黑白名单只读工具查询类可以直接执行写操作工具改配置、发指令必须由人工确认后再放行。这个设计在交付时特别加分安全团队看了眼睛都亮了。2.3 记忆与上下文工程Agent 聊着聊着就“失忆”是内网部署时最容易暴露的问题。因为模型有上下文窗口上限会话一长早期的关键信息会被挤掉。我在这**个项目里把记忆分成两层短期记忆就是当前会话窗口内的对话内容直接拼接进上下文喂给模型但要做滑动窗口裁剪只保留最近几轮。长期记忆把用户偏好、关键结论、实体信息存入内网数据库或者向量库在每轮对话开始前根据当前问题做相似度检索把相关的历史片段注入上下文。这种记忆架构代码量不大但收益非常明显。我们的 Agent 服务里用户经常第二次登录后问“上次帮我查的那个服务器现在怎么样了”如果没有长期记忆模型只能装傻有长期记忆它能从向量库里捞回上次讨论的主机名再做一次实时查询这不就是真实有用的 Agent 体验。上下文裁剪还有一个重要原则相关的保留不相关的果断丢弃。有些团队图省事把所有历史对话全塞给模型结果上下文爆炸模型反而抓不住重点。我在实现里做了个摘要机制超过窗口长度后先把旧对话用模型生成一段压缩摘要再把摘要加回上下文。实测下来摘要模式比简单截断的准确率高不少。3. 实操过程与核心环节实现3.1 搭建最小可用的内网智能体服务这一节我直接给出可以抄作业的流程完整走一遍从零到一的最小实现。第一步准备一台 GPU 服务器。我们的配置是一张 24GB 显存显卡、64GB 内存、1TB 固态操作系统是 Ubuntu 22.04。如果是纯 CPU 环境只能跑 7B 量化模型速度大概每秒几个 token做那种不追求实时性的审批助手可以做对话体验会很吃力。第二步部署推理引擎。我用 Ollama 做演示因为它上手最快内置了对 OpenAI 接口的兼容层。安装很简单解压后执行一条命令即可。然后把模型文件用离线方式导入ollama serve # 在目录里放入模型文件执行导入 ollama create my-qwen --file ModelfileModelfile 里可以自定义模型的上下文长度、温度等参数。我一般把 context 设为 8192既保证对话连贯又不至于因为上下文太长拖慢推理。第三步启动推理服务验证接口。Ollama 默认监听 11434 端口我用 curl 测一轮对话curl http://127.0.0.1:11434/v1/chat/completions \ -H Content-Type: application/json \ -d {model:my-qwen,messages:[{role:user,content:你好}]}看到正常的 JSON 返回推理层就通了。第四步写编排服务。用 Rust 做异步服务代码逻辑可以抽象成这样的循环async fn run_agent(user_input: String) - ResultString { let mut messages load_context(user_input).await?; loop { let resp llm_chat(messages).await?; // 判断模型是否请求调用工具 if let Some(tool_call) resp.tool_calls { let result execute_tool(tool_call).await?; messages.push(format!(工具返回{}, result)); } else { return Ok(resp.content); } } }这里最关键的是“判断模型是否请求调用工具”。我用的协议里模型会返回一个tool_calls数组里面包含函数名和参数 JSON。代码要做的就是从响应里把这个数组解析出来先做合法性和权限校验再执行对应函数。我在这个阶段踩过一个非常典型的坑模型的工具调用参数是 JSON 字符串里面嵌套了数组和转义字符直接serde_json::Value解析会出现字段丢失。解决办法是先校验 JSON 语法再用强类型结构体反序列化任何一步失败都向模型回传“参数格式错误请重新生成”。这个设计让整个循环的稳定性提升了太多。3.2 把内部工具接进来推理和编排跑通之后Agent 还只是个“会聊天的空壳”。真正体现价值的是把内网里的系统接进来。我这边接了三个业务工具每个都不复杂数据库查询工具输入自然语言或 SQL 条件工具内部通过只读账号连上 MySQL执行 SELECT 语句返回结果。整个过程严格遵守只读限制账号权限最小化。知识库检索工具内部文档系统导出成文本切块后灌入向量数据库。Agent 收到事实类问题时先向量检索相关段落再把段落和问题一起喂给模型。运维状态工具通过内网接口读取服务器监控数据返回 CPU、内存、磁盘使用率。这些工具的接入路径完全一致定义一个 JSON Schema、实现一个 Rust handler、注册到工具列表里。注册列表本身要传给模型模型才知道有哪些工具可用。这里有一个内网环境的特殊问题传输给模型的工具描述不能太长一次只能传大约十几个工具多了模型会“眼花”反而选错。如果工具数量超过 20 个我建议做工具分组先让 Agent 判断属于哪个组再在该组内选择具体工具。3.3 并发、缓存与流式优化Agent 部署到内网后马上会面临真实用户同时使用的压力。推理引擎默认是串行处理请求的一旦几十个人同时问问题等候队列会拉得很长。我的优化三板斧并发队列在编排层为每个会话创建独立任务异步等待推理结果而不是同步阻塞。Rust 的 Tokio 运行时在这一层极其顺手几百个任务同时挂起也不占资源。前缀缓存内网环境中很多用户问的问题高度相似比如“员工的入职流程是什么”。我在编排层加了一个语义缓存相同的提问在 24 小时内直接返回历史答案不再调用模型。实测缓存命中率能到 30% 左右GPU 压力小了一大截。流式输出把模型生成过程以 SSE 方式推给前端。用户第一屏看到的是逐字出现的答案体验上比干等十几秒再一次性返回好太多。实现上只要让推理引擎以流式模式返回增量编排层做转发即可。我在测试中还注意到推理引擎的批处理参数对吞吐影响很大。vLLM 在这点上比我用的 Ollama 更强它支持 Continuous Batching多个请求共享一个 batch 计算吞吐能翻好几倍。如果并发量超过 20 个同时在线建议把推理引擎换成 vLLM编排层不用动因为走的都是 OpenAI 兼容协议。4. 常见问题与排查技巧实录实战三个月我整理了一份问题速查表基本上覆盖了内网 Agent 项目里最高频的故障。4.1 模型加载失败与显存不足模型加载到一半报 OOM 或者 Illegal instruction这是最常遇到的。先说 OOM核心原因是模型量化档位和显存不匹配。我按经验给一个估算公式显存占用约等于模型参数量乘上量化后每参数字节数比如 14B 模型的 Q4 量化大约需要 14 × 0.5 7GB加上 KV Cache 和运行时开销24GB 卡跑 14B Q4 很轻松。Illegal instruction 十有八九是 CPU 指令集不匹配或者模型文件损坏。解决办法是在拷贝前算好 SHA256运行前用工具检测 CPU 是否支持 AVX2。隔离内网的旧机器经常是五六年前的 CPU不支持新指令集我后来在部署脚本里直接加了一项硬件检测。4.2 工具调用失灵与参数幻觉这是 Agent 项目里最让人头疼的问题表现在模型明明调用了工具但参数完全是编的。比如查询服务器状态模型编了一个不存在的hostname。排查思路如下第一步检查工具描述是否写清楚了字段的取值范围和必填条件。描述里如果写了“如果不知道主机名请向用户询问”基本能避免一半的幻觉。第二步检查模型量化程度是否过高。我实测 Q4 模型在简单工具调用上和 Q5 差距不大但涉及多个参数组合时Q5 明显更少出错。如果业务对准确性要求高直接上 Q5 量化。第三步加入工具结果校验。工具执行后返回的错误信息要原文喂回给模型让它基于真实错误修正调用参数。比如“查无此主机请确认主机名”模型会意识地再问用户或者换一个工具。4.3 服务发现与日志观测隔离内网没有现成的注册中心的时候多个 Agent 服务之间互相调用会非常麻烦。我的做法是统一维护一个静态配置文件记录所有内部服务的内网 IP 和端口Agent 启动时读取这个文件生成服务调用路由表。改动配置时通过配置中心下发热更新而不是停机重启。日志观测更要注意。隔离内网不能接云端日志平台我就在编排层自己做结构化日志每条都带上会话 ID、模型调用耗时、工具调用结果、错误码。积累到本地日志系统后再用 Elasticsearch 或轻量级的 ClickHouse 做全文检索。有一次用户反馈“Agent 答非所问”我查日志一看工具返回的是超时错误但编排层没把错误传给模型模型自己瞎编了一个答案。这就是日志的价值没有日志这个问题可能要在可视化界面上猜好几天。再分享一个可观测性的小工具在每个 Agent 请求入口打一个唯一 request_id全链路所有日志带上这个 ID排障时只要拿到一个 ID 就能串出完整链路。写在最后隔离内网做 AI Agent最大的认知转变是不要把它当成联网应用来做而是当成一个内网基础设施来运营。模型、依赖、工具、日志、安全边界每一层都要有离线预案。我个人体会最深的是公网环境下随便能用 pip 装依赖、随便能用云端打标注到了隔离内网全都要提前打包、提前审批、提前验证。这种约束反而逼着你把所有外部依赖都锁死、把每个环节都文档化系统稳定性和可维护性因此高了一个档次。最后再分享一个细节这个项目里我没有用任何复杂度高的编排框架核心循环用 Rust 手写也就是一两百行的事情。框架带来便利的同时也带来了隐藏深度在没法搜文档、没法问外网社区的内网环境里越是自己可控的原生代码越能让你在深夜排查问题时睡得着觉。如果你的团队也面临同样的隔离环境建议从最小闭环开始先把一个技能链路跑通再慢慢往上加记忆、加并发、加更多工具。这条路并不快但稳。
返回列表