ARTICLE DETAIL

资讯详情

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

隔离内网中部署AI Agent的完整实战指南

隔离内网中部署AI Agent的完整实战指南 1. 为什么要在隔离内网里跑 AI Agent先说结论这件事最痛的不是“AI Agent 怎么搭”而是“在没有外网的环境里所有现成方案都默认你能访问外网”。我这次做的是企业内部的智能工单处理助手部署在一台完全不能出网的服务器上要求它读私有文档、调用内部系统接口、自动生成处理建议。传统做法当然是接云端大模型API但数据合规不允许。于是只能硬着头皮把整套 AI Agent 体系全部搬到内网从模型权重到 Python 依赖、从向量库到 Agent 运行时全部离线化。隔离内网是很多行业的默认前提——政企、金融、医疗、制造业的生产网基本都这样。它带来的限制不只是“没有外网”还包括不能直接 pip install、不能拉取 GitHub 代码、不能下载模型文件、甚至有些环境连镜像站都访问不了。与此同时业务方又希望 Agent 能正常工作能理解自然语言、能检索知识库、能调用内部 REST API、能给出稳定的输出。说白了你要在一个“断网”的环境里拼出一个具有自主决策能力的数字员工。这篇文章适合两类人一类是正在被隔离内网折磨的工程师想知道依赖、模型、框架这些坎怎么一步步迈过去另一类是打算做私有化部署的技术决策者想提前知道成本结构和风险点在哪。我会把整个实战过程拆开讲包含选型逻辑、离线化步骤、Agent 编排细节以及我踩过的几个坑。所有内容不涉及任何外网访问手段只谈“在封闭环境内把事情做成”的纯工程方法。2. 环境评估与方案选型2.1 先摸清家底网络边界与硬件约束动手之前不要急着装软件先花半天时间把环境情况摸清楚。我这次的目标环境有三条硬性限制服务器位于独立网段防火墙只开了 TCP 22 和若干内部业务端口没有 DNS 解析外网域名能力即便物理链路通也无法访问任何公网资源硬件资源有限单台物理机32 核 CPU内存 128 GB显卡是一张 24 GB 显存的 GPU没有多卡另外还有一块 4 TB 的普通 SAS 硬盘。如果你是做技术方案评审这三条直接决定了后面的所有选择。硬件不够就降模型规模网络不通就走离线介质端口受限就只在局域网内做服务暴露。我建议你第一步先用lscpu、free -h、nvidia-smi和df -h把这些信息记录下来形成一份基线表后面所有容量规划都从这里出发。还有一个容易忽略的点确认是否有堡垒机或运维代理可以用于文件导入。很多隔离内网虽然业务服务器不能访问外网但运维区有一台专用的“摆渡机”可以通过管理员手动拷贝或通过审批流程把文件带进去。这个东西存在与否决定了你后面获取模型权重、依赖包的方式是“U盘拷入”还是“刻盘导入”。我当时是借助运维审批把整体离线包以 ISO 形式挂载进服务器效率比一个个文件传高很多。2.2 Agent 框架选型在线依赖越少越好市面上的 Agent 框架五花八门但现在主流实现多少都带着“云端优先”的基因。比如一些框架天然依赖云端模型网关、在线 Embedding API、外部工具商店这在隔离内网里都是致命伤。选型的时候我给自己定了三条硬标准框架核心代码必须能完全离线运行不能硬编码任何外部 API 地址模型接入层要支持本地 OpenAI 兼容接口——这样可以用本地模型服务替代云端工具/插件机制要简单最好能通过自定义函数或 HTTP 调用方式扩展方便对接内部系统。最终我选择的是基于 Rust 生态的 Agent 运行时作为底座再配合 Python 的 LangChain 做上层业务逻辑。选 Rust 的原因不是赶时髦而是它编译出的单二进制在隔离内网里部署太方便了——不需要在一台离线机器上折腾 Python 虚拟环境直接扔一个可执行文件就能跑。而 LangChain 胜在生态丰富工具类、记忆类、Prompt 管理类模块齐全社区里大量代码片段可以直接借鉴省去很多手写胶水代码的时间。这里要专门提醒一句千万不要选一个需要“在线注册”或“首次启动必须连接云端激活”的框架。这类隐性联网检查往往到现场才暴露到时候没有外网连框架都启动不了非常被动。我的做法是在离线环境里用一个最小 Agent 示例跑通“用户输入-LLM 推理-工具调用”全链路作为框架选型的验收标准。2.3 模型部署方式的取舍在线 API 的完美替代隔离内网里没有云端 API但我们可以自己开一个本地推理服务而且完全兼容 OpenAI 格式。这样上层代码几乎不用改只需要把base_url指到本机端口即可。我评估了三个方案方案优势劣势适用场景Ollama安装简单一条命令起服务支持 OpenAI 兼容接口并发吞吐一般自定义采样参数不够灵活快速验证、轻量 Agent、资源有限的单机vLLM高并发、高吞吐PagedAttention 省显存OpenAI 兼容依赖较复杂离线安装需要处理大量 Python 包并发要求高、有较大显存、需要稳定服务llama.cpp纯 C/C 实现单文件可执行CPU 也能跑动态批处理需要手动配置生态工具偏底层无 GPU 环境、极端依赖受限场景我最终选了 vLLM因为业务要求并发处理多路工单请求而且我们的 GPU 有 24 GB 显存足够跑 13B 参数量的量化模型。如果只是自用或日均请求量小于几千次Ollama 绝对够用但一旦面对真实业务压力vLLM 的连续批处理能力能明显提升吞吐并且显存管理更高效。模型参数的权衡上我选用的是 Qwen2.5-7B-Instruct 的 AWQ 4bit 量化版本。为什么不是 72B因为 24 GB 显存跑 72B 量化模型太勉强推理速度会降到不可用为什么不用 3B因为 Agent 需要多轮工具调用和指令理解模型太小会出现“忘记工具返回结果”的情况。7B 是目前“质量-显存-速度”平衡点。如果你硬件只有 16 GB 显存可以考虑 7B 的 4bit 量化加部分 offload如果显存只有 8 GB建议选 3B~4B 的量化模型并接受智商下降的事实。3. 依赖与模型资产的离线化准备3.1 构建离线 Python 依赖仓库这是整个项目里最耗时、最繁琐、却最决定成败的一环。在隔离内网中不能直接pip install xxx所以必须在有外网的环境里提前把所有依赖打包好。我采用的方案是“双环境制”在一台可访问外网的 Linux 机器上根据目标机器的 Python 版本3.10和操作系统CentOS 7构建 wheel 包仓库再用 ISO 方式整体拷贝进内网。步骤大致如下在目标离线机器上执行python -m pip freeze得到一个最小依赖清单但此时很多包还缺所以需要先在联网环境里边跑边补联网机器上创建一个干净虚拟环境Python 版本和离线机保持一致编写一个requirements.txt包含 Agent 框架、模型库、向量数据库客户端、HTTP 框架等全部依赖逐项下载pip download -r requirements.txt -d /packages --platform manylinux2014_x86_64 --python-version 310如果某些包是源码包需要编译要提前在联网机上用pip wheel构建好二进制 wheel 再拷贝将整个 packages 目录打包用运维审批流程导入离线机执行pip install --no-index --find-links/packages -r requirements.txt。这里最大的坑是依赖传递。你明明只装了 LangChain但它会拉出一堆像是dataclasses-json、langsmith、pydantic、tenacity这样的子依赖。如果pip download时没有使用--no-deps它会自动递归解析但如果目标环境已经预装了某些包版本冲突就会很离谱。我的建议是先在一台干净容器里做一次完整安装演练然后把freeze出的全量版本锁定再用锁定的版本列表去下载。宁可打包多一点也不要漏掉一个。3.2 模型权重与 Embedding 模型获取模型文件的获取是整个隔离内网方案里不可避免的一个环节。你不能从 Hugging Face 直接下载只能通过镜像站或统一模型库先拉到有外网的中间机校验 hash 后再导入。这里有一个容易被忽略的细节Hugging Face 上的大模型仓库往往包含多个子文件除了model.safetensors还有tokenizer.json、config.json、generation_config.json。不要只拷 safetensors否则模型加载会失败。为了减少传输体积我一般只保留必要的文件config.jsontokenizer.jsontokenizer_config.jsonmodel.safetensors如果仓库是多分片就全部带上generation_config.jsonvocab.json针对部分模型另外如果你用 vLLM还需要准备chat_template——有些模型仓库会把模板写进tokenizer_config.json如果缺失vLLM 启动时不会自动生成对话格式Agent 的输入会被当作文本补全效果会非常诡异。检查方法是启动后用curl请求/v1/chat/completions如果不识别messages字段多半是模板缺失。Embedding 模型也不能落下。Agent 做知识库检索时通常需要一个本地向量化模型我选的是bge-large-zh-v1.5。它针对中文效果好而且是国内团队出品离线部署时资源占用低。显存够的话可以常驻 GPU不够的话用 CPU 跑也没问题因为 embedding 请求通常不是瓶颈一次请求只有毫秒级延迟。3.3 本地工具链替代没有“云端搜索”怎么活Agent 的典型价值在于调用工具但大多数现成工具默认走云端 API比如在线搜索、网页抓取、地图查询、天气查询。在隔离内网里我们必须对这些工具做“本地替代”。我的思路是抽象出一层工具接口让 Agent 以为自己在调用通用搜索实际上后端对接的是内部数据源。举个例子工单助手需要“查历史工单相似案例”。传统方案是让 Agent 调用搜索引擎或向量库但内网没法搜公网所以我把工具定义为search_internal_tickets(keywords)这个函数直接查询内部的 MySQL 工单表把匹配结果拼成文本返回给 LLM。关键在于不要让模型感知你换了一个数据源你只需要保持一致的输入输出结构。另一个常见能力是“读取网页”。外网环境有现成的爬虫工具内网则要面对各种内部 Web 系统。我用的方案是自建一个无头浏览器服务通过 CDPChrome DevTools Protocol暴露操作接口Agent 用browser_click、browser_fill等函数驱动它但前提是这些内部系统如果必须登录还得配合内网的统一认证体系。这块工作量大建议优先做“只读查询”类工具交互型操作放到二期再做。另外任何工具调用都应该有超时和最大重试次数避免 Agent 因为一个不可用的工具而无限卡死。4. 工程实现从骨架到可运行 Agent4.1 基础 Agent 编排流程与 Prompt 设计Agent 的核心是循环接收消息 → 调用模型 → 模型决策是否调用工具 → 如果调用则执行工具并返回结果 → 再次调用模型 → 循环直到模型给出最终回答。这个循环在 LangChain 中使用AgentExecutor或新版的create_tool_calling_agent都可以实现但在隔离内网中我建议你显式地控制循环层数不要依赖默认值。我给 Agent 设置的最大循环次数是 8 次。为什么是 8经验公式是单轮复杂工具调用链通常需要 3~5 次模型调用例如“理解问题 - 调搜索 - 读搜索结果 - 再调详情接口 - 总结”。如果超过 8 次还拿不到结果多半是进入了死循环例如工具返回格式不合法或者模型在反复修正同一个错误。设置最大次数可以快速失败至少不会拖垮整个服务。Prompt 设计上隔离内网与公网场景有一个重要差异你的模型不知道互联网上的最新信息所以你必须明确告诉它“只能根据提供的文档和工具结果回答”。否则模型会一本正经地编造出不存在的外部数据。我在 System Prompt 前部加了一句你是一个运行在内部安全网络中的智能助手。你无法访问互联网所有信息均来自内部知识库和工具调用结果。如果工具返回为空请直接说明没有获取到相关信息不要推测或虚构。这句话的效果立竿见影显著降低了幻觉率。它本质上是在给模型做一个“认知边界”把模型拉回到工程可控的范围内。4.2 设计 Agent 与内部系统对接的“插件层”很多 Agent 实战文章讲完框架和模型就结束了但真正工程上卡人的地方在“工具接入”。每个内部系统都有自己的鉴权方式、参数格式、返回结构如果让 Agent 逻辑直接散落在各处后面维护会非常痛苦。我采用的方案是插件化中间层每个工具独立成一个 Python 模块统一接口# tools/base.py from pydantic import BaseModel class ToolResult(BaseModel): ok: bool content: str error: str data: dict {} class InternalTool: name: str description: str args_schema: type[BaseModel] BaseModel def run(self, **kwargs) - ToolResult: raise NotImplementedError具体实现比如查内部知识库的工具# tools/search_kb.py import requests from tools.base import ToolResult, InternalTool class SearchKB(InternalTool): name search_kb description 搜索内部知识库返回相关文档片段 args_schema SearchKBSchema def __init__(self, endpoint, token): self.endpoint endpoint self.token token def run(self, query: str, top_k: int 5) - ToolResult: resp requests.post( self.endpoint, json{query: query, top_k: top_k}, headers{Authorization: fBearer {self.token}}, timeout10, ) if resp.status_code ! 200: return ToolResult(okFalse, errorresp.text, content调用知识库失败) return ToolResult(okTrue, contentformat_docs(resp.json()))这个统一抽象带来的好处是Agent 侧根本不需要关心工具是怎么实现的只要让模型知道“有这样一个工具它的作用是×××参数是×××”。同时插件层也方便你单独为每个工具写测试用例在离线环境中用模拟数据验证调用正确性。实际开发中我强烈建议给每个工具加上“返回内容截断”。模型上下文有限如果知识库搜索返回了几千字Agent 会把注意力分散到无关内容上反而影响最终回答质量。我的做法是每个工具默认最多返回 1500 字超出部分用“...”截断并在返回文本末尾加一句“以上为截断内容如需更多细节请用更具体的关键词重新搜索”。这样既保护了上下文窗口又能引导模型精细化检索。4.3 模型服务与 Agent 服务的局域网部署隔离内网环境下服务部署形态最常用的三种单机一体化、前后端分离、容器化。我这次没有用容器原因是容器镜像也需要提前做好离线导入而且很多内网环境没有私有镜像仓库用 docker 命令行手动导入镜像会比直接跑进程麻烦得多。我的做法是 systemd 托管两个服务模型服务vLLM 监听 8000 端口绑定0.0.0.0OpenAI 兼容接口Agent 服务FastAPI 应用监听 8080 端口对外提供POST /agent/run接口。两者之间通过 HTTP 通信模型服务的base_url由环境变量注入Agent 服务读取LLM_BASE_URL设置为http://127.0.0.1:8000/v1。局域网内其他业务系统要访问 Agent直接调用 8080 端口的接口即可。为了安全我在 Nginx 层加了内网 mTLS 认证白名单对外开放。这里要特别说一下多模型并发场景。如果一台机器既要跑 7B 对话模型又要跑 embedding 模型建议用不同端口加载两个模型服务比如 vLLM 起一个 8000 端口的对话服务再用一个轻量服务起 8001 端口的 embedding。显存分配上7B 量化模型大约占 8~10 GBembedding 模型占 2 GB剩余留给 KV cache。如果你把两个模型放到同一个 vLLM 实例里新增 embedding 模型会导致动态加载可能在请求高峰时出现显存不足不如分开部署来得可控。5. 典型问题与排查实录5.1 模型服务启动后反复 OOM这是一个让我折腾了一整个下午的问题。vLLM 在加载 7B 量化模型时显存占用正常但一旦并发请求上来GPU 内存就莫名其妙涨满进程被 kill。后来排查发现是我忽略了一个关键参数--max-model-len。默认情况下 vLLM 会根据模型配置推断最大序列长度有些模型配置是 32768这会导致 KV cache 预留显存过大而瓦塔缓存其实根本用不到。解决办法很简单在启动命令里显式设置--max-model-len 8192。Agent 场景下单轮输入的历史消息和工具结果加起来很少超过 8k token8k 的上下文长度足够用而且 KV cache 占用会大幅下降。如果你的显存特别紧张可以进一步调低到 4096但要小心长文档检索场景会截断。5.2 Agent 一直不调用工具复读用户问题这是新手最容易踩的坑。模型明明支持工具调用但 Agent 就是不进入工具分支每次都说“好的我来帮您查询请稍等”然后就结束了。这种问题十有八九是 Prompt 里的描述和工具 description 写得太含糊。比如你写search_kb搜索知识库模型根本不知道这个工具能解决什么问题也不会想到去调用它。你需要提供“什么情况下用、入参长什么样、出参是什么”。我改良后的描述是当用户询问某系统的操作步骤、报错解决方案、产品功能说明时使用 search_kb 工具。入参 query 为搜索关键词尽量提取用户问题中的核心实体。工具返回相关文档片段列表。注意只根据返回片段回答不要臆造。注意工具描述要给出“触发时机”这对 LLM 的行为引导非常关键。好的工具描述应该是行为化的而不是功能化的。另外如果你用的模型是 7B 或以下工具调用能力相对较弱尽量把工具数量控制在 5~8 个以内。工具太多模型会记混参数名出现把query写成q或漏传必填参数的情况。我在前期测试时挂了 12 个工具准确率只有 70%精简到 6 个之后准确率恢复到 95% 以上。这不是模型变聪明了是选择空间变小了。5.3 离线包导入后仍报缺包即使你使用--find-links从本地安装也可能会遇到“ModuleNotFoundError: No module named xxx”的情况。最常见的原因是你在联网机器上用pip download时没有锁全版本导致下载了一部分与目标环境不兼容的版本。例如pydantic在 2.x 和 1.x 之间的 API 完全不同LangChain 的不同版本依赖不同的 pydantic 接口很容易翻车。我的建议是分模块逐个验证。先建一个最小虚拟环境安装核心框架和模型库跑通一个最简单的“Hello LLM”脚本再逐步添加向量库、工具库、Web 框架。每加一批依赖就用freeze重新锁定版本。这样一旦出现缺包或版本冲突你能很快定位到是哪个模块的问题而不是面对一堆纠缠不清的依赖报错。另一个经验是如果服务器上有空闲空间可以保留一个完整的 Anaconda 安装包因为 Anaconda 自带几百个常用包和编译工具链很多第三方库的源码安装都需要 gcc、make 等工具而隔离内网往往没有安装编译器。利用 Anaconda 自带的 conda 仓库离线安装部分包可以绕过很多繁琐的依赖编译问题。5.4 向量库检索与模型答案“对不上”知识库检索是 Agent 最常见的辅助能力但经常出现“检索出来的片段相关但答案仍答非所问”的情况。这个问题分为两层第一层是 embedding 模型不够好第二层是 Prompt 没有把片段利用规则说明白。我之前用text2vec类的小模型时检索结果的语义匹配度很差换成bge-large-zh-v1.5后明显改善。如果你受限于内网资源只能用小模型建议在入库前做“语义增强”把文档的关键词、摘要和原始内容拼接为一条扩展文本再进行切分与向量化这样检索返回的片段信息密度更高。Prompt 层面我会在 System Prompt 中增加这样一段系统会提供若干“参考资料片段”。在回答问题时优先使用这些片段并在回答末尾附上来源编号。如果参考资料片段不足以支持完整回答请明确说明“根据现有资料无法完全回答”。这个细节看起来简单但对输出质量影响非常大。它既约束了模型不能脱离资料乱编又给予了模型承认不确定的空间实际上提高了用户对系统的信任度。6. 从一次上线到长期维护的运行心得前面讲了大量硬核步骤最后我再分享一些软性的运行维护经验这些内容往往比代码更关键属于“做了才知道”的隐性成本。第一点是版本锁的重要性。在隔离内网中没有外网就意味着一旦升级某个依赖包后续所有包都要同步升级而且升级过程中很可能遇到新的兼容性问题。所以非必要不升级每次升级前要在中间环境做全量回归测试并做好回滚方案。我的习惯是在离线机的/opt/packages-backup里保存上一版完整包目录升级失败时可以秒级回退。第二点是 Agent 的可观测性。很多 Agent 框架的日志是把整个 Prompt 和工具调用链打印到控制台但当请求多起来后这个日志量会非常庞大且难以检索。我会在框架层自定义一个钩子把每次调用的thought,action,action_input,observation以 JSON 格式外发到内部的日志采集服务然后用 Grafana 做一个简单的仪表板展示工具调用成功率、平均循环次数、失败分布。没有可观测性的 Agent 就是一个黑盒出了问题只能靠猜。这在内网环境尤其重要因为你不能像公网那样方便地接入外部 APM 服务。第三点是定期抽查回答质量。模型不是人它会突然在某些天表现得特别差有时候是输入 Prompt 中隐藏了微妙的 pattern有时候是后端某个内部工具的参数发生了变化。我设计了一个简单的回归测试集包含 50 条典型的工单问题每次模型版本或工具逻辑变更后跑一遍并人工抽查其中 10 条。虽然不够自动化但性价比很高。你也可以利用 LLM 来给 LLM 的回答打分但我个人发现还是人工重点抽查更可靠。第四点是资源预留。隔离内网环境申请资源流程很慢所以尽量在初期就多留出 30% 的计算和存储余量。我的 4 TB 硬盘看着不小但大模型、向量库、日志、离线安装包加起来用不了多久就去掉了大半。建议给模型目录和向量库目录单独立卷并开启定期清理策略避免日志和临时文件把根分区塞满。最后说说 Agent 的“守卫”问题。内网系统往往比公网系统更脆弱很多老系统的接口并发能力弱Agent 一旦进入循环调用极有可能拖垮下游应用。我给所有工具调用加了一层全局限流默认每个工具每秒最多调用 2 次同一个会话中单个工具最多调用 20 次。这样即便 Agent 出错也不会引发内部系统雪崩。另外所有工具的写操作默认禁用只有显式配置白名单后才开放这是真正经历过生产事故的人才会理解的保守策略。隔离内网下的 AI Agent 工程表面上是技术问题本质上是系统化的工程取舍问题。模型可以小一点但工具层和运维层必须扎实。只要把依赖、模型、工具、可观测性四条线理顺Agent 完全可以在一个断网环境里稳定服务业务。希望这份实战记录能给你省下几个月的试错时间。
返回列表