
说个得罪人的大实话在隔离内网里做 AI Agent和在公网环境完全不是一回事。公网环境下那些调个 API、写两段 prompt 就能跑的玩法放到隔离内网里每一步都会被卡脖子——模型服务不在线、依赖装不上、数据出不去、并发一上来就雪崩。这半年我一直在这类环境里折腾从零搭了一套真正面向业务落地的 Agent 系统踩过的坑比写过的代码还多。这篇文章不是教你怎么写 prompt而是把我从需求分析、架构设计、技术选型到并发改造、故障排查的完整过程摊开来讲。重点说清楚隔离内网下 AI Agent 工程化要过的几道坎以及每道坎具体怎么过。适合内网信息化团队、私有化部署实施人员还有所有准备在受限网络环境里把 Agent下地干活的人阅读。看完你至少能避开我踩过的那大半坑。1. 先想清楚隔离内网里做 Agent到底难在哪1.1 你面对的不是 AI 问题而是工程环境问题很多人拿到需求第一反应是选个模型、接个框架、开写但在隔离内网里最先跳出来打脸的往往是环境本身。我归纳了一下隔离内网至少给你设了四道关卡网络隔离。办公网和业务网之间有一道看不见的墙服务器没有公网出口更不可能直接访问任何外部大模型 API。所有模型推理能力必须落在一台你摸得着的内网服务器上要么自己部署推理服务要么接入公司已有的算力平台。这个过程会遇到大量模型服务怎么暴露、鉴权怎么做、多业务线怎么共享的问题。依赖受限。pip、npm、maven 这些包管理工具默认走公网源在内网环境里十有八九是连不上的。你写好的 Agent 代码如果用到 LangGraph、FastAPI、向量库客户端就必须提前准备好内网镜像源或者离线 wheel 包。我见过不止一个团队代码都写完了结果部署时依赖装不上被迫推翻重来。数据敏感。内网系统里的数据往往是不能出网的prompt 也好、工具调用结果也好、向量化后的文档内容也好全部在内网闭环里流转。这意味着你不能偷懒去用公网 embedding API得自己部署 embedding 模型向量库也得搭在内网。运维通道有限。没有便捷的云端监控和日志平台所有可观测性建设都得自己搭。出了问题能靠的就只有内网日志、内网监控面板和团队成员的经验。这四道关卡决定了你的技术选型凡是依赖公网 SaaS 的方案一律砍掉凡是需要在离线环境里自托管的组件尽早规划。这个判断在整个项目里是最高优先级的后面所有决策都建立在它之上。1.2 两条架构路线单机嵌入 与 服务中台我见过隔离内网里的 Agent 架构基本可以归成两派。单机嵌入型是最常见的起步形态。把模型推理服务比如一份 Ollama 或者 vLLM 进程和 Agent 代码放在同一台机器上模型API地址直接写成 127.0.0.1所有任务在这台机器内闭环。优点是部署简单、调试方便、不需要跨网络协调最适合做 PoC 或者单业务线的小工具。服务化中台型是把 Agent 能力拆成独立服务供多个业务系统复用。模型推理服务独立部署Agent 编排服务独立部署中间通过公司内部 API 网关互相调用再加上任务队列、向量数据库、统一的可观测性平台。优点是好扩展、好治理、不同业务线可以共用同一套 Agent 底座缺点是前期投入大网络拓扑复杂需要运维配合。我整理了一个对比方便你根据自己团队的处境判断维度单机嵌入型服务化中台型部署成本低一台上线高需多组件协作扩展性差单机性能封顶好可横向扩容多业务复用难各写各的容易能力统一沉淀治理与审计弱难统一管控强可集中做工具权限、数据审计适合阶段PoC、试点、工具型产品正式业务系统、平台型产品1.3 我的选择服务化中台 受控工具我们这个项目的业务是给内部运营人员提供自然语言查询和任务代办能力运营同事在对话框里问一句上个月华东区的退单率趋势怎么样Agent 自己去查数据平台、调报表工具、汇总结论再返回。这显然不是单机玩具能扛住的所以我选了服务化中台路线。但真正让我坚定这个选择的是这个考量Agent 的本质是规划 调用工具而工具就是数据流入口。在隔离内网环境中数据合规是死线任何工具调用都必须可审计、可控制。服务化中台可以很自然地把所有 Agent 内部需要访问的数据接口集中收敛到一个工具网关后面每次调用都有记录、有审批、有权限校验。单机嵌入型想做这件事难度会翻好几倍。另外一个很实际的原因是模型资源。内网里部署一个能用的推理服务不便宜如果每个业务线都自己搞一份单机模型GPU 资源浪费极其严重。中台化之后模型推理服务可以被所有 Agent 任务共享再配合请求排队和优先级控制资源利用率能高出一大截。工程上的选择从来不是哪个技术更酷而是哪个方案在约束条件下更省心。2. 技术选型把能跑变成能交付的关键决策2.1 模型推理服务内网一切能力的地基隔离内网里模型推理是整个 Agent 系统的动力源。没有模型服务Agent 连思考都做不到。我这边最终选了基于 vLLM 搭建内网推理服务用 OpenAI 兼容协议对外暴露。为什么是 vLLM 而不是别的两个原因第一吞吐量高用的是 PagedAttention 机制显存利用更充分同样的 GPU 能扛更多并发第二协议兼容性好下游不管是 LangChain、LangGraph 还是自研代码接一个 OpenAI 风格的 base_url 就能跑。选模型参数量之前先把显存账算清楚。这是最容易被忽略的一步。以 16bit 精度推理为例7B 模型的权重约 14GB70亿参数 × 2字节加上 KV Cache 和激活值单张 24GB 显存的卡能勉强跑13B 模型权重约 26GB单卡 24G 就放不下了至少要 40GB 以上70B 级别模型权重更是上百GB通常要量化到 4bit/8bit 之后才能塞进单卡或者多卡并行。实际项目里我选了 13B 量级的开源基座模型配 4bit 量化部署在两块 40GB 卡上。这样做的原因是内部任务以工具调用 结构化输出为主不需要太强的自由创作能力13B 在指令遵循和 JSON 输出上已经够用推理耗时要控制同样一张卡小模型响应快很多用户体感完全不一样。这里给所有内网项目的建议是先跑通最小验证再根据业务数据调模型千万别一开始就追大参数。2.2 Agent 编排框架LangGraph 与自研的取舍Agent 编排层是系统的大脑回路。我的技术栈里原本备选方案有 LangChain、LangGraph、AutoGen以及完全自研的状态机。LangChain 在早期尝试里被我去掉了原因很朴素它的抽象层级太多调试链路长出了 bug 经常要翻三层源码才能定位问题。相比之下LangGraph 的图编排 状态管理设计更贴近 Agent 的真实运行逻辑——Agent 本质上就是一个有状态、多步骤的图执行过程。LangGraph 的核心思路很好理解你把 Agent 的思考过程拆成节点比如规划节点、工具执行节点、结果分析节点节点之间用边连接通过条件判断决定下一步走向哪个节点。每一步都会更新一个全局状态对象这个对象记录了任务目标、历史消息、工具返回结果等关键信息。from langgraph.graph import StateGraph, END class AgentState(dict): messages: list tool_results: dict def plan_node(state): # 基于当前任务生成执行计划 ... return {messages: plan_output} def act_node(state): # 调用一个或多个注册工具 ... return {tool_results: outputs} def observe_node(state): # 汇总工具结果判断是否需要继续执行 ... def should_continue(state): if state.get(need_more): return act return END builder StateGraph(AgentState) builder.add_node(plan, plan_node) builder.add_node(act, act_node) builder.add_node(observe, observe_node) builder.set_entry_point(plan) builder.add_edge(plan, act) builder.add_edge(act, observe) builder.add_conditional_edges(observe, should_continue, {act: act, END: END}) graph builder.compile()但我也没全盘照搬 LangGraph它在隔离内网里有一个现实问题装依赖得走离线源版本升级也要手动管理。于是我在 LangGraph 外面包了一层自己写的运行环境适配层把依赖收敛到一个固定的版本组合里统一打进内网镜像源。实际踩下来的感受是框架选型不是一次性的你要做好用框架但不被框架绑架的准备。2.3 内网依赖仓库与模型权重分发这一节讲的不是 AI但往往决定 AI 项目能不能顺利上线。我在项目初期就吃了大亏代码在开发机上跑得好好的一到内网服务器就报 ModuleNotFoundError原因是服务器连不上公网 PyPI。后来我建了一套内网包管理方案基本分三层Python 依赖库在开发机上用pip download把所有依赖的 wheel 包包括传递依赖全部下载下来装到内网一台专门跑 Sonatype Nexus 的机器上作为私有 PyPI 源。之后所有内网服务器统一通过pip install --index-url http://nexus.internal/repository/pypi/安装。前端与系统包同理用 Nexus 代理 npm、apt 等源按团队实际用到的包定期同步。模型权重与 Embedding 模型模型权重文件动辄几十 GB不能走包管理提前拷到内网规定的存储区推理服务启动时按路径索引。这一步必须提前和运维沟通好存储配额和传输通道否则等到了上线日才发现模型根本传不进去就很被动了。依赖管理是典型的前期不愿意做、后期哭着补的工程债。越是网络受限的环境越要在项目第一天就把私有源方案定下来。3. 核心模块实现Agent 主循环、工具与 API 服务3.1 Agent 主循环与工具注册机制框架层选定之后核心工作就落在了 Agent 的主循环和工具接入上。我们的实现思路是Agent 模型推理 工具注册表 记忆管理 循环控制。模型推理负责决策工具注册表决定它能干什么记忆管理决定它记不记得上下文循环控制决定它什么时候停下来。工具注册我设计成了一个装饰器模式把工具定义和业务实现解耦TOOL_REGISTRY {} def register_tool(name, description, parameters_schema): def decorator(func): TOOL_REGISTRY[name] { func: func, description: description, parameters_schema: parameters_schema, } return func return decorator register_tool( namequery_order_stats, description按日期范围查询订单统计摘要, parameters_schema{ type: object, properties: { start_date: {type: string}, end_date: {type: string}, }, required: [start_date, end_date], }, ) def query_order_stats(start_date: str, end_date: str): # 内部统一走数据服务网关发查询请求 ... return json.dumps({order_count: 128, total_amount: 98210.50})这个注册表的价值在于Agent 每次决定调用工具时都会先从注册表里拿到工具的描述和参数格式填充模型返回的工具调用参数再真正执行。参数错误、工具不存在、执行超时这些异常全部可以在这一层统一兜住而不是散落在各个业务函数里。主循环最核心的控制逻辑是最大步数限制。Agent 跑飞是常态尤其在没有网络约束的隔离内网里模型一旦陷入反复调用工具但拿不到有效结果的死循环会把系统拖垮。我给的默认值是 6 步超过直接终止并返回当前已收集的信息。这个数字可以根据任务复杂度调但一定得设上限。3.2 记忆与上下文管理防止 Agent失忆隔离内网场景里Agent 的上下文管理比公网场景更吃设计。原因是业务数据分散在多个内部系统Agent 需要在多轮交互里不断把查到的事实带进对话如果上下文管理不好用户在第三轮问我前面说的那个订单呢Agent 根本接不上话。我实践下来比较顺手的是三层记忆方案短期对话记忆用滑动窗口控制送进模型的历史消息数量比如只保留最近 10 轮。窗口之外的对话不再参与推理防止上下文过长导致模型注意力分散也降低 Token 消耗。阶段性摘要记忆当对话轮次超过窗口上限时用模型把旧对话压缩成一两句摘要随新对话一起送入。这种先摘要、后截断的方式我在长表单填写场景里实测非常有效。业务记忆把用户在任务里沉淀的关键实体客户名、订单号、日期范围抽出来存进内网向量库。Agent 在执行新任务时按相似度检索相关业务记忆作为背景知识注入 prompt。向量库我们内网部署的是单机版 Milvus文档量级在百万以内完全够用。你要特别留意的是 embedding 模型也得内网部署否则向量化这一步就把数据送出网了。我用的是一个轻量级中文 embedding 模型单卡 GPU 就能跑batch 化处理之后百万级文档建立索引大概只花了几小时。3.3 用 FastAPI 封装对外服务Agent 内部逻辑跑通之后下一步是把它封装成业务系统可以调用的服务。我用 FastAPI 做这一层最大的原因是异步原生成熟——Agent 任务里有大量 I/O 等待模型推理、工具调用、向量检索异步事件循环能最大化吞吐。对外接口我设计了两个一个是同步查询接口适合快速问答一个是异步任务接口适合耗时的流程型任务。同步接口的核心代码长这样from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(titleInternal Agent Service) class AgentRequest(BaseModel): query: str session_id: str default_session timeout_seconds: int 30 class AgentResponse(BaseModel): code: int 0 message: str data: dict | None None app.post(/agent/run, response_modelAgentResponse) async def run_agent(req: AgentRequest): try: result await agent_runner.run( queryreq.query, session_idreq.session_id, timeoutreq.timeout_seconds, ) return AgentResponse(dataresult) except TimeoutError: raise HTTPException(status_code504, detailAgent task timeout) except Exception as exc: # 统一兜底避免框架异常直接暴露给调用方 raise HTTPException(status_code500, detailstr(exc))流式输出也是内网场景的高频需求。运营同事问一个复杂的分析问题如果必须等 Agent 全部跑完才能看到结果等待体验会非常差。FastAPI 的StreamingResponse配合异步生成器可以做到模型每生成一个 token 就推送给前端。实践下来流式接口的心理等待时间比同步接口至少降低一半。但流式输出对并发控制提出了更高要求这就引出了下一章要讲的重点并发和稳定性。4. 并发与稳定性隔离内网里怎么扛住流量4.1 先算清楚你的并发瓶颈在哪AI Agent 怎么扛并发这段关键词不是玄学核心就一句话瓶颈几乎永远在模型推理而不在 Agent 业务代码。你在 FastAPI 里写一万个协程都没问题可一旦超过模型服务的并发上限所有请求都会排队排队一长超时、雪崩、用户体验崩坏一样不少。我给大家一个保守的容量估算公式。假设平均执行一次完整 Agent 任务需要T秒包含模型推理和工具调用业务要求每秒处理Q个请求那么系统里至少需要能同时跑C Q × T条任务。举个例子一次报表查询任务平均耗时 8 秒业务高峰期要求每秒处理 2 个请求那你就需要至少 16 条并发任务的能力。这 16 条任务背后模型推理服务至少要支持 16 路并发如果单实例只能跑 4 路并发你就得准备 4 个推理实例或者接受排队。内网场景经常有个错觉我们用户量不大不需要考虑并发。这个错觉会害死人。因为 Agent 任务是耗时型任务不是普通 HTTP 接口那种毫秒级返回的快速型任务哪怕同时只有 10 个人点了一下查询也会瞬间堆积 80 秒的工作量。所以容量规划必须从第一天就做否则系统一上线就会被流量打穿。4.2 并发控制的三种手段我在这套系统里用了三层并发控制层层兜底。第一层模型推理服务的并发上限控制。vLLM 本身支持--max-num-seqs参数限制同时处理的序列数量。剩下的请求会在模型服务内部排队。这个参数要根据显存和任务长度调我这边设的是 8。超过这 8 路模型进程不会崩但延迟会显著上升。第二层Agent 服务层的信号量限流。用asyncio.Semaphore限制同时进入 Agent 主循环的任务数。比如推理服务能跑 8 路我就把信号量设为 8多余的请求直接走排队等待或提示稍后再试。这比让请求全部砸进模型层再排队要可控得多因为你能在应用层直接给出友好提示而不是让用户干等十几秒后超时。from asyncio import Semaphore agent_semaphore Semaphore(8) async def run_with_flow_control(query: str, session_id: str): async with agent_semaphore: return await agent_runner.run(queryquery, session_idsession_id)第三层任务队列异步化。对于耗时超过 20 秒的流程型任务比如批量生成周报并发送到指定邮箱同步等待已经不合适了。我在内部接了一个轻量任务队列接口接收到请求后把任务塞进队列立刻返回任务已受理后端 Worker 再把任务从队列拉出来逐个执行。这一步直接解决了 HTTP 超时问题也让系统在高峰期有了天然的缓冲。队列实现上用 Redis 的 Stream或者直接接 Celery按团队熟悉程度来就行。4.3 重试、降级与幂等设计稳定性不是只靠并发控制就能完成的。在隔离内网里业务数据的波动性比公网 API 更大工具调用常常遇到下游系统响应慢、返回格式不对、甚至临时不可用的情况。重试机制里有一个关键教训一定要区分任务类型。对于查询类任务重试是安全的哪怕同一个查询被执行两次结果不变对于写操作类任务比如创建工单发送通知如果没有幂等设计重试会造成重复数据。我的做法是所有写操作工具在参数里强制要求一个request_id下游系统按这个 ID 做去重。这样即使 Agent 因为超时重试了三次下游也只会真正创建一条记录。降级策略同样要提前想好。碰到模型推理服务过载时我采取的是限流熔断 友好提示返回一个标准的系统繁忙响应而不是让用户看到一屏堆栈错误。碰到某个数据源系统宕机时Agent 会把这个工具标记为不可用然后在回答中明确告诉用户订单数据服务暂不可用建议联系数据中心。让 Agent 学会说我查不到比硬编一个错误码给用户要友好得多。4.4 可观测性建设没有监控等于盲开内网环境没有现成的 APM 服务可观测性要自己搭。我用了最务实的三件套结构化日志、Prometheus 指标、Grafana 看板。结构化日志里有一个字段是必须的链路 ID。每次请求进来先生成一个 UUID然后把这个 ID 贯穿 Agent 主循环的每一步——模型调用日志、工具调用日志、向量检索日志全部带上这个 ID。排查问题时拿用户反馈的时间点一顿 grep 就能把一次完整任务的所有执行细节拉出来。指标监控我至少看五组数据请求量按接口和业务线拆分任务平均耗时与 P95 耗时模型 Token 消耗量工具调用成功率与失败率排队任务数与队列深度指标口径上踩过一个坑只看平均耗时会把问题掩盖掉。内网任务经常是多数简单任务秒回、少数复杂任务跑到 60 秒以上平均值看起来还行P95 已经高得离谱。所以我把 P95 和 P99 单独上了告警阈值分别设为 15 秒和 30 秒超过就往项目群发告警。这个习惯帮我提前发现了两次模型服务显存泄漏问题——P95 异常飙升比任何监控页面都能更快地暴露问题。5. 常见问题与排查技巧实录5.1 离线环境依赖装不上的自救手册这是隔离内网项目里出现频率最高的一类问题。我的排查顺序是先确认基础环境对不对再确认包源配没配好最后确认有没有编译依赖缺失。一个很常见的坑是lxml、pydantic-core 这类带 Rust/C 扩展的包在离线环境里没有预编译 wheel 时会在安装时触发本地编译而内网服务器又没有对应的 Rust 工具链和编译依赖报错五花八门。解决办法是在开发机上用pip download --platform manylinux2014_x86_64 --python-version 3.11 --only-binary:all:之类的方式把所有依赖的二进制 wheel 下载齐再传到内网私有源里。原则就是只保留二进制 wheel不在内网环境做源码编译。Docker 是另一个离线分发利器。在开发机上把整个 Agent 运行环境打包成镜像通过离线介质导入内网服务器的本地镜像仓库依赖问题直接绕开。但注意镜像也得提前做好安全扫描和版本冻结别让内网环境成为随便 pull 镜像的地方。5.2 模型输出的不可靠怎么治Agent 工程里最烦人的问题模型不按格式输出。明明你在 prompt 里要求只输出 JSON它偏要在前后加几句好的我来为您生成以下内容。我的处理分两层。第一层是约束解码vLLM 支持引导模型按给定的 JSON Schema 生成从解码阶段就限制输出结构不是事后补救。这个方案在工具调用场景下能极大提升成功率。第二层是宽松解析不管模型怎么输出我先用正则把代码块、注释、多余前后缀去掉再尝试json.loads解析失败就触发一次带错误信息反馈的重试。重试 prompt 里明确说上次输出 JSON 解析失败请只输出 JSON这一招能救回不少翻车案例。参数调优方面我固定了这样一组temperature0.1、top_p0.9、max_tokens2048、repetition_penalty1.05。temperature 压低是为了让工具调用和格式输出更稳定但也不是越低越好太低模型容易陷入机械重复rep penalty 适度开启能有效抑制长文本生成时的重复片段。每换一个模型基座这组参数都要重新验证一遍不能拿来就用。5.3 上下文污染与串话问题隔离内网系统通常不止一个业务线接入Agent 一旦把 A 业务的上下文带到 B 业务的对话里后果很严重。有个真实案例运营人员在查 A 区订单时问了一句顺便把上次那个方案也发我Agent 如果共享全局上下文就可能把 B 区的方案摘要也带出来这在数据合规上是不能接受的。我做的第一件事是会话级隔离。每个 session 有独立的上下文存储Agent 初始化时只从当前 session 加载历史。第二件事是工具结果截断。工具返回的数据动辄几十 KB不能全量塞进上下文既费 Token 又容易让模型迷失重点。我按工具类型做了定制化压缩数据查询类的工具只保留聚合摘要和 top 10 明细文档检索类的工具只保留命中片段状态类的工具只保留关键字段值。第三件事是过期清理session 超过 24 小时没有活跃就把它的缓存和向量记忆清掉避免内存里堆着大量僵尸会话。5.4 并发上不去先查这四个地方我把后端同事问得最多的并发排查问题整理成了一张速查表按以下顺序排查基本能定位 80% 的瓶颈排查项快速判断方法常见根因模型推理并发看 vLLM 监控里的 seq 数是否长期顶满--max-num-seqs配太小或显存不足应用层信号量看任务排队时间是否大量任务等在 Semaphore 前信号量数值和推理并发不匹配下游工具接口看工具调用耗时分布是不是某个接口偶发 5 秒以上延迟数据平台慢查询、无索引数据库连接池看报错里有无too many connections连接池上限小于任务并发数遇到并发上不去的现象先从这四张图开始拉数据不要上来就猜。我发现很多并发不行的问题最终都落在下游工具接口上——Agent 代码优化得再好一个数据中心接口在高峰期需要 3 秒才能返回整个系统的吞吐就被按在地上摩擦。对这类慢接口我的做法是给 Agent 工具层加本地缓存和超时熔断把对下游的依赖降到最低。最后说点实在的这条链路跑顺之后我很长时间里都在琢磨一个问题隔离内网给 AI Agent 到底带来了障碍还是保护客观说障碍是真的——环境受限、依赖难装、数据闭环每一步都比公网模式费劲。但换一个角度想也正是因为隔离内网的高约束逼着我们把工程的底子打得更扎实工具收敛成标准接口、上下文做成可追溯的会话、并发控制形成量化模型、监控指标落实到每一个请求。这套东西放到任何环境里都是通用的反而比那些公网一把梭、prompt 走天下的方案更经得起压力测试。最后再分享一个实用的小技巧给 Agent 每次模型调用的 prompt 末尾加一行固定的输出指令比如如果工具调用结果为空请明确回答暂无数据不要编造这行指令在减少幻觉上的回报率比调任何参数都明显。这个小改动是我们团队自测中效果最立竿见影的优化之一你做 Agent 工程时不妨直接抄去试试。