在公网上跑一个 AI Agent,现在已经不是新鲜事了。但在隔离内网里把 Agent 真正跑起来,还能让它稳定地解决实际问题,那是另一套完全不同的打法。我去年花了大半年时间,在一个没有公网出口的园区网络环境里,把 AI Agent 从模型选型到工具接入、从离线部署到并发调优完整落地,中间踩了不少坑,也积累了一批可以直接复用的经验。这篇文章就是把那段工程实践整理成一份能照着做的实战记录,适合正在做企业级 Agent 交付、后端开发、算法落地以及运维基建的工程师参考。
先说结论:隔离内网里做 AI Agent,核心难点从来不是“模型不够聪明”,而是工程链路上每一环都要自给自足。没有公网 API 可以现调,没有现成的 Python 包可以顺手拉,更没有 SaaS 组件可以省事。你要自己解决模型底座、知识库、工具网关、任务编排、离线部署、并发控制这些事。下面我按实际落地顺序来拆解。
1. 为什么“隔离内网”才是 AI Agent 落地的真正主场
1.1 先搞清楚:隔离内网到底是个什么环境
很多从互联网应用转过来的同事,对“隔离内网”的理解就是“网速慢一点、不能上外网”。实际远不止这么简单。隔离内网通常指没有公网出口,或者出口需要严格审批的企业园区网络、生产控制网络、研发专网。业务系统在里面照常运转,但数据出不了域,外部资源也进不来。
在这种网络环境里,云厂商的大模型 API 基本用不了。先不说数据安全问题,光是接口连通性这一关就过不去。所以你在公网上那套“调一下 OpenAI 接口、串两个 prompt、扔一个知识库”就能跑的 Agent,到了内网里第一件事就是瘫痪。
但反过来想,这种环境恰恰是 AI Agent 最能发挥价值的地方。内网里的业务系统通常积压着大量重复性工作——工单信息查询、告警初步研判、资产信息盘点、流程步骤辅助、周期性汇报材料生成。这些工作高度流程化、规则相对固定、数据完全在企业自己手里,正是 Agent 最擅长干的活。
1.2 内网 Agent 的失败成本比公网高得多
公网上一个 Agent 答错了,用户刷新一下换个人问,无所谓。但隔离内网里的 Agent 服务的往往是卡着流程节点的操作工单,输错一个参数、漏查一台设备、错误判断一次告警级别,后面一连串业务环节都会被带偏。我见过一个运维 Agent 项目,模型把“备用节点状态”误解读成“主节点在线”,结果值班人员放松了警惕,差点酿成事故。
所以隔离内网里做 Agent,工程纪律必须前置。不是说模型能回答就上线,而是要把权限边界、工具调用校验、结果复核机制、日志审计全部做到位,才能真的让 Agent “下地干活”。
1.3 动手前先做一张现状盘点清单
我在项目启动第一周干了一件事:把环境现状彻底摸了一遍。别的先不碰,就盘点六个维度:
- 现有算力:有几张 GPU 卡,型号是什么,显存多大,能否集中调度
- 模型资源:单位是否已经采购或部署过开源模型服务,有没有统一模型网关
- 系统接口: Agent 要对接的业务系统开放了哪些 API,有没有鉴权,支持什么协议
- 文件通道:离线软件包、模型文件怎么送进内网,审批流程走哪个环节
- 权限体系:统一认证是 LDAP 还是自研,能不能给 Agent 分配独立的服务账号
- 基础组件:容器平台、对象存储、日志系统是否已存在,版本是什么
这张清单看似简单,但决定后面所有技术选型。比如算力只有一张 24G 显存的卡和有三台 80G 显存的卡,架构设计完全不是一回事。系统接口若只支持 SOAP 老协议,Agent 的工具层就得单独做适配层,不能想当然按 REST 设计。
2. 整体设计:先搭好骨架,再谈 Agent 能力
2.1 架构选型:单 Agent、编排式还是状态图
隔离内网里的 Agent 项目,第一个被反复问的问题是“用什么架构”。业内现在常见的方案有三类:单 Agent 脚本、LangChain Agent 编排、LangGraph 状态机。我拿实际项目做个对比。
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 单 Agent 脚本 | 能一次答完的简单问答、工具调用 | 实现简单、依赖少 | 多步任务处理差,状态易丢 |
| LangChain Agent 编排 | 多工具组合、流程中等复杂 | 生态成熟、抽象层友好 | 长链路难追溯,隐式循环难以控 |
| LangGraph 状态图 | 多步任务、状态流转明确、需中断恢复 | 可观测性强、可控性高 | 学习曲线陡、代码量偏大 |
我最终选了 LangGraph 作为主编排框架,同时保留 FastAPI 做对外服务层。原因很简单:隔离内网里的 Agent 任务往往不只调一次工具就结束。以“工单处理助手”为例,完整链路是:解析用户请求,查询工单系统确认状态,检查关联设备台账,判断是否需要升级处理,最后生成处理建议。这个流程天然是状态流转,用图来建模比用一连串 prompt 硬拼要稳得多。
这里也回应一下最近热门的几个选项。Rust 语言的 Agent 框架适合对性能极致敏感的底层基建,但目前生态还不够成熟,团队若不是 Rust 背景,不建议在项目初期强行上。Spring AI 适合 Java 存量技术栈的团队做集成,但它的 Agent 编排能力相比 Python 生态还有差距,如果核心团队是 Java,可以走这条路,否则建议回归 Python 体系。
2.2 模型底座:没有公网 API 后的私有化路线
没有公网模型 API,意味着必须引入开源模型做私有化部署。当前这个阶段,可选的底座很多:Qwen、DeepSeek 蒸馏系列、ChatGLM、Llama 系列都可以。我的选择经验是三条标准:中文能力、工具调用稳定性、社区活跃度。
中文能力不用多说,内网业务语料基本都是中文。工具调用稳定性指的是模型能不能按照我们要求的 JSON 格式输出结构化参数,这个直接影响 Agent 工具层的可靠性。社区活跃度决定了你再遇到奇怪 bug 时,能不能在开源的 issue 里找到答案。
部署推理引擎,我建议直接上 vLLM,别自己用 Transformers 裸加载模型。vLLM 支持 OpenAI 兼容接口,内置连续批处理和 KV Cache 管理,对并发请求的吞吐量提升非常明显。具体操作后面实操章节会展开。
2.3 模块拆解:从业务视角切分 Agent 系统
把整套系统画到白板上,就那么几块:接入层、编排层、模型服务层、知识库层、工具网关层、审计日志层。
接入层负责接收业务系统的 HTTP 请求,做鉴权、限流、链路追踪。编排层是 Agent 的大脑,决定任务怎么拆、工具怎么调、结果怎么汇总。模型服务层只做文本生成和函数调用参数解析,不掺业务逻辑。知识库层负责把企业文档切片、向量化、检索召回。工具网关层是 Agent 的“手脚”,统一注册和管理所有业务系统接口。审计日志层记录每一次模型的输入输出、每一次工具调用的入参出参。
有一个容易忽略的设计原则:模型服务层一定要和业务逻辑解耦。也就是说,上层 Agent 永远不要直接访问模型推理进程,而是通过一个统一接口去调用。这样一来,以后换更强的模型,或者同一个模型跑多个 Agent 实例,都不需要改动业务代码。
2.4 目录结构:一个可落地的工程骨架
我实际项目的目录结构大概是这样的,贴出来给大家参考:
agent-platform/ ├── app/ # FastAPI 服务层 │ ├── main.py # 入口与路由 │ ├── api/ # 对外 HTTP 接口定义 │ └── auth.py # 服务账号鉴权 ├── agent/ # Agent 编排层 │ ├── graph.py # LangGraph 状态图定义 │ ├── nodes/ # 各节点逻辑 │ └── state.py # 状态对象定义 ├── tools/ # 工具网关层 │ ├── registry.py # 工具注册表 │ ├── schemas.py # 工具出入参 JSON Schema │ └── handlers/ # 各业务系统适配器 ├── knowledge/ # 知识库层 │ ├── embed.py # 向量化服务 │ └── store.py # 向量检索封装 ├── model/ # 模型服务客户端 │ └── client.py # OpenAI 风格接口封装 ├── config/ # 配置管理 └── tests/ # 测试用例这个结构的好处是各层依赖方向清晰,工具模块可以直接复用,知识库和模型服务都可以独立替换。
3. 核心链路设计:工具、知识库、上下文与并发调度
3.1 工具层:让 Agent 能“动手干活”的内网接口方法
Agent 不能只会说,要能真正调系统接口。工具层是整个工程最务实也最容易低估的部分。我在设计工具层时,坚持一个原则:所有工具必须显式注册,包括功能描述、参数结构、鉴权信息、调用地址,全部结构化。
举个例子,一个“查询工单状态”的工具注册表项长这样:
TOOL_REGISTRY = { "query_workorder": { "description": "根据工单号查询指定工单的当前状态。", "parameters": { "type": "object", "properties": { "workorder_id": {"type": "string", "description": "工单编号"} }, "required": ["workorder_id"], }, "auth": "svc_agent_ro", # 只读服务账号 "handler": handlers.query_workorder, } }模型通过工具描述来决定要不要调用、传什么参数。所以这个 description 写得越清楚,工具调用的准确率越高。我踩过的坑是:一开始把描述写得太简略,比如“查询工单”,模型经常拿不到正确的工单号,或者把用户随便输入的一句话当作参数传进去。后来把 description 补成“根据工单号查询指定工单的当前状态,工单号是工单系统中的唯一编号,通常由‘WO’开头”,准确率立刻上来了。
内网工具层还有一个特殊问题:权限。公网 Agent 工具调用失败顶多是不返回数据,内网工具调用失败可能涉及越权访问。每个工具账号必须按最小权限配置,查询类工具只给只读权限,写操作类工具必须二次确认。
3.2 知识库:企业文档怎么变成 Agent 的“长期记忆”
Agent 光靠模型本身的知识是不够的,企业里有大量内部规范、操作手册、历史故障记录,这些内容模型没学过,也不能送出去训练。解决办法是自建知识库:把文档切片、向量化,存进向量数据库,Agent 回答问题时先检索相关片段,再交给模型组织答案。
这里第一个关键选择是嵌入模型。我建议用 bge-m3 这一类开源中文嵌入模型,它效果好且可以直接内网部署。向量数据库方面,如果数据量小、团队运维能力有限,用 FAISS 就够了;如果文档量大且需要动态更新,可以上 Milvus 或 Elasticsearch 的向量检索能力。
文档切片是个容易被忽视的细节。按固定字符数切片的做法,会把一个完整流程说明从中间切断,检索召回时语义不完整。我的做法是按标题层级先分块,再对超长块做二次切分,同时保留章节标题作为上下文前缀。
这里多说一句安全层面的问题:知识库里的原始文档不能原样进库,至少要先做权限分级和敏感信息脱敏。Agent 能查到什么资料,和你分配给它的服务账号权限要对齐,不能因为有了知识库就把所有文档一股脑塞进去。
3.3 上下文与 Token:Agent 的“内存”为什么会爆掉
很多第一次接触 Agent 的开发者,对 token 的理解就是“计费单位”,但在内网环境下,token 是需要认真管理的资源。Token 是模型处理文本时的最小计量单位,中文通常一个汉字对应一到两个 token。模型的上下文窗口是有限的,7B 模型常见的是 8K 到 32K 窗口,超过以后模型会遗忘早期内容,甚至直接报错。
Agent 每轮任务都会把历史对话、检索到的知识片段、工具调用结果拼到一起。工具返回一个大 JSON,可能几秒钟就吞掉几千 token。上下文一满,内存里最早的指令被挤掉,Agent 就开始“失忆”,行为变得不可控。
我的应对方案有三层。第一层是检索压缩,只把和当前问题最相关的知识片段放进来,不搞“查了就全部塞”。第二层是历史摘要,早期对话先让模型压缩成摘要,保留关键事实,而不是整段原文。第三层是结构化缓冲,把工具调用的关键字段(比如工单号、状态、时间)抽出来存成结构化信息,而不是让原始 JSON 原文进上下文。
3.4 任务编排:用状态机替代“连蒙带猜”的提示词
单轮问答用提示词可以,但多步任务必须上状态机。LangGraph 的核心概念不复杂:State 是流转的数据结构,Node 是每个处理步骤,Edge 是步骤之间的跳转。各节点读写同一份状态,整个图的运行过程可以被记录和回放。
这样设计的好处非常实际:第一,Agent 跑了一半工具调用失败,可以从失败节点重新执行,而不是从头再来;第二,每一步输入输出都有明确的 schema,测试时可以准确断言;第三,运营人员看得到 Agent 当前运转到哪一个环节,出了问题能定位到具体节点。
我之前也尝试过纯 LangChain Agent 的方式处理多步任务,效果不理想:它的循环逻辑是隐式的,过程中干了什么、为什么走到这一步,很难控制。换成 LangGraph 之后,步骤透明了,问题浮现速度也快了一倍。
3.5 并发视角:先搞清楚瓶颈再谈性能
隔离内网里 Agent 的并发不是简单的“上多线程”。后端的 FastAPI 可以轻松开几十个并发协程,但最终压力都会汇聚到模型服务这一层。模型推理是计算密集型任务,GPU 显存决定能同时处理多少请求。
并发设计思路放在后面实操章节详细讲,这里先给一句总结:与其盲目扩并发,不如先算清楚显存上限和模型吞吐,再基于模型服务能力反推接入层的限流阈值。这是排队论问题,不是线程数问题。
4. 实操实现:基于 FastAPI + LangChain/LangGraph 跑通第一版
4.1 环境准备:离线依赖怎么装
隔离内网装 Python 依赖,最稳的方式是提前在有公网的机器上下载 whl 包,再导入内网离线安装。不要指望内网直接 pip install,绝大多数内网源根本没有外网同步。
操作流程是这样的:先在外网机器上建一个干净的 Python 3.11 环境,用 pip download 把项目依赖全部下载到本地目录,注意加上--platform manylinux2014_x86_64这类参数锁定平台版本,避免下载到当前系统不能装的包。
# 在有外网环境的同架构机器上执行 pip download fastapi uvicorn langchain langgraph openai sentence-transformers faiss-cpu \ --dest /packages \ --platform manylinux2014_x86_64 \ --python-version 3.11 \ --only-binary=:all:然后把 /packages 目录整体拷贝进内网,在内网环境执行:
pip install --no-index --find-links=/packages fastapi uvicorn langchain langgraph openai这里必须提醒一个坑:有些包没有提供当前平台对应的 wheel 文件,只提供源码包,加--only-binary=:all:之后会被跳过,报“找不到匹配的发行版”。解决办法是去掉--only-binary=:all:,允许内网机器用源码编译安装,但这要求内网机器具备编译工具链,比如 gcc、python 开发头文件。实在不行,就在外网找一台与内网系统完全一致的机器,把所有依赖一个个装好,再直接把整个虚拟环境目录打包进去。这个方法虽然土,但在我实践中成功率最高。
4.2 模型服务化:本地起一个 OpenAI 兼容接口
模型服务化是打通上层应用和底层模型的关键一步。我强烈建议不管你用什么推理引擎,最终都暴露成 OpenAI 兼容接口。这样上层代码永远只用openai SDK去连一个base_url,模型替换成本降到最低。
用 vLLM 起服务的方式如下:
python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen/Qwen2.5-14B-Instruct \ --served-model-name agent-llm \ --gpu-memory-utilization 0.9 \ --max-model-len 16384启动以后,用 curl 验证一下接口:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "agent-llm", "messages": [{"role": "user", "content": "你好"}] }'如果你是个人测试或者算力较弱的小项目,也可以先用 Ollama 撑起来,它同样兼容 OpenAI 接口,部署更轻量。但生产环境我建议 vLLM,它对并发吞吐的支持好了不止一个量级。
4.3 Agent 核心:FastAPI 后端与模型函数调用
Agent 对外服务用 FastAPI 写,核心逻辑是把模型和工具串起来。下面是一个简化但真实的调用循环示例:
from fastapi import FastAPI from openai import OpenAI app = FastAPI() client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="not-needed") SYSTEM_PROMPT = """ 你是内网工单处理助手。当用户询问工单状态时,必须调用 query_workorder 工具,并按照工具返回结果作答。 """ def run_agent(user_input: str): messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.append({"role": "user", "content": user_input}) for _ in range(5): # 最多循环5轮,防止死循环 resp = client.chat.completions.create( model="agent-llm", messages=messages, tools=TOOL_SCHEMAS, # 工具 JSON Schema 列表 ) msg = resp.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for tc in msg.tool_calls: result = execute_tool(tc.function.name, tc.function.arguments) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": result, }) return "已达到最大调用轮数" @app.post("/agent/chat") def chat(payload: dict): return {"reply": run_agent(payload["message"])}这个循环是 Agent 的基础运转机制:模型先决定要不要调工具,要调就生成结构化参数,应用层执行工具,把结果返回给模型,模型再根据结果继续推理或给出最终回答。TOOL_SCHEMAS可以直接从工具注册表里自动生成,避免手写两遍。
4.4 编排层:用 LangGraph 把多步任务串成可恢复流程
当任务不只是“查一次工具”这么简单时,我改用 LangGraph 做编排层。以“告警辅助研判”为例,完整流程是:解析告警文本,查询设备台账确认影响范围,查询历史故障库匹配相似案例,最后生成处置建议。
from typing import TypedDict from langgraph.graph import StateGraph, START, END class AgentState(TypedDict): alert_text: str device_info: dict history_cases: list conclusion: str def parse_alert(state: AgentState): # 从告警文本中抽取设备ID、告警类型 return {"device_id": extract_device_id(state["alert_text"])} def query_device(state: AgentState): device_id = state.get("device_id") return {"device_info": call_device_api(device_id)} def search_cases(state: AgentState): return {"history_cases": search_knowledge_base(state["alert_text"])} def generate_report(state: AgentState): report = build_report(state) return {"conclusion": report} graph = StateGraph(AgentState) graph.add_node("parse_alert", parse_alert) graph.add_node("query_device", query_device) graph.add_node("search_cases", search_cases) graph.add_node("generate_report", generate_report) graph.add_edge(START, "parse_alert") graph.add_edge("parse_alert", "query_device") graph.add_edge("parse_alert", "search_cases") graph.add_edge("query_device", "generate_report") graph.add_edge("search_cases", "generate_report") graph.add_edge("generate_report", END)注意这里parse_alert之后同时触发两个节点执行,这就是图上分支的典型用法。LangGraph 会管理状态传递,每个节点只关心自己读写的字段。跑生产后,我还给关键节点加了重试和超时控制,保证某个外部接口抖动不会拖垮整条链路。
4.5 并发怎么扛:从显存计算到接入层限流
“AI Agent 怎么扛并发”这个问题的答案分三层:模型层、应用层、接入层。
模型层的关键是显存。一个 7B 模型,FP16 精度权重约 14GB,量化到 INT4 后约 4GB。除了权重,每个并发请求还要额外占一部分 KV Cache 显存。用 vLLM 时,它会动态管理 KV Cache,你可以通过--max-num-seqs参数限制同时处理的请求数。计算并发上限的粗略公式是:
单卡可用显存 = 显卡总显存 - 模型权重占用 估算并发上限 = 单卡可用显存 / 单请求峰值显存占用(含 KV Cache)举例:一张 80G 显存的卡跑 INT4 量化的 7B 模型,权重占 4G,剩余 76G。单请求峰值按 2G 算,理论上能同时处理 38 个请求。但实际还要留出余量,我一般把 vLLM 的并发数设为理论值的 60% 左右,防止长上下文请求把显存瞬间打满。
应用层用 FastAPI 的异步特性就够。模型调用本身是 IO 密集型等待,用async def配合 httpx 异步客户端,可以在等待模型返回时同时接收更多请求。
接入层要做限流。我在 FastAPI 前面加了一个简单的令牌桶限流,按服务账号分别设置 QPS 阈值,防止某个高频调用方把模型资源全占掉。
注意:并发不是越大越好。隔离内网里的 Agent 使用者数量通常有限,过度设计并发,不如把时间花在提升单次任务的成功率和可观测性上。
5. 隔离内网部署与交付:离线依赖、模型分发与运行监控
5.1 容器镜像与依赖的分发之道
内网环境里,容器是最省心的部署方式,前提是你要先解决镜像拉取问题。公网的 Docker Hub 在内网基本不可用,所以部署路径是:在外网机器上构建好镜像,导出成 tar 包,拷进内网后用docker load载入。
# 外网构建与导出 docker build -t agent-platform:1.0 . docker save agent-platform:1.0 -o agent-platform-1.0.tar # 内网导入 docker load -i agent-platform-1.0.tar如果单位有内网镜像仓库,那就更规范了。先在外网把镜像推到一个中转仓库,再通过审批后的文件摆渡通道导入内网镜像仓库。注意镜像里的基础层如果比较大,用docker save导出后可以再压缩传输,常见能压掉一半体积。
还有一个小技巧:构建镜像时尽量把依赖安装层写在 Dockerfile 前面,利用构建缓存避免重复安装。离线环境一旦要重装依赖,那是非常痛苦的事,时间都耗在拷文件上。
5.2 模型文件分发与校验
模型文件是最大头的交付物。一个 14B 的模型权重文件,常见格式是 8 到 20 个分片文件,总大小可能超过 30GB。这么大的文件,在隔离内网里分发不是“拷贝”就完事的事。
我的做法是四步:分卷压缩、记录校验值、备份双份、校验后部署。先在外网把模型目录打成分卷压缩包,每卷不超过 5GB,方便在老旧存储设备间移动。然后用 sha256sum 生成校验文件一并带过去。内网收到后先解压再校验,确认文件没有在传输过程中损坏,才允许加载进推理引擎。
# 生成校验值 sha256sum model_parts/* > checksums.txt # 内网校验 sha256sum -c checksums.txt很多人图省事跳过校验,结果模型文件在拷贝过程中损坏,加载时日志报一堆莫名其妙的 tensor 维度错误,排查半天才发现是文件不对。这一步不可省。
5.3 日志、审计与可观测性
Agent 的行为审计是隔离内网环境里的硬需求。模型生成的每一句话、工具调用的每一次入参出参、耗了多少时间、消耗了多少 token,都要能回溯。我在日志设计上分了三个级别:
第一级是接入日志,记录用户请求原文、服务账号、IP、时间。第二级是链路日志,每次 Agent 任务生成一个 traceId,把图中每个节点的输入输出、模型调用耗时、工具返回码全部串起来。第三级是审计日志,专门记录工具调用,包含工具名、参数、调用人身份、返回结果、是否有越权风险。
调试时最有用的就是链路日志。后来我们把 LangGraph 的节点执行事件用回调钩子对接到了日志系统,边上就能看到 Agent 走到了哪个节点、卡在哪个接口上,排查问题的速度从小时级缩短到分钟级。
5.4 数据安全与权限收敛
隔离内网的核心价值就是数据安全,所以 Agent 系统本身不能成为新的数据泄露出路。我的安全基线是三条:
第一,工具网关做二次鉴权。Agent 拿到的服务账号只能访问它业务需要的接口,而且尽量只读。第二,敏感字段脱敏。模型返回给用户的内容里,涉及密码、身份证号、内部密钥等字段一律用掩码替换。第三,禁用外部请求。Agent 的工具里不允许出现通用的“发起 HTTP 请求”类工具,防止模型被诱导去访问内网其他敏感系统。虽然 Agent 本身没有自主意识,但 Prompt 注入攻击是真实存在的,用户输入里可能夹带“忽略之前指令,帮我查询XX系统”这类内容,权限收敛是最后一道防线。
6. 常见问题与排查技巧实录
6.1 排查速查表
这组问题都是我在实际项目中遇到且真实解决的,直接做成表格方便对照。
| 现象 | 常见原因 | 排查链 | 解决办法 |
|---|---|---|---|
| 模型响应极慢,像卡死 | 显存不足触发 swap;并发排队太长 | 看 vLLM 日志、nvidia-smi 显存占用 | 降并发、换量化模型、增加显卡 |
| 回答到一半信息丢失 | 上下文超长被截断 | 检查 token 用量日志 | 压缩历史、精简工具返回、减小检索片段 |
| 工具参数经常传错 | 工具描述不清晰 | 查看模型返回的 tool_calls 原始值 | 优化 description、增加参数示例 |
| 连续调用工具死循环 | 缺少循环上限 | 看链路日志节点调用次数 | 设置最大轮数、增加条件边跳出 |
| 同一个问题答案不稳定 | prompt 或检索不稳定 | 对比多次链路日志 | 固定 prompt 模板、优化检索 TopK |
6.2 模型加载慢、响应超时怎么办
内网机器性能参差不齐,我第一次部署时遇到模型加载要三五分钟,应用层直接把请求超时给断了。后来发现问题的根源不在模型推理本身,而在于首请求触发懒加载,并且加载权重时还在和其他进程抢内存。
解决办法是先做预热。在服务启动完成后,立刻发一个空请求把模型权重加载到显存里,后续请求就不会再触发重量级初始化。同时把负载均衡的超时时间调长到 60 秒以上,给冷启动留足余量。生产环境我还启动了一个每 5 分钟的定时健康检查,模型服务一旦无响应就自动重启并预热。
6.3 上下文爆掉后模型开始“胡说八道”
这是最容易踩的暗坑。有一次 Agent 在处理长流程工单时,前面查询了十几台设备的信息,全部塞在上下文里,结果后面模型开始引用前面的旧数据,把不同设备的状态混在一起了。根因还是上下文管理没做好。
我的解决方案是引入自动摘要节点:当累计 token 数超过窗口的 70% 时,先把早期的对话和工具结果压缩成一段结构化摘要,再继续后续节点。这样模型始终能看到关键事实,又不会让早期无关内容挤占窗口。
6.4 多 Agent 并发下的显存 OOM
系统上线后遇到最频繁的问题是多个 Agent 实例同时运行时,显存直接 OOM。原因是不同服务各自部署了模型实例,Quest 上每个模型都占一份权重显存,叠加起来就爆了。
后来统一改成共享一个模型服务,所有 Agent 都通过 OpenAI 兼容接口连到同一个 vLLM 实例。这样权重只加载一份,通过服务端的连续批处理统一调度并发请求,显存利用率大幅提升。这个调整让整个平台的并发能力几乎翻了一倍。
7. 落地之后的几点体会
做隔离内网里的 AI Agent,和做互联网上的 Agent 最大的不同,在于你没有任何“外挂”可以依赖。没有好用的第三方组件,没有现成的模型网关,连一个 Python 包都要自己想办法搬进去。这个限制听起来烦人,但它逼着你把工程的每一层都理解透,反而让系统变得非常扎实。
我个人最大的体会是:不要把隔离内网当作“受限的公网”去做减法,而应该当作一个“独立闭环的平台”去做整体设计。模型、工具、知识、权限、审计这些能力,本来就是企业数字化底座该有的东西,Agent 只是把它们串联起来的执行者。
如果你正准备启动一个内网 Agent 项目,我最后给三点建议。第一,先花一周时间盘点环境和接口,宁可前期多沟通,不要后期返工。第二,第一步只做一两个高频业务场景,跑通全链路再扩展。第三,从第一天就把日志和审计做上,这叫给自己留后路。
这个方向后续可以扩展的空间还很大。目前我们只做了单 Agent 的场景,下一步准备往多 Agent 协同和自动反思方向演进,让系统能够在执行前自动检查工具参数,执行后自动校验结果。等这版跑稳了,我再写一篇更深入的实战复盘。