ARTICLE DETAIL

资讯详情

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

隔离内网AI Agent落地全记录:模型离线化、RAG与工程调优

隔离内网AI Agent落地全记录:模型离线化、RAG与工程调优 接手这个项目之前我其实挺怀疑的在完全隔离的内网里做 AI Agent听起来就是把开源模型一装、向量库一配、LangChain 一接的事能有多难等真把开发机搬进内网才发现从模型权重到 pip 包、从并发压测到可观测性每一步都在走钢丝。这篇东西算是我把整套方案从零到一落地的记录包含选型思路、关键参数、配置和踩过的坑希望能给同样在隔离网络环境下做 Agent 工程的同学省点时间。1. 为什么要在隔离内网里跑 Agent1.1 所谓隔离内网到底隔离了什么先聊清楚隔离内网这四个字背后意味着什么。很多从互联网开发环境转过来的同事对隔离的理解停留在不能上外网实际远不止这么简单。隔离网络通常有三层含义第一层是物理隔离数据包出不了域内外网之间是硬断开的第二层是软件供应链隔离你没法直接执行pip install从 PyPI 拉包也没法上 Hugging Face 下载模型权重Docker Hub 基本也等于不存在第三层是约定俗成的合规隔离就算某些内网机器其实有出网策略业务数据也不能随便往外送日志、用户输入、检索内容都必须是域内闭环的。这三层隔离对 AI Agent 工程的冲击是致命的因为 Agent 全流程都依赖外部组件。大模型推理靠公网 API不行。Embedding 模型在线拉取不行。Agent 要用搜索引擎、天气接口、地图服务这些内置工具也不行。甚至你想用一个比较冷门的 Python 依赖库版本号写进requirements.txt之前都得先确认内网私有源里有没有。所以真正适合在隔离内网里跑 Agent 的场景往往是三类一是企业内部知识库问答数据敏感度很高员工提问内容本身可能是商业机密二是生产运维辅助Agent 需要读取监控指标、调用工单系统这类系统天然就在内网里三是对稳定性要求极高的自动化流程公网接口的抖动和限流不可控而内网服务可以用内部负载均衡和自建推理服务完全掌控。如果你属于这三类场景之一隔离内网就是必须攻克的阵地而不是可选项。1.2 部署在隔离内网要解决的核心问题把 Agent 从能演示的 Demo变成能上线扛业务的工程需要解决四个问题这四个问题在隔离内网里会被放大好几倍。第一个是模型侧你必须把推理模型和 Embedding 模型完全本地化。模型文件动辄几个 GB 到几十个 GB跨网搬运本身就是一场灾难搬运完之后还得在某台 GPU 服务器上稳定服务要考虑显存、量化、吞吐。第二个是架构侧Agent 的编排逻辑必须清晰可控。在隔离内网里你大概率接不到外面那些成熟的 SaaS 化 Agent 平台一切都要自己用 FastAPI、LangGraph 这类框架搭状态管理、工具调用、上下文处理都得自己设计。第三个是数据处理侧知识库里的资料都是内部文档格式杂、质量参差不齐OCR、解析、清洗、切片这些活儿一个都跑不掉而且不能依赖在线 SaaS 的解析接口。第四个是运营侧内网环境下基本没有现成的监控报警体系Agent 调用失败、模型推理卡死、上下文溢出这些问题都需要自己造轮子去发现和处理。我见过很多团队在内网里把模型装上、跑通一个问答 Demo 就觉得完事了结果一到并发场景就挂、一到长文档就爆、一遇到工具调用参数错误就懵。原因就是前面这四个问题只解决了第一个后面三个没有系统设计。这篇文章的主体部分就按照模型侧 → 架构侧 → 数据侧 → 运营侧这条链路逐一拆开讲。2. 整体架构设计与技术选型2.1 两条主流技术路线的对比技术选型是整个项目里最容易被低估的一步。很多人上来就纠结用 LangChain 还是 LangGraph纠结半天发现方向错了。以我的经验隔离内网里 Agent 的技术路线其实可以粗分成两条。一条是 Python 生态为主的方案核心组件是 LangGraph 或者自研的状态机 FastAPI 做服务层 vLLM 做推理服务再加一个向量库。这条路线的好处是开发效率高LangGraph 的图编排模型特别适合复杂的多步任务社区生态和资料都多而且 Python 在数据处理和模型调用上有天然优势。缺点是并发能力上限取决于你写异步代码的水平GIL 在 CPU 密集场景下会造成瓶颈好在 Agent 场景大多数时间在网络 I/O 和模型推理侧等待Python 异步其实够用。另一条是 Rust 或者 Go 这类高性能语言自己实现 Agent 引擎和工具调度。社区里确实有不少基于 Rust 的 Agent 项目热度很高,它们的特点是单机并发能力极强、内存占用可控。但代价也很现实Rust 的 Agent 生态远没有 Python 成熟你要自己处理模型调用的协议封装、工具 Schema 校验、复杂的状态持久化开发周期会翻倍。如果你是做高吞吐的 Agent 网关、或者对单机并发有极端要求Rust 值得考虑但对大部分企业内网应用这个复杂度不值得。两条路线的选择本质上是个 trade-off开发效率 vs 极限性能。我的建议是除非你的业务规模已经大到 Python 明确扛不住了否则优先选 Python 生态。等把业务逻辑验证清楚、性能瓶颈量化出来之后再把热点模块用 Rust 重写成 sidecar 服务也不迟。另外还有一个容易被忽略的路线是 Java 生态的 Spring AI如果你所在团队全是 Java 工程师、运维体系也全是 JVM 那一套那用它融入现有技术栈的收益会超过生态劣势这也是很多老牌企业团队的实际选择。2.2 LangGraph FastAPI 为主体的落地架构我最终选择的是LangGraph FastAPI vLLM这套组合配合 PostgreSQL pgvector 和 Redis。整体结构用一个图来理解外部请求进来FastAPI 负责 HTTP 层、限流、鉴权然后把请求打包成一个任务交给 LangGraph 的图执行引擎图里面定义了意图识别 → 工具调用 → 结果整理 → 生成回复这样的节点流转每个节点之间通过状态对象传递数据工具层是注册好的一组函数比如查工单库、读监控接口、执行预定义脚本模型层对接 vLLM 暴露出来的 OpenAI 兼容 API。LangGraph 的图模型解决了一个关键问题Agent 在多步调用时上下文怎么管理。用传统的 LangChain Sequential 链每步之间通过一个消息列表传递链路一旦分支复杂就非常难维护。LangGraph 把整个流程定义成一张有向图每个节点可以读写一个共享的 State 对象并且支持条件跳转、循环和人工介入节点。比如做故障排查 Agent图里可以有读取告警信息节点、查询历史变更节点、生成诊断结论节点如果诊断信息不足可以回跳到补充提问节点。这种设计在隔离内网环境里特别好用因为内网的业务流程往往是非常固定的你可以把专家经验直接固化成图的路线而不完全依赖模型的自由发挥。FastAPI 在这套架构里的角色是门面加网关。我选择它不是因为性能比 Flask 高多少而是因为异步支持成熟async def配合asyncio.Semaphore做并发控制非常顺手。Agent 请求是很典型的 I/O 密集任务一次请求要等工具返回、等模型生成 token大量时间在阻塞等待上这时候异步就能让同一个进程同时处理多路请求。如果你的其他服务是 Java 或者 Go把 FastAPI 单独部署成一个 Agent 服务也是合理的它不负责业务主链路只负责把所有 Agent 相关的能力收口。2.3 工具调用的安全边界与权限设计隔离内网里的 Agent 和公网 Agent 最大的不同是工具调用的后果更严重。公网的 Agent 调一个天气接口出错最多是天气查错了内网的 Agent 如果调了一个删除工单的接口那可能就是生产事故。所以工具层的安全设计必须前置不能等出了事再补。我在这套方案里加了三个保护层第一层是工具白名单Agent 能调用哪些工具在配置文件里写死不在列表里的工具即使模型幻觉生成了调用请求也会被拒绝第二层是参数校验每个工具都定义了严格的 JSON Schema模型给出的参数必须通过校验才能执行类型、取值范围、必填项都由代码控制第三层是人工审批节点针对高危操作比如写数据库、发消息、执行 shell 命令在 LangGraph 图里插入一个需要人工确认的节点Agent 生成操作请求后挂起等审批人通过 API 或者管理页面确认之后才真正执行。这三层设计给 Agent 加了一道保险也让业务方更容易接受让 AI 干活这件事。一开始业务同事对 Agent 自动执行脚本是很不放心的加上人工审批节点之后他们能看着每一步操作怎么来信任度明显提升了。等跑了一段时间积累了足够置信度再逐步放开某些低危操作的审批限制。3. 模型侧的离线化与推理优化3.1 本地推理模型的选型与量化实践模型选型是整个项目里最一失足成千古恨的环节。内网环境没有试错空间模型下载不像在公网 Hugging Face 上随时可以换一个搬运一次几十个 GB 的模型文件要花大量人力。所以必须一次性选对。我的选型原则有三条一是优先选开源许可允许商用的模型避免法律风险二是优先选社区活跃度高、生态工具支持好的系列因为整个内网后续无论做量化、做推理服务、还是微调都得依赖这个生态三是根据显存预算反推模型规模而不要先定模型再买卡。显存估算有个简单经验公式FP16 精度下模型显存需求大约等于参数量乘以 2 字节。7B 模型 FP16 大约需要 14-16 GB 显存加上 KV Cache 和推理开销14B 模型需要约 28-32 GB量化到 INT4 之后显存需求大约降到 FP16 的四分之一左右但推理速度和精度会有一定损失。我最终选择的是 14B 量级的模型量化到 INT4 之后再配合 vLLM 在单张 24GB 显卡上跑。为什么选 INT4因为隔离内网很难说随时扩容 GPU单卡 24GB 是很常见的配置INT4 能让我在一张卡上同时跑推理模型和 Embedding 模型还可以留出余量给比较大的上下文窗口。精度损失在 Agent 场景下是可以接受的Agent 更依赖的是模型遵循指令的能力和工具调用的格式准确性而不是极致的文本生成质量。实测下来 INT4 相比 FP16 在指令遵循类任务上的下降幅度很小但显存开销差了一倍多这笔账非常划算。3.2 推理服务的选型vLLM 是首选本地模型选好了接下来要选推理服务。这里有个常见的误区很多人直接装 Ollama 或者 llama.cpp因为简单。但真上了生产环境Ollama 的短板就出来了并发能力有限、OpenAI 兼容接口的完整性一般、对工具调用Function Calling的原生支持弱。我更推荐 vLLM特别是做 Agent 场景。vLLM 的吞吐量优势来自它的 PagedAttention 机制简单说就是显存管理更精细能同时处理更多的并发请求序列。它还实现了 OpenAI 风格的/v1/chat/completions接口这意味着你在内网里可以把 vLLM 当成一个假的 OpenAI API 服务来用LangGraph、LangChain 这些框架天然就能对接工具调用协议tools参数和tool_calls响应格式也支持得比较完整。启动 vLLM 的几个关键参数值得单独说一下。--max-model-len控制上下文窗口上限我设成了 32768太大虽然能处理更长文档但会显著增加显存占用和推理延迟--max-num-seqs控制同时并行处理的序列数其实就是并发上限我按业务需求设成 64--gpu-memory-utilization控制显存利用率我设成 0.9剩下 10% 留给一些临时操作--enforce-eager有些版本需要加否则在一些不支持 CUDA Graph 的环境里会启动报错。这组参数在 24GB 卡上实测可以稳定支撑 20-30 路并发请求而不出现显存溢出。还有一个常常被忽略的参数--served-model-name。这个参数可以让你给模型起一个别名。这样内部代码里模型名写的是你定义的别名后续想换模型只需要改 vLLM 启动参数代码完全不用动。这个设计在隔离内网里非常实用因为模型替换是一个很重的过程能减少上游的业务代码变更。3.3 Embedding 模型和 Rerank 模型的独立部署很多人做 RAG 只关注大模型本身把 Embedding 模型当成附属品。实际上 Embedding 模型对 RAG 效果的影响至少和推理模型一样重要而且它完全独立于对话模型需要单独部署、单独维护。我选了 bge-m3 这一系列的 Embedding 模型支持中文效果不错而且同时支持 Dense 向量和 Sparse 向量为后面的混合检索留了空间。Embedding 服务的部署方式和推理模型完全不同它不需要 vLLM 这种大吞吐框架用 Sentence-Transformers 或者专门的服务框架比如 FastEmbed起一个轻量服务就行。因为它只负责把文本变成向量单次推理量小但调用频率极高每个文档切块都要跑一次每次查询也要跑一次所以要把 Embedding 独立成一个单独的服务单独做并发和监控不要和对话推理服务混在一台机器上。Rerank重排序模型是我在中后期加上的。RAG 检索很多时候单纯靠向量召回排序结果并不理想尤其是内部文档里相似标题很多的时候。加上 Rerank 模型之后先用向量模型粗召回 Top 50 候选块再用 Rerank 模型精排 Top 5-10 送入上下文。这一步对最终回答质量的提升非常明显实测在评测集上准确率能提升 8-10 个百分点代价是每次查询要额外花 30-80 毫秒。在隔离内网里检索性能充裕的话强烈建议加上。4. 知识库与 RAG 的离线落地4.1 离线文档的加载、清洗和切分策略内网知识库的数据源是我见过最野生的。PDF 有扫描件、有文字版Word 文档有各种版本还有 Wiki 网页导出的 HTML甚至有一堆从来没有规范化过的 Excel 表格。这些文档要直接喂给 Agent前提是经过一个完整的离线处理流水线。第一步是格式解析。PDF 优先用 PyMuPDFfitz它对文字版 PDF 速度快、效果好扫描件必须接 OCR我用的 PaddleOCR 在内网部署有不少依赖坑但胜在中文效果好离线可用。Word 文档用 python-docx 抽取这里注意一定不要丢失表格结构Agent 在回答某个指标的数值是多少这类问题时表格被拍平成纯文本会导致答案错误。HTML 用 BeautifulSoup 抽取正文需要自己写规则过滤掉导航、页脚等噪音。第二步是清洗。这个环节最脏也最容易被跳过。内部文档里常见的问题是页眉页脚每个页面重复标题编号混乱项目专有名词存在多种写法比如QPSqps每秒查询数混用。如果不做清洗直接进知识库检索出来的片段会带大量噪音大模型看到这些噪音容易产生幻觉。我的做法是写了一套清洗规则包括删除页眉页脚模式、统一常见缩写、把乱码字符替换成统一标记。实测清洗前后 RAG 评测集的准确率能差 5-6 个百分点。第三步是切分。切分策略我踩过不少坑。最朴素的做法是按固定字符数切分比如每 500 字一块加 50 字重叠。但这种切法会频繁切断语义完整的段落检索召回时经常拿到半截话。我最终的策略是结构化优先先按文档标题层级H1/H2/H3切分出小节每个小节内部如果超过最大长度比如 800 字再按句号、换行符做二次切分。这样切出来的块天然带有标题上下文检索时可以附带标题信息一起嵌入效果比盲切好很多而且每个块还能带上来源章节的定位信息方便 Agent 回答时引用出处。4.2 向量库选型与混合检索的工程细节向量库的选型是 RAG 方案里另一个容易走弯路的地方。很多人上来就选 Milvus因为在向量数据库圈子里名气大。但对大部分内网团队来说引入 Milvus 意味着要运维一套分布式系统它的核心依赖 etcd、MinIO、Pulsar 这些组件在内网部署和运维成本都很高。除非你有 PB 级向量数据否则我建议选一个更轻的方案。我最终的选择是 PostgreSQL pgvector 扩展。原因很简单公司内网大概率已经有 PostgreSQL 了pgvector 作为一个扩展一个命令就能装好不需要额外引入任何新组件。向量数据量在千万级以下pgvector 的检索性能完全够用。它还有一个巨大优势向量数据和业务元数据可以存在同一个数据库里做过滤查询不需要在向量库和业务库之间同步数据。比如 Agent 要查财务部门最近的报销制度只需要在 SQL 里执行WHERE department finance ORDER BY embedding - $1 LIMIT 10一条查询搞定跨库联查的烦恼直接消失。检索不只有向量业内管这叫混合检索。单独用向量检索的短板是过度依赖语义近似如果查询词里的关键术语不在文档里出现过专业缩写、内部代号向量召回的效果会明显下降。我的方案是 Dense 向量 Sparse 向量BM25 文本检索两条路并行各自召回 Top 50然后合并、去重、过 Rerank 精排。pgvector 的tsvector可以做原生的全文检索所以文本检索也不用引额外的 Elasticsearch一个 PostgreSQL 就能同时覆盖向量检索和全文检索。这套组合方案在隔离内网里运维压力非常小一台常规服务器就搞定了。4.3 评测集隔离内网里最容易欠的债RAG 系统的开发进度如果不和数据评测绑定基本等于闭眼走路。我在项目里专门维护了一套评测集目前有 300 多条问答对每条包含用户问题、标准答案、期望引用的知识块 ID、难度等级。这些评测集怎么做出来的前期靠人工从真实业务问题里挑后期靠 Agent 自己生成候选问题再由人工审核筛选。评测跑起来也不复杂每次对知识库或检索逻辑做改动就用脚本把这 300 条问答跑一遍统计命中率、回答正确率、引用召回率。这个环节看着费力其实回报非常直接。很多改动从直觉上认为效果更好跑完评测集才发现某些用户的提问方式就根本召不回正确文档某次切分调整甚至让整体表现倒退。内网环境里试错成本高、线上验证周期长所以评测集是唯一高效的质量反馈手段。我甚至可以说评测集的价值超过任何一个具体的技术选型。5. 工具调用与 Agent 编排5.1 工具的定义、注册与状态管理Agent 能干活的关键在于工具调用而工具调用能不能稳定又取决于你有没有一套严格、规范的工具定义方式。我用的是 OpenAI 风格的 JSON Schema每个工具声明它的名称、描述、参数对象、每个参数的格式和约束。描述非常关键因为模型完全靠描述来判断什么时候该调用这个工具、参数怎么填。描述写得含糊模型就会乱来。比如一个查工单的工具描述不能只写查工单至少要写清楚根据工单编号查工单状态和详情适用于电话、邮件、IM 等渠道提交的工单查询这样模型在用户说我的那个投诉处理到哪一步了的时候会尝试提取工单号并调用这个工具。工具注册进 LangGraph 的过程也要设计。我维护了一个 Python 字典key 是工具名value 是工具函数和 Schema。LangGraph 节点的代码里拿到模型的 tool_calls 输出后遍历调用请求逐个做参数校验后执行再把执行结果塞回 State 作为下一条消息。这里要特别强调工具的返回结果格式要统一最好统一成 JSON 字符串并带一个status字段表示成功还是失败。否则某个工具返回纯文本、另一个返回 JSON模型处理起来会非常不稳定。状态管理是 Agent 多轮调用能跑下去的基础。我用的 LangGraph State 里包含messages对话历史、memory长期记忆 key-value、tool_results最近一轮工具结果这几个字段。整个状态对象在每次节点跳转后被序列化存入 Rediskey 用会话 ID 加时间戳设置 TTL 比如 30 分钟。这样如果 Agent 进程重启或者单次请求超时还能从 Redis 恢复上下文继续跑。Redis 在这里起到了一个很重要的作用——让 Agent 服务做到水平扩展因为 Agent 的完整状态不在进程内任何一台节点都能接管同一个会话。5.2 多轮上下文管理从全量塞入到分层摘要所有 Agent 项目做久了都会撞上一个天花板上下文超长。LangGraph 内部的多轮对话会不断累积 messages一旦超过模型的最大上下文长度轻则回答质量急剧下降重则直接报错。隔离内网里这个问题更大因为内部业务流程往往十步八步每一轮的 tool result 可能是一大段 JSON累积起来爆发得特别快。我采用的分层策略是这样的第一层是滑动窗口保留最近 6-8 轮完整的对话消息这是模型回复最需要的短期记忆第二层是摘要压缩每累积 10 轮左右就用一次独立的模型调用把更早的历史消息总结成摘要替换原始消息存入上下文第三层是长记忆把用户在整个时段内表达的关键偏好、确认过的事实写进 State 的memory字段每次新会话开头注入永不放进对话历史里。这套分层策略的效果很直观能让 Agent 稳定跑到几十轮而不爆上下文。代价是每次摘要压缩要额外花一次模型调用以及在摘要阶段可能丢失一些细节。我的折中方案是把摘要这一层做成可回溯的摘要文本里保留关键实体的完整形态比如工单号、金额、日期不缩写不归纳这样即使细节在上下文里被压缩关键信息依然可用。5.3 解决Agent 幻觉式调用工具的工程手段真实生产环境里最常见也最难缠的问题是模型认为自己需要调用某个工具给的参数却完全对不上。比如让查工单它生成一个工单号123456但实际上系统里根本没有这个号让它查监控时间参数填了个昨天而不是具体的 RFC3339 时间戳。这类问题一般有几个层面的原因和对应的处理手段。第一个原因是工具描述不够明确导致模型猜着填。解决办法是把描述和 Schema 写得更死比如时间参数写明必填格式为 YYYY-MM-DD HH:MM:SS由系统当前时间倒推计算不允许填相对时间。第二个原因是参数要从用户的话里抠模型抠错了。解决办法是在工具调用之前加一个参数抽取校验节点先用代码逻辑检查提取结果不符合约束就要求模型重新提取。第三个原因是模型面对模糊用户输入时选择了硬猜而不是反问。这时候需要在工具节点之前加一个澄清路由检测到参数缺失时不走工具调用而是让模型向用户提问确认把猜变成问。第四个原因更隐蔽工具调用本身成功了但返回结果是空的或者异常模型因为没看到有效结果自行开始脑补一个结果来回答用户。这个我是在排查一个Agent 自己编造了工单处理结果的严重事故时发现的。处理办法很明确工具返回值里必须带一个明确的状态标志节点代码里检查到工具失败时生成一条系统消息告诉模型工具调用失败请告知用户查询失败不要自行推断结果把话直接说死不给模型自由发挥的空间。6. 并发与性能优化实录6.1 Agent 场景的并发瓶颈到底在哪AI Agent 怎么扛并发是一个被反复问的问题但很多人从一开始就问错了方向。Agent 服务和传统 Web 服务的并发模型完全不同一次 Agent 请求不是一次简单计算而是一连串的模型调用、工具调用、状态读写。如果只是压测 QPS会掩盖真正的瓶颈。我来拆一下一次典型的 Agent 请求链路用户发来问题 → FastAPI 接收 → LangGraph 启动 → 第一次调模型意图识别或生成回复→ 如果需要工具调查询接口 → 把工具结果拼回上下文 → 第二次调模型生成最终回复。这里至少有两三次模型调用、一次工具调用、若干次 Redis 读写和向量库查询。单次请求的真实耗时可能是 5 到 15 秒如果直接按传统 QPS 的思维压 20 并发实际上模型推理服务同时处理的是 40-60 个推理请求已经远超单卡 vLLM 的舒适区。所以扛并发不是一个指标而是一组指标。我在项目里监控的核心指标是P95 端到端延迟、VLLM 的吞吐量和队列长度、工具调用平均耗时、Redis 和向量库的 P99 耗时。压测工具我用 Locust写一个模拟用户行为脚本让一部分虚拟用户发简单问答只调一次模型一部分发需要工具的真实业务问题调多次模型按比例混合起来测这样压出来的结果才接近真实。6.2 并发目标和参数调优的实战计算我先定了一个现实的并发目标业务高峰期最多同时有 20 个用户在使用 Agent每个用户平均每 30 秒发一条消息每条消息平均触发 2.5 次模型调用每次生成平均 200 个 token。算一下20 用户 × (60 秒 / 30 秒) 40 条消息/分钟40 条消息 × 2.5 次模型调用 100 次模型调用/分钟约等于 1.67 次/秒每次生成 200 token每秒需要生成约 333 token。如果 P95 端到端延迟目标在 10 秒以内考虑到单条请求内部串行调用多次模型模型单次生成的 P95 不能超过 3 秒也就是单次生成速率至少约 67 token/秒。这个负载在单张 24GB 显卡上跑 14B INT4 模型是可行的vLLM 实测吞吐能达到每秒 1000-2000 token远超过 333 token/秒的需求所以模型侧其实不是瓶颈。真正的瓶颈在工具调用链路如果工具接口本身慢比如某个内部工单系统查询要 5 秒那这个延迟会直接叠加到端到端延迟里而且无法通过增加 GPU 解决。所以我后来把工具调用的超时控制从默认 10 秒收紧到 3 秒超过就返回查询超时宁可告诉用户暂时查不到也不要拖死整条链路。我把这套计算过程拿给团队看大家才明白为什么 GPU 看着利用率不高用户却反馈慢——瓶颈在别处。这也是我在这个项目里最想强调的一点Agent 的端到端性能分析必须从整个调用链拆不能只盯着模型。6.3 实测压测结果和几个重要的调优参数用 Locust 压测时我记录了不同并发档位下的数据。10 并发时端到端 P95 约 4-6 秒20 并发时升到 7-10 秒30 并发时开始出现超时P95 冲到 15 秒以上。这个结果基本符合预期30 并发超时主要是既有工具接口和 Redis 操作叠加导致。定位之后做了几个针对性调优。第一个是 FastAPI 侧的信号量并发控制。没加限制之前服务层会无脑把请求全部抛给模型和下游导致 vLLM 队列积压所有请求一起变慢。加了asyncio.Semaphore(10)之后超过 10 路的请求在入口排队等待而不是打进系统实测 P95 反而下降了 30%-40%因为避免了雪崩。第二个是 vLLM 的--max-num-seqs我从默认的 256 调到了 64。这个参数说白了是 vLLM 一次最多同时解码多少条序列。设得太大GPU 显存碎片化严重每条序列分到的显存变少反而影响单条生成速度设成 64 之后配合信号量的 10 路并发模型侧 P95 稳定在 2.5 秒以内。第三个是工具层的并发连接池。查内部 API 时连接池默认大小是 10压测时经常出现连接排队。我把aiohttp连接池上限调到 30的确缓解了。这里我要提醒一句连接池不是越大越好上游系统不经打的话扩大连接池只会把压力传导给上游把别人打挂。压测之后还需要做一项长稳测试我一般让它跑 30-60 分钟观察 GPU 显存有没有渐进式增长。vLLM 有显存碎片的可能性长时间跑下来显存占用缓慢爬升如果爬到百分之百就会 OOM。解决办法是定时重启推理服务或者在 vLLM 配置里开启--enable-prefix-caching来优化前缀复用减少重复计算。前缀缓存的效果在 Agent 场景特别大因为工具调用和系统提示词是同一个前缀反复出现开启后显存和计算压力都小不少。7. 部署、监控与常见问题速查7.1 隔离内网里一套可复用的部署流程整套系统最终要落到隔离内网部署流程就不能靠手工要沉淀成一套可复用的离线发布手册。我把流程拆成四个阶段依赖搬运、模型搬运、镜像搬运、配置发布。依赖搬运是第一步。开发机上有网执行pip download -r requirements.txt -d packages/把全部依赖包下载成 wheel 文件然后打包拷贝到内网。这一步要注意pip download 默认只下载当前平台对应的包如果你的开发机是 macOS 而内网服务器是 Linux必须加--platform manylinux2014_x86_64 --python-version 3.11 --implementation cp --abi cp311 --only-binary:all:这类参数交叉下载。我在这里踩过一次大坑下载好的包拿到内网装不上发现是因为没有指定平台和 Python 版本全部要重来。第二步是模型搬运。模型权重文件很大我建议用 Hugging Face 的huggingface-cli download先把模型完整下载到本地并确认文件校验和然后通过移动硬盘建议 exFAT能兼容各种系统拷贝进内网。拷贝后第一件事不是启动服务而是执行一次模型完整性校验比如跑transformers加载模型并打印 config防止文件损坏。模型文件损坏在拷贝过程中其实很常见特别是移动硬盘 USB 供电不稳时。第三步是容器镜像搬运。如果你用 Docker 部署同样面临 Docker Hub 不可用的问题。解决方案是在开发机上docker pull完基础镜像后用docker save -o image.tar保存成 tar 包拷贝进内网后docker load导入。内网里再搭一个私有镜像仓库Harbor 或者简单的 registry统一管理。第四步是配置发布。隔离内网和开发环境的配置差异要做到一处集中管理每个环境一份.env文件包含模型服务地址、数据库连接串、各个 API key通过 CI 流程从配置中心下发禁止在代码里写死任何环境相关参数。这步虽然朴素但能避免很多为什么开发环境正常、内网就报错的玄学问题。7.2 常见问题与排查速查表我把项目里遇到的典型问题整理成了一张速查表每次排障先对照它自查能省不少时间。现象可能原因排查方法解决办法模型服务启动报 OOM上下文窗口过大或并发序列过多查看 vLLM 日志里的显存占用信息nvidia-smi确认实际显存调小--max-model-len或--max-num-seqs降低--gpu-memory-utilization前的预留余量内网 pip 安装依赖失败私有源地址配置错误或依赖版本不匹配pip config list检查 index-url查看错误日志里具体的 404 包用pip download重新打包确认平台和 Python 版本参数向量检索召回结果质量差切分策略不合理、缺少 Rerank 或混合检索打开检索日志看召回块的内容和分数调整切分策略加 Rerank必要时开启 BM25 混合检索Agent 频繁调用错误工具工具描述模糊、Schema 约束不严查看工具调用日志确认模型生成 tool_calls 的原始内容优化工具描述加参数校验加澄清路由多轮对话上下文爆炸消息列表无截断策略查看 Redis 中 messages 的长度和 token 数启用滑动窗口加摘要压缩把较早消息替换为摘要单请求跨多轮工具调用后状态丢失Redis 里状态过期或被覆盖检查 Redis key 的 TTL 和写入日志延长 TTL给每个会话状态加版本号并做幂等处理Agent 回答引用了不存在的内容RAG 召回错误文档或模型幻觉检查检索到的文档 ID 和引用来源加固引用机制要求模型必须引用文档原文否则拒答这张表不完整但大部分问题都能覆盖。我强烈建议项目一开始就记录运行日志和报错上下文否则排查时没有历史数据让你对照只能靠猜。7.3 隔离内网环境下的可观测性建设公网环境可以用各种 SaaS 监控平台内网环境就得上自给自足的方案。我的监控体系分三层指标、日志、追踪。指标用 Prometheus 采集主要监控 FastAPI 服务QPS、P95/P99 延迟、信号量队列深度、vLLM吞吐、显存利用率、正在处理的序列数、Redis内存占用、命中率和向量库查询延迟。日志用 Loki 集中采集服务打结构化日志JSON 格式字段统一包含request_id、session_id、node_name、duration_ms关键日志事件包括工具调用开始/结束、模型调用开始/结束、状态快照写入 Redis。追踪这一层最容易被忽视。Agent 是多节点流水线单个请求跨多个服务log 不关联根本没法排障。我实现了一个轻量追踪每个请求进来时生成request_id通过请求头传给所有内部调用各层日志都带这个 ID再写一个简单脚本把日志里的调用链重建出来。LangGraph 也支持回调机制可以在每个节点执行时自动记录耗时和输入输出摘要。这一套虽然不能和开源的分布式追踪系统比但在隔离内网里足够好用了。可观测性的意义在于把Agent 没用了这种模糊报告变成具体可调查的事件。有一次用户反映Agent 回复特别慢我看追踪数据发现某个工具调用占用了 8 秒多而工具本身只花了 500 毫秒——再深入看是这个工具在等待系统里另一个线程的锁。如果没有追踪数据这个排查可能要花上一个下午有了数据十分钟就定位了。7.4 几点实操心得按照惯例把一些零散但很实在的经验写在这里。第一Agent 的初始版本不要一上来就全交给模型自由发挥。我建议先用 LangGraph 把业务流程固化成一个死流程图每个节点的输入输出、条件跳转都提前定好模型的自由度只体现在从预设选项里选择下一步或者生成某个节点的局部内容上。等整体稳定了再逐步放开让模型走更自由的路径。这就像新人开车先走驾校固定路线熟悉了再上城市道路直接上赛道必翻车。第二给 Agent 的每一个动作都留下痕迹。模型调用、工具参数、工具结果、用户反馈这些数据越早积累越好。隔离内网里没有外部的模型评测社区你唯一能依靠的就是自己的数据。我后来做模型版本升级时完全靠这些历史数据做回归评测——把旧系统的真实交互记录重放一遍对比新旧版本的回答质量而不是凭空打分。这个习惯帮我在模型升级时避免了一次明显的质量回退。第三人工反馈循环要设计得轻。我一开始设计了一个复杂的用户对每条回答打分功能结果没人用。后来改成一个简单的点赞/点踩按钮点踩时必须选原因回答错误、检索错误、无法回答、太慢数据质量立刻有了质的提升。简单、低摩擦的反馈机制远胜功能丰富但没人碰的复杂系统。第四隔离内网不是一次放进去就永远不动。软件的迭代还得继续模型版本升级、知识库重建、工具接口变更都要周期性地在内网重复前面的流程。所以建立一套离线发布流水线尽量把搬运和部署自动化能省下大量的重复劳动。第五也是最根本的一点别神话 Agent。它就是一套软件系统有边界会犯错需要监控需要迭代。隔离内网的环境反而逼迫你用更工程化的方式去做这件事这可能是绕开公网快速路最大的收获。这个项目从设计、部署到调优前后大概花了两个月。回头看踩过的坑大多集中在环境差异和链路协作上真正的大模型推理反而不是最难的部分。如果你也正准备做类似的事希望这份记录能帮你提前避开这些坑。
返回列表