最近接了个私有化部署的 AI Agent 项目,环境一上来就把我整不会了:服务器在内网,外网被掐断,装个 Python 依赖都要折腾半天,更别提调云端大模型 API。头两周基本在踩坑,后来把一条能跑通的 Agent 工程链路梳理清楚,才意识到隔离内网做 AI Agent 工程实战,核心不是模型选型,也不是花哨的 Agent 架构,而是把“依赖、模型、工具、并发、观测”这几件事全部内生化。这篇就把我的实操过程、踩过的坑、以及最终验证过可行的方案完整写出来,给同样在隔离环境里做 Agent 落地的朋友一点参考。
1. 先把“隔离内网”这四个字拆清楚
1.1 三种常见的隔离强度
很多人一听到“隔离内网”就默认是那种完全断网的机房,但实际做项目会发现,隔离的强度千差万别,直接决定了你后面技术方案怎么定。
我这次遇到的情况属于“白名单单向访问”,即内网服务器可以访问一些经过审批的企业内部域名,但普通互联网访问基本不可用。另外两种常见的形态:一是完全物理隔离,机器不能插外网网线,文件只能通过介质或者企业内部的文件摆渡系统进入;二是逻辑隔离,有出网能力但需要经过网关审批,速度极不稳定,且随时可能被关停。
建议拿到项目先花半天做一次环境盘点,别上来就写代码。盘点项包括:服务器能不能访问公网、有没有企业内部软件源或制品库、大模型推理要用 GPU 还是 CPU、容器环境是 Docker 还是 Kubernetes、网络策略是否允许跨机调用。这一步做得越细,后面越少返工。
1.2 隔离给 Agent 工程带来的五连击
隔离内网会让常规的 Agent 开发流程直接失效,我总结下来至少是五类问题:
- 模型依赖拿不到。默认想用云端大模型 API 或者 H ugging Face 上下载开源模型权重,在隔离内网里全部走不通。你需要先解决“模型从哪来、怎么进内网”的问题。
- Python 依赖装不上。pip install 直接超时,项目里几十个间接依赖,每一个都要手动处理,还得考虑依赖版本互相打架的问题。
- 工具生态被卡死。Agent 要用的 Web 搜索、地图、天气、OCR、翻译这些第三方服务,公网 API 全都连不上,工具集必须重新设计。
- 数据回流没有现成通道。Agent 产生的日志、轨迹、评估结果需要持久化,而常用的云端监控、日志服务都在外网,得自己搭建或另想办法。
- 并发和性能的约束完全变了。云端大模型的并发能力不需要你操心,隔离内网里跑本地模型,显存、吞吐、排队全部变成你的工程问题。
这五连击意味着,你在隔离内网里做的不是“Agent 应用开发”,而是“Agent 基础设施自建”。心态和节奏都要调整。
2. AI Agent 主流架构与选型逻辑
2.1 绕不开的技术栈盘点
AI Agent 的热度起来之后,市面上的架构方案五花八门,但如果要工程落地,真正主流的其实就那么几条路。
开发框架层面,Python 生态里 LangChain 和 LangGraph 用得最多,LangChain 适合快速搭原型,LangGraph 则胜在把 Agent 的流程定义成有状态的状态机,节点、边、循环都能显式管理,在复杂业务场景里可控性更强。Java 技术栈的团队往往会看 Spring AI,它把 LLM 调用、提示词模板、结构化输出这些基础能力统一封装成 Spring Boot Starter,对已有 Java 中后台体系非常友好。Rust 系最近也有不少项目冒出来,比如基于 rig-core 的轻量 Agent 框架,性能表现不错,但生态还在快速演进,适合有充足精力深入贡献的团队,不适合急着交付的业务项目。
平台层也有选择,像扣子这类智能体应用平台能通过可视化编排迅速做出 Agent 原型,POC 阶段效率非常高,但隔离内网私有化场景下,数据要出域、平台和模型要绑定对外服务,往往绕不开“需要企业版私有部署”这道坎。
2.2 为什么我推荐 FastAPI + LangGraph 的组合
这次实战我最终选了 FastAPI + LangChain + LangGraph 的组合。不单是因为 Python 在 AI 生态里最成熟,更核心的原因是这套组合把三个问题处理得很干净。
第一是开发交付能力。内网 Agent 本质是一个被反复调用的服务,FastAPI 天然支持异步接口、参数校验、请求上下文管理和 OpenAPI 文档,用来做 Agent 的统一入口非常顺手。第二是流程可控性。LangGraph 可以把“意图识别 -> 工具调用 -> 结果汇总”之类的流程显式建模,业务方想插一个审批节点、加一个兜底逻辑,直接在图里加一条边就行,不用把逻辑堆在 prompt 里。第三是排查维护的友好度。LangGraph 的状态快照和中间步骤记录,在隔离内网这种不能随便造数据结构的环境里,排查问题能省一半时间。
2.3 单 Agent 还是多 Agent?
另一个容易被带偏的决策是“要不要上多 Agent”。市面上很多教程一上来就展示多 Agent 辩论、多角色协作,看起来很酷,但工程上多 Agent 意味着多次模型调用、更长的链路时延、更大的状态管理复杂度,以及成倍的排查难度。
我现在的判断标准很简单:能用单 Agent 解决的场景,坚决不上多 Agent。比如检索问答、工单分类、字段抽取、报表生成,这些用“一个 Agent + 多个工具”就足够了,LangGraph 里一个节点做规划,一个节点执行工具,一个节点生成答复。只有当任务确实可以并行拆解,比如同时查库存、查物流、查价格,并且单条链路对时延有明确要求时,才拆出并行分支甚至多 Agent 协作。隔离内网里的模型推理资源本来就紧张,这一步克制非常关键。
3. 隔离内网的依赖与模型资产准备
3.1 Python 依赖离线导入
先解决环境依赖。我当时的做法是在一台具备外网访问权限的构建机上,用 pip download 把需要的包连同依赖全部拉下来,打成离线包目录,再通过企业内部的摆渡系统传到内网机器。
执行时重点注意三点。第一,构建机 Python 版本要和内网目标机器保持一致,最好连小版本都对齐,否则很多二进制 wheel(比如 pydantic-core、numpy、tokenizers)会因为平台标签不匹配而装不上。第二,不能用pip install 包名 --download这种简单逻辑,要采用:
pip download -r requirements.txt -d wheelhouse --platform manylinux2014_x86_64 --python-version 3.11 --implementation cp --abi cp311进入内网后:
pip install --no-index --find-links=./wheelhouse -r requirements.txt第三,如果团队规模不小、项目数量多,更推荐直接在内网搭一个 Nexus PyPI 私服,把构建机下载的包全部上传进去,后续所有机器统一从私服安装,省掉每个项目单独拷贝轮子的麻烦。
3.2 模型权重与 Embedding 本地化
模型资产是隔离内网最容易卡住的一环。开源大模型权重动辄十几 GB 到上百 GB,跨内网传输本身就是体力活。
我的实践经验是:先把模型下载到一台可联网的服务器,用校验工具算好 SHA256,然后通过内部文件服务器或离线硬盘拷入内网。模型文件原则上全部放到一个统一目录,按“模型类型/模型名/版本号”的组织方式管理,避免散落在各个项目的虚拟环境里,后面想做版本回退会非常痛苦。
对内网 Agent 来说,Embedding 模型往往比主对话模型更常用。知识库检索需要它,工具结果向量化需要它,有些场景还会用它做路由和分类。建议优先本地化部署一个小而快的 BERT 类或 text-embedding 类模型,口粮再紧张也要保证 Embedding 服务稳定,否则 Agent 的召回能力会极大退化。
模型服务化我用的是 vLLM + OpenAI 兼容接口的方式,内网服务可以暴露成一个本地地址,Agent 代码里只需要把 base_url 指到它就行。对于没有 GPU 的环境,Ollama 也支持纯 CPU 推理,虽然速度一般,但胜在部署极简,适合验证和开发调试。
3.3 Docker 镜像离线分发
如果内网有 Kubernetes 环境,容器镜像也必须离线化。在可联网的构建机上执行docker pull准备好所有镜像,再用docker save导出为 tar 包传入内网,最后用docker load导入到节点的本地镜像仓库,或者推送到内网 Harbor。
这里最容易踩坑的是基础镜像版本不一致。AI 项目的镜像里有 CUDA、cuDNN、Python 运行时、模型推理库,任何一个组件版本不对,容器跑起来就是各种诡异报错。建议项目一开始就把基础镜像锁死,所有服务统一基于同一个方案构建,不要在 Pod 级别各写各的。
4. Tool 内生化与 Token 管理
4.1 内网 Agent 的工具画像
隔离内网里没有 Web 搜索、没有公网 API,Agent 的工具集全部得靠内网资产来定义。我整理下来,常见的可内生化工具大致这几类:
- 内网业务接口封装:比如查订单状态、查库存、提交审批、拉取报表,本质是把 HTTP 接口封装成 Agent 可调用的函数,输入输出都做结构化约束。
- 数据库查询工具:封装成只读的 SQL 执行接口,让 Agent 能回答“上个月退货率是多少”这类数据问题。
- 脚本执行工具:运维和自动化场景很常见,Agent 生成命令后通过受控的执行器去跑,比如远程执行巡检脚本、批量处理文件。
- 消息推送工具:企业内部办公软件的机器人接口,Agent 可以把结果主动推送到群组或者个人。
- 内部知识库检索:把文档、Wiki、FAQ 全部向量化之后,做成检索工具,这基本上是内网 Agent 的最强刚需。
很多人问我“能不能让 Agent 去做期货交易,或者挂个小红书账号自动发消息”,本质上这些都是工具编排问题:行情源、交易接口、发帖接口全是工具,Agent 是决策调度层。技术上不是不能做,但涉及自动交易、对外发布这类动作,业务风险、合规边界和异常兜底必须前置设计,我建议先想清楚容错和责任归属,再谈自动化。
4.2 权限、审计与工具安全
工具内生化之后,安全问题会被放大。Agent 一旦可以通过自然语言触发工具调用,工具的边界就变成了模型可控范围的边界。我这边做了几层防护:
- 工具注册表白名单:Agent 能看到的工具列表是硬编码维护的,每个工具都有明确的入参 schema 和出参 schema,不允许模型自由发明工具名。
- 参数校验前置:工具入口统一走 Pydantic 校验,数据库工具强制 only-read,脚本工具只允许执行白名单目录下的受控脚本。
- 敏感操作二次确认:涉及审批、转账、群发消息这类高影响操作,Agent 不能直接执行,必须生成一个待确认请求,由用户点击确认后真正调用。
- 全链路审计日志:每轮对话、每次工具调用的参数和结果都要落库保留,隔离内网环境里出了问题没法甩锅给外部,日志就是唯一的现场证据。
4.3 搞懂 Agent 里的 Token 到底指什么
很多刚接触 Agent 的人看到网上讨论 Token 就懵。Token 在 Agent 里有双重含义:一是 LLM 对文本切分的基本单元,一个中文汉字往往对应一个到一个多 Token,它是模型计费和上下文窗口容量衡量的基础;二是在并发工程里,Token 也是资源分配和限流的核心计量单位。
我在做 Agent 服务时,会把“Token 预算”拆成两层。第一层是单次请求预算:一个请求最多允许消耗多少输入、输出 Token,防止某次超长上下文把推理服务拖垮。第二层是全局速率预算:单位时间内所有请求累计消耗的 Token 总数不能超过模型服务的承载上限。在代码里我维护了一个简单的令牌桶计数器,按分钟统计已消耗 Token 量,超过阈值就返回“系统繁忙,请稍后重试”,这比让请求无限排队要优雅得多。
5. 并发与性能:AI Agent 怎么扛住生产流量
5.1 先从同步串行到异步并发
内网 Agent 服务刚上线时,很多人写的接口是同步的:一个请求进来,调用模型推理,卡住等模型返回,整个过程占用一个工作线程。模型推理动辄几秒,并发一上来工作线程很快耗尽,请求全部排队。这个阶段其实还没到模型能力瓶颈,卡的是代码层的 IO 模型。
必须把 HTTP 层改成异步。FastAPI 的 async 接口对于等待型操作(HTTP 调用、数据库查询、消息队列)能极大提升单位资源的请求承载量。举一个直观的例子:同样的 4 核 8G 机器,同步写法能同时处理 10 个请求已经在抖,改成异步后可以轻松挂住几百个等待中的请求,虽然推理本身没有变快,但系统不再因为线程耗尽而拒绝服务。
5.2 连接池、线程池与任务队列
模型推理虽然最终是算力密集型,但 Agent 链路里的外围依赖大多是 IO 型的,这些依赖必须全部池化。
对大模型推理服务的 HTTP 调用,用连接池复用长连接,避免每次请求都走 TCP 握手;对数据库访问,连接池大小按并发峰值调节,太小会排队,太大浪费内存;对脚本执行器这种 CPU 密集型工具,用独立线程池隔离开,避免 Agent 链路里某个脚本把整个服务拖死。
如果流量再上一个量级,就要引入任务队列。把请求先放到 Redis/RabbitMQ 之类的队列,再让消费端以固定速率从队列里取任务去请求推理服务。这本质上是一种背压机制:模型服务扛不住的时候,队列帮你缓冲,而不是直接雪崩。生产环境里,消息队列的弹性远比代码里塞各种超时重试要可靠。
5.3 限流、超时与降级策略
隔离内网的资源是封闭的,一旦出问题,没有外部资源可以借力,所以限流和降级是保命手段。
限流维度我参考如下表格,实际值根据资源和业务容忍度调整:
| 维度 | 限流手段 | 推荐值/策略 |
|---|---|---|
| 用户维度 | 每个用户每分钟请求数 | 5~10 次/分钟 |
| 服务维度 | 全局并发请求数 | 推理服务并发上限的 60% |
| Token 维度 | 每分钟总 Token 消耗 | 按模型 TPS 实测估算 |
| 队列维度 | 最大排队长度 | 超过 50 直接拒绝 |
超时策略也很重要:模型调用超时、工具调用超时、整条 Agent 链路超时,必须分别设置,且链路超时要小于下游超时。我的经验是:模型推理单次超时设 60 秒,工具调用看具体接口,一般 10~15 秒,Agent 整链路 120 秒。超时后统一返回降级答案,比如“内部服务暂时不可用,请稍后再试”。
5.4 推理侧吞吐调优实测
隔离内网的 GPU 资源一般很稀缺,推理服务的吞吐优化比代码并发更重要。我实测中几个有效手段:
首先,vLLM 的连续批处理机制能显著提升吞吐。相同的 Qwen 系列模型,开 vLLM 和原生 transformers 接口做同样并发压测,吞吐能提升 4~5 倍。其次,max_concurrency 和 max_num_batched_tokens 要按显存实测调,不要照抄默认值。我这边一张 24G 显存的卡,量化到 INT4 后并发调到 8~16 是一个比较稳的区间,太高了显存溢出,太低了浪费算力。
另外一个很容易忽略的点是 Embedding 模型和主模型不要抢资源。把 Embedding 推理单独放在独立小容器里,避免文档检索和对话推理互相干扰。实测下来,隔离内网环境下,这一条能让单次对话延迟稳定降低 15% 以上。
6. 可复现的最小实战:内网订单问答 Agent
6.1 需求与架构
为了让上面的方法落地,我拆一个完整的小案例:内网订单问答 Agent。场景是业务运营同学在群里问“A 客户今天出了多少单、有几单还没发货”,Agent 先去查订单数据库,再调内部物流接口核实发货状态,最后返回一段自然语言结论。
整体链路是这样的:用户在客户端发起对话请求,FastAPI 网关接收请求后进入 LangGraph 编排,Agent 根据问题路由到订单查询工具和物流查询工具,工具底层访问内网数据库和 HTTP 接口,中间所有日志写入本地监控表。不需要 GPU 也能跑,用小一点的 7B 量化模型就够了。
6.2 FastAPI 服务代码
先写一个简单的 FastAPI 入口,把 Agent 的核心逻辑包进来:
from fastapi import FastAPI, HTTPException, Depends from pydantic import BaseModel from contextlib import asynccontextmanager from agent_core import run_agent app = FastAPI(title="InnerNet Agent Gateway") class ChatRequest(BaseModel): user_id: str message: str class ChatResponse(BaseModel): answer: str trace_id: str @app.post("/api/chat", response_model=ChatResponse) async def chat(req: ChatRequest): try: trace_id, answer = await run_agent(req.user_id, req.message) return ChatResponse(answer=answer, trace_id=trace_id) except TimeoutError: raise HTTPException(status_code=504, detail="agent chain timeout") except Exception as e: raise HTTPException(status_code=500, detail=str(e))run_agent 内部负责构造请求 ID、调用 LangGraph 图、把整链路状态快照和最终回答都返回出去。注意这里全部是 async 函数,后续工具调用才能享受异步红利。
6.3 内网 Tool 封装
每个工具就是一个带 schema 约束的函数,我用 LangChain 的 @tool 装饰器来做:
from langchain_core.tools import tool import aiohttp @tool async def query_order_count(customer_id: str, date: str) -> str: """查询指定客户在指定日期的下单数量""" async with aiohttp.ClientSession() as session: async with session.get( f"http://internal-order-api/orders/count", params={"customer_id": customer_id, "date": date}, timeout=aiohttp.ClientTimeout(total=10) ) as resp: data = await resp.json() return f"订单数: {data['count']}" @tool async def query_delivery_status(order_id: str) -> str: """查询指定订单的物流发货状态""" async with aiohttp.ClientSession() as session: async with session.get( f"http://internal-logistics-api/delivery/status", params={"order_id": order_id}, timeout=aiohttp.ClientTimeout(total=10) ) as resp: data = await resp.json() return f"发货状态: {data['status']}"工具返回的字符串会被模型当作观察结果继续推理。所以工具的描述要写得让模型能看懂,输出的内容要结构化,最好能原文拼接成自然语言推断的依据,这能显著降低模型“发挥”的空间。
6.4 LangGraph 编排
接下来用 LangGraph 把流程定义出来,这里我只建四个节点:入口节点 agent_route 负责判断问题需要哪些工具;query_order 和 query_logistics 分别做数据查询;answer 节点汇总结果生成最终回答。
from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated class AgentState(TypedDict): message: str order_count: str delivery_status: str answer: str def build_graph(): g = StateGraph(AgentState) g.add_node("agent_route", agent_route) g.add_node("query_order", query_order) g.add_node("query_logistics", query_logistics) g.add_node("answer", answer) g.set_entry_point("agent_route") g.add_edge("agent_route", "query_order") g.add_edge("agent_route", "query_logistics") g.add_edge("query_order", "answer") g.add_edge("query_logistics", "answer") g.add_edge("answer", END) return g.compile()LangGraph 真正有价值的是它允许你在节点之间增加条件和循环,比如当工具查询失败时,可以让 Agent 重新规划而不是直接摆烂。这也是我选择它的核心原因。
6.5 部署联调与实测
部署时我把服务拆成两个部分:推理服务单独启动,Agent 网关单独启动。内网联调时最常遇到的问题是模型返回不稳定,同一个问题在五轮测试里可能有一轮直接给了一个瞎编的数字。后来我在 answer 节点里强制加入“如果工具返回值为空,必须如实说查询不到,不许猜测”的约束,情况才好转。
实测下来,单用户连续提问的响应时间大约在 8~15 秒,其中 80% 的时间都花在模型推理上。并发 10 个请求时,未做限流的版本直接导致推理服务 OOM,加上令牌桶限流后系统稳定在“部分请求排队、无失败”的状态。这个数据说明,隔离内网环境下,资源天花板决定了体验天花板,该排队就排队,比反复重试导致雪崩要强。
7. 常见问题与排查实录
7.1 依赖与镜像问题
现象:内网机器 pip install 时报 “Could not find a version that satisfies the requirement”。原因多半是离线 wheel 包里根本没有匹配当前 Python 版本和系统架构的轮子。解决办法是在构建机上用更严格的 platform tag 重新下载。如果是二进制包的 glibc 版本不匹配,直接换 manylinux 标签或者改用纯源码包重新构建。
另一个高频问题是 Docker 镜像导入后发现容器内缺 CUDA 库。这种优先检查基础镜像和推理镜像的 CUDA 版本是否一致,建议统一用同一个 PyTorch 官方镜像作为底座。
7.2 推理服务问题
现象:请求偶尔报 502,过一会儿又自己恢复。大多是显存碎片化或者并发超限导致的。建议在推理服务端开启持久化日志,观察最大并发数和显存峰值;同时对单实例设置保守的 max_concurrency,多实例横向扩展比单实例硬扛更稳。
如果模型推理速度越来越慢,看看是否上下文越积越长。对话历史无限累加会造成每次重新预填充整个历史,复杂度陡增。务必要做上下文滑动窗口,只保留最近 N 轮对话加系统提示词。
7.3 Agent 行为问题
现象:Agent 乱调用工具,比如回答“你今天怎么样”这种寒暄,却去调了订单查询。排查思路是先确认工具描述是否清晰,工具名和描述中关键词是否与真实业务术语接近;其次看 prompt 里是否明确给出路由规则,比如“只有涉及订单、物流、客户信息时才能调用数据库工具”。如果问题依旧,就退化为路由模型单独做意图识别,不要让同一个模型既做路由又做对话。
另一种常见情况是 Agent 把多个工具的返回结果搞混,A 客户的数据被当成 B 客户。这类问题靠 prompt 修不太稳,最好是让每个工具返回时带上明确的实体标识(客户 ID、订单号),并在回答节点要求模型必须引用工具返回中的真实 ID 字段。
7.4 日志与追踪问题
隔离内网没有现成的云监控链路,Agent 的调试往往像盲人摸象。我自己搭了一套极简方案:在 FastAPI 中间件里为每个请求生成 trace_id,LangGraph 的每个节点执行前后都往日志表写一条记录,包含节点名、输入状态、输出状态、耗时。这样用户报问题后,只要提供 trace_id,我就能把整条链路的执行记录拉出来,定位到具体是路由错了、工具超时了还是模型输出乱来了。
日志表的设计不需要很复杂,字段就五六个:trace_id、节点名、输入摘要、输出摘要、耗时、创建时间。实测下来,这套方案运维排查效率提升了不止一个量级。
最后说点个人体会。隔离内网里做 AI Agent,最大的难点反而不是模型效果,而是被环境倒逼着把工程基本功做扎实:依赖要可复现、资源要可计量、工具要有边界、链路要可追踪。这些都是好事,公网项目可能靠着容错糊弄过去的问题,在这里全部必须硬碰硬解决。后面如果条件允许,我还会把模型监控、Prompt 版本管理、Agent 自动评估这几块补上,让整条链路更完善。如果你也正在隔离环境里做 Agent,希望这篇能帮你少走几步弯路。