ARTICLE DETAIL

资讯详情

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

隔离内网中的AI Agent部署:从模型推理到工具调用的完整实战

隔离内网中的AI Agent部署:从模型推理到工具调用的完整实战 在隔离内网里跑 AI Agent是我今年做得最折腾、也最有成就感的一个项目。所谓隔离内网就是和公网物理隔离的办公/生产网络很多金融、政企、科研单位都是这种环境。标题里这两个词凑到一起麻烦就开始叠加Agent 天生依赖调用外部模型 API、联网搜索、实时查询知识库而这些能力在断网条件下几乎全被砍掉。更麻烦的是很多决策层以为内网部署大模型 在内网装个 OpenWebUI 聊天实际上真要落地一个能完成多步骤任务的 Agent从模型推理、依赖环境、工具调用、知识库到并发控制每一层都是坑。这篇文章就是我自己从头到尾跑通的一线经验适合正在做私有化部署、又在隔离网络环境里干活的工程师参考。1. 隔离内网里跑 Agent核心问题不是模型而是断网1.1 内网环境的真实情况比你想的更严苛很多第一次接触隔离内网的人会把隔离理解成没外网而已其他都一样。实际进去之后你会发现情况远比想象中复杂。首先内网机器不能直接访问公网这不只是浏览器打不开的问题。它意味着所有软件源、pip 源、npm 源、模型仓库比如 HuggingFace、Docker Hub 全部不可达。你在外网一条pip install就能装完的依赖在内网里要么提前打包好带进去要么就自己折腾离线轮子包编译——而很多中大型项目依赖一多依赖树的离线收集本身就是个大工程。其次内网往往还有安全管控比如禁止 U 盘插拔记录审计、禁止私自安装未经审批的软件、网络策略里只开放几个固定的端口。很多团队做着做着才发现不是技术跑不通是流程上根本不允许随意把公网上下载的东西拷进去。所以第一步不是写代码是先搞清楚介质准入和软件审批的规矩。再有内网的硬件条件通常和外网开发机不一样。GPU 型号可能老旧比如 Tesla T4、P40 甚至 K80显存有限驱动版本被 IT 统一锁死cuda 版本不能随便升级。这就导致你在外网用最新框架训练好的模型拿到内网里可能直接加载失败——版本兼容问题在离线环境下会被无限放大。这些限制叠加在一起决定了隔离内网里的 AI Agent 工程本质上是一个全链路离线交付的问题。你交付的不只是一个 Python 服务而是一整套可以脱离公网独立运行的应用生态。1.2 连接能力被砍掉Agent 就不成立了一个标准的 AI Agent 工作流通常包含模型推理、工具调用、记忆管理、知识检索几个环节。拿 LangChain 生态举例日常我们用 OpenAI 接口封装好的模型用 Tavily 搜索工具查资料用 SerpAPI 查新闻用各种 API 操作业务系统。这些在公网环境下开箱即用但在隔离网里全部失效。那 Agent 是不是就废了不是。关键在于把外部连接能力替换成内网自有能力。模型接口可以替换成本地推理服务网络搜索可以替换成内网知识库检索RAG外部 API 工具可以替换成内网业务系统的 HTTP 接口记忆存储可以替换成内网数据库。本质上 Agent 需要的不是公网而是可调用的能力源只要这些能力源能在内网落地Agent 就能跑起来。这里我踩过一个很痛的点最开始我试图让 Agent 直接通过调用内部知识库 API 来实现检索增强结果被网络策略拦住了因为那个知识库系统跑在另一个隔离网段只有特定的服务账号能访问。后来我们干脆在内网单独部署了一套向量数据库把知识文档全部本地化才彻底绕开这个问题。所以架构设计上第一步就要盘点内网能用的资源而不是先假设Agent 什么都能干。2. 整体架构设计先想清楚哪些东西要本地化2.1 核心选型LangGraph 编排 FastAPI 服务层 vLLM 推理隔离内网的 Agent 技术选型我最终定下来的组合是LangGraph 负责 Agent 的流程编排FastAPI 封装对外服务接口vLLM 承担本地大模型推理。先说为什么是 LangGraph。Agent 的核心能力是多步骤任务编排经典 LangChain 的AgentExecutor在复杂业务面前已经力不从心缺少状态管理多分支跳转要靠自己 hack对循环和条件路由的控制也不够直接。LangGraph 把 Agent 的每一步抽象成图节点节点之间通过状态对象传递数据支持循环、条件分支、甚至人工介入human-in-the-loop这套逻辑非常契合生产环境的真实需求。而且它在离线环境下跑的是纯 Python 包不依赖网络服务天然适合内网部署。FastAPI 没什么好纠结的就是一个轻量、异步、自带接口文档的 Web 框架用来暴露 Agent 的调用接口。你当然也可以用 Django但 FastAPI 和 LangGraph 这种异步密集型的任务配合更顺畅而且离线环境下依赖更少。vLLM 是推理层的核心。它支持高并发推理、PagedAttention 显存管理、连续批处理比直接用 Transformers 的generate方法吞吐量高出一个量级。隔离内网里 GPU 资源通常有限一个 Agent 任务要反复多次调用模型思考、调用工具、再思考推理性能直接决定 Agent 的响应速度所以这块不能省。2.2 系统分层与交互链路整体架构我分成了四层接入层FastAPI 服务负责鉴权、参数校验、请求排队把外部请求转换成 Agent 任务。编排层LangGraph 图定义负责 Agent 的 ReAct 循环比如分析问题 → 选择工具 → 执行调用 → 汇总结果。能力层包括本地部署的大模型推理服务vLLM、向量数据库Milvus/Elasticsearch、业务系统 API 网关、内部数据库。数据层配置文件、业务文档、知识库向量索引、对话历史存储。交互链路大致是用户请求 → FastAPI 接口 → LangGraph 开始任务 → LLM 推理vLLM→ 判断是否需要工具 → 调用内网工具/检索向量库 → 再次推理 → 输出结果 → 返回用户。这套链路里有一个非常容易忽略的点用户请求上下文和 LLM 请求上下文是两套体系。用户发给 Agent 的可能是帮我写一份三季度分析报告但真正传给 LLM 的是经过提示词封装的系统指令、历史对话、工具描述、检索到的知识片段拼起来的长文本。内网落地时这块的 token 占用和显存占用要提前估算否则并发一上来显存先爆了。2.3 为什么坚持内外网双环境开发隔离内网项目能不能只在内网开发理论上可以实际上效率太低了。我的做法是外网开发环境负责代码编写、依赖验证、模型试跑内网只做部署、集成测试和最终验收。两个环境之间通过离线介质同步代码打包、依赖快照、模型文件拷贝并且严格记录版本号。这么做有几个好处外网环境有完整的工具链调试方便外网能直接拉取最新的开源包和模型省去内网搬运的麻烦内网环境则保持干净稳定只运行验证过的版本。但这里要提醒一下两个环境的 Python 版本、CUDA 版本、系统库版本必须尽量一致否则经常出现在外网好好的一到内网就缺libgomp.so.1或者GLIBC版本不够的情况。我建议一开始就统一使用 Docker 镜像来固化环境后面会把具体做法展开讲。3. 离线模型部署全流程从镜像到推理服务3.1 模型选型Qwen 系列是内网部署的稳妥之选隔离内网场景下模型选型的第一原则不是效果最强而是能跑、好搬、可控。我在对比了十几个模型之后主用 Qwen 系列。原因很直接Qwen 的中文能力强对 Agent 场景的指令遵循能力也够用模型权重在 HuggingFace 和 ModelScope 上都能下载方便提前准备离线包社区生态完善遇到问题有处可查。相比 Llama 系列Qwen 的中文词表利用效率更高同样显存下能塞下更多有效上下文。根据显存选择模型我的经验参考如下显存大小推荐模型量化方式备注8GBQwen2.5-7B-InstructAWQ 4bit 量化够跑简单工具调用16GBQwen2.5-7B-Instruct不量化 / FP16效果和速度平衡24GBQwen2.5-14BFP16 / AWQ复杂推理能力更好48GBQwen2.5-32B / 72BFP16 或 INT8多 Agent 并发推荐这个表是个参考底座实际还要结合你的并发需求来调整。有一点要特别提醒不要迷信量化无损的说法。AWQ 4bit 量化之后模型的数学推理和长文本能力会有肉眼可见的下降如果你要 Agent 处理严谨的报表分析尽量上不量化的版本或者用 INT8 折中。3.2 离线环境导入模型与推理镜像模型到了内网搬运是个技术活。我的标准做法是在有外网的机器上先把模型权重下载下来保存成一个目录然后用移动硬盘/光盘介质按要求审批后带进内网。模型权重文件通常很大7B 模型 FP16 大约 15GB建议下载的时候直接用huggingface-cli带断点续传避免下到一半重新开始。下一步是处理推理环境。我强烈建议用 Docker 而不是直接在宿主机上配环境因为 vLLM 对 CUDA、Torch 版本的依赖非常敏感直接配很容易陷入依赖地狱。在外网环境里把 vLLM 官方镜像拉下来然后把模型目录挂载进去跑一遍确认没问题之后用docker save导出镜像 tar 包带进内网再用docker load导入。这是一条成本低、成功率高的离线迁移链# 在外网机器上 docker pull vllm/vllm-openai:latest docker save vllm/vllm-openai:latest -o vllm-image.tar # 模型文件也一起移到内网例如放在 /data/models/qwen2.5-7b/ # 在内网机器上 docker load -i vllm-image.tar docker run -d --gpus all \ --name vllm-server \ -v /data/models/qwen2.5-7b:/model \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model /model \ --served-model-name local-qwen \ --max-model-len 8192 \ --gpu-memory-utilization 0.9这里你可能会遇到一个非常尴尬的问题镜像 tar 包动辄几个 GB审批流程又长又烦。我的经验是提前问清楚内网有没有统一的镜像仓库Harbor有的话直接用仓库中转没有才走物理介质。Harbor 通常允许网段内机器访问效率比人肉搬运高太多。3.3 推理参数与资源估算内网跑 Agent和跑普通聊天机器人很不一样。聊天机器人每次请求是一个来回Agent 一次任务可能要调用五六次模型而且每次还带着越来越长的上下文。所以推理服务的参数设置要围绕 Agent 场景调。--max-model-len我建议根据实际任务长度来设不要贪大。很多团队上来就设 32768觉得长上下文好结果显存都被 KV Cache 占了并发能力直线下降。如果你的 Agent 任务是本地知识库问答一次任务大概也就 3k-6k token设 8192 完全够用并发能力反而能翻倍。--gpu-memory-utilization一般设 0.85-0.9给 CUDA context 留一点余量。但如果你边上还要跑 Embedding 模型或者别的服务要手动减到 0.7 左右否则会 OOM。还有一个很实际的参数是--max-num-seqs它控制 vLLM 同时处理多少请求。默认值是 256但如果你每请求的上下文都很长建议调低到 32-64不然显存被打满请求全在排队平均延迟反而恶化。这个参数没有绝对标准得拿自己的真实请求去压测。4. 应用服务层落地FastAPI LangGraph 调度实现4.1 工程代码结构应用服务层的代码组织我比较推荐这种结构agent-service/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置管理模型地址、工具列表、知识库地址 │ ├── api/ │ │ ├── routes.py # 对外接口 │ │ └── schemas.py # 请求/响应模型 │ ├── agent/ │ │ ├── graph.py # LangGraph 图定义 │ │ ├── nodes.py # 各个图节点的逻辑 │ │ └── state.py # Agent 状态对象 │ ├── tools/ │ │ └── registry.py # 工具注册与分发 │ └── rag/ │ └── retriever.py # 知识库检索封装 ├── requirements.txt └── Dockerfile这个结构谈不上多高级但很实用。核心思路是API 层和 Agent 层完全解耦Agent 层不关心请求来自哪工具层独立注册新增一个工具就是加一个函数不影响主流程配置外部化模型地址、知识库地址这些环境相关的信息都走环境变量内网外网一套代码。为什么要这么拆因为隔离内网环境的特殊之处在于部署时会频繁调整资源地址、认证方式等配置。如果这些信息被硬编码在代码里每次调整都要重新过审批、重新打包。拆出来之后内网部署只需要改环境变量代码根本不用动。4.2 关键实现细节状态管理、工具注册与条件路由LangGraph 的核心是StateGraph。Agent 的每一步都在往 state 里写数据节点函数读 state、处理、再写回。以 ReAct 模式为例state 至少要有三块messages对话历史、tool_calls本轮要调用的工具列表、retrieved_docs知识检索返回的相关片段。工具注册我用了装饰器模式每加一个工具就是一个普通函数挂上注册表# tools/registry.py TOOL_REGISTRY {} def register_tool(name, description): def decorator(func): TOOL_REGISTRY[name] {func: func, description: description} return func return decorator register_tool(query_internal_db, 查询内部业务系统的订单数据输入是订单编号) def query_internal_db(order_id: str): # 在这里调用内网业务 API return {order_id: order_id, status: 已发货, amount: 299.00}这个设计的好处是LLM 生成的工具调用请求到了服务端只需要查表分发不需要写一堆if/else。而且工具的 description 可以直接拼进 prompt让模型知道我现在能用什么工具、什么时候该用。LangGraph 的条件路由是 Agent 能不能动起来的关键。我的经验是不要让模型每次都有百分之百的自由度而是设置明确的边界先尝试工具调用如果工具返回结果就进入观察-总结节点如果模型判断不需要工具直接走生成最终回答节点。遇到多次工具调用失败比如业务接口超时要设置最大重试次数然后走兜底回答。4.3 一个贴近业务的示例查订单 分析原因举个具体例子假设业务方要一个订单异常排查 Agent用户问订单号 20250112 为什么还没发货整个流程是这样的模型收到问题判断需要查询内网业务系统调用query_internal_db工具传入订单号工具返回该订单状态已支付但库存锁定失败异常原因代码 E401模型看到 E401判断需要查一下异常原因表调用query_exception_dict工具工具返回E401 仓库库存不足系统自动拦截发货模型汇总结果返回给用户订单因为库存不足被系统拦截建议联系仓库补货后手动解锁。这个看起来很简单的流程实际上经历了 5 次模型推理。如果每次推理 3 秒总响应就要 15 秒这还是单请求。所以应用层一定要做异步设计内部跑 Agent 任务用asyncio或者独立线程池不能把 FastAPI 的 worker 卡死等模型推理。5. 隔离网内知识库与向量检索实战5.1 向量库选型与离线化Agent 要在隔离网里回答企业内部问题光靠 LLM 自身参数知识远远不够必须挂企业内部知识库。这就是 RAG 的场景。向量数据库的选型我在 Milvus 和 Elasticsearch 之间纠结过。最终选了 Milvus一是它是专门的向量库相似度检索性能好二是支持 Milvus Lite 单机模式离线部署非常简单三是官方提供离线安装包不需要在线拉取依赖。如果你所在环境已经有一套 Elasticsearch 在跑那也不一定非要引入新组件ES 的dense_vector功能也能做向量检索关键是看团队已有的运维能力。向量库的部署比大模型简单很多可以直接用 Docker 跑一个单机 Milvus初始化一个 collection然后把文档切片灌进去。整个流程是文档解析 → 文本分块 → Embedding 向量化 → 写入向量库查询时问题向量化 → 相似度检索 → 取 TopK 片段 → 拼进 prompt。5.2 中文切词与 Embedding 模型这里我要专门说一个内网场景下的坑Embedding 模型的选择直接决定检索质量。很多团队图省事直接拿一个通用 embedding 模型跑结果中文检索效果差得离谱——苹果和水果语义匹配度很高但和iPhone匹配度反而低因为模型没学好中文语境。我的建议是优先选择专门针对中文优化的 embedding 模型比如 BAAI/bge-m3 或者 GTE 系列。bge-m3 支持中英双语维度较低1024 维显存占用不大离线部署无压力。Embedding 模型跑在 CPU 上也能凑合但并发高的时候延迟会明显上升最好放 GPU 上或者单独分配一个 4GB 显存的推理服务。文本切块的 size 和 overlap 参数也需要调。我踩过的教训是不要把文档切得太碎比如固定 200 字一块这样很多语义完整的段落被拦腰截断检索出来的片段前言不搭后语LLM 根本没法用。比较稳的参数是 chunk_size512、overlap50既保证上下文连贯又不会让单片段信息密度太低。如果你的文档是固定结构的制度文件可以考虑按章节标题切块效果会比固定窗口好得多。5.3 检索增强的效果调试检索效果不好不要急着换模型先检查三个地方第一是查询改写。用户问题通常很短帮我看看 XX 设备为什么报警直接拿原句去检索效果一般。我的做法是先用 LLM 把用户问题改写成适合检索的关键词形式比如XX设备 报警 原因 排查再做 embedding 检索命中率高很多。第二是 TopK 的设置。TopK 太大很多不相关片段混进来干扰 LLM 作答太小有用的信息丢了模型只能瞎编。我一般先设 5看回答质量再调整。最有效的办法是人工标一批测试问法对比不同 TopK 下的回答准确率用数据说话。第三是最近还测过 rerank。第一次检索先取 TopK 较宽比如 20 条然后接一个 rerank 模型精排取前 5 条进 prompt。这个方案在隔离内网里完全可行因为 rerank 模型很小CPU 就能跑。加了 rerank 之后回答质量提升非常明显强烈建议有条件就上。6. 并发、稳定性与性能优化6.1 请求排队与连接池Agent 服务和底层模型推理服务之间连接管理是最容易出问题的地方。vLLM 本身支持并发请求但如果应用层的 HTTP 客户端不做连接复用每次都新建连接性能损耗很大。我用的httpx.AsyncClient会显式设置连接池大小和 keep-alive避免频繁握手。更关键的是一层排队设计。vLLM 一次能处理的并发是有限的假设单卡 7B 模型实测并发 8 路时延迟还能接受第 20 路请求进来时延迟就会不可控。我在 FastAPI 层加了一个信号量asyncio.Semaphore限制同时进入 Agent 编排层的请求数多的请求先排队等。配合一个简单的等待提示返回用户体验比全部挤进模型导致整体超时要好得多。有人可能觉得排队会影响体验但你要知道在隔离内网里用户主要是内部员工他们对任务在排队是有预期的。真正不能接受的是点了按钮20 秒后报错——那才叫失败。6.2 超时、重试与降级策略模型推理超时、业务 API 超时、知识库检索超时内网环境网络波动少但系统故障并不少。我给整个链路设计了三层超时接口层整体超时比如 120 秒、单次模型推理超时60 秒、工具调用超时10-15 秒。任何一层超时都要有明确的日志和返回信息。重试策略要尤其小心。LLM 推理超时重试没问题但工具调用比如写数据库、发审批流超时后重试要带上幂等控制否则重复提交就是事故。我在这块吃过亏Agent 调用内部接口发起了一个审批流程第一次请求超时实际已成功Agent 又自动重试了一遍结果流程被提交了两次。后来所有写操作都由调用方生成唯一的 request_id服务端根据 request_id 做幂等才从根上解决。降级策略也要提前想好。如果模型服务挂了呢一个高可用的设计是预置多个模型实例做负载均衡但内网资源有限退而求其次必须保证失败信息足够友好系统繁忙请稍后重试而不是让前端拿到一个晦涩的异常堆栈。6.3 性能压测与优化记录这轮压测我用的是 Locust模拟 20 个并发持续 5 分钟。初始结果是惨不忍睹的平均响应时间 75 秒成功率只有 40%。一个一个排查发现了三个热点问题第一是 Embedding 模型的请求串行阻塞。Agent 每次检索知识库都要做一次问题向量化而这个调用是同步的导致请求在做检索的时候整个 worker 被卡住。改成异步调用后这个瓶颈立刻缓解。第二是上下文不断膨胀。Agent 在一次任务里每一轮推理都会把之前的工具调用结果重新塞进上下文导致越到后面单次推理的 prefill 时间越长。我做了优化只保留最近两轮的工具调用详情更早的细节压缩成一句摘要上下文长度控制住了推理速度恢复。第三是模型服务的max-num-seqs默认值太高。16GB 显存卡上同时处理太多请求导致显存紧张部分请求被强制排队等待。手动调低参数后整体吞吐反而提升……压测数据从平均 75 秒降到了 22 秒成功率回到 97% 以上。7. 常见问题与排查技巧实录7.1 问题速查表现象可能原因排查命令/方法vLLM 启动即崩溃CUDA 版本与 torch 不匹配nvidia-smi查看驱动版本用对应 CUDA 版本的镜像模型加载 OOMgpu-memory-utilization设置过高调低到 0.7 或关闭其他显存进程中文回答乱码或效果差模型词表与实际任务不匹配换 Qwen 系列而不是 Llama工具调用一直失败LLM 生成的函数名与注册表不一致打印模型原始输出看函数名和参数 JSON 是否合法检索知识库命中不相关文档分块太大/太小未加查询改写按 512 字切块加 LLM 查询改写考虑加 rerank请求全部超时并发打满未做排队加信号量排队调低 vLLMmax-num-seqs数据同步到内网后运行报缺包依赖未完整收集在开发环境用pip download收集所有依赖或直接用镜像内网无法解析模型服务的域名DNS 没配置直接用 IP 访问或者在内网 DNS 加一条记录7.2 几个容易忽略的小坑第一个坑是时区问题。内网业务系统返回的订单时间可能是 UTC模型做推断的时候没意识到时区差异给用户的分析直接错了一天。我的做法是在 prompt 里明确注入当前时区和当前时间一个变量current_datetime在每次请求开始时赋值。第二个坑是日志里的敏感信息。Agent 在处理业务数据时日志里很容易泄露出内部订单号、员工信息、甚至合同金额。隔离内网对数据安全的要求极高我专门做了一个日志脱敏中间件把手机号、身份证、金额字段打码后再写日志否则审计过不了。第三个坑是 Python 虚拟环境和 Docker 的墙中墙问题。有时候你打包了一个镜像进入内网启动后发现容器里面的 Python 代码是对的但内网没有libaio、libGL这种底层系统库程序跑不起来。所以建立镜像的时候尽量用官方基础镜像不要用slim版本省掉一堆底层依赖的麻烦。7.3 运维工具离线巡检脚本隔离内网的运维比外网麻烦因为没有监控 SaaS 可用。我写了一个离线巡检 shell 脚本每隔一段时间跑一次检查 GPU 使用情况、模型服务健康状态、知识库连通性、磁盘剩余空间。脚本不依赖任何外部监控系统日志直接写到本地文件超过阈值就触发告警通知。这个脚本其实非常简单核心就几条命令nvidia-smi看显存curl探测 vLLM 的/health端点df -h看磁盘然后用grep简单判断阈值。但就是这么个不起眼的东西在我出差期间帮团队发现了两次显存泄漏问题和一次磁盘写满事故。在内网环境别迷信花哨的监控系统先把手里的基础命令用好。最后再说一个心得隔离内网做 Agent最大的阻碍不是技术而是想到但做不到——你想用某个新框架得先过审批你想升级某个依赖得先验证不影响现有业务你发现自己用错了模型重新搬运一次模型文件又是几天时间。所以在这个环境下干活节奏必须稳先设计清楚、路径验证一遍、然后严格执行每一步都留好回滚方案。这种慢工出细活的姿态反而让最终交付的系统比很多快速迭代的公网项目稳定得多。至少在我这边项目上线至今没有再因为部署环境问题翻过车这大概就是隔离内网逼出来的好处。
返回列表