隔离内网AI Agent落地实战:从离线模型部署到并发调优
这两年AI Agent火得很快但多数教程默认你能访问外网能拉镜像、调云端API。真到了银行、电力、政企这类完全断网的隔离内网环境一切“开箱即用”都会失效。我们要做的是一个内部知识问答和自动化执行Agent环境就一条铁律数据不能出域依赖不能随便拉模型不能靠云端。这篇文章是这次落地工程的完整复盘从资源盘点、模型选型、LangGraph编排、FastAPI服务化到并发压测和排障实录都会讲到。适合正准备在内网或私有化环境里做AI Agent的工程师、想搭Agent中台的架构师以及那些对“Agent怎么扛并发”有疑问的开发者。很多人刚接到需求时会觉得AI Agent不就是一个大模型加提示词吗真上手才发现隔离内网里的每一个环节都比想象中麻烦。模型要离线部署依赖要离线搬运工具要对接内网系统并发还要考虑整条链路的承载能力而不是单次模型推理。踩过的坑一多你就知道这事不是“会写Python就能做”的。下面我把整个实操过程按顺序拆开讲尽量把关键决策背后的原因也讲明白。1. 隔离内网里跑 Agent先算清三笔账1.1 第一笔账算力与模型规模必须提前匹配隔离内网不能调用外部大模型API所有模型只能本地私有化部署。选多大的模型不是看“哪个榜上分数高”而是看服务器显存能撑起多大的模型、多大并发。这里我习惯先算一笔粗糙的显存账模型规模4bit量化后权重大约8bit量化后权重大约常见可用显存范围7B4~6 GB7~9 GB单张24G能跑14B8~12 GB14~18 GB单张40G/80G较稳32B16~20 GB30~36 GB单张80G勉强建议多卡70B35~45 GB60~70 GB需要双卡或4卡张量并行但权重只是基线后面还要算KV Cache和推理引擎开销。KV Cache会随并发请求数增长并发越高显存占用越大。我们实际遇到过一台8卡80G的服务器看着很大同时跑两个服务加一个向量模型还没压测就快满了。所以第一件事是盘点机型的GPU型号、显存总量、CPU内存、磁盘IO再决定模型规模。如果想同时支撑多个业务Agent建议在架构上就预留一个“模型资源池”别只盯着一张卡能不能跑。1.2 第二笔账依赖和数据的离线搬运成本在内网环境不能直接pip install、npm install或mvn dependency:get。所有依赖都必须提前下载好或者内部搭建私有源。很多项目死在“代码写好了但依赖装不上”所以这一步要当成独立工程来做。最朴素也最可靠的方法是在一台有外网的机器上下载所有依赖包到本地目录再通过审批后拷贝进内网。Python项目我一般用pip download生成wheelhouseJava项目用mvn dependency:go-offline拉完本地仓库再整体打包。这里特别容易出问题的是PyTorch、transformers、vLLM这类大包它们体积大、版本敏感一个版本不对就可能启动失败。依赖搬运不只是Python还有Docker镜像。内网一定要有私有镜像仓库比如Harbor。所有镜像通过docker save导出、docker load导入或者推到内网Registry。这个流程看起来很笨但它是隔离内网的常规操作谁跳过谁踩坑。我们当时因为一条依赖版本不可控浪费了整整两天时间后来把所有依赖和镜像都锁版本问题才算根治。1.3 第三笔账Agent要“干活”就必须有业务边界Agent如果只做纯聊天离“干活”还远。真正要落地的Agent需要接入内部系统统一身份认证、工单系统、数据库、文件服务、消息通知等。但这些内网系统不是你想调就能调的。需要提前做系统盘点明确哪些API开放、哪些数据表可查、哪些操作需要审批。尤其是自动执行类工具比如修改工单状态、发送通知、触发任务一定要有权限控制和人审机制否则业务部门不敢用。我在不少项目里发现业务集成的复杂度往往超过模型部署本身。模型是一个纯计算组件但工具要面对各种内网系统的鉴权、格式、稳不稳定。所以做隔离内网Agent第一步不是写代码而是跟业务方把工具清单、操作边界、数据权限谈清楚。边界不清后面返工极痛。2. 选型思路模型、框架、服务化三件事一次想清楚2.1 模型选型别只看榜单要看“断网下能不能跑”模型是Agent的“大脑”但在隔离内网里可选范围会明显变窄。云端模型肯定不在考虑范围内只能选允许本地部署的开源模型。我们当时重点看了Qwen系列、GLM系列和DeepSeek系列主要原因有三点中文效果稳定社区资料多量化方案成熟。这里提醒一点商用或内网使用前一定要看开源许可证是否允许闭源商用、是否允许私有化部署。这个合规问题不能省。选型的时候不要只看跑分要看推理吞吐和显存占用。像7B模型适合简单问答、意图识别14B模型适合做工具调用和多轮Agent32B以上适合复杂推理、长文档理解。我们最终选了14B量化模型作为主力因为它在效果、单卡部署、并发能力之间最平衡。推理引擎用的vLLM它支持连续批处理和PagedAttention对并发非常友好。后面会给出具体启动参数。2.2 Agent框架选型LangGraph、LangChain、Spring AI Agent、扣子怎么选现在可选的Agent开发方式很多我个人的判断是Python技术栈优先选LangGraphJava技术栈可以考虑Spring AI Agent纯粹做原型可以先用LangChain。扣子这类云平台在隔离内网里基本用不了除非有私有化版本。原因很简单扣子通常要连云端编排服务和模型这正好触到“数据不能出域”的红线。我们当时还盘点了市面上的Agent中台产品发现不少产品已经支持私有化但落地到真正完全断网的环境还是得依赖自己的运维和二次开发能力。LangChain是生态最全的但抽象层级比较多复杂Agent的调试和状态控制并不轻松。LangGraph在LangChain基础上提供了图状态编排能更精细地控制Agent的节点、条件分支、回退和并行。这对工具调用、人工确认、多步任务特别合适。如果团队是Java背景Spring AI Agent能很好地融入Spring Boot体系但它对Python生态的模型和工具兼容性会弱一些。我们最终选择的是FastAPI LangGraph vLLM这条路线原因后面会说。2.3 对外服务形态直出接口还是做成Agent中台当一个系统里有多个Agent、多个模型、多种工具时如果每个业务都直接连模型和Agent代码整个调用关系会非常混乱。隔离内网环境反而更需要中台化思维把模型网关、Agent编排、工具注册、权限校验、并发控制、日志追踪都集中到一个服务层。业务系统只对接统一API不直接面对模型地址和工具细节。这种“Agent中台”在2026年的国内Agent产品里已经很常见了。很多商业化产品本质上就是一个私有化Agent中台。我们在内网里做的事就是照着这个思路自建一个轻量版本。好处非常明显模型服务可以独立扩缩容工具接口可以统一管理并发控制可以在网关层做审计日志也能完整记录。坏处是前期工作量会多一些。如果你们团队只做一个简单Demo可以先不做中台但一旦有多个业务接入中台层就是刚需。3. 核心细节从模型到Agent之间全是“暗坑”3.1 离线模型服务加载、量化与推理参数模型服务是整个Agent系统的底层。我们用的是vLLM的OpenAI兼容接口方便后面LangGraph直接接入。启动命令大致是这样python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-14B-Instruct-AWQ \ --quantization awq \ --served-model-name agent-model \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85这里有几个参数很关键。--max-model-len控制最大上下文长度越长KV Cache占用越大并发能力越低。如果Agent只用短文本没必要开到32K否则显存很快被吃光。--gpu-memory-utilization给KV Cache预留显存比例一般设0.85左右留一部分给模型和调度。--quantization awq是启动AWQ量化模型时需要的如果模型是原始的BF16就不要加这个参数。多模型时可以通过--served-model-name给模型起别名前端网关按名字路由。为什么要花精力搞量化因为同样的14B模型BF16可能并发能力一般AWQ 4bit后显存占用降低接近一半能撑住的并发数明显提升。代价是推理效果会有轻微波动但实际业务场景里差异不大。如果你是第一次做先用BF16验证逻辑再切量化做压测这样排错更容易。3.2 依赖与软件包的内网化不只有pip还有模型缓存依赖搬运是隔离内网的老问题但细节决定成败。Python依赖我一般这样做在有外网的机器上执行pip download -r requirements.txt -d wheelhouse --only-binary:all:再把整个wheelhouse目录拷贝进内网。注意--only-binary:all:表示只下载wheel格式避免内网编译。如果必须编译比如某些包没有对应平台的wheel就要准备一套与内网目标机一致的编译环境提前编译好再拷贝。模型缓存也不能忽略。transformers默认从HuggingFace下载模型隔离内网必须提前下载好模型文件并放到指定目录。我们通常用huggingface-cli download或直接在vLLM启动时指定本地路径。还要设置环境变量HF_HOME/data/huggingface避免第一次运行时又尝试访问外部地址。如果团队里有多台机器都要用同一个模型建议先拷到共享存储再复制到各节点省得重复搬运。Docker镜像的处理方式类似。内网必须搭Harbor或Nexus所有基础镜像打包上传。这个环节特别需要耐心因为一个镜像可能依赖几十个基础层缺一个docker load就会失败。我建议所有镜像都用docker save | gzip压缩后离线传输导入时用docker load -i验证。3.3 让Agent真正“下地干活”工具调用与内网数据源打通Agent要干活核心是工具调用。在LangGraph里工具本质上是一个一个Python函数函数必须有清晰的参数定义。模型会根据用户输入从工具描述和参数Schema里判断该调用哪个工具。这里的关键是工具描述要写得足够清楚比如“查询工单状态”和“查询工单历史记录”如果描述模糊模型很容易选错。我们实现了一个典型的内网工具链路Agent先调用知识库检索工具找到内部文档再调用工单系统API查询状态最后综合信息生成回答。每个工具都设置了超时和异常处理防止某个内网系统慢响应把Agent整条链路卡死。代码结构类似这样async def query_ticket(ticket_id: str) - str: async with httpx.AsyncClient(timeout10) as client: resp await client.post( f{INTERNAL_API}/ticket/query, json{ticket_id: ticket_id} ) resp.raise_for_status() return resp.json()[data]这类工具的返回值尽量是纯文本或JSON方便模型理解。要做好权限判断不是任何用户都能调用所有工具。这里顺带回应一个网上常见问题“个人使用AI Agent可以做期货交易吗”放在隔离内网场景里首要问题不是模型能不能决策而是行情数据能不能接入、交易通道有没有风控审核。如果执行层没有任何人工闸门让Agent直接下单风险太大了。我们做工具调用的原则是只接“只读类”工具可以直接跑凡是有写操作必须走人工审批节点。3.4 并发扛不扛得住取决于“链路”而不是单模型很多人刚接触Agent并发时只盯着模型推理速度这不对。一个Agent请求往往包含多轮模型调用和多次工具调用比如“用户提问 - 思考 - 检索知识库 - 再思考 - 生成答案”这个链路里可能调了3到5次模型。单次模型推理再快整个链路也可能被工具等待拖慢。所以并发设计要看整条链路而不是单点。我们压测之后得出一个结论Agent系统的瓶颈通常有三个地方。第一是模型服务的KV Cache和批处理能力第二是工具依赖的内网API响应速度第三是应用层的线程模型是否阻塞。第一个用vLLM解决第二个要加超时和缓存第三个就是把整个调用链改成异步IO。FastAPI对异步支持很好配合asyncio.Semaphore可以限制最大并发数防止雪崩。后面会给出完整代码示例。4. 实操记录一个“让AI下地干活”的完整链路4.1 环境基础与硬件清单我们的硬件配置不算豪华两台GPU服务器每台4张A100 80G加上一台普通的应用服务器64核CPU、512G内存。软件环境是Ubuntu 22.04、CUDA 12.x、Python 3.10、PyTorch 2.x、vLLM 0.6.x、LangChain 0.2.x、LangGraph 0.1.x、FastAPI 0.104。这个清单看起来简单但版本锁定特别重要。LangGraph版本迭代快不同版本API差别很大我们中途因为升级小版本导致接口不兼容回滚花了不少时间。还要准备一台“搬运机”也就是有外网权限、用来下载依赖和模型的机器。这里建议用与内网目标机器相同操作系统架构的机器避免下载的包因为平台不兼容装不上。我在实践中发现很多包在manylinux下是通用的但有些带有C扩展的包必须精确匹配Python版本和CUDA版本提前对准能少踩坑。4.2 第一步离线部署模型服务先把模型文件放到/data/models/Qwen2.5-14B-Instruct-AWQ再用vLLM启动。启动后可以先用curl验证服务是否正常curl http://127.0.0.1:8000/v1/models如果能看到模型列表说明模型加载成功。再简单测试一次对话curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: agent-model, messages: [{role: user, content: 你好}], max_tokens: 100 }返回正常文本就说明接口通了。这里要强调一点内网模型服务别裸奔服务端口只对内网开放网关层加鉴权。如果多个模型服务需要统一入口可以加Nginx反向代理按路径或模型名转发。这个步骤看着简单但真正耗时的地方在之前模型文件怎么从外网搬运到内网、能不能找到兼容的量化权重、vLLM与CUDA版本是否匹配等。建议把这部分时间预留充足我第一次操作时光模型文件拷贝就花了半天。4.3 第二步搭建LangGraph Agent编排我们用LangGraph做一个带工具的Agent。核心思路是用一个状态图把节点串起来LLM节点、工具节点、条件判断。当模型认为需要调用工具时进入工具节点工具返回结果后再回到LLM节点直到模型生成最终回答。代码大致如下from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.prebuilt import ToolNode, tools_condition from langchain_openai import ChatOpenAI import os class AgentState(TypedDict): messages: Annotated[list, lambda a, b: a b] llm ChatOpenAI( base_urlhttp://model-server:8000/v1, api_keyEMPTY, modelagent-model, temperature0.1, ) tools [query_ticket, search_knowledge] tool_node ToolNode(tools) model_with_tools llm.bind_tools(tools) def llm_node(state: AgentState): return {messages: [model_with_tools.invoke(state[messages])]} graph StateGraph(AgentState) graph.add_node(llm, llm_node) graph.add_node(tools, tool_node) graph.add_edge(llm, tools) graph.add_conditional_edge(llm, tools_condition) graph.add_edge(tools, llm) graph.add_edge(llm, END) app graph.compile()这里实际上用了一个一致的回路tools_condition会在模型输出没有工具调用时直接跳到END有工具调用时跳到tools节点。为什么选LangGraph而不是纯LangChain因为当Agent步骤变多时图结构更直观中途增加“人工确认”“重试”节点也更方便。代码里用了OpenAI兼容接口连接vLLMapi_key在内网里可以直接写占位符但注意不要让这个配置出现在对外暴露的日志中。4.4 第三步FastAPI接口层与并发控制Agent编排好后需要用服务把它暴露出去。我们用FastAPI写了统一接口。这里最关键的一点是全程异步使用httpx.AsyncClient调用vLLM用asyncio.Semaphore控制最大并发。原因很简单Agent链路里有IO等待如果写成同步阻塞一个请求卡在工具调用上整个进程就会被拖住并发能力极差。一个简化版实现如下import asyncio from fastapi import FastAPI from pydantic import BaseModel app FastAPI() semaphore asyncio.Semaphore(20) class AgentRequest(BaseModel): query: str session_id: str default class AgentResponse(BaseModel): answer: str app.post(/agent/query, response_modelAgentResponse) async def agent_query(req: AgentRequest): async with semaphore: result await app.ainvoke({ messages: [{role: user, content: req.query}] }) answer result[messages][-1].content return AgentResponse(answeranswer)Semaphore(20)的意思是同一时间最多有20个Agent请求在跑其余请求排队。这个数字不是越大越好要配合模型服务能力、工具API的响应速度来调整。如果模型并发能力只有8你硬开20可能只会让请求大量超时。建议先压测再反过来调这个值。另外如果业务需要流式输出可以改用StreamingResponse把模型的token增量推给前端。流式输出对交互体验提升很大但会让模型调用方和使用方的连接都变成长连接内网网关的监听参数也要相应调整这个细节容易漏。4.5 第四步压测数据与调优过程我们做了几轮压测场景分三种单轮闲聊、带知识库检索的问答、带工具调用和数据库查询的复杂任务。压测工具用的wrk和自研脚本。在4卡A100的机器上14B模型在AWQ量化下的结果大致如下场景并发数平均响应时间(s)吞吐(req/s)成功率单轮闲聊52.12.399%单轮闲聊205.43.694%知识库问答54.61.097%知识库问答2011.21.791%复杂工具任务59.80.594%复杂工具任务2023.50.886%这个数据告诉我们越复杂的Agent链路并行度越差。原因是多轮模型调用和工具等待时间加起来单请求耗时变长单位时间吞吐自然下降。成功率下降主要是因为工具API偶发超时少量是模型生成参数不符合格式。优化思路有两个方向一个是给工具调用加缓存和熔断减少重复查询另一个是把模型服务的--max-num-seqs调大让vLLM自己更激进地做动态批处理。压测中发现了一个很典型的现象把--max-num-seqs从256调小到32后单请求延迟下降了但总体吞吐反而下降。因为vLLM需要足够的并发序列来填充批处理空隙。这个参数要跟实际业务并发匹配不是越大越好也不是越小越好。最终我们通过多次压测把参数稳定在--max-num-seqs64同时把应用层Semaphore限制在15系统表现最稳。5. 常见问题与排查技巧实录5.1 依赖安装失败报错“No matching distribution found”这是离线环境最常见的问题。原因通常是wheelhouse目录里缺少某个包或者包版本与目标平台不匹配。排查思路是先看完整报错定位缺的是哪个包再去有外网的机器补下载。如果之前用了--only-binary:all:那纯Python包一般不会出问题容易出问题的是带C扩展的包比如numpy、pydantic-core、hnswlib等。版本必须非常精确建议把内网环境的uname -m和python --version提前记下来在下载时加--platform和--python-version参数。这里有一个经验把所有依赖按“项目直接依赖”和“传递依赖”分开记录。直接依赖写在requirements.in传递依赖通过pip freeze生成requirements-lock.txt。离线环境一定要用锁文件否则几个月后你想重新部署下载到的依赖版本已经不是原来的了极容易翻车。我踩过一次因为transformers小版本升级导致模型Tokenize结果不同整个Agent回答质量下降排查了很久才发现是依赖漂移。5.2 模型加载OOM或启动直接报显存不足模型加载失败常见原因有三一是模型权重格式和推理引擎版本不匹配比如用AWQ权重但没有传--quantization awq二是显存计算没留足KV Cache空间三是多卡环境没有配置张量并行。排查时先用nvidia-smi看显存占用再对照vLLM启动日志里的显存估算信息。如果日志显示“GPU memory is not enough”优先尝试调低--gpu-memory-utilization到0.7或者减小--max-model-len。我用过一个很实用的办法先启动一个最小配置比如--max-model-len 2048 --gpu-memory-utilization 0.5确认能跑通再逐步加大参数观察显存余量。这样能快速定位是模型本身太大还是KV Cache设置太高。如果单卡完全跑不动再考虑多卡张量并行。vLLM的张量并行比较简单启动时加--tensor-parallel-size 2即可但要注意跨卡通信带宽普通PCIe机器跑大模型的效果会差一些。5.3 Agent工具调用超时或返回乱格式工具调用过程中模型偶尔会生成无法解析的JSON参数或者把工具名称“编”错。这个问题在量化模型上更明显。我们的解决办法是在工具节点外面包一层“解析校验器”先尝试把模型输出当JSON解析如果失败就返回一条修复提示给模型让它重新生成工具参数。这比直接报错要稳定得多。代码大致是def parse_tool_call(raw_output): text raw_output.strip() if text.startswith(): text text.strip(json) try: return json.loads(text) except json.JSONDecodeError: return None还有一个重要技巧将所有工具调用统一走一个InternalToolGateway网关内部处理鉴权、超时、格式化这样即便工具API返回格式变化也只需要改网关而不动Agent逻辑。每个工具超时时间建议独立设置数据库查询类工具可能5秒到10秒外部进程类工具可以放宽但不要无限等待。超时后要给模型一个反馈信息比如“工单系统查询超时请稍后重试”模型会根据反馈重新规划步骤。5.4 并发一高链路偶发卡死或请求串话这是我们在压测中遇到最头疼的问题。表面上看是偶尔一个请求超时后来发现是共享状态被污染。LangGraph的compiled graph本身可以在并发中调用但如果使用了自定义的session memory或者全局变量保存中间状态并发请求就会互相覆盖。隔离内网的很多内部系统接口本来就是同步阻塞的如果应用层用同步请求一个慢API就可能拖垮整个event loop。解决方法是把每个请求的会话状态尽量隔离不要把业务数据存在全局变量里。需要记忆时用MemorySaver或数据库checkpointer并确保其线程安全。FastAPI接口中所有耗时操作都走异步方式。如果某个内网SDK不提供异步接口可以用asyncio.to_thread丢到线程池执行但要注意控制线程池大小。我们最终用了“每个会话一个AgentState实例”的方式彻底解决了串话问题。5.5 内容安全与权限校验不能只在模型层做隔离内网的Agent对接的是内部敏感数据数据安全比公网场景更严格。模型层可以加系统提示词让Agent拒绝越权操作但真正的权限控制必须在业务层实现。比如用户查询某张表Agent在调用工具前要校验用户角色和部门范围输出结果时要对手机号、身份证号等字段做脱敏。日志系统不能打印完整请求报文更不能把内网数据传到任何外部系统包括云端的监控和分析平台。我们当时专门加了两个节点一个叫“权限检查节点”在工具调用之前判断当前会话是否有权限执行该工具另一个叫“输出脱敏节点”在最终回答前处理敏感信息。这两个节点的增加确实让链路响应时间变长但安全合规是企业内网不可妥协的底线。如果Agent有写操作比如创建工单、发送通知就必须额外加人工确认步骤这是运维和业务审计的共同要求。6. 一点额外的心得项目做下来最大的感受是隔离内网没有降低对开发能力的要求反而把软件工程的复杂度全部暴露出来了。依赖管理、版本控制、并发隔离、权限设计每一个环节都用得上而且一个都不能省。如果你第一次负责这类项目建议先不要追求“多聪明的Agent”而是搭一个最小闭环离线模型服务 - 一个工具节点 - FastAPI外壳 - 压测通过然后再往里加记忆、多Agent、中台能力。最后分享一个小技巧内网Agent的调参和Debug比公网慢很多因为日志、监控、复盘工具都要自己搭。所以一开始就要把日志结构化做好记录模型请求的工具参数、耗时、成功与否。我们后来能快速定位问题靠的就是这套日志。Agent这个东西仿真环境里再顺真正接到内网业务上才见真功夫。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →