
1. 为什么要在隔离内网里折腾 AI Agent先把场景说清楚。所谓“隔离内网”就是那种物理上跟公网断开、或者只允许单向数据摆渡的办公网、生产网、研发网。你在这种环境里想跑一个 AI Agent第一反应通常是模型怎么调依赖怎么装工具怎么接因为绝大多数现成的 Agent 框架、MCP 服务、Skills 市场默认都假设你能随时访问外部 API 和包仓库。我所在的团队去年接手了一个内网知识库问答 工单自动分派的项目整个环境没有外网出口只有一台跳板机能做文件摆渡。一开始我们想得很简单把开源模型拉进去、把 LangChain 装进去就完事了结果踩了整整两周的坑。后来才慢慢摸出一套“离线优先”的工程打法也就是这篇要聊的核心隔离内网下的 AI Agent 工程实战。这篇文章适合三类人看一是在内网环境做 AI 落地的工程师二是想搞清楚 Agent 工程化到底难在哪的开发者三是正在评估 MCP、Skills 这些新概念能不能搬进内网的技术负责人。我会把方案选型、依赖离线化、MCP 与 Skills 的内网适配、并发扛压、排查技巧全部拆开讲尽量给到能直接抄的步骤和参数。需要先明确一个前提内网 Agent 的核心矛盾不是“模型够不够聪明”而是工程链路能不能闭环。模型可以退而求其次用本地量化版但依赖、工具协议、状态管理、并发调度这些环节一旦断链整个 Agent 就是废的。所以下面的内容重心会放在工程而不是算法上。2. 整体架构设计与方案选型思路2.1 内网 Agent 的分层结构怎么切在公网环境里大家习惯把 Agent 拆成“模型层 编排层 工具层 记忆层”。到了内网我建议再补一层“离线资源层”专门管模型权重、Python wheel 包、npm 包、MCP 服务二进制、Skills 描述文件这些东西。这一层是内网工程的地基没有它上面四层全是空中楼阁。我们最终落地的分层是这样的离线资源层本地模型仓库GGUF / safetensors、私有 PyPI 镜像、私有 npm registry、MCP 服务离线包、Skills 清单。模型层本地推理服务用 vLLM 或 Ollama 起一个 OpenAI 兼容接口。编排层LangGraph 或自研状态机负责 Agent 的循环、工具调用、重试。工具层MCP Server 集群把内网数据库、工单系统、代码仓库封装成标准工具。记忆层本地向量库Milvus / Qdrant 关系库PostgreSQL存会话和审计。这么切的好处是每一层都能独立做离线化验证。比如模型层只要保证 OpenAI 兼容接口通编排层就不用关心底层是 vLLM 还是 Ollama工具层只要 MCP 协议通Agent 就不用关心背后是 MySQL 还是 REST。2.2 为什么选 MCP 而不是自己写工具协议热词里反复出现 MCP很多人问“MCP 是软件协议还是硬件协议那个概念”。这里直接给结论MCPModel Context Protocol是一套软件层的通信协议类比的话更像“AI 工具界的 USB-C 接口标准”不是硬件协议。它规定了 Agent 和工具之间怎么描述能力、怎么传参、怎么返回结果。在内网里自己写一套工具调用协议也能跑但我不推荐原因有三个复用成本MCP 生态里已经有大量现成的 Server 实现数据库、文件系统、浏览器等内网虽然不能直接拉但可以把源码摆渡进去自己编译省掉从零写协议的工作。协议稳定性自己写的协议往往在并发、超时、错误码上考虑不全MCP 至少有一套社区验证过的语义。可替换性哪天要换 Agent 框架MCP 这层不用动工具层是解耦的。当然MCP 在内网也有坑主要是它的默认传输方式stdio / SSE和依赖发现机制都假设有外网。这部分我在第 4 节会详细讲怎么改。2.3 Skills 在内网里到底扮演什么角色Skills 这个词最近很火前端开发 skills、superpower skills、codex skills 各种说法都有。我的理解是Skills 是给 Agent 的“操作手册 工具组合包”。它比单个工具高一层描述的是一类任务的完整做法比如“生成一份周报”“排查一个线上告警”。在内网场景里Skills 的价值反而更大因为内网用户往往不熟悉 Agent 的调用方式你给他一个裸的对话窗口他不知道怎么问。但如果你预置一批 Skills比如“查工单”“查知识库”“生成巡检报告”他点一下就能用。Skills 的落地形式我们用的是“YAML 描述 本地脚本 MCP 工具引用”的组合。YAML 里写清楚这个 Skill 需要哪些工具、参数怎么填、输出格式是什么Agent 编排层读这个 YAML 来决定调用链。这样即使不会写代码的同事也能通过改 YAML 来加新 Skill。2.4 模型选型的取舍不是越大越好内网环境里模型选型的第一约束是显存第二是推理延迟第三才是效果。我们试过 72B 的量化模型效果确实好但单次响应要 8 秒以上工单场景根本扛不住。最后选了 14B 级别的量化模型配合 RAG 补知识响应压到 2 秒内业务方反而更满意。这里有个经验内网 Agent 的效果70% 靠 RAG 和工具30% 才靠模型本身。与其纠结模型大小不如把知识库切分、检索召回、工具返回格式这些做扎实。3. 离线依赖与资源准备的核心细节3.1 依赖离线化的完整流程内网装不了包是所有内网工程的第一个拦路虎。我们的做法是“外网准备、摆渡导入、内网建源”三步走。外网准备阶段在一台能上网的机器上用pip download把所有依赖的 wheel 包拉下来pip download -r requirements.txt -d ./offline_packages \ --platform manylinux2014_x86_64 \ --python-version 311 \ --only-binary:all:注意--platform和--python-version必须和内网目标机一致否则拉下来的包在内网装不上。我们第一次就栽在这外网是 macOS内网是 CentOS包全废了。摆渡导入阶段把offline_packages目录打包通过跳板机或摆渡U盘传进内网。这一步要注意文件完整性校验我们用的是sha256sum生成清单内网侧逐个核对避免传输损坏。内网建源阶段用pip install --no-index --find-links./offline_packages安装或者干脆用devpi在内网起一个私有 PyPI 源后续增量更新方便很多。npm 包同理用npm pack或者verdaccio私有源。MCP Server 如果是 Node 写的这一步绕不开。3.2 模型权重的摆渡与加载模型权重动辄几个 G 到几十个 G摆渡是个体力活。我们的经验是优先选 GGUF 格式单文件、好校验、Ollama 直接吃。分片传输用split -b 2G切成小块内网侧cat合并避免单文件过大传输中断。校验必须做模型文件损坏的表现是加载时报奇怪的 shape 错误很难排查。加载的时候如果用 vLLM注意--tensor-parallel-size要和 GPU 数量匹配如果用 OllamaModelfile里的num_gpu参数要调对否则会退化成 CPU 推理慢到怀疑人生。3.3 MCP Server 的内网编译与部署MCP Server 大多有现成的 npm 包或 Python 包但内网不能直接npx。我们的做法是外网把 MCP Server 源码 clone 下来npm install或pip install完依赖。把node_modules或site-packages一起打包摆渡。内网直接跑node index.js或python server.py不走包管理器。这里有个坑有些 MCP Server 启动时会去外网拉配置或做版本检查内网会卡住。解决办法是在启动脚本里加环境变量禁用这些行为或者直接改源码把相关代码注释掉。我们改过至少三个 Server 的启动逻辑都是这个原因。3.4 Skills 清单的本地化组织Skills 我们放在一个独立的 Git 仓库里内网自建 GitLab目录结构是这样的skills/ weekly-report/ skill.yaml prompt.md scripts/ fetch_data.py ticket-query/ skill.yaml prompt.mdskill.yaml里定义工具依赖和参数 schemaprompt.md是给模型的指令模板scripts是辅助脚本。Agent 编排层启动时扫描这个目录把所有 Skill 注册进去。这样加新 Skill 只需要往目录里丢文件不用改编排层代码。4. 实操过程与核心环节实现4.1 本地推理服务的搭建我们用的是 vLLM 起 OpenAI 兼容服务命令大概是这样python -m vllm.entrypoints.openai.api_server \ --model /models/qwen2-14b-awq \ --served-model-name local-model \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 2 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9参数说明--tensor-parallel-size 2是因为我们有两张卡--max-model-len 8192是权衡显存和上下文长度后的选择再大显存不够--gpu-memory-utilization 0.9留 10% 给系统避免 OOM。起好之后用curl测一下curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {model:local-model,messages:[{role:user,content:你好}]}能返回就说明模型层通了。这一步是整个链路的基础务必先单独验证。4.2 Agent 编排层的实现要点编排层我们用 LangGraph核心是一个带工具调用的状态图。关键代码结构from langgraph.graph import StateGraph, END from langchain_openai import ChatOpenAI llm ChatOpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY, modellocal-model ) def agent_node(state): response llm.invoke(state[messages]) return {messages: state[messages] [response]} def should_continue(state): last state[messages][-1] if last.tool_calls: return tools return END graph StateGraph(AgentState) graph.add_node(agent, agent_node) graph.add_node(tools, tool_node) graph.add_conditional_edges(agent, should_continue) graph.add_edge(tools, agent) app graph.compile()这里的关键是base_url指向本地推理服务api_key随便填vLLM 不校验。工具节点负责把 MCP 调用包装成 LangChain Tool。4.3 MCP 工具接入的完整链路MCP 接入分两步起 Server接 Client。起 Server 以文件系统 MCP 为例node /opt/mcp-servers/filesystem/index.js /data/shared接 Client 在编排层里from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client params StdioServerParameters( commandnode, args[/opt/mcp-servers/filesystem/index.js, /data/shared] ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools()拿到tools列表后动态注册成 LangChain Tool。这样 Agent 就能调用内网文件系统了。4.4 并发扛压的实测与调优热词里有人问“ai agent 怎么扛并发”这是内网 Agent 最容易被忽视的问题。我们实测下来瓶颈通常不在模型而在工具调用的串行等待。一开始我们的 Agent 是串行调工具的一个请求要等 5 个工具依次返回QPS 上不去。后来改成并行调用import asyncio async def parallel_tools(tool_calls): tasks [call_tool(tc) for tc in tool_calls] return await asyncio.gather(*tasks)配合 vLLM 的--max-num-seqs参数调大我们设的 64QPS 从 3 提到了 15 左右。另一个瓶颈是数据库连接池。MCP Server 如果每次调用都新建连接并发一高就炸。解决办法是在 Server 里用连接池比如 Python 的asyncpgpool初始化时建好复用。实测数据单卡 14B 模型并发 20 时 P99 延迟 3.2 秒并发 50 时 P99 涨到 8 秒基本到极限了。要再往上只能加卡或者换更小的模型。5. 常见问题与排查技巧实录5.1 内网 Agent 典型故障速查表现象可能原因排查方向解决办法模型加载报 shape 错误权重文件损坏校验 sha256重新摆渡pip 安装报找不到包平台/版本不匹配检查 wheel 文件名重新 downloadMCP Server 启动卡住启动时访问外网看启动日志禁用外网检查Agent 响应超时工具串行等待看工具调用日志改并行调用并发高时连接报错连接池不足看 DB 连接数加连接池检索结果不相关切分粒度问题看召回内容调 chunk size5.2 几个踩过的坑和独家技巧坑一摆渡文件权限丢失。U盘摆渡经常把可执行权限弄丢MCP Server 启动报 permission denied。技巧是摆渡前tar打包内网tar -xzf解压权限能保留。坑二时区问题。内网机器时区经常是 UTCAgent 生成的报告时间对不上。统一在容器里设TZAsia/Shanghai。坑三模型幻觉调用不存在的工具。内网工具少模型容易编。解决办法是在 system prompt 里明确列出可用工具并且编排层做一层校验工具名不在注册表里直接拒绝。技巧一给 MCP Server 加健康检查。编排层启动时 ping 一遍所有 Server不通的直接标记不可用避免运行时才发现。技巧二Skills 的 prompt 模板要写“失败兜底”。比如“如果查不到数据返回‘暂无数据’而不是编造”。内网场景下编造数据的代价比公网大得多。技巧三日志一定要落盘。内网排查问题全靠日志我们用的是结构化日志JSON 格式每条记录带 trace_id方便串联一次完整调用。5.3 内网穿透相关概念的澄清热词里出现不少“内网穿透”相关的词这里必须澄清一下内网穿透是让外网访问内网服务的反向操作和本文讲的“隔离内网里跑 Agent”是两回事。隔离内网的核心诉求是数据不出网任何形式的穿透都是违背这个原则的。所以本文所有方案都不涉及穿透全部在内网闭环完成。如果你的场景确实需要内外互通那是另一个话题且必须走合规的审批流程。6. 内网 Agent 的扩展方向与个人体会6.1 从单 Agent 到多 Agent 协作单 Agent 跑通之后下一步自然是多 Agent。内网场景下多 Agent 的典型用法是“一个调度 Agent 多个专业 Agent”。比如调度 Agent 负责理解用户意图分派给“工单 Agent”“知识库 Agent”“报表 Agent”。实现上每个专业 Agent 就是一个独立的 LangGraph 子图调度 Agent 通过工具调用触发。MCP 在这里依然是解耦的关键每个专业 Agent 暴露成 MCP Server调度 Agent 通过 MCP 调用。要注意的是多 Agent 会放大延迟内网环境本来资源就紧张建议先从两个 Agent 试起别一上来就搞五六个。6.2 Skills 生态的内网演进Skills 目前还是各家自己定义没有统一标准。内网里我们做了两件事让它更好用一是版本化每个 Skill 带版本号Agent 调用时记录版本方便回溯二是灰度发布新 Skill 先给内部测试组用稳定了再全量。未来如果 MCP 生态成熟Skills 有可能标准化成一种描述文件那时候内网迁移成本会更低。但现在还是得自己维护一套。6.3 我个人的几点体会做内网 Agent 这一年多最大的体会是别把公网那套“先跑起来再优化”的思路带进来。内网环境调试成本极高一次摆渡可能就要半天所以每一步都要在外网验证充分再往里搬。第二个体会是文档比代码重要。内网团队往往人少一个人走了他搭的那套东西没人能接。我们后来强制要求每个 MCP Server、每个 Skill 都写 README写清楚依赖、启动方式、参数含义。这个习惯救过我们好几次。第三个体会是别追求技术新潮。热词里那些新概念MCP、Skills、各种 Agent 框架内网落地时都要打折扣。选成熟稳定的方案比选最新的方案重要得多。我们到现在还在用 LangGraph 的早期版本就是因为升级一次要重新摆渡一堆依赖不划算。最后分享一个小技巧内网 Agent 的测试用例一定要覆盖“工具失败”场景。公网环境工具挂了可以重试内网工具挂了往往就是挂了Agent 必须有优雅降级的能力。我们专门写了一批“模拟工具超时”的测试用例每次发版都跑一遍能挡掉不少线上问题。