隔离内网AI Agent实战:私有化模型+LangGraph+FastAPI部署指南

发布时间:2026/10/2 12:48:59
隔离内网AI Agent实战:私有化模型+LangGraph+FastAPI部署指南 1. 甲方说不能连外网AI Agent 还怎么做先交代一下背景。半年前我接到一个内部项目需求很直白在隔离内网里搭一套 AI Agent 服务让业务人员用自然语言去查询和操作内部系统中的数据。当时的第一反应是——这不就是接个大模型API包一个Agent壳子吗结果等甲方把网络拓扑图甩过来我才意识到事情没那么简单。整个生产环境是物理隔离的内网不能访问任何外部API连用pip装个包都要走审批流程。这意味着平时熟悉的OpenAI SDK、在线Embedding、云上向量库全部用不了。那段时间我查了不少资料也踩了不少坑后来逐步沉淀出一套在隔离内网里从零搭建 AI Agent 工程的完整方案。这篇文章不是讲理论就是一次真实的工程复盘包括技术选型逻辑、私有化模型部署、Agent工作流编排、服务封装与并发处理以及在内网环境下操作的具体避坑细节。如果你也遇到类似场景——无论是企业内部系统、涉密项目还是机房环境——希望这篇能让你少走几步弯路。先说结论在隔离内网里做 AI Agent核心不是模型本身而是三件事。第一把模型、依赖、镜像全部以离线方式搬运进去第二设计一个能支撑多步推理和工具调用的工作流编排层第三用一个能扛住真实并发压力的服务框架把能力暴露出来。下面我按这个顺序展开。2. 技术栈选型为什么是私有化模型 LangGraph FastAPI选型这件事我在内网环境下重新思考了很久。说实话在外网做 Agent技术栈选择非常多比如可以直接用云端 LangChain 生态、或者各种商业 Agent 平台。但在隔离内网里每个组件都要能够离线运行而且组件之间的交互不能依赖外部网络这个约束直接把可选项压缩了一大截。最终我定的组合是Qwen 系列开源模型 LangGraph FastAPI外加一个内网向量库和关系型数据库做记忆存储。逐个说一下理由。模型层我选的是 Qwen2.5 系列。原因很实际首先是开源协议比较友好可以商用部署其次是中文场景的表现在同等参数规模的模型里确实能打更重要的是它有一整套配套的部署工具模型权重可以一次性离线导入不需要任何联网验证。参数量上我根据实际情况做了区分——复杂任务用 14B 的模型做推理轻量分类和意图识别用 7B 的模型后面会细讲。编排层为什么是 LangGraph 而不是直接用 LangChain 的 Agent 或者自己硬写状态机LangGraph 本质上是一个有状态的工作流框架它把 Agent 的每一次推理、工具调用、结果回填都建模成图上的节点和边。对于隔离内网里的业务场景我们需要的是可控、可追踪、可恢复的多步流程而不是一个黑盒式的循环调用。LangGraph 的 StateGraph 模型恰好匹配这个需求而且它支持 Checkpoint 持久化这个特性在长耗时任务里非常重要。服务层FastAPI 在 Python 生态里几乎是异步服务的事实标准。Python 的 GIL 决定了多线程模型在 CPU 密集场景下很吃亏但 Agent 服务的核心负载其实是外部IO——等待模型推理、等待工具调用返回。这种场景下异步IO的优势非常明显。FastAPI 基于 asyncio加上它原生支持 OpenAPI 文档生成对内部系统对接非常友好。有一个很容易被忽略的点是内网环境下的模型访问端口问题。当时我在 TensorRT-LLM 和 vLLM 之间纠结了一阵两者都是很成熟的推理框架最后选了 vLLM原因是它在离线部署时更方便做显存控制和并发策略且配置项文档更丰富。大家在实际部署时一定要确认好推理服务的端口能被应用服务器访问别只在本地 curl 通了就当成功。下图是整个系统的架构分层虽然不是标准的架构图但可以直观看到各层的位置模型层vLLM 托管的 Qwen2.5-14B 和 7B 两套推理服务编排层LangGraph 构建的 Agent 状态机负责意图识别、任务拆解、工具调度工具层内部 API 封装、数据库查询、文档检索三大类工具服务层FastAPI 应用接收 HTTP 请求并转发给 Agent 编排层存储层PostgreSQL 存会话与状态向量库存 Embedding这个分层的好处是隔离内网环境下的故障边界非常清晰模型卡住了查模型层流程卡住了查编排层接口超时了查服务层和工具层。线上问题排查效率比大一统的应用架构高很多。3. 内网离线部署模型绕过没网就用不了的关键操作模型部署是整个工程里最基础也最坑的一步。在外网环境pip install transformers然后from_pretrained就能把模型从 HuggingFace 拉下来。但在隔离内网这条路完全走不通。你必须预先在外网环境把模型权重、依赖库、配置文件全部下载好再通过移动介质或者审批后的网关传输进去。3.1 离线依赖的完整搬运清单我整理了一份离线搬运的检查清单按这个顺序操作基本能一次通过模型权重通过modelscope或者 HuggingFace 的snapshot_download提前下载完整模型目录注意要下载全部文件包括 tokenizer.json、config.json 这些看似不起眼的小文件少一个都可能启动失败。Python 依赖在有网环境用pip download把 whl 包全部拉下来要注意目标机器的 Python 版本和系统架构比如 Python 3.10 的包不能装到 3.8 上。容器镜像内网如果需要用 Docker提前在能联网的机器上docker pull好 vLLM 和相关组件的镜像然后通过docker save导出成 tar 包。内网机器上用docker load导入这个方式比手动配容器快得多。基础配置时区配置、代理排除配置、日志输出级别等一些环境变量提前写到环境变量文件里避免内网启动时因缺少配置导致默认路径挂掉。3.2 vLLM 启动参数里的实战要点vLLM 启动时有一堆参数但真正需要重点关注的其实就那几个。我用了比较典型的一组参数可以在隔离内网环境跑得很稳定python -m vllm.entrypoints.openai.api_server \ --model /data/models/qwen2.5-14b-instruct \ --served-model-name qwen2.5-14b \ --tensor-parallel-size 4 \ --max-model-len 32768 \ --max-num-seqs 32 \ --gpu-memory-utilization 0.92 \ --port 8001 几个容易踩坑的参数我展开说一下tensor-parallel-size是张量并行度取决于你有几张 GPU。这个参数设大了会占满所有通信带宽设小了显存放不下模型。我们的服务器是 4 张 A100 80G所以设成 4负载均衡和显存占用都比较理想。如果是两张卡设成 2 就行。gpu-memory-utilization控制显存利用率。我见过很多人不敢把它调高默认 0.9 其实还好如果你同时还要在同一批 GPU 上跑别的推理服务记得适当降一降比如 0.8。max-model-len决定最大上下文长度。内网场景下业务文档可能很长上下文窗口不够的话后面的内容会被截断。但把它调大也要付出代价——KV Cache 占用显存会显著增加。实测定下来32K 是我们的一个平衡点既有足够的上下文承载能力又不会因为窗口过大导致并发度下降。如果你的场景是短文本问答16K 其实完全够。3.3 Embedding 模型的部署策略Agent 工具里的文档检索能力离不开 Embedding。这里我选择部署一个独立的 Embedding 服务而不是复用生成模型来做向量化。原因很简单Embedding 的调用频次非常高每次工具调用可能都要做一次向量化如果和生成模型混在一起推理队列会互相阻塞影响响应速度。Embedding 模型我用的是 BGE-M3参数不大部署在单独的容器里。运行方式和 vLLM 类似只是模型路径和端口不同。这里要特别注意向量维度的一致性训练检索索引时用的 Embedding 服务和线上查询时用的一定要是同一个模型、同一个端口一旦中间换过模型新旧向量维度不一致检索效果会断崖式下降。3.4 一个验证部署成功的通用方法模型部署完怎么确认它是真正可用的我习惯先做一轮简单的推理验证再用真实业务请求做测试。验证命令如下curl -X POST http://内网IP:8001/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-14b, messages: [{role: user, content: 用一句话介绍人工智能。}], max_tokens: 200, temperature: 0.7 }如果返回了内容且没有明显错误日志说明推理链路通了。然后我会用几个业务上的问题测一下比如让 Agent 去查这个月某个部门的支出明细。这一步能提前暴露很多模型能力边界问题比如模型对工具描述的语义理解是否准确或者对复杂指令的遵循能力够不够。4. 用 LangGraph 编排 Agent 工作流从一次对话到完整任务模型部署只是地基真正让 Agent下地干活的是工作流编排层。这块我花的时间最多因为隔离内网场景下的任务往往不是简单的单轮问答而是需要多步骤推理每一步都可能调用不同的工具还可能有依赖关系。4.1 不写状态机让框架替你管状态如果在 LangGraph 里建模一个带工具调用的 Agent我最常用的套路是三步定义状态、定义节点、定义边。整个过程像画一张有向图节点是操作边是流转条件。核心代码如下from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): messages: List[dict] current_task: str tool_results: dict def llm_node(state: AgentState): # 调用 vLLM 的 API生成下一步动作 response call_llm(state[messages]) return {messages: state[messages] [response]} def tool_node(state: AgentState): # 解析模型输出中的 tool_call执行对应工具 result execute_tool(state[messages][-1]) return {tool_results: result, messages: state[messages] [result]} graph StateGraph(AgentState) graph.add_node(llm, llm_node) graph.add_node(tool, tool_node) graph.add_edge(llm, tool, conditionlambda s: has_tool_call(s)) graph.add_edge(llm, END, conditionlambda s: not has_tool_call(s)) graph.add_edge(tool, llm) app graph.compile()这段代码看着简单但里面有两个容易被忽略的设计细节。第一state[messages]是整个会话的完整消息列表每一步节点执行后都要把新产生的消息追加进去这样才能让模型在下一步推理时看到之前的工具调用结果。第二has_tool_call这个条件判断是动态路由的关键模型输出的 tool_call 字段是否存在决定了流程是继续调工具还是结束输出。4.2 工具即函数注册一个能被模型正确调用的接口LangGraph 的 tool 节点内部我维护了一个工具注册表每个工具本质上是一个带 JSON Schema 描述的函数。下面是我封装的一个获取系统运行状态工具的例子from pydantic import BaseModel from langchain_core.tools import tool class SystemStatusInput(BaseModel): system_id: str Field(description系统唯一标识如 SYS-1001) tool def get_system_status(system_id: str) - dict: 获取指定系统的实时运行状态、资源使用率和告警信息 result internal_api.query_status(system_id) return result这个工具看着朴素但关键全在描述里。大模型没有读代码的能力它只能通过函数名、输入参数描述和 docstring 来判断该不该调用这个工具、传什么参数。所以我在每一个工具的 docstring 里都写得特别具体比如获取指定系统的实时运行状态、资源使用率和告警信息而不是查询状态这种模糊描述。原因是我早期版本里有一个工具描述写得含糊模型经常在多个相似工具之间乱选。后来我把描述改成获取容器资源使用率参数传入容器名称返回 CPU、内存和磁盘指标误调用的频率明显下降。这也是一个经验工具描述里必须包含什么场景下用它、参数是什么含义、返回什么数据三要素。4.3 人工审核节点隔离内网里的安全阀隔离内网里Agent 往往要操作的是核心业务数据。模型再强也有犯错的概率。所以我在关键链路加了一个human_confirm节点凡是涉及写操作或者影响面比较大的查询都必须经过人工审核。这个节点在 LangGraph 里实现为中断机制也就是说当执行流走到这个节点时Agent 会暂停等待人在 web 界面上点击允许或拒绝然后再继续往下走。有人可能会问这样会不会拖慢效率实测发现大部分查询类操作不需要审核只有变更类操作才需要所以真实影响不大。但安全性提升了一个量级这在企业内部落地时是加分项。4.4 Checkpoint 持久化长任务失败不用从头跑LangGraph 的 Checkpoint 机制是我最欣赏的特性之一。在这个 Agent 里我每处理一次请求就自动保存一次快照。这意味着一个任务如果在第 5 步工具调用时挂了恢复到快照后可以从第 5 步开始继续跑而不是重新发一遍最初的请求。隔离内网环境下网络不稳定工具调用容易超时这个特性让我们的重试成本大大降低。要注意的是Checkpoint 需要配置一个存储后端。我用的是 PostgreSQL因为内网里它最容易部署和维护而且能承载会话状态。配置方式是在 LangGraph 的compile时传入一个checkpointer代码大致如下from langgraph.checkpoint.postgres import PostgresSaver checkpointer PostgresSaver.from_conn_string(postgresql://agent_user:passpg-host:5432/agent_db) app graph.compile(checkpointercheckpointer)这段配置里有个容易踩的坑PostgreSQL 的表结构需要提前初始化。如果checkpointer.setup()没执行运行时会直接报错。我第一次就是漏了这一步花了不少时间排查。5. FastAPI 服务封装与扛并发的真实体验编排层搞定之后Agent 还只是藏在 Python 环境里的一个对象对外要暴露成可以调用的 HTTP 服务。这一步我用 FastAPI 做封装并把服务做成一个标准化的生产级应用包含鉴权、限流、结构化响应、异步处理四个部分。5.1 一个最小可用的 Agent 服务接口先看核心接口代码from fastapi import FastAPI, Depends, HTTPException from pydantic import BaseModel, Field app FastAPI(titleAgent API Service, version1.0.0) class AgentRequest(BaseModel): session_id: str Field(description会话标识) user_input: str Field(description用户输入) need_human_confirm: bool Field(False, description是否开启人工审核节点) class AgentResponse(BaseModel): session_id: str output: str trace_id: str used_tools: list[str] app.post(/api/v1/agent/run, response_modelAgentResponse) async def run_agent(req: AgentRequest, user: str Depends(verify_token)): trace_id generate_trace_id() result await run_agent_async( session_idreq.session_id, user_inputreq.user_input, need_human_confirmreq.need_human_confirm, trace_idtrace_id, useruser ) return AgentResponse(**result)这里的run_agent_async是异步执行 Agent 任务的函数内部把 LangGraph 的app.ainvoke包装成一个可等待的任务。注意我在这里没有直接用run_agent函数名作为路由名而是用了/api/v1/agent/run这是为了方便多版本共存。内部系统对接时API 路径稳定比什么都重要。5.2 并发能力瓶颈到底在哪热搜里有ai agent 怎么扛并发这个问题我的实测经验是Agent 服务的并发瓶颈几乎从不在 FastAPI 层而在模型推理服务和下游工具调用。FastAPI 的异步模型可以同时挂起成千上万个 IO 等待但 vLLM 的并发能力是有上限的内部系统的 API 服务响应速度也是有限的。我做过一轮并发压测。用 200 个并发请求同时打 Agent 服务每个请求内部有一次模型调用和一次工具调用。在 vLLM 配置max-num-seqs32的情况下系统表现是约 30% 的请求在模型排队队列中等待平均响应时间从单请求的 1.8 秒拉高到 6 秒左右。整体没有报错但响应时间明显劣化。这说明系统的扛并发能力本质上取决于下游服务的吞吐上限而不是网关层。为了让 Agent 服务能撑住更高的并发我做了三层优化请求合并BatchingvLLM 支持 Continuous Batching它会把同时到达的请求自动合并成一个批次进行推理这个机制本身就能极大提升吞吐。关键是别人为限制max-num-seqs在显存允许的情况下可以适度调大实测 32 是一个稳妥值。异步工具调用如果 Agent 一次要调多个相互独立的工具不要串行等待。在 LangGraph 的 tool 节点里我用了asyncio.gather并发执行工具函数。比如某个任务需要同时查询销售数据和库存数据整体耗时从两次串行的 2 秒降到了并行的 1.1 秒。限流保护给自己留一条退路。对普通用户限流在每分钟 30 个请求对内部系统保留 50 个请求的配额。限流不是为了限制用户而是为了防止突发流量把模型推理服务打爆。下面做一个简单的对比优化策略优化前优化后效果vLLM Batching单请求推理多请求共享批处理吞吐提升约 2.5 倍工具并发调用串行等待asyncio.gather多工具场景耗时降 40%限流保护无限制配额控制避免雪崩式失败5.3 隔离内网里的鉴权怎么设计外网服务可以直接用 OAuth2、JWT 等标准方案但内网环境下很多时候没有现成的统一认证中心。我的做法是给每个接入系统分配一个 API Token服务端用中间件校验。代码比较直接from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials security HTTPBearer() app.middleware(http) async def check_token(request: Request, call_next): if request.url.path in [/docs, /openapi.json]: return await call_next(request) token request.headers.get(Authorization, ).replace(Bearer , ) if not validate_token(token): return JSONResponse(status_code401, content{detail: Invalid token}) return await call_next(request)Token 存在哪我用 PostgreSQL 的一张表字段包括app_id、token_hash、expires_at。每次校验查库即可。内网环境不需要太复杂的密钥管理但一定要做好过期轮换。5.4 超时与重试网络抖动不可怕可怕的是没有兜底隔离内网环境里网络抖动、服务重启、交换机链路不稳定都是家常便饭。我早期因为没做超时控制经常出现接口一直挂起、占用连接资源的情况。后来统一规定了三层超时FastAPI 层请求超时 60 秒工具调用层每次工具 API 调用超时 10 秒模型推理层单次模型调用超时 30 秒超时之后统一走重试逻辑重试次数 2 次两次之间间隔 1 秒。如果重试还失败返回一个标准化的错误响应告诉调用方具体失败在哪一步。这个设计习惯是任何一次外部依赖的调用都必须假设它可能失败且必须有明确的失败表现。6. 内网部署后的避坑清单我踩过的六个坑你直接绕开整个项目实测部署后我整理了一份避坑清单都是真实遇到过的不是网上抄来的。坑 1模型文件不全启动 10 分钟才发现报错。解决方案是提前做find /data/models -name * | wc -l确认文件数量再和下载时的文件数对比。少文件通常在解压阶段才暴露很浪费排查时间。坑 2PostgreSQL 连接数爆掉。LangGraph 的 Checkpoint 每次写入都会占用一个连接高并发下连接池默认配置很容易被打满。解决方案是设置pool_size20和max_overflow10并调大数据库的最大连接数。坑 3工具函数里用了外网地址。内网环境没有 DNS 解析外网域名的能力。我的工具层里曾经有一个日志上报函数内部硬编码了一个公网地址导致工具调用永远失败。排查时发现这个工具根本没被模型调用过但其他工具偶尔会挂。后来统一自查了一遍所有工具代码凡是要外网访问的通通改走内网代理。坑 4Embedding 模型被误杀。某次重启机器后Embedding 服务没有配置开机自启结果文档检索工具全部报错但模型推理服务是正常的。这种部分依赖挂掉的场景比整体挂掉更隐蔽。我增加了健康检查接口定期探测所有服务的存活状态并接入告警。坑 5上下文太长导致模型截断。内网业务文档动辄几万字超出模型 max-model-len 后后面的内容会被静默截断Agent 可能会忽略关键信息。我的解法是在接入 Agent 之前先做一个预处理把长文档拆分成多个分段再按需检索后拼成上下文。这个方案既省 token又不会漏关键信息。坑 6人工审核节点卡住整个流程。如果某类请求设置了需要人工确认但审核界面没有访问入口任务会永远卡在等待状态。我在系统里加了一个强制超时逻辑如果审核请求在 5 分钟内没有响应自动标记为拒绝并通知管理员。这让任务不会无限期占用连接资源。我个人的深刻体会是隔离内网里的 AI Agent 工程最大的敌人不是模型能力不够而是可用性设计的缺失。外网环境下很多问题可以被默认可用的公共组件掩盖比如云上的日志服务自动重试、公共模型 API 的高可用集群。一旦搬到内网所有不可用性都会暴露出来。所以做隔离内网 Agent 工程请把 50% 的精力花在容错和处理不可用上剩下的才去优化功能效果。7. 这套方案还能怎么扩展文章写到这主体内容基本讲完了。最后聊一点后续的扩展方向供大家参考。第一如果要做AI Agent 中台也就是多个业务线共享同一套 Agent 能力可以考虑把工具注册表改造成一个可配置的管理平台让各个业务方通过界面注册自己的工具和提示词而不是改代码发版。这样能极大降低后续维护成本。第二关于会话记忆。目前我用 PostgreSQL 保存了会话状态但如果未来承载更多用户建议引入专门的向量检索来做长期记忆——把聊天历史中的关键信息转成向量存起来新会话开始时优先找回相关记忆Agent 的连续对话体验会好很多。第三多模型路由。内网环境里模型资源是有限的可以做一个路由层简单问题走 7B 小模型复杂问题走 14B 大模型甚至可以让模型先自评任务复杂度再决定走哪条通道。我们实测做了一个简化版整体资源消耗降低了约 30%同时高难度问题的准确率没有下降。隔离内网不等于落后它其实逼着你把工程上的细节想明白。希望这篇实战记录能给你一些参考少走几步我当时走过的弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询