ARTICLE DETAIL

资讯详情

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

隔离内网AI Agent落地实战:从模型部署到并发架构全解析

隔离内网AI Agent落地实战:从模型部署到并发架构全解析 1. 项目概述这件事绕不开的三个难题前段时间团队接到一个硬任务在某单位的隔离内网环境里搭一套能真正干活的 AI Agent 系统。所谓隔离内网就是物理断网、无外网访问、私有的包仓库和模型仓库所有依赖都得手工搬运。由于数据不能出域大模型推理必须完全本地化部署。题目看着不大真正动起手来才发现把 Agent 从“能跑demo”推到“能扛业务”每一步都是坑。先说结果最后我们在完全无外网、只有 CPU 和少量 GPU 的受限环境下跑出了一套基于 FastAPI LangChain LangGraph 的 Agent 服务支持多工具调用、任务持久化、并发受控和基本的可观测性。虽然谈不上大规模高可用但作为工程原型已经能稳定地把一轮轮真实业务跑完。这篇文章就把整个过程中的架构选型、模型部署、并发设计、工具封装和排坑记录完整拆开讲给同样在隔离网里做 Agent 的朋友一个可复用的参考。聊之前先把隔离内网 AI Agent 工程的三个关键问题定位清楚。第一模型从哪来。隔离网里没有 OpenAI、没有 Claude能依赖的只有本地推理引擎和权重文件。也就是说“Agent 的脑子”这件事从第一天就要自己做决定而且这个决定会直接影响后文所有架构设计。第二网络和依赖怎么解决。pip、npm、apt 全部失效代码库、模型权重、依赖包都得靠离线文件拷贝进去。这不仅是体力活还涉及版本兼容、传递依赖、哈希校验等一不留神就在内网里卡上几天。第三并发和稳定性怎么保证。Agent 的本质是“多轮循环调用大模型 工具”单次任务时长可能拉长到几十秒甚至几分钟。如果直接拿同步 HTTP 请求去编排后端很快会被卡死。真实业务一上来你就必须面对“Agent 怎么扛并发”这个问题而这个热词背后是一整套异步化、持久化和消息队列的设计。这篇文章适合谁如果你正在做企业级 RAG、自动化助手、智能工单、资管分析之类的项目并且有“一上真实环境就崩”的忧虑那这篇内容基本就是为你写的。如果你还在用云端 API 开发 Agent也可以先看一眼离线版的架构差异很多经验是共通的。先说清楚这不是一篇纯理论文章里面的代码和方案都经历过真实环境检验步骤如下。2. 技术选型隔离内网里做架构决策的完整逻辑2.1 三个硬约束如何倒逼选型任何架构都不是凭空选的先看你面对的是什么约束。第一个约束是网络断连。所有组件间的通信只能走内网外部 API 一律变成不可用选项。这意味着 Agent 的模型推理、向量检索、BaaS 能力全部要本地化。很多人第一反应是“那我用自研协议呗”实际上这是个误区——隔离网不代表要重复造轮子而是要善用本地可用的成熟协议和开源中间件比如 HTTP、gRPC、消息队列这些内网完全跑得通。第二个约束是资源有限。我们当初只有 2 张老旧 GPU单卡 24GB 显存和 32 核 CPU。这决定了模型规模的上限也决定了并发能力不可能靠“堆大模型”实现只能靠工程手段去扛。第三个约束是交付与维护。内网环境通常伴随严格的变更管控系统上线后迭代很慢。这要求架构不能太复杂中间件要少而精出了问题要好排查。如果为了炫技引入一堆框架后面运维就是给自己挖坑。这三个约束叠加在一起技术选型的核心原则就清晰了本地优先、简化链路、以可运维为第一目标。你每一步都围绕这个原则做决策后面就不会跑偏。2.2 Agent 框架为什么选 LangChain LangGraph 而不是 Spring AI 或自研市面上主流的 Agent 框架有 LangChain/LangGraph、Spring AI、自研中间层以及扣子这类平台型智能体应用。说下我们为什么最后选了 LangChain LangGraph。先看 Spring AI。它对 Java 技术栈的团队很友好封装也比较干净抽象了 ChatModel、EmbeddingModel 等接口起步很快。但它的生态相对年轻社区的教程和工具封装远没有 LangChain 丰富。我们的核心链路里需要用到循环网络、条件分支这些偏图执行的能力Spring AI 在“有状态多轮编排”上还比较弱这对 Agent 很致命。再看不成熟的自研方案。很多人觉得“Agent 太复杂不如自己写一个编排器”这思路在特定场景没问题但在项目初期就自研等于把模型调用、工具调度、状态管理、重试机制全部自己造一遍一个月后你会发现写了个四不像的框架还背一身基础服务债务。Agent 的难点根本不在于“调通大模型”而在于实体抽取、工具协议、长上下文取舍、容错恢复这些边边角角这些坑 LangChain 社区基本都踩过一遍了。LangChain LangGraph 组合的优势在于前者提供了丰富的连接器和工具封装后者的 StateGraph 执行模型非常适合 Agent 的多轮回路、分支判断和人工介入。LangGraph 的核心思路是“把 Agent 跑成一个有状态图”每个节点是一个动作每条边是一个跳转条件状态通过结构化对象传递。这个抽象直观且好调试跟隔离内网场景的需求匹配度很高。当然LangChain/LangGraph 离线安装也不是拿来就能跑的。我们通过内部私有 PyPI 镜像把依赖包全部下载好后用pip --no-index --find-links方式离线安装这一步如果没提前做后面就是灾难。版本务必锁死尤其注意 langchain-core、langchain、langgraph 三者版本要配套不能混装。还有一条经验把 Frame 封装成你自己的 Provider 接口而不是在业务代码里到处直接调 LangChain API。这样后续就算替换框架成本也只在接口适配层。我们在每个 Agent 外层包了一层AgentService内部接口只有run()和cancel()隔离网内的项目变更管控很严这种接口隔离是保命设计。2.3 模型选择Qwen 为主的本地模型矩阵Agent 的大脑必须本地化这是整个系统里最不能出错、也最花钱花时间的一环。可选的模型无非几个系列Qwen、ChatGLM、Yi、DeepSeek 系列以及偏英文的 Llama 系列。我们最终选定 Qwen2.57B~14B作为主模型配 BGE-M3 作为向量模型。为什么是 Qwen三个原因一是中文指令遵循和工具调用能力在开源模型里表现很稳二是它的 Function Calling 协议跟 OpenAI 兼容LangChain 可以直接套openai风格的接口省一层适配三是对显存和推理框架的适配比较好AWQ、GPTQ、GGUF 格式在 vLLM 上都能跑量化后的质量损失可控。不同参数规模的取舍是这样的7B 模型在 CPU 推理上勉强能动但速度很慢单轮生成可能超过 20 秒不适合做在线服务14B 模型量化后在 24GB 显存上可以流畅跑质量明显更好能满足绝大多数业务。如果你有 A100 或 H 系列那直接上 32B/70BAgent 的推理质量会上一个台阶。显存不够就想办法量化别死磕全精度。字段模型也值得单独说。Agent 在跟工具交互时输入输出都要做结构化校验。我们给模型设计了“意图识别 实体抽取 自然语言生成”三段式。意图识别和实体抽取用较小的模型比如 Qwen2.5-3B 量化版来跑只有最后用户可读回答才走大模型。这样既能省算力又能提升响应速度。实体抽取的准确性其实远比自然语言生成质量重要因为答案对不对取决于有没有理解对用户问题。2.4 部署拓扑一张图说清服务怎么落地整个系统的服务边界我用文字先给你画个草图后期运维的人会感谢你这个设计的。内网的核心节点有三类模型推理节点GPU 服务器跑 vLLMAgent 编排节点CPU 服务器跑 FastAPI LangGraph以及基础服务节点跑 MySQL、Redis、消息队列、向量库。三者之间全部通过内网 IP 互通不暴露任何公网地址。模型推理节点暴露 OpenAI 兼容的/v1/chat/completions接口Agent 编排节点通过vLLM的 client 调用。这个设计让上层应用完全不用关心底层跑的是 vLLM 还是 TensorRT-LLM切换时只改一个 base_url。基础服务里向量库用的是 Milvus Lite 或 Qdrant 的离线模式存知识库切片和会话历史 embedding。消息队列我们选了 Redis Stream因为它不额外引入 Kafka 这类重组件Redis 本身在前置场景里几乎都有省事。持久化走 MySQL主要存任务状态、调用链路由和审计日志。这里有个经验隔离网里中间件越少越安全每多一个组件离线部署和后续运维的复杂度是指数级上升的。3. 模型部署与推理引擎离线环境下的性能调优全程3.1 离线模型文件迁移的几个反直觉坑隔离环境里模型不是直接拿来就能跑的。权重文件动辄十几 GB导入时常见的坑有三个一是文件完整性校验缺失模型加载到一半报 unexpected keyword argument排查半天发现是 SHA256 不匹配某个 chunk 在中转过程中损坏了二是文件系统大小写敏感问题项目里有人用 Mac 打包拷贝的目录Linux 上加载直接 NotFound三是模板路径写死本地绝对路径上线后被调用就 404。我的做法是所有模型和代码打包前统一用tarsha256sum校验并生成一个MANIFEST文件记录每个文件的校验和。进入内网后先跑一遍校验脚本再解压。这不是洁癖而是断网环境下无法重新下载文件一旦坏了整个项目停摆。权重文件这块还有个细节分开存放“基座模型”和“适配层”。我们按 HuggingFace 的结构把 config、tokenizer、权重分开目录管理然后在加载代码里用model_name_or_path统一指定。这样后续要切换不同量化版本只需要改路径不用动代码。3.2 vLLM 还是 TensorRT-LLM推理引擎选型复盘推理引擎这块我们实测下来 vLLM 是最平衡的选择尤其适合 Agent 场景。原因有三点。一是兼容性好。它对 HuggingFace 模型的覆盖度最高支持的量化格式也最多从 AWQ 到 GGUF 都能跑。你不需要为了某个模型再专门去做格式转换这点在隔离网里特别重要因为你没法去外网查教程、下转换工具链。二是吞吐高。vLLM 的 PagedAttention 机制在长上下文、多并发场景下优势明显。Agent 的调用模式是“短请求多、上下文长”比如你的系统提示词加历史会话可能就有 4K~8K tokensvLLM 对这种负载的优化很到位。三是 API 兼容 OpenAI。这意味着 LangChain 的ChatOpenAI只要改个base_url就能直连零适配成本。TensorRT-LLM 的推理性能上限更高但部署复杂度也高很多需要针对特定 GPU 做 engine 编译。在隔离内网里你没有办法随意调整编译参数、反复烧卡试错一旦编译失败调试环境又是另一套依赖非常痛苦。所以业务型 Agent 场景我更推荐 vLLM。3.3 显存不够怎么办量化方案的实测对比我们的工作机是 2 张 24GB 卡跑 Qwen2.5-14B 全精度会炸显存所以必须量化。实测三种方案的取舍如下量化方式加载后显存占用14B推理质量下降备注FP16/BF16约 28GB无单卡放不下需要张量并行AWQ 4bit约 9GB很小语感下降但工具调用路径基本无损GPTQ 4bit约 10GB很小兼容性略低于 AWQ加载速度稍慢GGUF Q4_K_M约 8GB中等推理速度较慢适合 CPU 或部分 GPU我们的结论是如果目标卡是 24GB直接上 AWQ 4bit推理质量和速度平衡最好。工具调用本身是格式预测问题不是语义生成问题量化带来的质量损伤主要在生成式回答的润色上而 Agent 业务的核心链路恰恰依赖结构化输出所以 AWQ 是够用的。如果你的场景是“CPU only”那 GGUF llama.cpp 是唯一解。我们早期在纯 CPU 机器上跑过 7B GGUF单轮推理 10~15 秒虽然慢但做后台异步任务勉强能接受。如果业务对响应时间有强要求CPU 方案基本不用考虑。3.4 Embedding 与向量库选型RAG 是 Agent 的地基Agent 解决实际问题绝大多数时候都要依赖知识库。内网里的 RAG 链路我们用了 BGE-M3 做 embedding向量库用 Milvus Lite。BGE-M3 可以输出 8192 维支持稠密稀疏混合检索但实际用下来对隔离网业务这种“限定域、文档量不大”的场景只用稠密向量就足够了高维反而拖慢检索速度。我们最终把维度裁剪到 1024 维召回率几乎没变查询延迟下降明显。如果你在隔离网里没有 GPU 推理 embedding可以考虑用 CPU 跑 BGE-small 或 MiniLM 中文模型。向量维度低、模型小CPU 也能扛单条 embedding 大约几十毫秒。Agent 场景里 embedding 的质量直接影响“工具选择是否正确”这块不能省。向量库选型更简单文档量低于百万级直接用 Milvus Lite 或 Qdrant 离线版别一上来就部署正式的分布式 Milvus。隔离网的环境没那么多机器轻量库足够支撑 POC 到中小型生产。4. 工程实现Agent 并发模型与任务编排的完整拆解4.1 为什么同步 HTTP 调用会卡死你的 Agent 服务很多人第一次写 Agent 后端会写成同步模式用户请求进来直接调 LLMLLM 返回再调工具工具返回再调 LLM……每一个环节都是同步阻塞一次请求几十秒。这在单用户 demo 里没问题并发一来FastAPI 的 worker 全部被占满新的 HTTP 请求全部排队最后直接超时。核心原因在于Agent 不是“一个请求”而是一个“请求图”。它包含多轮模型调用、工具调用、状态分支单次执行时间远大于常规 API 接口。隔离内网场景虽然并发量不一定巨大但即便如此同步执行也扛不住几个用户同时打开页面。解决方案是用“异步 Runtime 消息队列 状态持久化”模式把“用户请求”和“Agent 执行”解耦。用户请求只做一件事把任务写入队列并立即返回一个 task_id。真实的 Agent 循环在后台 worker 中异步运行执行状态写入 MySQL前端通过 WebSocket 或轮询获取进度。这样用户请求的 HTTP 连接很快释放服务端吞吐能力大幅提升。4.2 状态持久化为什么不能把状态放内存Agent 的状态包括当前节点、已收集的上下文、工具调用历史、模型输出缓存、用户偏好等。这些如果只放在内存里一旦服务重启或 worker 崩溃所有任务全部丢失。对于普通 API 这不是大问题但 Agent 任务动辄执行几分钟要做到可恢复、可重试状态必须持久化。我们设计了agent_task表核心字段有task_id、statuspending/running/success/failed/canceled、current_node、graph_stateJSON 序列化、retry_count、created_at、updated_at。LangGraph 的状态本身就是一个 dict可以被直接 JSON 序列化这为持久化提供了天然基础。LangGraph 的执行过程是状态传递——上一个节点的输出作为下一个节点的输入。我们在每个节点的入口处先尝试从agent_task表恢复状态再决定是继续执行还是从失败点重跑。这样即使某个节点因为模型超时挂了恢复后也不会从头开始而是直接从失败节点重跑省时省力。这个设计的另一个好处是支持“人工审批”节点。当 Agent 需要确认才能执行有风险的工具动作时我们把状态停在一个waiting_approval节点人工通过后直接续跑。业务方对这个能力非常满意因为很多操作不是“Agent 能自动做”就能放的必须有人把关。4.3 异步架构核心代码直接把方案拿走用下面这段代码是整体架构的核心骨架。先看服务端如何把请求快速入队from fastapi import FastAPI, BackgroundTasks from pydantic import BaseModel from redis import Redis from uuid import uuid4 app FastAPI() redis_client Redis(hostredis.internal, port6379, decode_responsesTrue) class TaskRequest(BaseModel): query: str user_id: str session_id: str app.post(/agent/run) async def create_agent_task(req: TaskRequest): task_id str(uuid4()) task_data { task_id: task_id, query: req.query, user_id: req.user_id, session_id: req.session_id, status: pending } # 先写数据库确保任务不丢 insert_agent_task(task_data) # 再入 Redis Stream redis_client.xadd(agent_tasks, task_data) return {task_id: task_id, status: pending} app.get(/agent/task/{task_id}) async def get_task_status(task_id: str): # 从 MySQL 读取最新状态返回给前端轮询 task fetch_agent_task(task_id) return {task_id: task_id, status: task[status], result: task.get(result)}后台 worker 的消费循环长这样async def worker_loop(): while True: # 阻塞式读取 Stream超时继续轮询 entries redis_client.xread( {agent_tasks: $}, count1, block5000 ) if not entries: continue task_id entries[0][1][0][1][task_id] await process_task(task_id) async def process_task(task_id: str): task fetch_agent_task(task_id) update_task_status(task_id, running) try: # 核心执行 LangGraph 的图 graph build_agent_graph() final_state await graph.ainvoke( {query: task[query], history: load_history(task[session_id])} ) update_task_status(task_id, success, resultfinal_state.get(answer)) except Exception as e: update_task_status(task_id, failed, errorstr(e))这里的graph.ainvoke是 LangGraph 推荐的方式内部已经支持状态传递但整个编排是被我们的异步 worker 包裹的所以即使图执行时间很长也不会阻塞 HTTP 层。这里的核心设计是FastAPI 层只管入队和查状态真正干活的是独立 worker 进程。这种模式天然支持多 worker 横向扩展你只需要多启动几个 worker 进程去消费同一个 Redis Stream 即可。4.4 并发控制Agent 怎么扛住“多任务同时跑”Agent 并发模型不能简单理解为“开线程池”。因为 Agent 任务本身是 CPU 密集 I/O 密集夹杂的模型推理占 GPU、工具调用占网络/CPU、向量检索占内存。如果无脑并发GPU 会被打满导致所有任务集体变慢反而“并发不如串行”。我们用了三种手段控制并发第一信号量限流。每个 worker 里限制同时执行的 Agent 任务数。这个数取决于 GPU 显存和模型 batch size。我们的经验值是并发数 显存可容纳的 batch 数减 1留一点余量给模型长上下文请求。import asyncio semaphore asyncio.Semaphore(4) # 最多同时执行 4 个任务 async def bounded_agent_run(task): async with semaphore: await process_task(task)第二按优先级区分队列。普通用户请求和内部紧急任务分开消费。我们用两个 Redis Stream 区分worker 优先消费高优队列。隔离网内的业务方常有自己的“紧急通道”需求这个设计后面被验证非常必要。第三长超时熔断。每个 Agent 子任务一次模型调用、一次工具调用都设定明确的超时时间。超时后主动取消该步骤而不是让执行线程一直挂着。这背后是 Agent 架构常见的“无限等待”问题模型卡住不返回你的 worker 就卡死了队列越积越长。我们的模型调用统一加了 60 秒超时工具调用 15 秒超时超过的直接记录异常并重试。4.5 工具调用封装如何让 Agent 真的“下地干活”Agent 光会聊天没用要能调工具才有工程价值。我们的工具集包括内网数据库查询、API 调用、RPA 脚本触发、知识库检索、工单创建等。每一样都可能涉及敏感操作所以工具层有严格的“三件套”白名单、参数校验、审计日志。工具调用协议的规范化很重要。我们沿用 OpenAI Function Calling 风格每个工具用 JSON Schema 描述入参模型输出结构化调用参数框架再来真正执行。这里有个容易踩的坑不要信任模型输出的参数。模型幻觉可能让参数越界所以执行前必须做 Schema 校验比如数值范围、枚举值、SQL 表名白名单这些都要在工具层二次过滤。举个例子我们的“查数据库”工具入门参数里有个table_name必须限定在预注册的allowed_tables列表内page_num、page_size也要限制区间。否则一个幻觉的模型可能给你整个库都查一遍不爆才怪。工具执行是纯函数我们把它放在独立进程池里跑避免工具内部抛异常把 Agent 主进程带崩。然后用超时 重试机制比如 RPA 脚本偶发失败重试三次后再判定工具失败。这里的最佳实践是工具失败不影响 Agent 主流程把错误信息塞回上下文让模型重新规划这是 Agent 比传统脚本强大的核心点。4.6 记忆管理上下文装不下了怎么办Agent 跑久了上下文一定膨胀。LangChain 的默认行为是把所有历史往 prompt 里塞结果就是 token 爆炸模型变笨响应变慢。我们做了两层记忆管理。第一层是“窗口记忆”只保留最近 N 轮对话这个最简单第二层是“摘要记忆”当历史超过阈值调用小模型把早期对话压缩成摘要。长短期结合效果很好。更进阶的做法是“语义记忆”把用户核心特征、偏好、结论存成结构化 JSON在每次新任务初始化时直接注入系统提示词。这比把所有聊天记录当上下文喂给模型更省 token也更精准。隔离网环境资源紧张这条优化能显著降低推理成本。5. 常见问题与排查技巧实录这里把整个项目里踩过的坑、以及网上同道人经常遇到的典型问题整理成速查表供你直接对号入座。现象可能原因排查步骤解决方案模型加载时报 unexpected keyword argument权重文件损坏或版本不匹配用 sha256sum 校验权重文件包重新拷贝权重文件确保校验和一致Agent 服务启动后所有请求超时同步调用阻塞了 worker 线程检查日志中是否有长时间未返回的请求改成异步 消息队列模式队列里任务一直 pending没有 worker 消费Redis Stream 消费者组配置错误或 worker 没启动查看 worker 进程和 Redis Stream 长度检查消费者组 ID重启 workervLLM 推理时 GPU OOM并发数太高或模型量化不充分看 vLLM 日志中显存占用降低并发换 AWQ/GPTQ 量化工具调用返回的参数是空值模型没按 Schema 输出看模型原始输出和解析器日志换大一点的模型或在 prompt 强调 JSON 输出Agent 循环一直调同一个工具模型陷入死循环打印 LangGraph 跳转记录增加最大轮次限制超次后强制终止向量检索结果相关性差中文分词或 embedding 模型不合适线下抽样测试 top5 命中率换 BGE-M3必要时走混合检索补充几个日志排查的独家经验。LangGraph 的图执行过程一定要有完整的 trace 日志。我们给每个节点都打了入参和出参的摘要截断长文本还把跳转记录写到审计表。一旦线上有问题直接查graph_run_log表按 task_id 串起整个过程快速定位是哪个节点的输出出了问题。这比在 IDE 里打 debug 高效太多。关于模型幻觉隔离网场景特别明显——模型没见过的公网知识它会一本正经编给你。我们的对策是给系统提示词强调“不确定就说不确定”并且对关键事实性的回答强制要求模型输出引用依据的document_id。没有引用的内容前端展示时打上“模型生成内容”的标记避免误导。最后说一个很关键的运维技巧所有模型的 Prompt 模板、System Prompt 版本必须跟业务代码一起纳入版本管理而不是存在数据库或配置文件里。因为 Agent 的输出质量直接受 Prompt 影响而 Prompt 一变行为就变回滚时必须连 Prompt 一起回滚。我们吃过一次亏模型输入输出全部混乱最后查出来是有人热更了 Prompt 但代码没变排查了两天才发现。后来 Prompt 全部进 Git每次修改必须带 MR这个坑从此再没出现过。6. 从 POC 到生产这条链路未来还能怎么延伸我个人在走完整个项目后最大的体会是隔离内网下的 AI Agent 工程难点从来不在“AI”而在“工程”。模型选型、量化、prompt 这些都是有公开路径可循的真正让你掉头发的是你得在一个没有任何外网帮助的环境里把所有依赖、权重、组件一个一个搬进去还得保证它们协同工作并且扛得住业务流量。这个架构跑起来之后如果你想继续往下延伸有几个方向值得优先考虑。多 Agent 协作是自然的一步LangGraph 已经支持多 Agent 图编排但隔离内网下要注意通信与状态同步的成本最好还是通过消息队列异步协作不要直接同步互调。模型微调也是一个很好的方向用业务积累的高质量问答和工具调用数据做 LoRA 微调让模型更贴合内部术语这比单纯换大模型省算力效果还更好。另外如果你有大量非结构化文档建议在内网里搭一套专业的文档解析流水线把 PDF、Word、扫描件统一清洗成 Markdown再做切片进知识库这才是 RAG 质量的最终天花板。最后再分享一个小技巧每次新增工具或模型时先在隔离网内做一次“最少可运行集”冒烟测试——只保留 LLM 节点 新工具节点跑通后再合入正式图。这样能快速定位问题发生在融合阶段还是原有逻辑里而不是等全套跑挂了再从头排查。这套方案算不上完美但它已经稳定支撑了我们内部的多条业务链路。如果你现在正被困在某个隔离网里不知所措希望这篇内容能帮你把思路理清楚——先管好模型再做编排最后解决并发一步一步来Agent 落地没那么玄乎。
返回列表