隔离内网部署AI Agent实战:从离线模型到工具编排的完整指南 单位里那台带GPU的服务器一直放在隔离内网里跑数据任务平时大家也就用用Jupyter和调度平台。直到领导说要在这台机器上部署一套AI Agent用于内部知识问答和自动化流程处理我才意识到这事儿的复杂度远远不止装个模型那么简单。断网环境下做Agent工程既要解决模型权重、依赖包、向量库的离线获取问题又要处理内网工具调用的编排问题每一步都在跟“没有外网”四个字较劲。这篇文章我就把自己这套隔离内网下AI Agent工程实战的完整流程拆出来。从技术选型、离线部署、Agent编排到常见坑点尽量写成可以直接照着复现的版本。不管你是单位信息处的运维还是私企内部做智能化改造的工程师应该都能找到能用的部分。1. 隔离内网部署 Agent 的整体思路拆解1.1 “隔离内网”到底在约束什么先理清一个概念隔离内网部署AI Agent本质是在一套无法访问公网的封闭环境中把“大模型推理、Agent编排、外部工具、知识检索”这几条链路全部实现本地化闭环。它跟公有云上调用API的做法完全不同CI/CD、模型下载、包管理这些常规操作全都要改变形态。我在实际工作里遇到的隔离内网通常分两类物理隔离和逻辑隔离。物理隔离就是机器完全不接外网USB口都封了文件传输只能靠经过审批的介质逻辑隔离则是网络策略限制服务器只能访问内部域名外网一律不通。不管哪一种核心约束都一样所有软件依赖必须在进入内网之前准备好一旦进去发现缺包从准备到导入的往返成本非常高昂。这个约束会带来三个层面的连锁影响。第一是模型来源不能用HuggingFace直接下载也不能指望一些在线模型仓库必须有内网可用的权重文件来源。第二是依赖管理Python的requirements、Node的npm包、系统级的动态库都要预先缓存成离线包。第三是运行架构Agent内部的工具调用不能依赖公网API比如天气查询、地图服务这类功能直接失效只能改成调用内网自建的服务或者干脆去掉。1.2 Agent 方案选型重框架还是轻自研Agent工程在内网场景下框架选型直接影响后面的坑多坑少。市面上现在主流的Agent方案大概能分成三类第一类是LangChain、LlamaIndex这种通用编排框架组件多、生态大但版本迭代快依赖树也极其复杂第二类是Dify、FastGPT这类平台化产品做知识库和简单工作流很方便但在封闭内网里部署它们的依赖同样不轻第三类是纯自研的轻量Agent调度逻辑用代码直接控制“模型调用工具执行循环判断”这三件套。我最终选的是第三类也就是轻量自研为核心、局部复用少量必要的库。原因很简单在隔离内网里框架的每一次运行报错排查成本都比有网环境下高好几倍。LangChain这类框架链路过长一旦中间某个Agent动作的输入输出格式对不上追踪问题会非常痛苦。自研调度逻辑其实并不难核心就是while循环里反复处理“模型返回什么、该执行哪个工具、结果怎么回传给模型”这个闭环。只要控制好提示词和工具的定义格式效果完全可以达到生产可用。当然这不意味着所有第三方库都不能用。我保留了python-dotenv、requests、pymysql等基础库以及必要的向量检索库这些依赖树浅、功能稳定的库在内网里更经得起折腾。至于Agent框架的复杂抽象层能省则省。1.3 内网环境下 AI Agent 的能力边界这里必须先泼一盆冷水隔离内网里的Agent能力是受限的。很多演示视频里“帮我看天气再帮我订个外卖”这类场景在内网环境里根本不可能实现原因就是没有外部服务的调用通道。所以做内网Agent能力边界要想清楚一般落在三个方向第一是内网知识问答Agent结合本地知识库回答制度规范、技术文档、历史案例的问题这需要搭建RAG链路。第二是操作内网业务系统通过调用内部API、执行SQL查询、读写内部工单系统等方式完成自动化的流程处理。第三是本地文件与数据处理帮助用户做表格整理、日志分析、报告生成这类不需要联网的操作。这三个方向最大的优势是数据不出去私密性和安全性有保证这也是很多机构选择内网部署的核心原因。但对应的代价是功能范围必须围绕内网现有资源来设计Agent本身能做什么取决于你给它接了什么内部工具。先把边界确定后面接工具的时候思路就清晰了。2. 核心技术栈选型与离线准备2.1 基础大模型与推理引擎的选择逻辑Agent的大脑这块我推荐优先考虑Qwen系列这种开源权重、社区资料多的模型。原因很实际在没网的环境里遇到问题主要靠模型文档和社区经验来排查选资料多的模型等于给自己留退路。我当前使用的版本是Qwen2.5-7B-Instruct主要在7B和14B之间做了权衡。选择7B而不是更大参数量的版本核心是受限于单张显卡的显存。我们的服务器是一张32GB显存的卡7B版本用4bit量化可以跑得非常舒服显存占用大概6-8GB剩下的显存还能留给向量检索和Agent编排程序。如果上14B虽然推理质量更好但对显存和推理延迟的要求直线上升在交互式的Agent场景里每次工具调用的等待时间会被放大很多。推理引擎方面我对比过Ollama和vLLM。Ollama胜在部署简单一条命令就能启动内置的OpenAI兼容接口很适合内网快速验证vLLM吞吐量高、支持并行推理但要配环境、调参数对离线内网来说首次搭建成本偏高。我的建议是如果Agent并发请求量不大直接用Ollama如果后续要做高并发的服务化部署再考虑vLLM和TensorRT-LLM。提示内网环境里不要盲目追求大模型优先考虑“整体链路快”而不是“单点模型强”。Agent每完成一个任务往往要调用模型好几次如果单次推理都要等十几秒整个体感会非常糟糕。2.2 依赖冻结与离线件包准备内网部署最容易被低估的就是依赖管理。我在这块吃过亏第一版方案直接拷了一个Python虚拟环境文件夹进去结果因为系统动态库不匹配NumPy直接崩掉。后来学乖了统一采用pip download配合pip install --no-index的方式在能联网的准备机上把依赖全部冻结成wheel包再和代码一起打进传输清单。具体操作为了适配我们内部的审批流程分了这么几步在一台与目标服务器操作系统一致同样的CentOS/Rocky版本、同样的Python小版本的联网机器上建好虚拟环境。用pip download -r requirements.txt -d ./offline_packages把所有依赖连同依赖的依赖全部下载为wheel文件注意后面要加--platform之类的参数时要小心尽量保持目标平台一致。将整个offline_packages目录压缩打包走内部介质审批流程拷入内网服务器。在内网服务器上建虚拟环境执行pip install --no-index --find-links./offline_packages -r requirements.txt完成安装。这个流程最关键的一点是**“平台一致性”**。准备机的glibc版本、Python版本、甚至CPU指令集都最好和服务器一致否则很容易出现wheel包装不上、或者装上就Segmentation Fault的诡异问题。宁可多花半天在两台机器上对齐环境也别贪图方便直接用现成的虚拟环境目录拷贝。2.3 模型权重的离线导入与校验模型权重的搬运也是重头戏。Qwen2.5-7B-Instruct的原始权重文件大概15GB如果用GPTQ或者GGUF量化版体积会小一些。我的做法是在联网环境下先把模型转换/量化为适合Ollama使用的GGUF格式或者直接下载官方Release里提供的GGUF文件然后打成多个压缩分卷拷贝进内网。这里有个细节值得注意模型的词表和配置文件跟权重一样重要。之前在断网环境里出现过只拷贝了权重文件、漏掉config.json和tokenizer相关文件的情况导致模型加载后输出乱码。后来我养成了习惯每次拷贝模型权重后都先跑一个十句话的冒烟测试确认中文输入输出的编解码正常再继续往下搭建。另外如果模型文件是零散的单文件传输建议传输完成后对着官方给出的SHA256哈希值做一遍校验。内网传输介质偶尔会有数据损坏模型文件动辄几十GB等到部署到一半才发现文件损坏再走一遍审批流程的代价非常肉疼。3. 离线环境搭建与模型推理服务部署3.1 基于 Ollama 快速部署本地模型服务Ollama在内网环境里确实是个好东西因为它把运行环境都打包好了。在有一台可以联网准备机的前提下把Ollama的Linux安装包和模型GGUF文件一起带进内网就能用。我这边是直接拷贝了Ollama的二进制包然后手动搭建服务操作步骤大致如下将下载好的ollama-linux-amd64.tgz包拷入服务器解压到/usr/local目录。把GGUF模型文件放到Ollama的模型目录下或者通过编写Modelfile在联网准备机上用ollama create提前创建好带自定义参数的模型再拷贝模型目录。设置环境变量OLLAMA_HOST0.0.0.0让服务监听所有网卡这样内网其他机器也能通过HTTP访问模型服务。启动服务后用curl发送一个测试请求确认接口返回正常的OpenAI格式响应。这里必须提醒一点千万别默认Ollama服务只监听localhost。默认安装的话Ollama的API服务只绑定在127.0.0.1其他机器根本调不通。内网环境里大家习惯把服务器当成共享资源如果服务只在本机可用后面Agent调度端和推理端跨机器部署时就会多出非常多的调试时间。启动命令参考# 设置监听地址 export OLLAMA_HOST0.0.0.0:11434 # 启动服务建议配合 systemd 托管 ollama serve如果要用systemd托管可以写一个简单的service文件设置好EnvironmentOLLAMA_HOST0.0.0.0:11434把服务做成开机自启。这样即使服务器重启推理服务也能自己拉起来不用每次都要人上去手动操作。3.2 局域网内的模型访问接口配置服务启动后Agent调度端需要统一封装一层模型接口调用。我这边没有直接拼HTTP请求而是在调度层写了一个LLMClient工具类统一处理连接到哪个地址、以及响应解析逻辑。这样后续如果从Ollama换成vLLM只需要调整这一个类Agent业务代码不用动。Ollama的接口是OpenAI兼容的所以请求格式很标准import requests def chat(messages, modelqwen2.5-7b, temperature0.7): resp requests.post( http://127.0.0.1:11434/v1/chat/completions, json{ model: model, messages: messages, temperature: temperature, stream: False }, timeout120 ) return resp.json()[choices][0][message][content]一个小细节内网环境没有公网DNS所以请求地址尽量用IP而不是主机名避免内网DNS解析配置不对导致连接超时。另外一个经验是Agent场景下建议把timeout设大一点因为工具调用循环中模型可能要带着工具返回的结果重新推理单次请求用时十几秒很正常超时设短了反而天天报错。3.3 显存与并发规划显存规划这种基础工程问题多做一步就能省很多事。我的服务器是32GB显存模型本身占大概7GB向量检索模型再占一点剩下的就全部用于并发请求的内存开销。给我的体感是7B量化模型在32GB显存上跑二三十个并发请求问题不大但如果用14B模型并发一高就很容易OOM。这里分享一个排查显存的小技巧用nvidia-smi监视推理过程中的显存变化重点观察是否存在持续攀升不回落的趋势如果有多半是模型推理服务的内存泄漏或者显存碎片问题。在Agent长任务场景下模型服务连续跑几个小时后显存被吃满的情况是真实存在的。我的应对办法是设置一个定时任务每天凌晨重启一次Ollama服务让显存彻底释放一下效果立竿见影。4. Agent 编排层与工具调用体系实现4.1 轻量自研 Agent 循环的构建思路Agent的核心循环其实不复杂把用户请求和系统提示词喂给大模型模型返回一个结构化意图如果这个意图是工具调用就执行工具把工具的执行结果以消息形式继续回传给模型模型再决定下一步该做什么直到模型认为任务结束、返回最终答案。我自研的调度循环里最关键的是“判别方向”的逻辑设计。第一版我让模型每次都自由输出文本再由代码做关键词匹配判断它想干嘛结果非常不稳定模型稍微换个说法就匹配不上。后来改成要求模型以JSON格式输出动作比如{action: search_kb, params: {query: 报销流程}}代码端做JSON解析和参数校验整个稳定性一下就上来了。核心循环的骨架代码大概是这个意思def agent_loop(user_input): messages [{role: system, content: SYSTEM_PROMPT}] messages.append({role: user, content: user_input}) for step in range(MAX_STEPS): resp llm_client.chat(messages) action parse_action(resp) # 解析 JSON 动作 if action[action] finish: return action[answer] tool_result execute_tool(action[action], action.get(params, {})) messages.append({role: assistant, content: resp}) messages.append({role: tool, content: json.dumps(tool_result, ensure_asciiFalse)}) return 超过最大步骤已中止这个循环结构里有两个关键参数。一个是MAX_STEPS我一般设成6到8防止模型在某个工具调用里陷入死循环另一个是SYSTEM_PROMPT这个提示词直接决定了模型“知道有哪些工具可用、工具的参数约定是什么、什么情况下该结束”。4.2 内网工具的定义、注册与安全边界Agent能干活的本质在于工具。我的工具注册方式采用了简单的Python函数加元数据描述的方式每个工具都有名称、描述、参数Schema和执行函数。代码层面就是一个字典遍历这个字典就能让模型知道所有工具能力。举个例子我内网里接了一个查询数据库中的项目进度的工具定义大概是TOOLS { query_project_progress: { description: 查询指定项目的当前进度、风险和里程碑完成情况, params: {project_id: string, 项目编号}, func: query_project_progress_impl } }这些工具描述会拼进系统提示词里让模型学会判断什么时候调用它。实际运行中有一类问题非常典型模型想调用工具但参数生成错了比如项目ID填了个不存在的值。所以我在工具执行层做了一层校验和异常捕获工具返回的结果统一加上{status: success | error, data: ...}结构这样模型就能理解“刚才操作的参数有问题”然后自己修正或者直接告诉用户原因。安全边界这块内网工具也不能放开乱调。我的做法是在工具执行层加一个白名单机制所有涉及写操作的工具比如执行SQL的INSERT/UPDATE/DELETE都必须经过二次确认即在Agent回答中明确提示“即将执行写操作是否确认”不让模型无监督地执行高风险动作。同时所有Agent操作都要写入审计日志记录干了什么、调了哪个工具、拿到什么结果方便事后追踪。4.3 基于 RAG 的内网知识库扩展Agent光靠模型本身的参数知识还不够内网里大量制度规范、SOP文档和项目数据需要走RAG链路把知识“喂”给模型。离线环境下做RAG最大的两个痛点是Embedding模型和向量库的离线部署。Embedding模型我选的是bge-small-zh体积小、中文效果稳整个模型文件也就100MB左右。向量库没有用需要单独部署服务的Milvus而是直接用chromadb或者纯本地的faiss。考虑到我的知识库体量在几万篇文档以内单机向量库性能完全够用运维复杂度也低很多。RAG链路是这样一个流程离线准备阶段把PDF、Word、TXT文档解析成纯文本。按固定长度做文本切片我一般用500字一块重叠100字防止句子被切碎。用bge模型把文本块向量化写入向量库。Agent在回答问题时先从向量库检索TopK相关片段再把片段和用户问题一起打包进提示词上下文。这里有个高频坑文档切片过大或过小都会影响RAG效果。切得太大检索出来的片段可能夹杂大量无关信息把模型注意力带偏切得太小又可能丢失上下文连贯性。我测试下来中文文档500字加100字重叠检索准确率和回答质量是相对平衡的。另外问法和文档原文的措辞差异很大时单纯向量检索可能召回率不高我额外加了一个关键词宽松匹配的并行召回通道把向量结果和关键词结果做加权合并整体效果会好不少。5. 工程实施中的常见问题与排查记录5.1 离线依赖安装过程中的典型报错离线装包最常遇到的问题就是pip install --no-index时提示找不到某个包。排查思路其实不复杂先看报错里的包名和版本号回到联网准备机上用pip download单独把这个包加上然后重新走传输流程。这种问题多半发生在两种情况下一种是requirements清单里忘记写某些隐式依赖另一种是pip的版本策略在准备机和目标机上不一致。还有一种更隐蔽的坑某些依赖的安装过程中需要联网执行post-install脚本比如一些包含C扩展编译的包离线环境下会卡在编译阶段找头文件。这个没法直接解决只能换等价的轮子包比如把某些源码分发的包换成预编译的manylinux wheel或者干脆找替代库。经验总结就是离线依赖准备一定要用虚拟环境做一次完整的“从零安装测试”在准备机上从空环境开始装requirements装通过之后再导出包清单。直接在已经装好各种包的环境里做pip freeze再下载经常带进来一堆无关的包又漏掉真正需要却在本地早就存在的隐式依赖。5.2 模型推理服务的超时与并发问题Agent场景下模型服务的超时问题非常突出。我们的Agent经常要在循环里多次调用模型但Ollama默认的并发请求处理能力有限同一时间如果多个Agent任务并发提交后面的请求就会排队一个请求可能要等很久。表现就是Agent任务莫名卡住日志里一直看不到模型侧的响应。我的处理方案是两层第一层在Agent调度端做请求排队控制限制同一时刻进入Agent循环的任务数量第二层在模型服务端调整Ollama的并发参数比如OLLAMA_NUM_PARALLEL控制并行处理的请求数OLLAMA_MAX_LOADED_MODELS控制同时加载的模型数让服务能更从容地处理多个请求。另外还有一个很值得注意的经验推理服务的超时时间要大于Agent循环的单次调用时间。我做了一个压测Agent任务平均要调模型5到8次每次可能5到15秒如果HTTP客户端超时设成30秒一个任务跑完大概率至少报一次超时。后来我把客户端超时统一调到180秒模型服务端不设硬超时才真正缓解了这个问题。5.3 中文场景下的编解码与格式问题内网环境里做中文内容的相关应用绕不开编码坑。最典型的现象是模型输出乱码、知识库检索乱码、日志乱码。排查这类问题我有个固定顺序先看数据库连接串是否设置了charsetutf8mb4再看Python文件头部是否声明了UTF-8编码最后看HTTP请求返回时是否强制指定了解码字符集。还有一个嵌套JSON的格式问题值得单独说Agent返回的工具调用JSON里如果包含了用户原文或者工具返回值里嵌了JSON很容易出现引号转义错误导致解析失败。我的办法是在给模型看的提示词里明确要求“JSON内的字符串必须用双引号不要用单引号不要携带多余的前后说明文字”同时在代码端用一个宽松的JSON解析器做容错比如正则剔除模型输出里多余的前缀文本再解析。另外Ollama的模型版本如果词表和代码端预期不一致也会出现神秘乱码。确认方法很简单直接跑一句“你好请自我介绍”的冒烟测试看看返回打印是否正常。只要冒烟测试通过就说明模型链路本身没问题排查方向可以转向业务代码。6. 数据不出内网Agent 的运维与后期扩展6.1 监控、日志与告警的落地实践Agent上线后最不能缺的就是一套可观测手段。因为内网环境大家普遍不太重视日志集中管理Agent出问题时最痛苦的就是在各台机器上翻文件。我给这套系统做了一套极简的日志方案Agent调度端的每次任务请求、模型调用、工具执行、最终回答都记录为JSON行格式的日志统一输出到一个目录再写一个轮询脚本把错误级别以上的日志抽出来汇总成日报。每一个Agent任务都要生成唯一的任务ID从用户输入、工具调用到模型回答全程都带上这个ID。这样排查问题时只要拿到一个任务ID就能把整个执行链路串起来直接定位是在哪一步出的问题。这套机制看着简单但在内网Agent这种链路长的系统里价值怎么强调都不过分。告警方面我没有上复杂的告警平台就用了一个最简单的逻辑日志轮询脚本发现连续3次推理请求失败就调用内部消息接口给运维人员发一条告警。隔离内网里Agent系统最大的风险就是“静默失败”用户发了一条消息Agent转了半天最后什么都没返回如果不看日志根本发现不了。告警要解决的正是这种无人值守的异常情况。6.2 从单机 Agent 到多机服务化的演进Agent初期部署在一台机器上问题不大一旦使用范围扩大到多个部门并发上来之后单机模式就很吃力了。我在后期做了一次架构拆分模型推理服务和Agent调度端分离模型服务留在GPU服务器上Agent调度程序部署到一台普通的CPU服务器上中间通过内网HTTP接口通信。这样做的直接好处是模型服务不用被Agent的CPU密集操作拖累而且后续如果想单独扩容模型服务也不需要动Agent调度代码。等到请求规模再上一个大台阶还可以引入更正规的任务队列让Agent任务先进消息队列再由worker节点消费这样就能实现Agent的横向扩展。不过这一步要根据实际使用量来推进前期并发量不高的话没必要把架构整得太复杂过度设计才是内网项目最需要避免的坑。6.3 后续扩展方向与经验沉淀从隔离内网这个特殊约束出发后期的扩展方向还是挺多的。比如把Agent接入内部的工单系统做自动分诊或者对接内部的邮件服务做邮件摘要和自动回复还可以基于Agent搭建内部的数据分析入口用自然语言查报表。本质上都是在现有的工具注册体系里不断新增工具Agent的能力半径就会慢慢扩大。我在这个项目里沉淀下来最大的一条经验就是隔离内网并不意味着不能做AI Agent它只是把AI Agent从“依赖云端能力”转变成“依赖本地工程能力”。在这样一个封闭环境里前期准备工作的颗粒度直接决定了后期的运行体验和排查成本。宁可把准备工作做得细细的也别让Agent在断网环境里裸奔。每次把新模型、新依赖导进内网之前都先在准备机上完整跑一遍流程这个习惯能帮你过滤掉至少一半的部署问题。最后再分享一个小技巧Agent的任务循环里可以把每一步的关键上下文都记录到本地SQLite里这样当某个回答质量不对时可以回放整个思考过程看看模型是在哪一步开始歪的。这个回溯能力对调试Agent的意义远大于多堆几个测试用例尤其是在内网没有外部可观测工具可用的前提下它几乎就是唯一的路。