隔离内网这个词,做AI Agent的人第一反应往往是“麻烦”,但干过一遍之后我更愿意叫它“照妖镜”:所有在公网Demo里被隐藏起来的工程问题,在这里全都会暴露出来。上个月我接了一个内部工单知识助手的活,对方IT负责人见面第一句话就是“办公网和生产网隔离,没有公网出口,所有东西必须私有化部署”。这句话其实直接否定了市面上九成“AI Agent”方案——不能调云端大模型API,不能在线pip install,不能拉模型权重,甚至连SaaS版的智能体平台都没戏。这篇文章就围绕这个真实场景,说说在隔离内网下从架构选型、依赖搬运到并发扛量、故障排查的完整实战过程。
这类环境在企业里非常普遍:研发内网、生产专网、涉敏数据区,它们和互联网物理隔离或逻辑隔离,目的只有一个——数据不出域。在这种前提下做AI Agent,本质不是在“做一个聊天机器人”,而是在“离线再造一套完整的大模型应用基础设施”。如果你正准备接手类似项目,或者只是好奇离线Agent怎么落地,这篇文章值得看完。
1. 这不是云上Demo:先搞懂隔离内网缺什么
1.1 一个看似普通的Agent项目,为什么在内网会翻车
先还原一下项目需求:某机构内部有大量工单、运维手册和制度文档,工程师每天花大量时间翻文档、查工单流转记录。目标是用AI Agent做一个内部知识助手,员工问一句“打印机驱动怎么装”“这个报错码什么意思”,Agent自己检索知识库、调工单系统查历史记录,实在答不了再转人工。
听着不难,对吧?但一旦落到隔离内网,环境变成这样:
| 依赖项 | 公网开发常态 | 隔离内网现实 |
|---|---|---|
| 大模型能力 | 直接调用云厂商API | 只能本地部署开源模型 |
| Python依赖 | pip在线安装 | 必须离线包或内网私有源 |
| 模型权重 | huggingface下载 | 需要审批后人工导入 |
| 向量数据库 | 可用云服务 | 私有化部署 |
| 外部工具 | 各类SaaS API | 全部换成内网HTTP接口 |
我看到过不少团队在这个环节翻车:开发机跑得好好的,一到交付现场,依赖装不上、模型加载失败、服务监听地址不对、内网调用超时……看起来都是小问题,但叠在一起能把工期拖垮。隔离内网项目的核心约束就一句话:任何外部依赖都要提前变成“本地资产”。
1.2 为什么不能把PoC方案直接搬进内网
很多PoC方案默认用的技术栈是“云端大模型 + 在线向量库 + 开源RAG框架”,这套东西在公网环境确实跑得飞快,但它有一个隐藏假设——所有资源在线可得。到了隔离内网,这个假设崩塌,你需要重新审视每一层:
- 模型层:没有公网API,只能选可在内网部署的模型权重。参数规模、量化精度、显存占用就成了头等大事。
- 推理层:需要自己搭建模型服务,这时候 vLLM、Ollama 这类本地推理引擎就成了必需品。
- 编排层:Agent 的思维链、工具调用、记忆管理放到哪里跑?如果团队主语言是Python,FastAPI + LangGraph 是一个成熟解;如果是Java团队,Spring AI 也值得评估。
- 知识层:向量化、存储、检索必须全部离线,Embedding模型也要部署在本地。
- 工具层:Agent 要调用的所有系统,只能是内网已有的系统,且接口权限要单独设计。
我把这个项目的最终形态总结成一句话:一个跑在内网GPU服务器上的模型推理服务,加上一组含RAG和工具调用的Agent编排服务,再通过FastAPI暴露给前端,全程不依赖任何公网资源。这个思路基本是隔离内网AI Agent的“默认解”。
2. 技术选型:离线约束下的主流Agent架构
2.1 先对照主流的Agent架构,别上来就堆复杂度
当下AI Agent的主流架构大致分这么几类:
- 单Agent + ReAct循环:一个Agent边推理边调用工具,适合工具数量少的场景。
- 多Agent协作:多个Agent各司其职,通过消息协调,适合复杂任务拆解,但调试成本高。
- 工作流式Agent:把流程显式编排成节点和边,每一步可观测、可回滚,适合企业内部确定性强的业务。
- 带记忆和反思的增强循环:引入长期记忆、自我纠错,适合开放域对话。
如果你只看了几篇“多Agent协作”的文章,很容易一上来就设计三四个Agent互相聊天,觉得这才叫智能。但在这个内网项目里,工具调用只有“知识库检索”和“工单系统查询”两条,任务链路清晰,状态有限,最合适的是“工作流式Agent + 单Agent ReAct”的组合。我选了LangGraph的StateGraph,把节点、边显式画出来,每一步输入输出都可追踪。
为什么不用SaaS平台比如Coze扣子这类低代码工具?因为隔离内网没有公网SaaS可用,很多企业也不允许数据出域;如果需要私有化部署低代码平台,又是另一套成本。所以企业内部这类项目,自研Agent编排依然是主流选择。
2.2 模型服务层:本地推理引擎怎么选
Agent的大脑是本地模型服务,这里有两个选择:vLLM 和 Ollama。
Ollama的优势是安装简单、模型管理方便,一个ollama serve就能起来,适合单机验证和小并发场景。vLLM的优势是高并发吞吐能力强,支持continuous batching和PagedAttention,适合真实业务流量。隔离内网项目往往会持续运行,我建议直接把底层定为vLLM,Ollama可以作为开发阶段验证用,生产不推荐。
模型参数选择上,受限于GPU显存,我用了一个32B参数的量化模型,4比特量化后权重约20GB,单张48GB显卡可以跑。如果你们的GPU只有24GB,建议降到14B级别的模型,或者用更激进的量化(4位或2位)。别看热搜上一堆“7B模型跑满效果”的话题,企业内部文档问答场景对准确率很敏感,模型太小幻觉率会明显上升。
2.3 应用层:FastAPI + LangGraph 的分层结构
应用层我拆成了三层:
- API层:FastAPI,负责鉴权、参数校验、流式响应。
- Agent编排层:LangGraph,负责跑状态图,串联RAG节点、工具节点、生成节点。
- 工具与知识层:封装内网工单系统接口、向量库检索接口、上下文压缩等。
这样分层的好处是每一层都能独立测试,尤其适合离线交付——你可以在不连模型服务的情况下,先用Mock数据跑通Agent编排逻辑。
这里有个贴士:FastAPI选async模式,因为Agent编排过程中大量时间是IO等待(模型推理、向量检索、HTTP调用),asyncio能把等待时间让给其他请求,后续并发章节会再讲。
2.4 顺带看一眼Spring AI和Rust生态
既然热搜里有Spring AI和Rust Agent,我多说两句。如果你的团队是Java栈,Spring AI确实提供了统一的模型访问和工具调用抽象,能复用Spring生态,但它在复杂状态编排方面没有LangGraph这么细,离线交付时依赖冲突也要花时间处理。
Rust生态做Agent的优势是资源占用小、并发能力强,适合嵌入式环境或对延迟极度敏感的场景,但开发效率相比Python还是低一些,对于企业内部知识助手这种业务逻辑频繁迭代的项目,不是最优选。技术栈选择始终要服从团队维护能力和业务节奏。
3. 依赖、模型与服务的离线落地
3.1 Python依赖的三条离线路线
这是内网项目的第一道鬼门关。公网环境下pip install一条命令的事,在内网会无限重试然后超时,因为pip默认指向公网源。三条离线方案我全试过:
第一,直接下载wheel包。在能上网的开发机上执行:
pip download -d offline_packages -r requirements.txt然后把整个offline_packages目录拷贝到内网服务器,安装时用:
pip install --no-index --find-links=offline_packages -r requirements.txt注意这里--find-links指向的目录里可以包含wheel包和依赖的递归下载结果,但有些包会动态拉取依赖,建议加上--no-deps的变体逐步排查。
第二,构建内网私有源。用Nexus或devpi搭一个pip镜像,把公网包提前同步进去,内网服务器配置index-url指向私有源即可。这个方法适合项目会持续迭代、依赖经常变化的场景,前期建设成本高,但一劳永逸。
第三,Docker镜像离线搬运。如果内网允许Docker,可以在开发机构建好完整镜像,然后docker save导出,内网docker load导入。这是最省心的方式,因为镜像自带全部依赖,连系统库都包含了,我这次Agent服务本身就是这么交付的。
3.2 模型权重和Embedding模型怎么进内网
模型权重是另一个重头。在一个允许U盘和移动硬盘经过审批导入的内网环境里,最稳妥的做法是:在开发机上下载好模型文件(包括权重、tokenizer、配置文件),用移动介质拷入GPU服务器。以vLLM加载本地模型为例,目录结构大致是:
/data/models/internal-llm/ ├── config.json ├── generation_config.json ├── model-00001-of-000xx.safetensors ├── model-00002-of-000xx.safetensors ├── tokenizer.json └── tokenizer_config.json加载时直接把--model参数指向本地目录即可,不要依赖Hugging Face的在线下载。同理,RAG链路里的Embedding模型也要一并离线导入,我用的是一个中量级的bge系列模型,生成向量维度和检索质量都能满足内部场景。
补充一点:内网如果有多台机器都要跑模型服务,建议把模型文件放到共享存储或统一目录,避免每台机器各存一份,后期升级模型权重也只需要替换一份。
3.3 推理引擎的启动参数,一个都不能改错
vLLM的启动命令我调了很多次,最终稳定参数如下:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/internal-llm \ --served-model-name internal-chat \ --host 0.0.0.0 \ --port 8001 \ --gpu-memory-utilization 0.80 \ --max-model-len 8192 \ --max-num-seqs 32逐个解释这些参数为什么重要:
--served-model-name:客户端调用时使用的模型名,建议固定成业务代号,防止以后换模型时客户端跟着改。--host 0.0.0.0:必须监听所有网卡,否则内网其他机器访问不到。--gpu-memory-utilization 0.80:告诉vLLM最多用80%显存做KV Cache,留出余量给输入输出激活和临时计算。设太满容易OOM,设太少并发吞吐上不去。--max-model-len 8192:控制最大上下文长度。设太长会让KV Cache占用暴涨,单并发能吃好几个GB显存。--max-num-seqs 32:限制一次推理Batch里最多32个请求,vLLM会做continuous batching,这是扛并发的基础。
启动后可以先用一行命令验证模型服务:
curl http://127.0.0.1:8001/v1/models返回模型列表说明服务没起错。
3.4 Agent编排的首次运行:一张图把流程钉死
LangGraph的玩法是定义状态图。我建了一个AgentState,包含对话历史、检索结果、工具返回和最终回答,然后把节点串起来:预测节点先判断要不要检索/调工具,检索节点查向量库,工具节点查工单系统,生成节点组织最终答案。
from typing import TypedDict, Annotated, List from langgraph.graph import StateGraph, START, END import operator class AgentState(TypedDict): messages: Annotated[List[dict], operator.add] need_retrieval: bool need_tool: bool retrieved_docs: List[str] tool_result: dict final_answer: str def predict_actions(state: AgentState) -> dict: # 调用模型判断是否需要检索和工具调用 ... def retrieval_node(state: AgentState) -> dict: # 查向量库,返回知识片段 ... def tool_node(state: AgentState) -> dict: # 调内网工单系统接口 ... def generate_node(state: AgentState) -> dict: # 组装上下文,生成最终回复 ... builder = StateGraph(AgentState) builder.add_node("predict", predict_actions) builder.add_node("retrieval", retrieval_node) builder.add_node("tool", tool_node) builder.add_node("generate", generate_node) builder.add_edge(START, "predict") builder.add_conditional_edges("predict", lambda s: "retrieval" if s["need_retrieval"] else "tool") builder.add_edge("retrieval", "generate") builder.add_edge("tool", "generate") builder.add_edge("generate", END) app = builder.compile()第一次跑通时,我强烈建议把每个节点的输入输出都打印出来,确认Agent的决策符合预期。这一步看起来慢,但能帮你提前发现“模型根本没理解该调哪个工具”之类的问题,而不是等到联调才炸。
4. AI Agent怎么扛并发:核心矛盾与工程手段
4.1 瓶颈其实在模型推理层
热搜里“AI Agent怎么扛并发”是大家最关心的问题。先说结论:Agent服务的瓶颈几乎永远在模型推理层,而不是FastAPI进程或向量库。
一次用户提问,Agent内部可能要先做意图判断,再检索知识库,再生成答案,其中两次到大模型。大模型生成是逐token来的,一句话几十到几百个token,一次请求耗时2到10秒都不稀奇,这比普通接口慢了好几个数量级。如果业务方一次性涌进来几十个请求,你狂开线程是没用的——GPU显存和算力撑不住,反而会把服务打挂。
所以要分三层来处理:模型服务层的批处理能力、应用层的流量控制和流式响应的体验优化。
4.2 应用层三件套:信号量、队列、流式输出
应用层我用了三件套:
第一,asyncio.Semaphore限制并发进入模型服务的请求数。FastAPI的接口是async函数,里面用一个全局信号量控制同时只有N个请求在跑Agent链路,超过N的请求直接返回429,让前端提示“系统繁忙”。
import asyncio from fastapi import FastAPI, HTTPException app = FastAPI() agent_semaphore = asyncio.Semaphore(8) @app.post("/v1/chat") async def chat(payload: dict): if agent_semaphore.locked(): # 已有8个请求在跑,直接拒绝 raise HTTPException(status_code=429, detail="too many requests") async with agent_semaphore: result = await run_agent(payload["query"]) return result第二,请求排队和超时控制。光有Semaphore还不够,还要设置整体超时时间,比如一个Agent请求最长20秒,超过就取消并返回兜底话术。内网环境网络波动少,但模型偶尔会抽风,没有超时控制很容易把进程拖死。
第三,流式输出SSE。答案不是一次性返回,而是通过Server-Sent Events把token一个个推给前端。这不能提高总吞吐,但能显著降低用户感知延迟。FastAPI里可以用StreamingResponse,配合sse-starlette更方便。
4.3 容量估算:一张卡到底能扛多少并发
容量估算别拍脑袋。我按这个思路算:先看单卡显存,比如48GB,模型4比特量化权重占约20GB,留给KV Cache的显存约48 * 0.8 - 20 = 18.4GB。再看单个请求在8192上下文下的KV Cache占用,根据层数、头数和量化精度不同,大概1到2GB。也就是说显存允许同时驻留约10到18路请求。所以我把应用层信号量保守设在8,给突发和长文本留缓冲。
但显存只是其中一个约束。模型本身的总吞吐量更重要。vLLM这类引擎靠continuous batching把多个请求凑成一个Batch推理,单卡总吞吐可能是每秒几百到一千多token。假设平均每个回答300 token,10个并发请求同时进行,理论上每秒能完成3到5个完整回答。也就是说,这个配置对外扛住2到4个QPS问题不大。真实业务往往远低于这个量级,所以可行。
如果并发目标更高,要么换更大显存的卡,要么加多张卡做分布式推理,要么把Agent链路拆成多个模型服务做负载均衡。总之,先算清显存账,再谈并发调优。
4.4 内网压测的简单方法
压测不需要复杂平台,一个Locust脚本就能撑起几百并发。我在内网环境写过一个简单脚本,直接模拟用户调用/v1/chat,统计响应时间和错误率。压测时重点看两组曲线:一是吞吐量有没有随着并发上升达到平台期,二是错误率在什么并发量开始飙升。平台期就是当前配置的安全上限。
压测时还要注意连带打爆下游系统。Agent一检索、一调工单系统,下游服务会不会挂?所以压测不要只压Agent服务,要把工具链里的内网工单系统、向量数据库一起纳入监控。我这次压测就发现工单系统的查询接口在20QPS时就出现明显延迟,最后给它加了一层本地缓存,问题才缓解。
5. 知识库与工具接入的细节
5.1 离线的RAG链路:切分、向量化、召回、重排
知识库是内部文档问答的核心。文档进入RAG链路的第一步是解析和切片。PDF、Word、PPT要变成纯文本,再按标题层级和段落语义切成800到1200字的片段,切太细丢上下文,切太粗检索容易漂。
Embedding模型要在本地加载。用LangChain的向量库封装,直接连接内网部署的Milvus或轻量的Qdrant,索引维度要跟Embedding模型的输出维度严格一致,比如bge系列是1024维,建Collection时别配错。
召回阶段我用的方法是向量检索 + 关键词检索双路召回,再做重排。重排模型也是个小模型,CROSS-encoder类型,把召回的几十个片段重新打分,选Top3送给Agent。这个环节对回答准确率提升非常明显,哪怕你纠结要不要上重排,我的建议是:内部知识问答场景,值得。
5.2 企业内部系统工具怎么接
工具层是Agent能不能“干活”的关键。这次内部工单查询,因为工单系统没有现成API,只有内部HTTP接口,我封装了一个工具函数:
def query_work_order(order_no: str) -> dict: # 内网HTTP接口,带签名鉴权,设置5秒超时 response = httpx.get( f"http://ticket.internal/api/v1/order/{order_no}", headers={"X-Internal-Token": token}, timeout=5.0, ) response.raise_for_status() return response.json()然后把工具函数暴露给LangGraph的tool_node,再给模型一份工具描述。这里的关键不是写代码,而是安全边界。
5.3 权限、白名单和审计日志
Agent的工具调用权限必须最小化。一个查询工单的Agent,不应该能删除工单;一个内部知识助手,不应该能改数据库。我在工具层做了三件事:
- 工具URL白名单,只允许Agent访问预先声明的内部域名和路径。
- 每次工具调用都写入审计日志,记录用户、问题、Agent决策、工具参数、返回结果,一旦有问题可以追溯。
- 对工具返回内容做长度截断和敏感信息过滤。某些工单包含员工手机号、账号信息,Agent不能原样输出。
这些在内网项目里不是加分项,是合规底线。你在验收时如果答不上来“Agent能查到哪些数据、调用日志在哪”,项目大概率过不了安全评审。
6. 隔离内网实战中的常见故障
6.1 依赖装不上的最常见原因
“pip install 一直Retrying”是最高频问题。原因很简单:默认源不可达。解决办法就是前面提过的离线包或者内网私有源。这里有个容易忽略的坑:有些包在pip download阶段会因为平台标记不同下载不同版本,比如Linux和Mac的wheel不一样,最好在目标环境的同架构机器上执行下载。
如果某次pip install --no-index --find-links=offline_packages提示找不到某个包,大概率是下载时没有递归拉依赖,可以用pip download -r requirements.txt --no-deps分步排查,或者直接换Docker镜像方案,让所有依赖固化成镜像。
6.2 模型加载OOM
vLLM启动时报CUDA out of memory,通常是显存规划问题。按这个顺序排查:先看nvidia-smi确认没有残留进程占显存,再调低--gpu-memory-utilization,或者减小--max-model-len。如果还不行,检查模型本身是不是FP16权重大模型,考虑换4比特量化版本。还有一个冷门原因:输入了超长文本,单个请求上下文爆炸,把显存瞬间吃满。这种情况需要在应用层限制用户输入长度。
6.3 服务监听0.0.0.0还是127.0.0.1
把服务跑起来,内网另一台机器访问不到,第一反应是防火墙,第二反应是监听地址。FastAPI默认启动时uvicorn如果没写--host 0.0.0.0,默认只监听127.0.0.1,从别的机器当然连不上。vLLM和Ollama同理,OLLAMA_HOST不设置时也只在本机可达。这类问题排查要按“端口通不通、地址对不对、防火墙放没放”三步走。
6.4 流式响应一直等到超时
用SSE推送时,如果经过内部网关转发,网关往往有缓冲,等Agent生成完才一次性吐给前端,这就失去了流式意义。排查方向是看内部网关是否开了HTTP响应缓冲,或者FastAPI是否把StreamingResponse错误地包在了非流式中间件里。把这些超时参数调到30秒以上,并禁用对SSE的缓冲,基本能解决。
| 故障现象 | 大概率原因 | 处理方向 |
|---|---|---|
| pip安装反复重试 | 公网源不可达 | 离线包或私有源 |
| vLLM启动OOM | 显存规划不足 | 调低利用率/换量化 |
| 跨机器访问不通 | 监听127.0.0.1 | 改0.0.0.0检查防火墙 |
| SSE首字一直不返回 | 网关缓冲 | 关闭流式缓冲调大超时 |
| Agent答非所问 | 上下文/RAG召回弱 | 加双路召回和重排 |
做完这个项目,我最大的体会是:隔离内网下的AI Agent比公网版本更考验工程基本功,因为所有“顺手能用”的便利都消失了,每一步都得自己铺路。若让我给准备接这类项目的朋友一个建议,那就是——别急着调Agent的提示词,先把依赖离线闭环、模型服务启动、内网连通性这几个月要才摸清的关键路径打通,再谈智能。系统稳定之后,Agent的聪明程度才有意义。