ARTICLE DETAIL

资讯详情

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

从vLLM到LangGraph:隔离内网AI Agent工程落地指南

从vLLM到LangGraph:隔离内网AI Agent工程落地指南 这两年AI Agent的讨论热度一直没降但大多数教程都默认了一个前提你的服务器能顺畅访问公网的大模型API。而我最近大半年做的项目几乎全是反过来的——AI Agent要跑在一个隔离内网里模型必须本地部署数据不出域业务系统要和内网OA、数据库、消息中间件直接打通甚至连镜像仓库和pip源都是内网私有的。这个场景听起来不够“前沿”但真正落地过的人都知道它比在云上调API麻烦得多。这篇文章把我在隔离内网环境下完整搭建AI Agent工程的全过程拆开讲一遍。从方案选型、架构设计到vLLM本地推理、LangGraph编排、技能包离线部署、FastAPI服务化再到并发治理和问题排查全部是实际项目里踩过坑之后总结出来的东西。适合三类人看手里正捏着内网AI项目需求不敢接的工程师已经在做内网Agent但被并发和网络折腾得头疼的同学以及想评估“本地模型做Agent到底靠不靠谱”的技术负责人。1. 场景与总体设计思路1.1 为什么会有这种需求先说清楚“隔离内网”到底是个什么概念。很多公司内部的办公网和生产环境之间是做了网络隔离的两个区域之间不能随意互通流量能互通的也必须经过审批和指定的通道。还有一些行业是强监管行业比如金融、医疗、政务数据必须留在私有网络内连外网的模型API都不能碰因为数据出域本身就是违规。我遇到过的最典型需求是这样的有一个内部知识库沉淀了大量客服话术和行业文档领导想让AI能自动回答员工问题、自动生成业务简报、甚至自动在OA系统里填单子。但公有云大模型API在安全评审这一关直接就被打回来了。于是方案就成了把模型权重部署到内网服务器把Agent编排代码部署到同一网段让Agent去调内网的数据库和业务接口来完成一系列任务。这种需求有一个鲜明的特点它不像互联网C端产品那样要求百万级并发但要求长时间稳定运行、结果可追溯、权限受控。说白了先保证能用再保证好用。这一点决定了后面所有的技术选型。1.2 技术路线选型三条路的对比我在设计这个项目时认真评估过三条路线这里把当时的对比逻辑直接放出来。路线实现方式优点缺点结论网关转发在内网部署转发服务把请求转发到云上模型模型能力强部署快数据出域不合规隔离网出口带宽不稳定链路长延迟高直接排除纯本地模型开源模型权重部署到内网GPU服务器数据完全不出域可控性强响应稳定模型能力弱于顶级云端模型需要自购GPU硬件本项目采用混合路由简单任务走本地小模型复杂任务走云端大模型兼顾成本和质量仍然有数据出域风险路由策略复杂部分场景备用最终的结论很明确纯隔离内网环境下只能选纯本地模型。这不是技术偏好问题是合规边界问题。你做技术选型的时候第一件事不是比谁的模型分数高而是先搞清楚“数据能不能出去”这条红线。1.3 方案边界哪些事必须做哪些别做还有一个特别重要的点是方案边界管理。很多内网Agent项目烂尾不是因为技术做不到而是因为预期管理没做好。比如有人问“能不能让Agent帮我们写PPT”这个可以。但“能不能让Agent写一份完整的产品技术方案并且直接能用”这个对7B、14B级别的本地模型来说就很吃力。我把这个项目的目标切成三层必须做到内网环境下模型能稳定跑起来Agent能按预设流程调用内部工具能通过API被业务系统调用。尽量做到并发请求不把GPU打崩任务执行状态可查询常用技能包能离线加载。坚决不做面向公网用户的开放式Agent服务实时音视频级别的低延迟交互需要复杂数学推理和深度代码生成的任务。把边界划清楚之后后面所有技术决策都会变得顺畅很多。你不用再纠结“怎么把模型能力再提一个档”而是会把重心放到“怎么让现有能力稳定服务好”。2. 核心架构拆解2.1 五层模型从模型到业务的完整链路做内网Agent工程最忌讳的就是把代码写成一坨“大模型套壳”。我建议先按五层架构去思考问题每一层职责清晰后面换模型、换工具、换框架都不会伤筋动骨。层职责选型关键点模型服务层加载模型权重提供OpenAI兼容的推理APIvLLM / Ollama统一API协议便于上层无感切换Agent编排层决定Agent“下一步做什么”LangGraph状态图机制支持分支、循环、人工审批工具调用层Agent执行具体动作MCP规范或Python函数注册把内网的数据库、API包装成可调用工具业务接入层对外开放统一接口对接业务系统FastAPI 异步队列入队即返回任务ID后台异步执行运维观测层日志、监控、审计、模型输出过滤Prometheus 数据库审计表内网环境的可观测性常被忽视必须补上每一层之间用标准接口衔接比如模型服务层对外暴露的是OpenAI兼容的/v1/chat/completions编排层只负责调度不关心模型怎么部署业务接入层只关心入队和回调不关心Agent内部状态。这样分层之后团队开发时可以并行推进我一个人也能在有限时间内把整条链路补齐。2.2 模型服务层vLLM还是Ollama隔离内网里部署模型我首选的推理框架是vLLM而不是Ollama。不是说Ollama不好它的体验确实很友好一条命令就能拉模型跑起来做原型验证最快。但到了生产环境Ollama对并发控制的粒度、对显存利用率的优化都不如vLLM精细。vLLM的拿手好戏是PagedAttention显存管理可以显著提升吞吐还能通过--max-model-len控制单请求的最大token长度避免一个超长请求把整个显存吃光。另外vLLM原生提供OpenAI兼容接口这意味着你之前写的那些面向OpenAI SDK的代码只需要改一下base_url就能切换到本地模型。模型参数量级的选型我按显存规模给一个参考表模型规模推荐显存适配场景说明7B~8B16GB以上文档问答、信息抽取、工具调用单张4090可跑性价比最高14B28GB以上复杂指令跟随、代码生成单张4090有点紧张建议A100/A80032B量化后约40GB高质量对话、复杂推理双卡或一张80GB卡我这边实际使用的是一张40GB显存的计算卡跑的是量化后的14B级别模型。之所以不直接上32B是因为内网Agent项目更看重延迟稳定性和长时间不出错14B在这个维度上的表现更可控。2.3 编排层为什么用LangGraph而不是传统ChainAgent编排层是整个架构里最能体现工程量的地方。很多初学者会用LangChain的Chain或者直接写死一个prompt - call_llm - response的线性流程。但真实的内网业务场景里Agent经常需要循环、分支、甚至人工审批节点。举个例子Agent拿到一个“查询本月设备故障率并生成报告”的任务。它先要判断这个任务需要哪些工具然后去查数据库查完之后可能发现数据不够还得再发一次查询最后把结果交给大模型总结。这个过程的流程分支不是线性的而是图状的。LangGraph的核心价值就是把流程变成显式的状态图每个节点是一个处理单元节点之间通过共享状态传递数据遇到分支条件可以走不同路径。用生活化的类比来说传统Chain像是流水线一个工位干完传给下一个工位方向是死的。LangGraph则像车间里的一套生产调度系统每一步做完都看当前状态再决定下一步派给哪个工位还能回头返工。以前有人问“基于Rust语言能不能做AI Agent”当然能做但Agent工程的核心难点在生态和编排能力目前Python生态在模型调用、数据处理、框架支持上依然是最省事的。Java那边也有Spring AI在做类似的事但论编排灵活度LangGraph这类专业Agent框架目前还是更顺手。2.4 工具层MCP规范与本地函数注册Agent要“下地干活”必须能调用工具。内网场景里的工具五花八门查询MySQL、写Redis、调用内部审批系统的接口、发企业微信消息、读文件目录等等。我在这个项目里主要采用了自定义Python函数注册的模式然后用MCP规范做了一层封装。两者并不矛盾底层是函数加了MCP协议之后工具的描述、参数Schema、调用约束都标准化了模型理解工具的成本会大幅下降。尤其是当你的技能包Skill越来越多时规范化的工具定义能让Agent在复杂场景里更准确地选择该用哪个工具。工具层设计里有三个容易踩坑的点工具描述一定要写清楚“什么时候用它”和“什么时候不要用它”大模型选错工具很多时候是因为描述太模糊。工具的入参要做严格的类型校验Agent传来的参数经常是字符串如果你直接拿去拼SQL会出大问题。所有外部调用必须有超时控制内网服务也有抖动一个工具卡住会把整个Agent流程拖死。3. 从零到一的实操部署3.1 内网服务器环境准备内网环境最讨厌的一点是很多自动化安装脚本默认走公网源所以在装环境时必须先确认能不能用内网镜像源。我在项目启动时先做了三件事第一检查GPU服务器的驱动和CUDA版本。执行nvidia-smi确认显存、驱动版本和CUDA版本驱动版本太旧直接会影响后面容器运行。第二确认Docker可用。因为vLLM官方镜像是直接从容器仓库拉取的如果内网有镜像仓库就先把vllm/vllm-openai镜像推进去。第三确认Python相关的包能通过内网PyPI源安装。如果内网没有PyPI代理源那就只能在有网的机器上把依赖包下载成离线包再拷贝进去。这几步听起来很简单但实际操作中我遇到过整台服务器都没有外网权限的情况连apt-get install都执行不了最后是拿着移动硬盘在内网外来回拷贝离线包才解决了环境问题。所以部署内网项目时建议把“离线安装包准备”当作一个正式任务列进计划别指望现场临时下载。3.2 启动本地模型推理服务环境准备好之后启动模型服务我用的是vLLM的Docker方式。这里放一个可以直接参考的启动命令docker run --runtime nvidia --gpus all \ -v /data/models:/models \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model /models/Qwen2.5-14B-Instruct-Q4_K_M.gguf \ --served-model-name local-agent \ --max-model-len 8192 \ --gpu-memory-utilization 0.92 \ --dtype auto有几个参数值的取舍必须说清楚。--gpu-memory-utilization 0.92表示允许模型使用92%的显存留一点余量给KV cache和推理过程中的中间张量填满100%很容易在长上下文场景里直接OOM。--max-model-len 8192限制了模型处理的最大上下文长度1个token约等于不到1个汉字8K上下文对内网文档问答基本够用再大就要吃更多显存。--served-model-name设置的是对外暴露的模型名内网其他系统调用时只用这个名字后续换模型文件不影响调用方。启动之后用curl http://127.0.0.1:8000/v1/models验证一下是否返回模型信息。能返回基本就说明服务起来了。这时候建议再发一个测试请求确认整体链路没毛病再去写Agent编排代码。3.3 技能包HarnessSkill的内网落地很多Agent框架里都有“技能包”的概念有些叫Skill有些叫Harness。它本质上是把一组工具使用说明、提示词模板和示例放在一起统一打包给Agent让它知道“在什么场景下可以用什么技能、怎么用”。有一个比较常见的需求场景DeepSeek Harness附带了一批Skill你要把这套东西部署到内网服务器上。我实际操作下来步骤并不复杂在一台能访问公网的机器上把Harness仓库和Skill内容完整拉取下来。把Skill里的所有引用的Python依赖做离线打包内网机器安装时走内网PyPI源或者离线wheel包。把Skill的配置文件里的模型地址全部改成内网的vLLM服务地址比如http://192.168.10.20:8000/v1。把Skill目录放到指定的配置路径下然后用框架自带的加载器启动。看起来就这么四步实际上最耗时间的不是拷贝文件而是依赖冲突和路径适配。比如SkillA依赖pandas2.0SkillB依赖pandas1.5两个塞在一起很容易崩。我的习惯是给每个核心技能包建一个独立的Python虚拟环境Skill之间通过Agent主进程做调度避免依赖互相污染。技能包写好之后建议用一个元数据文件来登记每项技能的名称、描述、入口函数和调用方式。这样编排层在决定“该用哪项技能”时可以快速从元数据里选出匹配项。3.4 用LangGraph编写Agent主流程等模型服务和技能包都就位就可以写Agent调度逻辑了。我用LangGraph画整个执行流程核心节点包括“接收任务”“任务拆解”“调用工具”“结果生成”“终止判断”。下面是一个简化版本的关键代码结构from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated import operator class AgentState(TypedDict): task: str messages: list tool_calls: Annotated[list, operator.add] result: str def plan_node(state: AgentState): # 调用本地模型把任务拆解成若干子步骤 pass def tool_node(state: AgentState): # 根据子步骤选择并执行技能包里的工具 pass def generate_node(state: AgentState): # 汇总工具执行结果生成最终回答 pass def should_continue(state: AgentState): # 判断任务是否完成或者是否需要重新规划 if state.get(result): return END return plan graph StateGraph(AgentState) graph.add_node(plan, plan_node) graph.add_node(tool, tool_node) graph.add_node(generate, generate_node) graph.set_entry_point(plan) graph.add_conditional_edges(plan, should_continue, {continue: tool, END: generate}) graph.add_edge(tool, generate) graph.add_edge(generate, END) app graph.compile()这个代码虽然简化了细节但能看出关键设计思路Agent不是一次性生成完结果而是“规划-执行-判断”循环。这样当某个工具调用失败时模型可以在下一轮规划里修正自己的方案而不是直接放弃。另一个重点是状态管理。AgentState里我刻意用了Annotated[list, operator.add]这样多个工具调用返回的结果会按顺序累积不会被覆盖。内网Agent跑长任务时工具调用可能有十几轮每一步的过程数据都要留得住便于事后审计。3.5 用FastAPI把Agent包成一个服务单纯跑通Agent还不行业务系统要调用它必须暴露一个HTTP接口。我选了FastAPI作为接入层框架原因很简单它有原生的异步支持配合任务队列能轻松解决同步阻塞问题自带Swagger文档内网其他团队对接时直接看文档就能调。接口层我设计了两个核心Endpointfrom fastapi import FastAPI from pydantic import BaseModel app FastAPI() class TaskRequest(BaseModel): task: str biz_type: str default callback_url: str app.post(/api/agent/run) async def create_task(req: TaskRequest): task_id await queue.put(req) return {task_id: task_id, status: pending} app.get(/api/agent/task/{task_id}) async def get_task(task_id: str): return task_store.get(task_id)入口接口负责接收任务并立即返回任务ID不阻塞调用方。任务真正执行由后台Worker负责Worker处理完把结果写入任务存储同时如果有回调地址则通知调用方。这个模式在工程上叫“异步任务处理”好处是即使任务执行需要几分钟调用方也不用一直等HTTP连接挂着。实际项目中我还加了鉴权逻辑所有进来的请求头上都要带一个内网签发Token。内网不代表绝对安全Agent能调工具、能查数据如果接口裸奔被扫到后果比公网泄露还严重。3.6 局域网访问与Webhook回调对接模型服务和Agent服务都启动后内网的其他业务系统想访问直接用服务器内网IP加端口就行了。比如http://192.168.10.20:8000是模型服务http://192.168.10.20:9000是Agent服务。这一步只要网络策略放行了端口基本没什么问题。真正麻烦的是Webhook回调。比如Agent执行一轮任务后需要通知另一个部门的系统对方系统又在我们这台Agent服务器的另一个网段两边互相不主动访问。很多人第一反应是“搞内网映射”我的建议是别碰这类方案内网安全策略大概率不允许而且也很不稳定。正确做法是顺着网络架构设计连接方向。我遇到的实际情况是Agent需要等待另一个系统处理完一项审批后再生成结论。最后选了Agent主动查询方案即Agent定期调用对方系统的查询接口拿到结果后再继续后续流程。不强行要求“对方系统回调我们”而是从“查询者的角度”去设计编排流程整个链路反而简单稳定得多。4. 并发问题内网Agent怎么扛住真实业务压力4.1 先搞清楚瓶颈在哪一层“AI Agent怎么扛并发”是很多团队关心的问题但答案必须先看瓶颈在哪里。在隔离内网场景下系统链路有这么几层HTTP接入层、Agent编排层、推理服务层、模型性能层。用一张表格来看每一层的并发瓶颈链路层并发瓶颈解耦手段HTTP接入层连接数限制FastAPI异步接口快速返回任务IDAgent编排层状态存储压力任务状态存Redis工具调用层内网业务系统QPS限制工具调用加信号量限速推理服务层GPU显存与计算吞吐vLLM批次调度控制并发数模型性能层单token生成时间降低上下文长度、选更小模型这里的实践经验是绝大多数情况下最开始被打爆的不是GPU而是第三方内网业务系统。比如Agent并发执行时每个任务都去查OA系统的接口对方系统是若干年前的老架构一秒能扛的请求量也就几十个Agent一开并发立刻就被封了IP。所以真正的并发控制不是只盯着模型服务而是要给工具调用层做限速。4.2 用任务队列和异步Worker做并发控制我在FastAPI接入层和Agent执行层之间加了一个基于asyncio.Queue的任务队列配合固定数量的Worker进程去消费任务。核心代码如下import asyncio queue asyncio.Queue(maxsize100) WORKER_COUNT 4 async def worker(): while True: req await queue.get() try: result await run_agent(req) await notify_done(req, result) except Exception as e: await notify_error(req, str(e)) finally: queue.task_done() app.on_event(startup) async def startup(): for _ in range(WORKER_COUNT): asyncio.create_task(worker())这里解释一下为什么用队列而不是开几百个协程直接执行任务。Agent任务不是普通HTTP请求它内部会调用大模型单个任务可能耗时几十秒甚至几分钟如果无限并发只要来100个请求GPU直接爆显存。控制Worker数量就是控制同时对GPU的占用数量。另外队列的maxsize100也很关键队列满了新请求马上被拒并提示“任务排队中请稍后重试”。这套机制保证了在突发流量下系统不会雪崩只是会正常排队。4.3 超时、重试与优雅降级内网环境虽然整体比公网稳定但也不是没故障。我用一套比较保守的容错策略策略参数原因模型单次调用超时60秒本地模型通常30秒内能返回给足余量工具调用超时10秒内网接口一般很快10秒不返回说明有问题单任务总超时300秒长任务也有上限避免排队堆死工具调用重试次数2次最多重试一次再失败就交给模型规划新的路径总任务失败后动作记录审计日志并通知人工内网Agent涉及的业务往往重要不能静默失败这种策略在实际运维中非常管用。比如某个内网接口偶尔慢一下10秒超时重试2次基本能绕过如果连续3次都失败就说明对方系统可能挂了这时候让模型去尝试备用工具如果还是没有备用方案就明确告诉用户“当前无法处理”而不是生成一个看似合理但错误的结果。对Agent来说“诚实地承认失败”比“强行编造结果”重要一百倍。4.4 一组可以拿来当参考的实测数据我觉得光讲理论不够这里贴一组我真实跑过的数据设备是单张40GB显存卡跑的14B量化模型任务类型是“内网文档知识库问答调用查询工具生成周报”。并发Worker数平均单任务耗时最慢任务耗时服务端成功率148秒91秒100%252秒103秒100%463秒135秒99.5%881秒180秒92%可以看到Worker从4个加到8个后成功率反而开始下降因为并发推理导致显存压力上升、batch等待变长单个任务耗时明显增加。最后我把生产环境的Worker数固定在了3个并用排队队列挡住多余的请求整体运行非常稳定。这可能和很多人的预期“并发越大越好”相反但GPU推理的真相是盲目加并发等于谁都跑不动合理限流才是对内网算力的最大利用。5. 内网部署的常见问题与排查实录5.1 高频问题速查表内网Agent部署不像云上那么“友好”什么问题都可能冒出来。我把自己遇到的频率最高的问题整理成了一个速查表后续新项目直接照着查。现象可能原因排查步骤解决办法模型服务启动就退出显存不足或驱动和容器版本不匹配执行nvidia-smi查显存查看vLLM容器日志调低gpu-memory-utilization升级驱动请求超时上下文太长推理变慢并发太高排队看模型服务监控查当前活跃请求数降低max-model-len减少Worker数调用Agent返回服务不可用内网机器无法解析服务器主机名ping服务IP能通就换IP访问不能通就查防火墙统一走IP或维护内网hosts文件Webhook回调失败对方系统无法主动访问Agent网段在Agent服务器上尝试连对方系统的回调地址改Agent主动轮询对方接口时间不同步导致登录态失效服务器时间偏离JWT校验失败对比Agent服务器和业务系统时间配置内网NTP时间同步模型答非所问用了过大的max-model-len占用显存检查显存占用和KV cache指标精简上下文及时清理历史消息第4个问题是高频中的高频我单列一节展开讲。5.2 一次真实排查Webhook回不来到底卡在哪这个项目上线后没多久收到一条告警Agent执行完一个流程调用业务系统A的回调接口时失败了。一开始我看日志以为是对方系统没启动手动用curl去访问对方接口完全是通的但那台回调服务器确实收不到Agent发来的请求。排查到最后才发现问题不在Agent也不在对方服务而在于网络策略。Agent部署的服务器和目标系统不在同一个安全域两个安全域之间只允许“目标系统所在网段主动发起连接”的单向通道反过来从Agent网段向目标网段发起访问是受限的。这事给了我一个非常深刻的教训内网架构里“网络方向”比“网络连通性”重要得多。很多系统之间并不是物理不通而是策略上只允许某些方向的访问。后来我再设计Agent和外部系统交互时第一件事就是问清楚“谁可以访问谁”然后顺着现有方向设计流程。Agent需要查系统B的数据如果系统B不能主动推给Agent就设计成Agent定时轮询Agent需要通知系统C结果如果Agent不能访问系统C就找消息中间件作为中转。本地网络通信能力是需要慎重设计的资源。5.3 安全与合规的工程落地细节内网Agent的安全问题不像公网Web服务那么强调WAF和防SQL注入但每一条都同样要命。第一点是能力权限最小化。Agent要访问数据库就创建一个只读账号只授权到特定库表要调OA接口只给一个只能处理指定流程的专用账号。大模型有时候会“自作主张”发起不存在的请求如果账号权限太大就是灾难。第二点是全链路审计。Agent的每一次规划、每一次工具调用、每一次最终输出都要写审计日志。我见过不少团队只记录最终回答一旦出问题复盘时完全还原不了现场。我自己做的方案是把规划链和执行链都存到数据库里哪个环节出问题按任务ID一查就能定位。第三点是输出内容过滤。大模型生成的内容不能直接展示给用户我会在输出层挂一个过滤组件屏蔽掉手机号、身份证号等敏感信息同时对涉及金额、审批结论等严肃场景的内容强制采用“工具返回结果优先”的策略不允许模型在关键参数上自由发挥。模型负责组织语言业务事实以工具返回为唯一依据。最后说点实在的做内网Agent工程有一段时间了我最大的体会是这个项目的难点从来不在模型多聪明而在工程连接。外网环境里十几分钟能搞定的接口对接在内网里可能要为了一个网络方向问题调上好几天云端随手拉的镜像在内网里可能要带着移动硬盘来回拷好几趟。但恰恰是这些“不酷”的部分才是真正决定项目能不能落地的关键。如果你正准备在隔离内网环境里上Agent项目我的建议是先别急着调模型、写Prompt而是花时间把网络分区、访问方向、离线依赖、权限边界这几个基础问题摸透。基础打牢之后再照着“模型服务、Agent编排、技能工具、业务接入、运维观测”这条链路一层一层把系统填起来。最后再分享一个小技巧内网环境里先跑通最小闭环哪怕只是“用户提交问题-模型回答”的纯对话场景也比盯着完整的架构图规划半个月更实在。能跑通的系统才有资格谈优化。
返回列表