
1. 为什么隔离内网里的 AI Agent 工程是另一套玩法先把场景说清楚。所谓隔离内网指的是开发机和生产环境都跑在一个没有公网出口、没有外部包源、没有在线模型 API 的封闭网络里。你能用的只有内网镜像仓库、内网文件服务器、内网模型推理服务以及一台能进出的跳板机。这个前提一摆出来网上那些五分钟搭一个 AI Agent的教程基本全部作废——它们默认你能pip install、能调云端大模型、能拉 GitHub 上的 MCP 服务而这些在隔离内网里全是断的。我在这种环境里做过几轮 AI Agent 的落地最大的感受是内网 Agent 工程的难点从来不是Agent 本身而是依赖治理和能力边界。公网环境下你写 Agent思路是缺什么装什么内网环境下你写 Agent思路必须反过来变成我手上有什么就用什么拼。这两种思维方式的差异直接决定了你的架构选型和工程组织方式。这篇文章面向的是这样几类人一是在金融、制造、能源这类有强隔离要求的环境里做 AI 应用落地的工程师二是想把 Agent 能力搬进内网、但被依赖问题卡住的开发者三是正在评估 MCP、Skills 这类新范式在内网可行性的技术负责人。我会把整个工程链路拆开讲——从依赖怎么搬进去、模型怎么接、MCP 和 Skills 怎么在内网落地到并发怎么扛、踩过哪些坑。所有内容都基于真实可复现的实践不讲空话。需要先明确一个概念边界隔离内网不等于完全没有网络它通常有内网 pip 源、内网 npm 源、内网容器镜像仓库。真正物理隔离、连内网源都没有的极端情况我会单独在依赖搬运那一节讲。绝大多数人遇到的是前者别自己吓自己。2. 依赖搬运把公网生态搬进内网的三种姿势2.1 先搞清楚你到底缺什么很多人一上来就想着把整个 PyPI 镜像同步进来这是典型的用力过猛。正确的第一步是做依赖清单审计。你需要的不是全量生态而是你这个 Agent 项目实际 import 的那几十个包以及它们的传递依赖。我的做法是在一台能联网的机器上用干净虚拟环境装一遍项目然后导出精确依赖python -m venv .venv source .venv/bin/activate pip install -r requirements.txt pip freeze requirements.lockrequirements.lock里是带版本号的完整依赖树这才是你要搬的东西。注意一定要锁版本内网环境最怕的就是今天能跑明天不能跑而版本漂移在无法随时联网排查的环境里是灾难。2.2 三种搬运姿势的取舍姿势适用场景优点代价离线 wheel 包依赖量小、变更少简单直接可控每次加依赖都要重新打包内网私有源团队多人、长期迭代一次搭建长期受益搭建和维护成本高容器镜像整体搬运环境复杂、依赖重环境一致性最好镜像体积大传输慢离线 wheel 是最常用的起步方案。在联网机器上把所有依赖下载成 wheelpip download -r requirements.lock -d ./offline_pkgs \ --platform manylinux2014_x86_64 \ --python-version 311 \ --only-binary:all:这里有个坑我必须提醒--platform和--python-version必须和内网目标机完全一致否则下载下来的 wheel 装不上。我踩过一次联网机是 Python 3.11、内网机是 3.9结果一半的包因为 ABI 不兼容直接报错白搬了一趟。搬运前先在内网机上跑python -V和uname -m把这两个值记死。搬进去之后在内网机上这样装pip install --no-index --find-links./offline_pkgs -r requirements.lock--no-index是关键它强制 pip 不去联网找源只用本地 wheel。如果这一步报找不到某个包说明你的 wheel 没下全通常是某个包只有源码包没有 wheel需要单独处理。2.3 那些没有 wheel的包怎么办总有一些包只提供源码sdist比如某些带 C 扩展的库。这时候你有两个选择一是在联网机上先编译好 wheel 再搬二是把编译工具链也搬进内网。我强烈建议选第一个因为在内网里配编译环境是纯粹的浪费时间。编译 wheel 的命令pip wheel -r requirements.lock -w ./offline_pkgs --no-deps--no-deps表示只编译当前包不递归依赖避免重复下载。编译出来的 wheel 是平台相关的同样要注意目标机架构一致。提示搬运依赖时顺手把pip、setuptools、wheel这三个基础包也下进去。内网机的 pip 版本往往很老装新格式的 wheel 会失败先升级基础工具能省掉一堆莫名其妙的报错。3. 模型接入内网里没有云端 API 时怎么让 Agent 跑起来3.1 内网模型服务的两种形态隔离内网里让 Agent 有脑子无非两条路内网自建的推理服务或者内网部署的开源模型。前者通常是团队已经有的统一推理网关后者是你自己用 vLLM、TGI 这类框架在内网 GPU 机器上起的服务。不管哪种对 Agent 来说它就是一个 OpenAI 兼容的 HTTP 接口。所以你的 Agent 代码里模型调用层要做的第一件事就是把 base_url 和 api_key 做成配置项而不是硬编码。这是内网工程的基本素养因为内网的服务地址经常变硬编码会让你改到怀疑人生。# config.py import os LLM_CONFIG { base_url: os.getenv(LLM_BASE_URL, http://internal-llm.svc/v1), api_key: os.getenv(LLM_API_KEY, internal-placeholder), model: os.getenv(LLM_MODEL, qwen2.5-32b-instruct), timeout: int(os.getenv(LLM_TIMEOUT, 120)), }注意api_key这里给个占位符就行内网服务通常不做鉴权但很多 SDK 强制要求这个字段非空不给会直接报错。3.2 内网模型的三个现实约束用内网模型和用云端 API体感差异巨大主要体现在三点第一是上下文窗口小。内网部署的模型往往只有 8K 或 32K 上下文而云端动辄 128K。这意味着你的 Agent 不能无脑把历史对话全塞进去必须做上下文压缩。我的做法是保留最近 N 轮完整对话更早的用摘要替代摘要本身也由模型生成。第二是并发能力弱。一台内网 GPU 机器能同时处理的请求数很有限超过就会排队甚至超时。这直接决定了你的 Agent 架构不能是每个用户请求都直接打模型必须有队列和限流。这一点我在第 6 节会展开。第三是输出稳定性差。开源模型在结构化输出比如要求返回 JSON上的遵从度不如云端大模型。所以内网 Agent 里凡是需要模型返回结构化数据的场景都要加解析容错和重试不能假设它一定返回合法 JSON。import json import re def parse_json_safely(text: str, retries: int 2): for _ in range(retries 1): try: return json.loads(text) except json.JSONDecodeError: # 尝试从 markdown 代码块里抠出 JSON match re.search(r(?:json)?\s*(\{.*?\})\s*, text, re.S) if match: try: return json.loads(match.group(1)) except json.JSONDecodeError: pass text text.strip() raise ValueError(模型未能返回合法 JSON)这段代码看着朴素但在内网环境里救过我无数次。开源模型特别喜欢在 JSON 外面裹一层解释文字或者 markdown 代码块直接json.loads必挂。3.3 模型选型的经验判断内网能部署的模型参数量通常受限于 GPU 显存。我的经验是Agent 场景下模型会不会用工具比聪不聪明更重要。一个 32B 但工具调用格式规范的模型实际效果往往好过一个 70B 但经常乱输出工具调用的模型。选型时重点测三件事一是能不能稳定按格式输出工具调用二是多轮对话里会不会忘记系统提示三是长上下文下会不会开始胡言乱语。这三项过关参数量小一点完全可以接受。4. MCP 与 Skills 在内网的落地方式4.1 先分清 MCP 和 Skills 各自解决什么问题这两个概念经常被混着讲但在内网工程里它们的定位完全不同必须分清楚。MCPModel Context Protocol解决的是Agent 怎么统一接入外部能力。它定义了一套标准协议让 Agent 通过统一的客户端去调用各种工具服务——数据库查询、文件操作、内部 API 调用都可以包装成 MCP Server。它的价值在于解耦工具的实现和 Agent 的调用逻辑分开加一个新工具不用改 Agent 核心代码。Skills 解决的是Agent 怎么获得领域知识和固定流程。它更像是一份给模型看的操作手册把某个任务的步骤、注意事项、可用工具打包成一个可复用的能力单元。它的价值在于沉淀把老师傅的经验固化成模型能读懂的结构化文档。在内网环境里这两个东西的落地难度是不一样的。MCP 是纯内网可实现的因为它本质就是本地进程间通信或内网 HTTPSkills 更依赖模型的理解能力对模型质量要求更高。4.2 内网 MCP Server 的部署要点内网部署 MCP Server最省事的方式是本地 stdio 模式——MCP Server 作为子进程被 Agent 拉起通过标准输入输出通信完全不涉及网络。这种方式在内网里最稳因为它不依赖任何端口和网络配置。{ mcpServers: { internal-db: { command: python, args: [-m, mcp_servers.db_server], env: { DB_HOST: internal-db.svc, DB_PORT: 5432 } } } }如果工具需要被多个 Agent 共享那就得用内网 HTTP 模式SSE 或 streamable HTTP这时候要注意两点一是内网防火墙要放行对应端口二是要自己做一层简单的鉴权别裸奔。内网虽然相对安全但内网无威胁是个危险的假设。注意MCP Server 里凡是涉及文件路径、命令执行的工具一定要做白名单校验。内网 Agent 一旦被诱导执行了危险操作影响范围是整片内网这个责任谁都担不起。4.3 Skills 在内网的写法Skills 的落地核心是把流程写清楚而不是把知识堆进去。我见过很多人写 Skill 就是复制一堆文档进去结果模型根本抓不住重点。好的 Skill 应该像一份给新人的 SOP什么情况下触发、第一步做什么、每步用什么工具、遇到异常怎么处理。一个内网场景的 Skill 示例比如生成内网数据报表# 内网数据报表生成 ## 触发条件 用户要求生成某业务线的数据报表时使用。 ## 执行步骤 1. 用 internal-db 工具查询目标业务线的原始数据时间范围默认最近 7 天。 2. 用 python-exec 工具对数据做聚合输出 CSV 到 /data/reports/ 目录。 3. 用 internal-doc 工具把 CSV 转成带图表的文档。 ## 注意事项 - 查询必须带时间范围禁止全表扫描。 - 报表文件命名格式{业务线}_{日期}.xlsx - 如果查询返回空直接告知用户该时间段无数据不要编造。这份 Skill 的关键在于每一步都指定了工具且给了明确的边界条件。内网模型能力有限你越具体它执行得越准。4.4 MCP 和 Skills 怎么配合实际工程里这两个东西是互补的。MCP 提供手能操作什么Skills 提供脑该怎么操作。一个成熟的内网 Agent通常是 Skills 里引用 MCP 工具模型读到 Skill 后按步骤调用对应的 MCP 工具。这种分层的好处是工具变了只改 MCP Server流程变了只改 Skill互不影响。我在一个项目里就是靠这个分层把工具从 5 个扩到 20 多个Agent 核心代码一行没动。5. 内网 Agent 的工程结构怎么组织才不乱5.1 分层结构设计内网 Agent 项目最容易变成一坨因为大家都在往里面塞工具和逻辑。我的经验是强制分四层每层职责单一接入层处理用户输入、会话管理、权限校验。编排层Agent 主循环负责调模型、解析工具调用、决定下一步。能力层MCP Server 和 Skills 的集合是 Agent 的手脚和手册。基础设施层模型客户端、日志、配置、队列。这个分层不是画着好看的它解决的是变更隔离问题。内网环境里模型服务地址会变、工具会增删、流程会调整如果全糊在一起改一处崩一片。5.2 配置外置是内网工程的命根子内网环境最忌讳硬编码。所有会变的东西——模型地址、数据库连接、工具开关、超时时间——全部走配置文件或环境变量。我习惯用一个config.yaml加环境变量覆盖的机制# config.yaml llm: base_url: http://internal-llm.svc/v1 model: qwen2.5-32b-instruct timeout: 120 agent: max_iterations: 10 max_context_tokens: 28000 tools: enabled: - internal-db - python-exec - internal-docmax_iterations这个参数特别重要。Agent 主循环如果没有迭代上限遇到模型钻牛角尖就会无限循环把内网模型资源耗光。我一般设 10 到 15超过就强制中断并返回当前结果。5.3 日志要能事后复盘内网环境排查问题比公网难得多因为你不能随时上网搜报错。所以日志必须记全每次模型调用的输入输出、每次工具调用的参数和结果、每次决策的分支。我甚至会把完整的对话轨迹落盘方便出问题时回放。import logging import json from datetime import datetime def log_trace(session_id: str, event: str, payload: dict): record { ts: datetime.now().isoformat(), session: session_id, event: event, payload: payload, } logging.getLogger(agent.trace).info(json.dumps(record, ensure_asciiFalse))日志里注意脱敏。内网数据往往涉及业务敏感信息工具返回的内容落盘前要过滤掉关键字段。这个不是可选项是合规底线。6. 并发与稳定性内网 Agent 怎么扛住真实流量6.1 内网并发的瓶颈到底在哪很多人以为 Agent 的并发瓶颈在 Agent 代码本身其实不是。内网 Agent 的瓶颈几乎永远在模型推理服务上。一台内网 GPU 机器并发请求数超过它的处理能力延迟就会指数级上升最后全部超时。所以内网 Agent 的并发设计核心不是让 Agent 处理更多请求而是**让请求有序地排队别把模型打垮**。这个思路和公网完全相反——公网你可以靠弹性扩容内网你只能靠限流和排队。6.2 用信号量做模型调用限流最直接的限流手段是信号量。给模型调用加一个全局并发上限超过就等待import asyncio class ModelClient: def __init__(self, max_concurrency: int 4): self._sem asyncio.Semaphore(max_concurrency) async def chat(self, messages, **kwargs): async with self._sem: return await self._do_request(messages, **kwargs)max_concurrency设多少取决于你的内网模型服务能扛多少并发。我的经验值是从 2 开始试逐步加到延迟开始明显上升为止。别一上来就设 16那只会让所有请求一起变慢。6.3 请求队列与超时控制信号量解决的是同时打模型的数量但请求本身还需要排队。用一个有界队列队列满了就直接拒绝比让请求堆积到超时更友好import asyncio class RequestQueue: def __init__(self, max_size: int 100): self._queue asyncio.Queue(maxsizemax_size) async def submit(self, task): try: self._queue.put_nowait(task) except asyncio.QueueFull: raise RuntimeError(系统繁忙请稍后重试)超时控制要分两层单次模型调用超时和整个 Agent 任务超时。前者防止单次调用卡死后者防止 Agent 陷入多轮循环出不来。两个都要设缺一不可。6.4 缓存能省掉大量重复调用内网 Agent 场景里很多请求是重复的——同样的查询、同样的报表、同样的问答。加一层结果缓存能显著降低模型压力。缓存 key 用用户意图 关键参数的哈希命中就直接返回。但缓存要小心时效性。数据类查询的缓存时间要短比如 5 分钟知识类问答可以长一些。缓存过期策略没做好用户会拿到过期数据这比慢一点更糟糕。7. 踩坑实录内网 Agent 工程里那些让人抓狂的问题7.1 编码问题中文乱码的连环坑内网环境里中文乱码是个高频问题根源往往在文件读写没指定编码。Python 在有些内网机器上默认编码不是 UTF-8导致读进来的中文全是乱码模型再一处理输出就彻底崩了。解决办法是所有文件操作强制指定encodingutf-8包括读配置、写日志、读写数据文件。这个习惯能帮你避开 90% 的乱码问题。with open(data.txt, r, encodingutf-8) as f: content f.read()7.2 时间同步分布式节点的隐形杀手内网多台机器如果时间不同步会导致日志时间错乱、缓存过期判断出错、任务调度混乱。我遇到过一次Agent 节点和模型节点时间差了 3 分钟导致缓存一直未过期用户拿到的全是旧数据。排查方法很简单在每台机器上跑date对比一下。如果差异超过几秒就要检查内网的 NTP 配置。这个问题不解决后面所有基于时间的逻辑都不可靠。7.3 工具调用的幻觉参数内网模型能力有限经常会给工具调用编造参数。比如你定义的工具只接受start_date和end_date模型却传了个date_range进来。如果不做参数校验工具直接报错Agent 就卡住了。我的做法是在 MCP Server 里严格校验参数遇到非法参数返回明确的错误信息让模型有机会自我纠正def validate_params(params: dict, required: list, allowed: list): missing [p for p in required if p not in params] if missing: return f缺少必需参数: {missing} extra [p for p in params if p not in allowed] if extra: return f包含未知参数: {extra}请只使用 {allowed} return None返回的错误信息要具体且可操作模型看到请只使用 [start_date, end_date]才知道怎么改。含糊的报错模型是修不好的。7.4 长任务的中断与恢复内网 Agent 处理长任务比如批量数据处理时如果中途服务重启任务就丢了。解决办法是把任务状态持久化重启后能从断点继续。我一般用一个简单的状态表记录每个任务的进度字段说明task_id任务唯一标识statuspending/running/done/failedprogress已完成步骤checkpoint中间结果存储位置updated_at最后更新时间重启后扫描statusrunning的任务从checkpoint恢复。这个机制在内网环境里特别值钱因为内网服务重启往往没有公网那么平滑。8. 一些让内网 Agent 更好用的实战心得8.1 给 Agent 加能力自述内网模型对自己有什么工具的认知经常模糊。我的做法是在系统提示里明确列出所有可用工具及其用途并且每次工具集变化时同步更新。这能显著减少模型调用不存在的工具的情况。更进一步可以让 Agent 在不确定时主动询问用户而不是硬猜。内网场景下多问一句比做错一件事代价小得多。8.2 降级策略要提前设计内网模型服务不是永远可用的。当模型不可用时Agent 应该能降级到规则模式——用预设的规则处理常见请求而不是直接报错。这个降级逻辑要提前写好别等出事了才临时加。8.3 定期做离线演练内网环境没法随时上网查资料所以团队要定期做离线演练模拟模型服务挂掉、模拟依赖缺失、模拟网络分区看 Agent 能不能优雅处理。这种演练能暴露很多平时发现不了的问题。8.4 文档要跟着代码走内网项目最怕只有一个人懂。所有工具的定义、Skill 的写法、配置的含义都要有文档而且要放在内网能访问的地方。我见过太多项目因为核心开发者离职整个 Agent 变成黑盒谁都不敢动。我个人在实际操作中的体会是内网 Agent 工程七分靠工程规范三分靠模型能力。把依赖管好、把配置外置、把日志记全、把并发控住剩下的模型能力问题反而好解决。反过来如果工程基础没打好再强的模型也救不了你。这个领域没有银弹但有大量可以复用的经验希望上面这些能帮你少走点弯路。