1. 为什么要在隔离内网里折腾 AI Agent
先把场景说清楚。所谓“隔离内网”,就是那种物理上跟公网断开、或者只允许极少数白名单出口的环境,常见于金融、制造、能源、医疗这类对数据外流极度敏感的行业。你在这种环境里想跑一个 AI Agent,第一反应通常是:模型怎么进来?依赖怎么装?工具怎么调?外部 API 一个都连不上,Agent 不就成了一个只会聊天的空壳?
我前后在三个不同规模的内网环境里落地过 Agent 工程,从最初级的“单机跑个本地模型加几个脚本”,到后来带 MCP 工具链、带 Skills 编排、带并发调度的完整中台,踩的坑基本能写一本小册子。这篇就把这套东西完整拆开讲,核心围绕四件事:模型与运行时怎么在内网落地、MCP 协议怎么在无外网条件下跑通、Skills 体系怎么设计和分发、以及并发和工程化怎么扛住真实业务量。
适合谁看?如果你是被派去内网做 AI 落地的工程师、架构师,或者你手上有一台只能内网访问的服务器,想把它变成一个能干活的 Agent 平台,这篇基本可以当施工图用。如果你只是好奇 AI Agent 是什么,也能看懂,因为我会尽量用生活化的类比把每个概念讲透。
先给一个整体判断:内网 Agent 工程的难点从来不是模型本身,而是“依赖闭环”和“工具编排”。公网环境下你pip install一下就完事的东西,在内网可能要手动搬十几个包、处理三层依赖冲突。而 MCP 和 Skills 这两个概念,恰恰是解决“工具怎么标准化接入”和“能力怎么模块化复用”的关键,所以它们在内网场景里的价值比公网还大。
2. 内网 Agent 的整体架构设计与选型思路
2.1 三层架构:模型层、编排层、工具层
我在内网里用的架构基本固定成三层,这个分层不是拍脑袋定的,是被现实逼出来的。
模型层负责推理,内网里通常有两种选择:一是本地部署开源模型,二是内网已有的推理服务(很多公司会有自己的模型网关)。我一般优先复用已有推理服务,因为显存和运维成本摆在那,重复部署没意义。如果确实要自己部署,量化后的 7B 到 32B 模型是主流选择,具体看任务复杂度。
编排层是 Agent 的大脑,负责意图理解、任务拆解、工具调用决策、多轮状态管理。这一层我用过 LangChain、LangGraph,也用过更轻量的自研状态机。内网环境下我越来越倾向于轻量自研 + 成熟框架混合,因为框架的很多能力依赖外部服务,在内网里反而是负担。
工具层就是 Agent 的手脚,MCP 协议主要作用在这一层。它把数据库查询、文件操作、内部 API 调用、代码执行等能力标准化成统一的工具接口,Agent 通过协议去调用,不用为每个工具写一套适配代码。
提示:三层之间一定要有清晰的边界。我见过太多项目把工具调用逻辑写进编排层,结果换一个工具就要改核心代码,维护成本爆炸。
2.2 为什么选 MCP 而不是自己写工具适配
MCP 全称 Model Context Protocol,你可以把它理解成“AI 和工具之间的 USB 接口标准”。在它出现之前,每个 Agent 框架调工具的方式都不一样,你为 LangChain 写的工具,换到另一个框架就得重写。MCP 把这个事情标准化了:工具方按协议暴露能力,Agent 方按协议调用,双方解耦。
在内网里这个价值被放大了。因为内网工具往往是一次性开发、长期使用,如果每换一个 Agent 框架就要重写一遍工具,人力根本扛不住。用 MCP 之后,工具服务独立部署,Agent 只认协议不认实现,框架升级、模型替换都不影响工具层。
有人会问 MCP 到底是软件协议还是硬件协议。明确说,MCP 是软件层的通信协议,通常基于 JSON-RPC 走 stdio 或 HTTP/SSE 传输,跟硬件没关系。你在内网里部署,只要保证 Agent 进程和 MCP Server 进程之间网络可达就行,stdio 模式甚至连网络都不需要,同机进程通信即可。
2.3 Skills 体系的定位:把“会做某件事”封装成可复用单元
Skills 这个词最近很热,但很多人没搞清它和 MCP 的区别。我的理解是:MCP 解决“能不能调用工具”,Skills 解决“会不会用工具完成一件事”。
举个例子,查数据库是一个 MCP 工具,但“根据用户问题生成 SQL、执行、格式化结果、异常重试”这一整套流程,就是一个 Skill。Skill 里可以编排多个 MCP 工具,也可以包含提示词模板、参数校验、后处理逻辑。
在内网里,Skills 的最大好处是能力沉淀和分发。一个团队把常用能力做成 Skill 库,新项目直接引用,不用从零开始。而且 Skill 通常是纯配置或轻代码,内网分发成本低,不像模型文件动辄几个 G。
3. 内网环境下的依赖闭环与模型落地实操
3.1 离线依赖包的搬运与安装
这是内网工程的第一道坎。公网机器上pip download把所有依赖下下来,打包拷进内网,然后pip install --no-index --find-links=./packages。听起来简单,实操全是坑。
第一个坑是平台差异。你在 Mac 上下载的包,拷到 Linux 服务器上装不了,因为 wheel 文件带平台标签。正确做法是在跟目标环境同架构同 Python 版本的机器上下载,或者用--platform参数指定。我一般直接在内网找一台能临时联网的机器(如果有的话)做中转,没有的话就在本地起一个和目标一致的 Docker 容器来下载。
第二个坑是依赖冲突。Agent 框架的依赖树非常深,LangChain 一个包能拖出几十个间接依赖,版本还互相打架。我的做法是先在一个干净虚拟环境里装好,用pip freeze导出精确版本,再按这个清单下载。千万别用pip download不带版本约束,下下来的东西装的时候能让你怀疑人生。
# 在联网机器上,用干净虚拟环境导出精确依赖 python -m venv clean_env source clean_env/bin/activate pip install -r requirements.txt pip freeze > locked_requirements.txt # 按锁定版本下载所有包 pip download -r locked_requirements.txt -d ./offline_packages \ --platform manylinux2014_x86_64 \ --python-version 310 \ --only-binary=:all:注意:
--only-binary=:all:能强制只下 wheel,避免下到源码包在内网编译时缺编译器。但有些包没有 wheel,这时候就得单独处理,提前在内网装好编译工具链。
3.2 本地模型的部署与量化选择
如果内网要自己部署模型,显存是硬约束。我整理了一个经验对照表,基于常见的开源模型:
| 模型规模 | 量化方式 | 显存占用 | 适用场景 |
|---|---|---|---|
| 7B | FP16 | 约 14GB | 简单问答、分类 |
| 7B | INT8 | 约 8GB | 通用对话 |
| 7B | INT4 | 约 5GB | 资源紧张场景 |
| 32B | INT4 | 约 20GB | 复杂推理、代码 |
| 70B | INT4 | 约 40GB | 高复杂度任务 |
选型逻辑很简单:先看任务复杂度,再看显存。如果 Agent 主要做工具调用和简单决策,7B INT4 完全够用,别浪费显存。如果要做复杂代码生成或多步推理,32B 起步。
部署工具我推荐用 vLLM 或 Ollama。vLLM 吞吐高、支持并发,适合做服务;Ollama 部署简单,适合快速验证。内网里 vLLM 的依赖比较重,装之前先把 CUDA 版本对齐,不然编译能卡你一整天。
3.3 模型服务的接口封装
内网模型部署好之后,一定要封一层统一的 OpenAI 兼容接口。为什么?因为 Agent 框架基本都支持 OpenAI 格式的 API,你封成这个格式,框架就能直接对接,不用改代码。vLLM 自带这个能力,Ollama 也有兼容层,自己部署的话用 FastAPI 写一个转发层也就几十行。
# 简单的模型服务封装示例 from fastapi import FastAPI from pydantic import BaseModel import httpx app = FastAPI() class ChatRequest(BaseModel): model: str messages: list temperature: float = 0.7 @app.post("/v1/chat/completions") async def chat(req: ChatRequest): async with httpx.AsyncClient() as client: resp = await client.post( "http://localhost:8000/generate", json={"prompt": req.messages[-1]["content"]}, timeout=60.0 ) return {"choices": [{"message": {"content": resp.json()["text"]}}]}这层封装还有个好处:统一做限流和日志。内网模型资源有限,不加限流很容易被并发打爆。
4. MCP 工具链在内网的部署与打通
4.1 MCP Server 的两种传输模式选择
MCP 支持 stdio 和 HTTP/SSE 两种传输。内网里怎么选,取决于你的部署形态。
stdio 模式适合 Agent 和工具在同一台机器上的场景。Agent 进程直接拉起 MCP Server 子进程,通过标准输入输出通信。优点是零网络配置、延迟极低、天然隔离;缺点是工具和 Agent 绑死,没法跨机复用。
HTTP/SSE 模式适合工具独立部署、多 Agent 共享的场景。MCP Server 起一个 HTTP 服务,Agent 通过网络调用。优点是解耦、可复用、好扩展;缺点是要处理网络和鉴权。
我的建议是:开发验证阶段用 stdio,生产环境用 HTTP。内网里 HTTP 模式还能配合内网的服务发现和负载均衡,工具服务挂了也不影响 Agent 主进程。
4.2 内网 MCP 工具服务的开发要点
写一个内网 MCP Server,核心是把内部能力暴露成标准工具。以数据库查询工具为例,关键点有三个:
第一,参数校验要严。内网工具往往直接操作生产数据,参数不校验就是灾难。SQL 注入、越权访问这些在 Agent 场景里更容易发生,因为调用方是模型,它可能生成任何东西。
第二,返回结果要裁剪。模型上下文有限,你返回一个几万行的查询结果,直接把上下文撑爆。我的做法是默认限制返回条数,超过就截断并提示,需要全量的话让 Agent 显式请求。
第三,错误信息要友好。模型看不懂堆栈,你要把异常转成自然语言描述,它才能决定下一步怎么做。
# MCP 工具定义示例(伪代码结构) @mcp_tool(name="query_database", description="执行只读SQL查询") def query_database(sql: str, limit: int = 100): # 1. 校验:只允许 SELECT if not sql.strip().upper().startswith("SELECT"): return {"error": "仅支持只读查询"} # 2. 校验:禁止危险关键字 forbidden = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"] if any(kw in sql.upper() for kw in forbidden): return {"error": "包含禁止操作"} # 3. 执行并限制返回 result = db.execute(sql, limit=limit) return {"rows": result, "truncated": len(result) >= limit}4.3 工具注册与发现机制
内网里工具多了之后,怎么让 Agent 知道有哪些工具可用?我一般维护一个工具注册表,MCP Server 启动时向注册中心上报自己的能力清单,Agent 启动时拉取清单并生成工具描述注入到提示词里。
这个注册表可以很简单,一个内网可访问的 JSON 文件或者一个轻量服务就行。关键是工具描述要写清楚,因为模型是根据描述来决定调不调、怎么调的。描述写得好,Agent 的工具调用准确率能提升一大截。
实操心得:工具描述里一定要包含“什么时候用”和“什么时候不用”。我见过太多工具因为描述模糊,模型在不该调的时候乱调,白白浪费 token 和时间。
5. Skills 体系的设计、编排与内网分发
5.1 Skill 的粒度设计:别太大也别太小
Skill 粒度是个经验活。太粗,一个 Skill 干十件事,复用性差;太细,一个 Skill 就调一个工具,那还不如直接用 MCP。
我的经验法则是:一个 Skill 对应一个完整的业务动作。比如“生成周报”是一个 Skill,它内部可能调用查数据库、查日志、调模型总结三个工具,但对使用者来说就是一个动作。再比如“代码审查”是一个 Skill,内部包含读文件、静态分析、模型评审、生成报告。
判断粒度是否合适,有个简单标准:如果这个 Skill 的描述能用一句话说清“它帮你完成什么”,粒度就对了。
5.2 Skill 的组成结构
一个完整的 Skill 我一般拆成四部分:
- 元信息:名称、描述、适用场景、输入输出定义
- 提示词模板:指导模型如何完成这个任务
- 工具编排:需要调用哪些 MCP 工具,调用顺序和条件
- 后处理逻辑:结果格式化、校验、异常处理
这四部分里,提示词模板是灵魂。同样的工具,提示词写得好坏,效果差好几倍。我写提示词的经验是:把模型当成一个聪明但没背景知识的新人,该交代的背景、该给的示例、该说的边界,一样都不能少。
5.3 内网 Skill 库的分发方案
内网分发 Skill 有个天然优势:Skill 基本都是文本,体积小,用 Git 内网仓库就能管。我的做法是建一个内网 Git 仓库专门放 Skill,每个 Skill 一个目录,包含配置文件、提示词、测试用例。
Agent 启动时从仓库拉取 Skill 清单,按需加载。更新 Skill 只需要 push 到仓库,Agent 下次启动就能拿到新版。如果要做热更新,可以加一个版本检查机制,定期拉取。
# Skill 配置示例 name: weekly_report description: 根据数据库和日志生成周报 version: 1.2.0 inputs: - name: week type: string description: 周次,如 2026-W20 tools: - query_database - query_logs - summarize_text prompt_template: | 你是一个周报生成助手。根据以下数据生成结构化周报: 数据库指标:{db_metrics} 日志摘要:{log_summary} 要求:分点陈述,突出异常和趋势。注意:Skill 版本管理很重要。生产环境一定要锁定版本,别让 Agent 自动拉最新,不然某天 Skill 一改,线上行为全变了,排查起来要命。
5.4 Skill 与 MCP 的协作关系
很多人把 Skill 和 MCP 混为一谈,其实它们是互补的。MCP 是“能力接口”,Skill 是“能力用法”。一个 MCP 工具可以被多个 Skill 复用,一个 Skill 也可以调用多个 MCP 工具。
在内网里,我通常让 MCP 层保持稳定,尽量少改;Skill 层保持灵活,快速迭代。这样底层工具不动,上层能力可以随业务快速调整,工程上最稳。
6. 并发调度与工程化落地
6.1 Agent 并发模型的选择
“AI Agent 怎么扛并发”是个高频问题。内网里并发压力通常来自两方面:一是多用户同时用,二是单个任务内部并行调多个工具。
对于多用户并发,核心是模型推理的并发能力。vLLM 这类框架支持连续批处理,能显著提升吞吐。如果用的是单实例 Ollama,并发能力很有限,得靠排队或者多实例。
对于任务内并行,用异步 IO 就能解决。Agent 调多个工具时,用asyncio.gather并发发起,等所有结果回来再汇总。这样比串行调用快好几倍。
import asyncio async def run_agent_task(task): # 并行调用多个工具 results = await asyncio.gather( call_tool("query_database", task.sql), call_tool("query_logs", task.time_range), call_tool("search_docs", task.keyword), return_exceptions=True ) # 处理结果,异常降级 valid = [r for r in results if not isinstance(r, Exception)] return await summarize(valid)6.2 限流、降级与超时控制
内网资源有限,不限流就是等着雪崩。我的做法是在模型服务和工具服务前面都加一层限流,用令牌桶或者信号量控制并发数。
降级策略也要提前设计。模型服务挂了怎么办?工具超时怎么办?我的经验是:核心路径必须有降级方案。比如模型不可用时,返回缓存结果或者提示用户稍后重试;工具超时时,返回部分结果并标注哪些没拿到。
超时控制尤其重要。Agent 调工具如果不设超时,一个慢查询能把整个任务卡死。我一般给每个工具设 10 到 30 秒超时,模型推理设 60 到 120 秒,整体任务设一个总超时。
6.3 日志、追踪与可观测性
内网 Agent 出问题最难排查,因为你看不到外部调用,全靠日志。我的做法是给每个任务生成一个 trace_id,从用户请求到模型调用到工具执行,全链路打日志,用 trace_id 串起来。
日志内容要包含:输入、输出、耗时、调用了哪些工具、每个工具的返回摘要、异常信息。这样出问题时,拿 trace_id 一搜,整个执行链路清清楚楚。
实操心得:日志里千万别打完整的大模型输入输出,体积太大。我一般只打摘要和哈希,需要详情时再按需开启 debug 模式。
6.4 内网 Agent 中台的演进路径
如果你要做的不只是一个 Agent,而是一个中台,那要考虑的更多。我的演进路径建议是:
第一阶段,单 Agent 跑通,验证核心链路。第二阶段,抽离工具层和 Skill 层,形成可复用资产。第三阶段,加多 Agent 协作和任务调度。第四阶段,做统一入口、权限、监控、计费。
每一步都别跳。我见过太多项目一上来就做中台,结果基础链路都没跑通,最后烂尾。先把一个场景做深做透,再谈平台化。
7. 常见问题与排查技巧实录
7.1 内网部署高频问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| 依赖装不上 | 平台不匹配/缺编译工具 | 检查 wheel 标签 | 同平台下载或装工具链 |
| 模型加载 OOM | 显存不足/量化不当 | 看显存占用 | 换更小量化或更小模型 |
| MCP 工具调不通 | 传输模式配置错 | 检查 stdio/HTTP 配置 | 对齐 Agent 和 Server 配置 |
| Agent 不调工具 | 工具描述不清 | 看提示词里的工具描述 | 补充使用场景说明 |
| 并发上不去 | 模型单实例瓶颈 | 压测模型服务 | 多实例或换 vLLM |
| 任务卡死 | 工具无超时 | 检查超时配置 | 给每个工具设超时 |
| 结果不稳定 | 提示词太随意 | 看 Skill 提示词 | 加示例和边界约束 |
7.2 几个我踩过的深坑
坑一:stdio 模式下 MCP Server 日志污染通信。stdio 模式靠标准输出传数据,如果你的 Server 往 stdout 打日志,协议直接乱掉。解决办法是日志全部走 stderr,或者写文件。
坑二:模型上下文被工具返回撑爆。一个查询返回几千行,模型直接懵了。解决办法是工具层强制限制返回大小,超出的部分做摘要或者分页。
坑三:Skill 版本漂移导致线上行为突变。某次更新了一个 Skill 的提示词,结果线上 Agent 行为全变了。解决办法是生产环境锁定 Skill 版本,更新走灰度。
坑四:并发下模型输出串台。多请求共用一个模型实例时,如果没做好请求隔离,输出可能串。解决办法是用支持并发的推理框架,别自己写简陋的并发封装。
7.3 性能调优的几个实用技巧
模型层面,开启连续批处理能显著提升吞吐,vLLM 默认就开。调整 max_tokens也能省不少时间,很多任务不需要生成那么长。
工具层面,缓存高频查询结果。内网数据变化通常不快,缓存几分钟能省大量重复查询。合并小请求,多个小工具调用能合并就合并,减少往返开销。
编排层面,能并行就并行。前面说的asyncio.gather是基本操作。提前终止也很重要,如果某个工具返回了决定性结果,后面的工具就不用调了。
8. 一些关于内网 Agent 的个人体会
做内网 Agent 工程这几年,我最大的体会是:别被新概念带着跑,先把基础链路跑通。MCP、Skills 这些概念很好,但它们是手段不是目的。目的是让 Agent 在内网里真正能干活。
另一个体会是工具描述和提示词的质量,决定了 Agent 的上限。模型能力再强,你工具描述写得含糊,它也调不对。我花在打磨提示词和工具描述上的时间,比写代码还多。
还有一点,内网环境反而逼着你把工程做扎实。公网环境下很多问题可以用现成服务绕过,内网里绕不过去,只能自己解决。这个过程虽然痛苦,但做出来的东西更可控、更稳定。
最后分享一个小技巧:内网 Agent 上线前,一定要做离线评测集。准备一批典型任务和预期结果,每次改动后跑一遍,看准确率和耗时变化。没有评测集,你根本不知道改动是变好还是变坏。这个习惯帮我避免了好几次线上事故。