把 AI Agent 推进隔离内网的时候我最直观的感受是网上那些 Agent 演示项目到了内网几乎没有一个能直接跑起来。这不是代码写得不行而是它们默认的世界里什么都有——模型权重从 HuggingFace 拉、Python 依赖从 PyPI 装、搜索工具调在线 API、向量库连公网 SaaS 服务。一个断网环境把这些“想当然”全部打碎了。这篇就把我自己在隔离内网里从零搭建 AI Agent 工程的全过程写下来包括模型层怎么选、编排层怎么改、并发怎么压、以及最容易被忽略的评测和迭代问题。适合正在做私有化交付、国企/金融/能源类内网项目或者单纯想搞清楚 Agent 工程化到底比 Demo 多哪些事的人参考。我不会吹某个框架多强只讲实际推过去、稳下来的方案。1. 隔离内网部署 Agent先分清“断网”断到什么程度很多人一听到隔离内网就直接理解成“没有互联网”。实际上手之后你会发现同样叫内网限制条件完全不同。部署方案要跟着网络边界走第一步不是选模型而是搞清楚你到底在什么环境里干活。1.1 两种常见隔离形态决定了你的工程策略我这次遇到的是典型的逻辑隔离区业务服务器可以访问内网基础设施比如内部 DNS、内部数据库、NTP 时间服务器但出不了公网。也就是说不能从内网机器直接访问 HuggingFace、PyPI、Docker Hub 这些外部站点但可以和同一内网里的其他服务互通。比它更严格的是物理隔离区通常是那种连网线都物理断开的机房代码和模型只能靠刻盘或经过审核的专用通道导进去。物理隔离环境我经历过一次那会儿最折腾的不是 AI 部分而是“怎么把几十 GB 的模型权重和几百个依赖包安全地送进去”权限申请和介质审核的周期比技术落地还长。这两种环境下的工程做法有几处关键差异逻辑隔离区可以把构建机放在公网侧先同步依赖、打镜像再推到内网仓库后续迭代相对顺滑。物理隔离区最好一次性把依赖树、模型文件、安装脚本全部准备好现场尽量只做校验和启动不要指望临时下载任何东西。如果内网里已经有制品仓库比如 Nexus、JFrog 或自建的 PyPI 服务处境会好很多所有外部依赖转成内网制品仓库的地址就行。所以我的建议是动手之前先拿一张纸画出目标环境的网络拓扑标出哪些服务在内网可见、哪些完全访问不到。这一步看起来很基础但能帮你后面少拆三次环境。1.2 盘点整套 Agent 系统的依赖清单AI Agent 不是一个单体程序而是一组组件的组合。把这些组件按“默认依赖公网”和“内网替代方案”列成一张表项目边界一下子就清晰了。组件角色常见公网依赖内网可替代方案大模型推理HuggingFace 模型权重提前下载权重内网部署推理服务Embedding 模型在线向量化 API私有化 BGE 或 m3e 类模型Agent 编排LangGraph 依赖包锁定版本 私有 PyPI 仓库工具调用在线搜索/天气/地图 API改造为内部 RPA、API 网关或内部接口向量数据库云托管 SaaS自建 Milvus 或轻量用 SQLite-VSS前端可视化在线地图、公网静态资源本地静态资源包或内网地图服务这个表会直接影响架构设计。比如工具调用这块如果业务里必须用“联网搜索”能力而在内网没有搜索引擎服务那就要么自建一套搜索服务要么把 Agent 的能力边界主动收窄禁止模型发起外部调用。这不是技术问题而是需求边界问题一定要提前和业务方对齐不然 Demo 做出来现场一跑就露馅。2. 模型层落地先把推理底座钉死模型是所有 Agent 能力的地基。在内网环境下模型选型不只是“哪个效果更好”而是“哪个模型能离线跑、能在有限算力上稳定服务”。我这次遇到的约束是2 张 A100 80G还要腾出显存给 Embedding 和 rerank 模型使用。这个条件不算差但也没到可以随意挥霍的地步。2.1 开源模型选型别只看榜单要看部署友好度市面上的开源模型很多Qwen 系列、DeepSeek、GLM 系列都不错。但在内网场景我优先考虑的是三条硬指标权重是否完整公开、推理框架是否原生支持、社区踩坑是否足够多。为什么社区踩坑数量重要因为隔离内网出问题的时候你是没法上网搜答案的只能靠本地文档和已有经验排查。一个冷门模型的坑可能三天都填不完而主流模型遇到的问题大概率别人已经遇到过至少离线文档还能翻到些线索。我最终选了 Qwen 系列的 72B 量化版作为主模型原因是它在中文指令理解、工具调用格式上和 LangGraph 这类编排框架配合比较成熟量化后单卡也能跑吞吐够用。如果只是做垂直领域的简单问答7B 或 14B 模型加 RAG 反而性价比更高因为小模型在低延迟场景的优势是 72B 比不了的。2.2 推理框架选型vLLM 和 Ollama 的取舍模型权重只是原始数据真正在线上服务的是推理框架。我对比了三个常用方案vLLM吞吐量高支持连续批处理显存管理激进。适合高并发、长稳运行的生产环境我用它跑主模型。Ollama部署简单开箱即用适合单机场景和快速验证。但它的并发控制、自定义前后处理能力不如 vLLM 灵活。TGIHuggingFace 出的方案各方面比较均衡但有些新模型的算子支持会慢半拍。我的实际组合是“vLLM 跑生成模型 单独部署一个轻量 Embedding 服务”。生成和向量化分开跑避免两者抢显存互相干扰。还需要注意推理服务的接入方式。vLLM 默认提供 OpenAI 兼容的 HTTP API这意味着 Agent 编排层调用模型时代码可以复用标准的base_url API Key 方式。我让 LangGraph 里的模型地址直接指向内网推理服务的 IP端口 8000完全不用改模型调用代码。OpenAI 兼容协议在今天已经是事实标准这点在内网环境帮了大忙。2.3 显存与量化估算先把账算明白内网环境最怕的事是模型拉进来了结果显存不够跑不起来。显存估算必须提前做而且要留足余量。以 7B 模型为例FP16 精度下权重约占 14GB如果使用 AWQ 或 GPTQ 4bit 量化权重降到约 4GB但推理时 KV Cache 和激活值还会占显存所以实际上 7B 量化模型建议至少有 12GB 可用显存。我这次用 72B 模型千万不能用 FP16 直接上。单个 A100 80G 要跑 72B 全精度很勉强所以我改成 AWQ 4bit 量化权重约 42GB剩下约 30GB 放 KV Cache通过 vLLM 的--max-model-len参数控制最大输入长度把单请求的上下文限制在 16K 左右实测并发 10 的时候显存依然稳得住。这里有个经验模型的上下文长度不是越大越好。上下文越长KV Cache 占用越大并发能力越低。你得多测几组数据找到“业务够用”和“并发能扛”之间的平衡点。2.4 Embedding 模型与知识库Agent 的长期记忆Agent 不是只靠大模型死记硬背还要能检索内部知识。所以我单独部署了一个 BGE-large-zh 作为 Embedding 模型把内部文档切片后向量化存进自建的向量库。内网环境下检索链路有几个容易踩的坑文档切片长度要跟 Embedding 模型的 max sequence length 对齐BGE 一般支持 512 token超出部分会被截断检索效果直接打折。向量库不能用公网的托管服务我选择了 Milvus它在内网部署不算复杂但如果你项目规模不大用 PostgreSQL pgvector 也完全够。检索后最好接一个 rerank 模型不然 Agent 拿到一堆相关性不高的上下文回答质量会很飘。rerank 模型不大几百 MB 级别对显存压力很小效果提升却非常明显。3. Agent 编排层LangGraph 方案的内网改造模型底座稳了接下来就是让模型“长出手脚”——这就是 Agent 编排层的事。我最终用了 LangGraph但不用指望它原封不动搬进内网就能跑。编排层的改造工作比想象中琐碎得多。3.1 为什么还是选了 LangGraph而不是什么都自己写热搜词里有“基于 Rust 语言 AI Agent”“Spring AI Agent”这些方向各有各的道理。Rust 适合追求极致性能和资源占用的场景Spring AI 适合 Java 技术栈的统一团队。但我这次场景里团队后端以 Python 为主需要快速实现复杂的状态流转和工具调用LangGraph 的生态和资料最充足。LangGraph 的核心价值是把 Agent 的工作流建模成一张状态图节点是“调用模型”“调用工具”“判断是否结束”边是流转条件。相比裸写无限循环让模型自己决定下一步它能做到可控和可观测。这个特性在隔离内网尤其重要线上跑偏的时候你得能快速定位模型是在哪一步乱掉的。不过我不建议什么场景都上 LangGraph。如果你的 Agent 只是“一问一答”或者流程固定只有三四步手写状态机反而更轻。LangGraph 真正发挥价值是在多工具、多分支、需要人工介入审批节点的复杂流程里。3.2 离线依赖三件套锁版本、私有仓库、wheel 目录这是内网部署最容易卡壳的环节。LangGraph 的依赖树非常庞大它的不同版本对应不同版本的 LangChain Core接口行为有差异。如果在内网机器上盲装最新版大概率会碰到某个子依赖版本不兼容而且报错信息很长排查起来很痛苦。我的做法分三步在能联网的构建机上用pip freeze生成完整的 requirements.txt并锁定每一个传递依赖的精确版本。用pip download -r requirements.txt -d ./wheels把所有包和依赖下载为 wheel 文件。把 wheels 目录拷贝进内网执行pip install --no-index --find-links./wheels -r requirements.txt离线安装。这里容易忽略一个细节LangGraph 的部分功能会按需导入额外依赖比如某些 memory 后端。你得先通读代码里所有 import把隐藏依赖也补进 requirements不然跑起来才发现缺包。3.3 从“全自动图”回归“受控状态机”LangGraph 上手时大家很容易把图画得特别花哨恨不得让模型自己决定一切。但内网生产环境教会我Agent 越自由越难收场。我的建议是默认把流程走成受控状态机。比如“客服助手”这个场景第一步固定做意图识别第二步根据意图走固定分支第三步如果需要查知识库就走 RAG 子图第四步才让模型自由发挥生成回答。模型只在“生成”这一步有自由度前面的路由全部用规则控制。这样做的好处一是稳定二是容易排查。模型乱说话的概率大幅降低因为它的每一步都有人盯着出问题时看节点日志就知道走到哪断了不用靠猜。代码层面核心就是定义一个 StateGraph往里加节点和边from langgraph.graph import StateGraph, END class AgentState(TypedDict): query: str intent: str docs: list[str] answer: str graph StateGraph(AgentState) graph.add_node(intent, route_intent) graph.add_node(retrieve, retrieve_docs) graph.add_node(generate, generate_answer) graph.set_entry_point(intent) graph.add_conditional_edges(intent, lambda s: retrieve if s[intent] kb else generate) graph.add_edge(retrieve, generate) graph.add_edge(generate, END)这套结构的好处是边界非常清晰。你可以在任意一个节点前后插入日志、埋点、人工审核点。3.4 工具调用改造把在线工具全部换成内部接口Agent 之所以叫 Agent是因为它能调用工具。而内网环境里绝大多数公网工具 API 都不可用这一步不做改造整体方案就是空中楼阁。我把工具分成了三类内部系统 API比如查订单、查库存这类直接封装成 Python 函数用内网服务地址调用。知识库检索封装成向量库检索函数返回相关文档片段。外部不可用能力比如实时股价、公网天气这类直接砍掉或者在功能设计上明确告诉用户“此能力不可用”。工具改造还有一个细节给模型看的工具描述必须写清楚“什么情况下用这个工具”“参数是什么含义”“返回结果长什么样”。内网系统返回的数据结构往往很乱如果工具描述不清模型就会瞎猜参数浪费时间还不稳定。另外工具的鉴权方式要注意。内网系统的安全策略各不相同有的用内部 token有的用客户端证书还有的用 IP 白名单。我在工具层做了一个统一的鉴权封装对外只暴露call_tool(name, params)接口内部根据工具名分发到不同鉴权逻辑避免 Agent 代码里散落一堆密钥。3.5 用 FastAPI 封装一个长期运行的 Agent 服务Agent 编排层本身只是一个库要对外提供能力我选择用 FastAPI 做服务封装。它和 LangGraph 配合很自然async 接口也符合异步推理调用的需求。我的服务结构大致是这样from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class AskRequest(BaseModel): query: str session_id: str app.post(/agent/ask) async def ask(req: AskRequest): return await run_agent(req.query, req.session_id)选择 FastAPI 而不是 Django 是因为这层服务只需要提供 API不需要模板、ORM、Admin 那一套。如果你需要用 Django 开发 Agent 后台管理系统也不是不行只是没必要把 API 服务和业务管理系统耦合在一个进程里。关于“Agent 中台”这个概念在这个场景下其实就是把模型服务、向量库、工具层、编排层拆成独立模块通过统一 API 暴露给上层业务。内网部署更要重视模块间依赖方向尽量让编排层只依赖抽象接口不直接感知某个工具的内部实现这样后续替换模型、换向量库都不需要大改图定义。4. 并发与稳定性Agent 中台的扛压之路很多人在 Demo 阶段从没想过 Agent 服务也要扛并发直到联调时被业务方问“这系统能支持多少人同时用”才意识到问题的严重性。Agent 系统的并发瓶颈和传统 Web 服务不一样需要单独拆开说。4.1 瓶颈通常不在模型而在外围资源大家下意识觉得并发大了模型推理会先扛不住。其实在并发还没高到把 GPU 打满之前真正的瓶颈往往是外围资源。我实际踩过的坑向量数据库连接池被打满。Milvus 默认连接数有限并发查询一多客户端直接报连接超时。工具层调用的内部 API 有 QPS 限制。Agent 一个流程里可能多次调用同一接口稍不注意就把对方限流触发。同步阻塞代码把 FastAPI 事件循环卡死。如果某个节点里用了同步 requests 库并发一上来整个服务响应都会变慢跟死锁似的。单次 Agent 流程耗时长HTTP 客户端超时设太短。前端请求过来3 秒就断了但后端 Agent 还在跑造成资源白耗。所以我在设计阶段就把这些外围资源当成一等公民对待。4.2 异步入口 同步 Worker先接住请求再排队干活FastAPI 的 async 能力只是第一步它解决的是“并发接入”问题但不代表整个 Agent 流程都必须写成异步。Agent 编排通常涉及很多 IO 操作如果全程 async代码里到处是 await且 LangGraph 的部分实现不一定完全兼容异步回调调试起来非常痛苦。我采用的架构是“异步 API 入口 同步 Worker 执行”。具体做法FastAPI 收到请求后立刻把任务写入 Redis 队列或者直接发到进程内任务队列并向上层返回“任务已受理”。后台固定 N 个 Worker 进程消费队列同步执行 Agent 流程。客户端通过轮询或者 WebSocket 获取最终结果。这个模式牺牲了一点点接口实时性但换来了极高的稳定性。即使某个 Worker 因为异常卡死队列里的其他任务也能被其他 Worker 继续消费。压测给我的数据是在 2 个 Worker 的情况下单 Agent 流程平均耗时 8 秒系统能稳定支撑约 8 个并发任务如果把 Worker 加到 4 个并发能力翻倍但 GPU 显存的 KV Cache 也会等比例上涨。并发和成本是要放在一起算的。4.3 用压力测试把数字测出来而不是靠感觉“能扛多少并发”这个问题必须用数字回答。我的压测脚本很简单用 Python 的 asyncio 包发并发请求记录成功率和 P95 延迟import asyncio import httpx async def send_one(client, query): try: r await client.post(http://agent-service:8000/agent/ask, json{query: query, session_id: test}) return r.status_code except Exception as e: return str(e) async def main(): async with httpx.AsyncClient(timeout60) as client: tasks [send_one(client, f测试问题 {i}) for i in range(20)] results await asyncio.gather(*tasks) ok sum(1 for r in results if r 200) print(f成功 {ok}/20) asyncio.run(main())压测结果出来后我做的第一件事不是加机器而是看超时和失败分布。如果失败集中在“连接被拒绝”说明服务入口抗不住如果错误集中在“上游超时”说明模型推理或工具层是瓶颈如果错误集中在“向量库连接超时”那就去调连接池。定位到具体环节再优化比盲目扩容有效得多。4.4 超时、重试和幂等生产环境的“老三样”Agent 流程因为链条长很容易在某个环节超时。我设了一套分层的超时策略模型调用超时设为 60 秒因为长上下文的生成确实慢但超过 60 秒大概率是卡住了。工具调用超时设为 10 秒如果是内部 API 慢直接标记工具失败并让 Agent 换个路径。整个 Agent 流程设置总超时 120 秒超时后立刻返回兜底话术比如“处理超时请稍后重试”。重试要格外小心。模型调用失败后重试没有副作用但工具调用不一定。比如“创建工单”这种有副作用的操作重试会导致重复提交。我的做法是把工具分为幂等和非幂等两类非幂等工具失败后不自动重试而是把错误信息交给模型判断是否二次确认。幂等设计也很重要。我给每个请求生成一个request_id把它透传到所有工具调用里。内部系统支持用request_id做去重的话整个链路就可以放心重试。最后提一句热搜里总出现的“AI Agent 怎么扛并发”。结论是没有银弹。你先得把 Agent 流程拆成可排队、可重试、可观测的单元然后用压测数据去校准资源配比。框架层面选 Rust 可能带来性能优势但工程改造的核心永远是流程和解耦。5. 隔离网内的效果评测与持续迭代模型部署上去了Agent 也能跑了但这只是开始。真正让我头疼的是内网环境里怎么证明 Agent 效果好怎么在不让线上业务冒风险的情况下持续迭代。这一点往往被大家忽略但交付验收时它是致命问题。5.1 评测集先于代码落地我第一次做 Agent 项目时犯过一个错先把功能全写好了再想怎么测试。结果很尴尬因为业务方问“效果怎么样”我只能回答“我觉得还行”。“我觉得”在验收场景里没有说服力。正确做法是在写第一行代码之前先找业务方拿一批真实问题整理成评测集。评测集规模不需要很大但必须有代表性涵盖正常提问、模糊提问、超纲提问、恶意输入这几类。隔离内网环境里标注数据不能外包只能内部搞定。我这次是让业务方评了 200 条问题每条标注“期望回应类型”和“关键信息点”然后把它固定成自动化评测基准。之后每次改代码、换模型都拿这批数据跑一遍对比通过率谁好谁坏一目了然。5.2 全链路日志没有 TraceAgent 就是黑盒Agent 是多步决策系统如果只记录最终回答那么一旦效果变差你完全不知道是意图识别错了、检索没召回到、还是模型生成崩了。因此全链路日志必须从第一天就做好。日志的设计遵循“一个 session 一条链路”的原则2025-06-01 10:00:01.234 [agent] session_idabc123 nodeintent input怎么查昨天的订单 outputkb 2025-06-01 10:00:01.567 [retriever] session_idabc123 top1_doc订单查询操作手册.pdf score0.87 2025-06-01 10:00:03.890 [llm] session_idabc123 prompt_tokens1200 completion_tokens350 2025-06-01 10:00:04.200 [tool] session_idabc123 tool_nameorder_query statussuccess latency_ms310有了这样的日志复现问题时就能按session_id拉出整个决策链路看模型在哪个节点做了错误判断再对症下药。Agent 系统没有 Trace 就等于裸奔这句话在我这里是血泪教训。5.3 模型更新的灰度策略别一把梭内网环境虽然节奏比公网慢但模型还是要迭代的。直接一键切新模型是不可取的我习惯做“影子模式”新模型和旧模型同时跑但新模型的输出只进日志不返回给用户。等影子模式的评测通过率明显优于线上模型再灰度切一部分流量没问题再全量。隔离内网还有个特殊情况模型文件转移成本很高。几十 GB 的权重文件想更新一次光在审批和传输上就要耗不少时间。所以我建议模型迭代不要太频繁而是把优化重心放在提示词、工具编排、检索质量这些轻量改动上。这些改动只需要改配置不需要动模型文件迭代成本低得多。5.4 常见失败模式对照表最后整理一份我遇到的失败模式和应对手段给后来者一个排查起点症状可能原因处理方式回答和知识库完全无关Embedding 模型加载失败走了纯 LLM 裸答检查 Embedding 服务和向量库连通性同一问题每次答案漂移温度参数过高或上下文混入其他会话降低 temperature检查 session 隔离工具调用格式反复出错模型对工具描述理解不准确精简工具描述减少参数数量Agent 流程运行一半卡住某个工具接口无响应给工具调用加超时和熔断并发一高就大量失败Worker 数或连接池配置不足先看日志是卡在模型还是向量库内网模型答复带“幻觉”RAG 召回不足模型硬编优化切片质量和 rerank 策略写在最后隔离内网做 AI Agent和网上那些五花八门的 Demo 完全是两码事。它逼着你把每一步都钉死模型怎么进去、依赖怎么带过去、工具怎么接、并发怎么扛、效果怎么证明。这些事看着琐碎但任何一个环节掉了链子项目都推不动。我个人最大的体会是内网环境天然反对“花哨架构”它像一个过滤器把那些依赖公网便利性的设计全部筛掉留下的都是真正必要的复杂度。所以如果你正被隔离网折腾得焦头烂额别急着上更复杂的框架先把网络边界、依赖清单、评测基线这三件事做扎实项目成功率直接翻倍。最后分享一个小技巧搭建隔离环境的依赖包时不要只下载当前平台的 wheel最好在构建机上把 Linux、Windows 不同 Python 版本的 wheel 都拉下来存进制品仓库。现场机器环境往往和你预想的不一样多存一个版本就能少跑一趟介质审批流程。