ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

隔离内网AI Agent实战:架构选型、RAG构建与并发排障

隔离内网AI Agent实战:架构选型、RAG构建与并发排障 去年年中我接了一个很拧巴的项目一套 AI Agent 系统必须跑在物理隔离的内网里。客户方的业务人员想要一个能自然对话、能查知识库、能对接内部系统的智能助手但安全规范相当严格所有数据不允许出域公有云的模型 API 一概不能碰。这直接把我之前做 Agent 的路径全打乱了——以前是“调别人的模型、接别人的向量库、用别人的网关”这次全部要自己搭、自己养、自己护。整篇文章就是这次隔离内网下 AI Agent 工程实战的完整复盘从架构选型到数据准备从并发压测到排障实录再到上线后的迭代建议。如果你是做私有化部署、信创适配、或者任何“不能上网但还想用大模型”场景的工程师这篇应该能帮你少踩不少坑。先声明一下这篇文章不是纸上谈兵所有结论都来自我在真实项目里跑出来的记录。项目前后做了三个多月交付后系统每天要处理上千次查询模型服务、Agent 编排、检索链路全都跑在断网的物理环境里。我把过程里关键的决策、参数、错误和反思尽量完整地写出来希望对你有用。1. 为什么放着主流云服务不用非要在隔离内网里搭 AI Agent1.1 场景是怎么来的数据不出域Agent 就必须本地化当时客户给我的需求其实很简单OA 系统里的知识库要能被“问起来”分子机构的问题在系统内直接获得答案不需要人工一篇篇翻文档。但他们对安全的要求是硬性的业务数据、组织架构、合同条款、人员信息一律不能出企业内部网络。这意味着我不能用任何云端大模型 API不能把文档送到外部服务去解析甚至模型服务的日志都不能传到外部。这就是典型的数据合规驱动的私有化场景。这类需求在金融机构、央国企、政务系统里非常常见我对接的这家客户就是类似背景。他们不是不相信云厂商而是行业监管对数据主权、安全审计有明确要求。所以从立项那一刻起技术路线就注定和“接 OpenAI API”那种玩法完全不同。1.2 隔离内网和普通开发环境的真实差距很多人对“隔离内网”的理解就是一台没网的电脑。真做起来会发现它带来的麻烦是系统性的没有公共软件源pip、npm、apt、Docker Hub 全部不可达。所有依赖包、基础镜像、模型权重都要想办法先弄进去。证书环境不一样隔离内网里很多服务用的是自签名证书或企业 CAHTTP 客户端如果不做适配请求直接原地失败。模型权重成了“货”一个大模型动辄几个 GB 到几十 GB要过内外网隔离的传输审批流程拷一次盘就得走一两个星期。无法在线调试外部服务平时习惯了什么报错就搜一下但隔离网里只能靠自己的经验和系统性排查。这些差距不只是技术上的还是流程上的、资源上的。项目排期得把这些“非编码工作”全部算进去不然一定延期。1.3 先想清楚Agent 到底要干哪几件事在选型之前我逼着客户和我一起把 Agent 的使用边界定义清楚。我们最后收敛成三类任务知识库问答基于内部文档的自然语言检索和回答。内部系统工具调用通过 API 或 RPA 方式查询 OA/HR/合同系统的数据。长流程任务比如跨系统收集信息、生成报告草稿。我强烈建议每个团队在做隔离内网 Agent 之前先做这个动作。因为“Agent 越通用越难”尤其在离线环境里调一次模型、跑一次工具都是有成本的。明确任务边界后面所有的架构、并发、数据准备才有依据。2. 架构选型的三层决策模型推理、Agent 编排、应用服务2.1 模型推理层开源模型、量化方案与推理引擎的取舍隔离内网条件下模型选型几乎没有悬念只能选开源权重。我们当时对比了 Qwen2.5-7B-Instruct、Qwen2.5-14B-Instruct、ChatGLM4 系列和 Llama3.1-8B-Instruct。在中文知识库问答、内部工具调用这两个场景下Qwen 系列的表现明显更稳函数调用function calling的成功率也比 Llama 高出一截。最终我们定了 Qwen2.5-14B-Instruct 作为主模型同时配了一个轻量的 Qwen2.5-3B 作为意图识别和路由的小模型。模型权重是定了但推理要怎么跑还有讲究。最初我们直接用 Transformers 加载进行推理结果并发一上来根本扛不住。后来换了 vLLM 做推理引擎吞吐提升非常明显。这里有个关键参数我要专门说一下——GPU 显存估算。以 14B 模型为例FP16 权重大概占 28GB加上 KV Cache 和激活值最少得 48GB 显存。我们手头的机器是两张 A80080GB单卡就能塞下 14B 模型 较大 Batch 的推理另一张卡留给了 Embedding 模型和重排模型。如果显存不够可以量化为 INT8 或 AWQ14B 量化到 INT8 大概 14GB 权重一张 4090 也能跑但生成质量会略有下降需要在效果评测时做对比。推理引擎我们只考虑了 vLLM 和 llama.cpp。vLLM 的优势是吞吐高、支持连续批处理continuous batching适合服务化部署llama.cpp 更轻量适合单机或者 CPU 推理但做并发服务化要自己再包一层。隔离内网场景下我推荐 vLLM因为它自带 OpenAI 兼容接口后面接 Agent 框架时省掉很多事。2.2 Agent 编排层为什么用 LangGraph 而不是硬编码在隔离内网里做 Agent 编排很多人第一反应是“写 if-else 硬编码算了”。如果是两三个固定流程硬编码确实简单但如果工具多了、分支多了、还有循环、重试、暂停恢复这些需求硬编码会变成一场灾难。我们最终用了 LangGraph。这里不是无脑推荐而是从几个维度考虑过的状态管理LangGraph 把 Agent 的执行过程建模成一张状态图每个节点负责一个动作边负责状态转移。这对“需要跨多轮调用工具、随时可能中断”的任务非常友好。可观测性Agent 执行到哪一步、调用了哪个工具、上下文里新增了什么LangGraph 都能通过钩子打日志这对隔离内网里排查问题至关重要。条件分支与循环比如“查合同信息 - 信息缺失 - 再问用户 - 再查”这种循环逻辑用状态图表达比硬编码清晰得多。当然 LangGraph 也不是没有代价。隔离内网里没有外网所有依赖包都必须在建设期就导进来。我们当时把 LangChain、LangGraph 及其所有依赖做了一个完整的 wheelhouse 收集在另一台相同架构的机器上把所有包全部下载再搬到内网安装。这一步务必提前做千万别到现场发现某个依赖缺失。2.3 应用服务层FastAPI 做接口、任务队列做长任务应用层我们选了 FastAPI Celery Redis 的组合。FastAPI 负责对外提供 HTTP 接口Celery 处理异步长任务Redis 充当消息代理和缓存。为什么要拆一层任务队列因为 Agent 的调用不是瞬时返回的一个复杂的 Agent 流程可能要跑几十秒甚至几分钟。如果 HTTP 接口里同步等待前端连接会被长时间占用网关容易超时体验也差。我们的做法是HTTP 接口收到请求后把任务丢进 Celery 队列并立即返回一个 task_id前端用轮询或 WebSocket 订阅结果。这样既控制住了连接数又方便做并发控制。另外在 Agent 执行过程中我们用 SSEServer-Sent Events向客户端推送中间状态比如“正在检索知识库…”“正在调用合同系统…”这种进度提示。用户看到进度等待焦虑会小很多这在真实产品里是提升好感度的关键细节。3. 数据准备才是真正的头号工程清洗、切分、向量化全流程3.1 数据源盘点与清洗PDF 乱码、扫描件 OCR 都是拦路虎很多人把“知识库”想得太简单以为扔几个 PDF 进去就能问答了。实际上客户给我的文档五花八门有扫描版 PDF、有网页导出文件、有表格套表格的 Word、有图片截图。如果不去清洗直接切分检索质量会非常差。清洗环节我们做了三件事格式统一所有 Word、PDF、网页统一转成 Markdown方便后面按结构切分。扫描件 OCR扫描版 PDF 先过 OCR 识别成文本。这一步在隔离内网里很麻烦因为 OCR 模型也要本地部署。我们用的 PaddleOCR 离线版本效果不错但部署时要把模型文件和所有依赖一起导入。去噪去掉页眉页脚、目录、第 X 页 / 共 Y 页这种噪音表格保留但转成紧凑的文本格式。清洗的成本远超预期客户给了 800 多份文档光清洗和人工抽检就花了两周。但这一步不能省不然 Agent 的回答质量就在垃圾进、垃圾出。3.2 切分策略按结构切而不是一刀切切分chunking是整个 RAG 链路里最容易被低估的环节。很多人直接用固定窗口切每 500 个字一段重叠 50 个字。这样切出来的 chunk 经常在句子中间劈开或者把两个完全无关的段落缝在一起检索时非常尴尬。我们的方案是结合文档结构切分。Markdown 格式天然有标题层级我们优先按标题、列表、表格结构切分再对切出来的块设置长度上限。如果某个小节太长再按句子边界二次切分并让相邻块之间有 10% 左右的重叠。最终参数参考主切分块长度 512 到 800 个 token重叠 50 到 100 个 token。为什么是这个区间太短了语义不完整太长了 embedding 向量被稀释检索精度会下降。这个参数不是拍脑袋定的是拿了一批带标注的测试问题跑出来的召回率对比结果。实测下来按结构切分后TopK5 的召回准确率比固定窗口切分高了大概 8 个百分点。3.3 向量化与本地检索离线环境没有“免费午餐”Embedding 模型我们选的 BGE-M3中文效果在开源模型里是第一梯队而且支持 8192 长度的输入。隔离内网里没有在线 embedding API只能本地跑。要注意的是BGE-M3 在导入时也要专门下载权重而且它指令前缀instruction的用法会影响检索效果建议按官方文档配置 query 和 passage 的编码方式。向量索引我们对比了 FAISS 和 Milvus。项目早期数据量只有几万条用 FAISS 完全够后来越来越多为了支持线上更新和结构化过滤换成了 Milvus。如果你也是先小后大的节奏建议直接上 Milvus standalone 模式减少后面迁移的成本。另外一定要加rerank重排阶段先用向量检索召回 TopK20 的候选再用本地 rerank 模型精排取 TopK5 送给大模型。BGE-Reranker-v2-m3 在这类任务上很稳召回率提升是肉眼可见的。这里必须强调一个“但书”本地检索再准也只能找到“文档里的内容”。如果文档本身没有、或者内容过时Agent 再聪明也答错。所以上线前要做一轮知识库内容核对和业务方一起确认“标准答案”在哪篇文档里。4. 并发扛不住先搞清瓶颈在哪一层4.1 瓶颈链路分析模型推理是最大的“卡点”“AI Agent 怎么扛并发”是所有 Agent 项目逃不开的问题。我先说结论Agent 链路里最卡的不是 HTTP 接口、不是 Redis、也不是向量检索而是LLM 推理本身。一次完整问答要经历“意图识别 - 检索 - 生成”其中生成环节要几秒到几十秒且生成时 GPU 是持续占用的。并发 10 个请求如果模型推理没有批处理能力那 10 个请求就是一个一个排队后面的用户只能干等。4.2 vLLM 的连续批处理为什么它是并发的大救星传统 Transformers 推理是单请求独占 GPUvLLM 引入了 continuous batching可以动态把一个 batch 里已完成生成的请求踢出去再把新请求塞进来。这样显卡利用率大幅提升并发 20 个请求每个请求的平均响应时间也不会劣化太多。为了最大化吞吐我们调整了几个关键参数max_num_seqs控制最多同时处理的序列数我们设为 256默认值可能不够。gpu_memory_utilization模型加载后的显存利用率上限我们设为 0.9剩下的留给其他进程。max_model_len上下文窗口长度我们设为 32768。这个值会影响显存占用建议按实际需求设置不要盲目开大。我用下面这个表格记录过加压测试的数据环境是一张 A800-80GB14B 模型并发请求数单请求平均总耗时首 Token 延迟每分钟完成请求数是否有超时14.8s0.6s12无55.8s0.7s45无107.2s0.8s78无209.5s1.1s126无3014.3s1.8s108有少量可以看到并发到 20 时总量吞吐仍在上升但平均耗时已经明显变长到 30 时稳定性开始下降。生产环境我建议把并发控制在峰值吞吐的 70% 左右留出余量应对突发请求。4.3 任务队列限流与优先级让重要任务先走光靠模型层并发还不够应用层必须有队列管控。我们做了两层全局并发闸门用一个信号量或者 Redis 计数器控制同时进入 Agent 编排的任务数超过阈值直接返回 503让前端进行友好提示。任务优先级简单问答类的短任务走高优先级队列复杂报告生成这类长任务走低优先级。这样用户问“报销流程是什么”不会被一个正在跑报告的任务堵死。另外要设置单次 Agent 执行的超时时间。我们统一设置为 120 秒超过就终止并把中间结果返回给用户。这既是防滥用也是防模型“跑飞”。4.4 Token 级别的用量控制还有一个容易忽略的角度Token 消耗控制。Agent 每多调用一次工具上下文就会多出一轮往返。如果工具结果太长模型要处理的 token 数会爆炸推理延迟也会直线上升。我们的策略是工具返回结果做截断通常只保留前 1000 个字符并用提示词告诉模型“这是截断后的内容”。上下文累计超过 24000 token 时触发一次摘要压缩把前面的对话历史压缩成一段摘要再继续。所有请求记录 token 用量方便核算 GPU 负载和容量规划。5. 隔离内网排障实录三个典型坑的完整排查链路5.1 坑一自签名证书导致所有 HTTP 调用全线失败现象Agent 编排层调用内网的合同系统 API 时requests 直接抛SSLError。一开始我以为是对方服务没起来查了服务健康状态完全正常curl 也能通。排查链路先用 curl 访问发现需要加-k才能通过说明是证书校验问题。看代码里是用requests.post(url, jsonpayload)默认校验证书。进一步确认内网服务和 Agent 服务都是自签名证书而 requests 库的证书存储里没有这个 CA。解决把内网根 CA 证书导出挂到 Agent 容器里并设置环境变量REQUESTS_CA_BUNDLE指向该 CA 文件。同时给所有 HTTP 客户端加信任配置。这个坑最麻烦的地方在于它“部分绕过”某些服务走内部代理不带证书校验某些服务直接 TLS导致表现不一致排查时容易被误导。5.2 坑二LangGraph 离线依赖缺失启动即失败现象在内网环境第一次部署 LangGraph 时服务启动直接报ModuleNotFoundError: No module named langgraph.checkpoint。外网环境没这个问题因为 pip 自动安装了依赖内网环境安装时用的是离线 wheelhouse但收集依赖时漏掉了这个子包。排查链路在可联网的开发机上建一个虚拟环境用pip install langgraph后执行pip freeze导出完整依赖列表。用pip download -r requirements.txt -d ./wheelhouse下载所有包。这中间的问题在于 LangGraph 部分模块是延迟导入的只按 import 的顶层包收集会漏子依赖。教训离线环境收集依赖时一定要用pip freeze而不是手工整理。我后来又写了个脚本把所有依赖打包成 tar 包在内网机器上用pip install --no-index --find-links安装同时做了一次导入冒烟测试确保启动无误。5.3 坑三长文本超出上下文窗口Agent 问答结果断崖式变差现象用户提交一份 3 万字的合同文本做分析模型生成结果中途戛然而止或者只回答了一半合同内容。排查链路从日志看请求在 31,000 多个 token 处停止而我们的 max_model_len 设置的是 32768。单看数字没有超但实际请求是“系统提示 全文内容 用户问题 历史对话”拼在一起已经压在阈值边缘。一旦检索返回的 chunk 比较多直接爆掉。解决思路分三层应用层先对输入做摘要或者分段不让全量文本一次性灌入。检索层严格控制送入模型的 chunk 数量按相关性排序后只取 TopK5并且限制每个 chunk 长度。模型层调大max_model_len到 49152并相应调整显存分配。实测在 A800 上可以稳定运行但并发上限有所下降属于取舍。这个坑再次验证了一个原则上下文的“预算”必须由应用层统一规划而不是全甩给模型。6. 部署、上线与后续迭代的几点建议6.1 镜像和离线包的固化内网部署不能拿着 Dockerfile 现场 build。我们提前在可联网环境把所有服务构建成镜像vLLM 推理镜像、LangGraph Agent 镜像、FastAPI 应用镜像、Milvus 镜像全部docker save成 tar 包拷入内网后docker load。为了应对模型权重文件我们单独做了一个模型数据卷配合启动脚本自动加载。镜像版本一定要锁定到 commit 级别的 tag否则内网里无法拉取“最新版”出了兼容性问题根本无从查起。我把所有第三方依赖、模型文件、配置文件的版本号集中记录在一个 MANIFEST 文件里后续升级时能追溯。6.2 健康检查与监控指标体系隔离内网里的监控不能依赖外部 SaaS我们用 Prometheus Grafana 裸机部署了一套。重点监控四个指标GPU 显存占用、模型推理吞吐、任务队列积压数、检索耗时分位数。这里要提一个很实用的检查vLLM 服务启动后健康检查接口要等到模型完全加载完成才返回 200否则部署时容器处于“Running 但不可用”状态。我们写了一个启动脚本先轮询 vLLM 的/v1/models接口模型就绪后再启动 Agent 服务。6.3 评估集与回归机制上线前我们和业务方共建了一套 120 条的评估集覆盖知识问答、工具调用、拒答三类场景。每次修改系统提示词、切分策略、重排参数都跑一遍评估集记录准确率和满意度评分。Agent 项目如果没有评估集改提示词就像蒙眼开车上线全凭感觉。我强烈建议在交付初期就把“用户反馈按钮”做进去每个回答下方放“满意 / 不满意”不满意可备注原因。这些数据最终会成为下一轮迭代最重要的信号。6.4 迭代优先级先稳住确定性再谈智能性功能上线后我们收到的反馈集中在两类一是知识库没覆盖到的问题二是工具调用时偶发的参数解析错误。前者的解法是持续补充文档和调优检索后者的解法是完善工具定义 schema同时增加一次模型输出校验环节——如果模型生成的函数调用参数不合法让 Agent 重试一次而不是直接报错。在真实业务里用户对“稳定可用”的在意程度远超过“偶尔惊艳”。我们的迭代顺序是先保证 95% 以上的问题能被不报错地处理再逐步提升回答的准确性和丰富度。这套架构跑到现在稳定运行了三个多月。最大的收获反而不是技术上的在隔离环境里做 Agent每一个细节都必须提前验证因为网不好上、信息不好查试错成本极高。如果你也在做类似的项目我唯一想强调的就一句话——把可联网阶段能做的准备做到极致尤其是依赖收集、镜像构建、权重导入和评估集建设这四样前期越扎实后期越省心。至于模型选型、Agent 框架这种事真的没有银弹只能结合自己的业务场景去测、去比、去忍耐。祝你能把自己的 Agent 顺利也“下地干活”。
返回列表