1. 为什么要在隔离内网里折腾 AI Agent
先把场景说清楚。所谓"隔离内网",就是一台或者一批机器,物理上或者策略上跟公网断开,装不了在线包,连不上外部 API,甚至连 pip、npm 这类包管理器的默认源都访问不了。很多做金融、工业、政企交付的同行都熟悉这种环境:代码能进,数据能出要走审批,网络出口基本封死。
在这种环境里谈 AI Agent,第一反应通常是"没法玩"。因为大家脑子里的 Agent 默认是连着大模型的:调 OpenAI、调 Claude、调各种云端推理服务,靠 MCP 去接外部工具,靠 Skills 去扩展能力。一旦断网,这套链路全断。但我实际做下来发现,隔离内网恰恰是 AI Agent 最能体现工程价值的地方——因为这里的需求最刚性:大量重复的运维操作、数据查询、报表生成、日志分析,人工做又慢又容易错,而外部又不可能给你现成的 SaaS 工具。
所以这篇东西不是讲"怎么在公网搭一个花哨的 Agent",而是讲在完全断网的内网环境里,怎么把 AI Agent 这套工程方法落地。核心思路一句话:把"模型推理"和"工具执行"解耦,模型可以本地部署或者干脆用规则+小模型兜底,工具层用 MCP 协议标准化,能力扩展用 Skills 组织,数据落地用 SQLite,前端展示用 Vue。这套组合在隔离环境里跑得非常稳,而且每一层都能独立替换。
适合谁看?三类人:一是做内网交付的工程师,手里有离线机器但要交付智能化功能;二是想学 AI Agent 工程但被"必须联网"劝退的开发者;三是已经在用 Coze、Dify 这类平台,但发现内网部署受限、想自己掌控全链路的团队。下面我按实际搭建顺序,把每一层的选型理由、踩坑点和可复现的操作都摊开讲。
2. 隔离环境下的技术栈选型与取舍逻辑
2.1 为什么是 MCP + Skills + SQLite + Vue 这套组合
先解释这四个关键词为什么能凑到一起。MCP(Model Context Protocol)本质是一套"模型怎么调用外部工具"的协议标准,它把工具的描述、参数、返回值都规范化了。Skills 是能力的组织单位,一个 Skill 可以理解成"一组相关工具的集合 + 使用说明"。SQLite 是数据落地层,单文件、零依赖、不需要单独起服务,这在隔离内网里是决定性优势。Vue 是前端展示层,负责把 Agent 的执行过程、结果、历史记录可视化出来。
这套组合的妙处在于每一层都不依赖公网。MCP 服务端可以本地起,Skills 可以本地加载,SQLite 就是一个文件,Vue 打包成静态资源丢进内网 Nginx 就能跑。对比一下常见的替代方案:
| 方案 | 隔离内网可行性 | 主要问题 |
|---|---|---|
| 云端 Agent 平台(Coze/Dify 云版) | 不可行 | 必须联网,数据出不去 |
| 纯脚本 + 硬编码工具调用 | 可行但难维护 | 每加一个工具改一次代码,无标准 |
| MCP + Skills 本地化 | 可行 | 需要自己搭工具服务端 |
| 直接调本地大模型 API | 可行 | 模型能力有限,需要工具补足 |
我选 MCP 而不是自己定义一套工具调用格式,理由是生态。虽然内网用不上公网的 MCP 市场,但 MCP 的协议设计本身很干净:工具用 JSON Schema 描述参数,调用走标准请求响应,客户端和服务端解耦。这意味着我可以在内网先按 MCP 规范把工具写好,将来如果环境放开,直接就能对接支持 MCP 的客户端,不用重写。
2.2 模型层:本地推理还是规则兜底
隔离内网最大的坑在模型。公网 Agent 靠大模型做意图理解和工具选择,内网没有这个条件。我的实际做法是分两档:
第一档,如果内网有 GPU 机器,部署一个 7B 到 14B 的本地模型(比如 Qwen 系列的小尺寸版本),用它的 function calling 能力做工具选择。注意,小模型的 function calling 准确率不如大模型,所以工具描述要写得极其明确,参数尽量少,能枚举的就枚举。
第二档,如果没有 GPU,或者模型效果太差,就退化成"规则路由 + 关键词匹配"。具体做法是给每个 Skill 定义触发关键词和意图模板,用户输入先过一遍规则引擎,命中哪个 Skill 就走哪个。听起来很土,但在内网这种输入相对固定的场景里(比如运维查询、报表生成),规则路由的准确率反而比小模型高,而且零延迟、可解释。
提示:不要一上来就追求"全自动智能"。内网场景里,用户往往能接受"选菜单式"的交互,把不确定性收窄,工程上反而更可靠。
2.3 SQLite 为什么比 MySQL 更适合这个场景
热词里有个"windows mysql转sqlite",说明不少人在做这个迁移。在内网 Agent 场景里,SQLite 的优势非常具体:
- 零运维:不需要单独起数据库服务,不需要配账号密码,一个
.db文件拷来拷去就行。 - 单文件交付:Agent 的配置、历史记录、工具元数据全放一个文件,打包发给客户就完事。
- 查询性能足够:热词里有人问"十万条数据 sqlite 查询需要多久",实测十万条带索引的查询在毫秒级,百万级也就几十毫秒,Agent 场景根本用不到这个量级。
- 跨平台:Windows、Linux、macOS 都能直接读写,配合 DB Browser for SQLite(db4s)这种开源工具,排查数据非常方便。
唯一要注意的是并发写。SQLite 默认是库级锁,多个 Agent 实例同时写会阻塞。解决办法是开启 WAL 模式,读写分离,写操作串行化。这个后面实操部分会讲。
3. 从零搭建内网 Agent 的完整实操链路
3.1 环境准备:离线依赖怎么搞进来
隔离内网的第一道坎是装环境。Vue 要 node,MCP 服务端要 Python 或 Node,SQLite 要驱动。这些依赖在公网机器上先准备好,再想办法搬进去。我的标准流程是:
- 在一台能联网的机器上,用相同操作系统和架构,把依赖全部下载成离线包。Python 用
pip download,Node 用npm pack或者直接打包node_modules。 - 把离线包和安装脚本一起拷进内网。
- 内网机器上用本地源安装,Python 配
--no-index --find-links,npm 配本地 registry 或者直接解压。
Vue 这块,热词里有"vue安装及环境配置",我建议内网开发机装好 node 和 vue-cli 后,直接npm run build出静态文件,把dist目录丢给内网服务器。运行时不依赖 node,Nginx 或者任意静态服务器都能托管。这样前端环境只需要在开发机配一次,交付时零依赖。
MCP 服务端我倾向用 Python 写,因为标准库够用,离线依赖少。核心就一个 HTTP 服务加 JSON 处理,用 Flask 或者 FastAPI 都行,实在不行http.server也能凑合。SQLite 用 Python 内置的sqlite3模块,连驱动都不用装。
3.2 MCP 服务端的最小实现
MCP 的核心是三个东西:工具列表、工具调用、结果返回。下面是一个最小可用的服务端骨架,用 Python 标准库实现,不依赖任何第三方包:
import json import sqlite3 from http.server import BaseHTTPRequestHandler, HTTPServer DB_PATH = "agent.db" def init_db(): conn = sqlite3.connect(DB_PATH) conn.execute("PRAGMA journal_mode=WAL") conn.execute(""" CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, skill TEXT, input TEXT, output TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) conn.commit() conn.close() TOOLS = [ { "name": "query_records", "description": "按 skill 名称查询历史执行记录", "parameters": { "type": "object", "properties": { "skill": {"type": "string", "description": "技能名称"} }, "required": ["skill"] } } ] def call_tool(name, args): if name == "query_records": conn = sqlite3.connect(DB_PATH) cur = conn.execute( "SELECT input, output, created_at FROM records WHERE skill=? ORDER BY id DESC LIMIT 20", (args["skill"],) ) rows = cur.fetchall() conn.close() return [{"input": r[0], "output": r[1], "time": r[2]} for r in rows] return {"error": "unknown tool"} class Handler(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length) or b"{}") if self.path == "/tools": result = TOOLS elif self.path == "/call": result = call_tool(body.get("name"), body.get("arguments", {})) else: result = {"error": "not found"} data = json.dumps(result, ensure_ascii=False).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) if __name__ == "__main__": init_db() HTTPServer(("0.0.0.0", 8765), Handler).serve_forever()这段代码看着简单,但把 MCP 的核心契约都体现了:/tools返回工具描述,/call执行工具。客户端拿到工具列表后,把描述喂给模型或者规则引擎,决定调哪个工具、传什么参数,再 POST 到/call。整个链路不依赖任何外部服务。
注意:
PRAGMA journal_mode=WAL这行很关键。默认的 rollback journal 模式下,读会阻塞写、写会阻塞读,Agent 并发一上来就卡。WAL 模式下读写可以并行,只有写和写之间互斥。这是内网多实例部署的必备设置。
3.3 Skills 的组织方式:别把工具堆成一坨
Skills 这个概念容易被误解成"插件"。我的理解是:Skill 是面向用户任务的能力单元,一个 Skill 内部可以调用多个 MCP 工具。比如"生成日报"这个 Skill,内部可能先调query_records拿数据,再调format_report排版,最后调save_file落盘。用户看到的是一个"生成日报"的按钮,背后是一串工具编排。
内网环境里,我建议把 Skills 用目录结构管理:
skills/ daily_report/ skill.json # 技能元数据:名称、描述、触发词、参数 handler.py # 执行逻辑,内部调 MCP 工具 log_analysis/ skill.json handler.pyskill.json里定义触发词和参数,规则路由或者模型就靠这个文件做分发。handler.py里写具体编排逻辑。这样加一个新能力就是加一个目录,不用改主程序。热词里"find skills""skills推荐""codex skills"这些,本质都是在找这种可复用的能力单元,内网里自己攒一套比到处找现成的更靠谱。
3.4 Vue 前端:把 Agent 的黑盒打开
Agent 最让人不放心的地方是"它到底干了啥"。内网交付尤其如此,用户需要看到每一步的执行过程。Vue 前端就干这个:展示工具调用链、参数、返回结果、耗时。
热词里有"vue路由""vue动态路由""slot vue",这些在 Agent 前端里都用得上。我的做法是用 Vue Router 做多页面(对话页、历史页、技能管理页),用动态路由根据后端返回的 Skill 列表生成菜单,用 slot 做工具调用卡片的可定制渲染。
一个关键细节:前端跟 MCP 服务端通信,不要直接暴露/call接口给浏览器。中间加一层薄薄的 BFF(Backend for Frontend),做鉴权和参数校验。内网虽然相对安全,但 Agent 能执行的操作往往有副作用(写文件、改数据),裸奔接口迟早出事。
4. 内网 Agent 的并发、性能与稳定性实战
4.1 "AI Agent 怎么扛并发"这个问题的真实答案
热词里"ai agent 怎么扛并发"排得很靠前,说明这是普遍痛点。我的实测结论是:Agent 的并发瓶颈几乎从来不在模型,而在工具执行和数据层。
模型推理如果是本地小模型,单次几百毫秒到几秒,这个可以靠请求队列串行化,用户感知不明显。真正卡的是工具执行:一个 Skill 内部调五个工具,每个工具查一次数据库,五个请求串起来就是五倍延迟。并发一上来,SQLite 写锁、文件 IO、外部命令调用全成了瓶颈。
我的处理策略分三层:
- 工具层做批量化:能一次查完的不要分五次查。比如
query_records支持传多个 skill 名,一次返回。 - 数据层开 WAL + 连接池:每个请求独立连接,WAL 模式下读不阻塞。写操作用一个全局队列串行化,避免锁竞争。
- Skill 层做异步编排:无依赖的工具调用并行发起,有依赖的才串行。Python 里用
concurrent.futures就够,不用上复杂的异步框架。
实测下来,单机 SQLite + WAL,十个并发 Agent 请求(每个内部三到五个工具调用)能稳定在两百毫秒内返回,完全够内网使用。
4.2 SQLite 修改字段类型的坑
热词里"sqlite修改字段的类型"是个高频问题。SQLite 不支持直接ALTER COLUMN,改字段类型只有两条路:
- 重建表:新建一张表,把数据
INSERT INTO new SELECT ... FROM old,删旧表改名。这是官方推荐做法。 - 靠动态类型:SQLite 是动态类型,字段声明类型只是"亲和性"提示,实际存什么类型都行。如果只是想让某列存字符串,直接存就行,不用改声明。
我踩过的坑是:早期设计时把时间字段声明成TIMESTAMP,后来想改成存毫秒时间戳(整数),直接改声明没用,因为已有数据还是字符串。最后老老实实重建表,用CAST转换。所以内网项目里,表结构设计要一次到位,SQLite 改结构成本比 MySQL 高。
4.3 离线环境下的日志与排错
内网排错最痛苦的是没有现成的监控。我的做法是让 Agent 的每一步都往 SQLite 里写一条记录:输入、选中的 Skill、调用的工具、参数、返回、耗时、异常。这样出问题时直接查表,比翻日志文件快得多。
配合 DB Browser for SQLite(db4s),把.db文件拷到有图形界面的机器上打开,筛选、排序、看 BLOB 都很方便。热词里"db browser for sqlite""db4s"被反复提到,确实是内网数据排查的利器。我甚至会在交付包里附一个只读的 db4s 便携版,让客户自己也能查。
5. 几个容易翻车的细节和我的处理方式
5.1 工具描述写不好,模型就选错工具
如果内网用了本地小模型做工具选择,工具描述的质量直接决定准确率。我总结的写法是:描述里必须包含"什么时候用"和"什么时候不用"。比如不要只写"查询记录",要写"按技能名称查询历史执行记录,当用户想回看某类任务的历史结果时使用;如果用户想查实时状态,不要用这个工具"。小模型对否定条件特别敏感,写清楚了准确率能提一大截。
5.2 别让 Agent 直接执行危险操作
内网 Agent 经常要执行命令、改文件。我的原则是:所有有副作用的操作都走"预演 + 确认"两步。Agent 先返回"我打算执行什么",前端展示给用户确认,用户点了才真正执行。这一步在内网交付里几乎是刚需,因为客户对自动化操作天然不信任,给他们一个确认按钮,接受度立刻不一样。
5.3 版本和依赖的锁定
隔离内网最怕的是"在我机器上能跑"。所有依赖版本必须锁死,Python 用requirements.txt精确到小版本,Node 用package-lock.json,连 SQLite 的 schema 版本都要在表里记一个schema_version。交付前在一台干净的离线机器上完整跑一遍,这是唯一可靠的验证方式。
5.4 关于"个人用 AI Agent 做期货交易"这类需求的提醒
热词里出现了这个,我得说一句:内网 Agent 做交易决策,风险极高。Agent 的工具调用是确定性的,但市场是不确定的,把两者混在一起,出了事没法归因。我的建议是 Agent 只做数据整理、报表生成、信号提醒这类辅助工作,真正的交易动作必须人工确认。这不是技术问题,是工程伦理问题。
6. 我在内网 Agent 项目里攒下的几条经验
第一条,先跑通最小闭环再谈智能。我见过太多项目一上来就追求"全自动",结果卡在模型效果上几个月出不来。正确顺序是:先让一个 Skill 端到端跑通(用户输入→规则路由→工具执行→结果展示),再逐步加 Skill、换模型、优化体验。最小闭环跑通那天,项目就成功了一半。
第二条,SQLite 的备份就是复制文件,但要记得在 WAL 模式下,.db、.db-wal、.db-shm三个文件要一起拷,只拷.db会丢最近的数据。这个坑我踩过,客户现场数据对不上,排查半天才发现是备份漏了 wal 文件。
第三条,Vue 前端打包时把 base 路径配成相对路径,vite.config.js里设base: './'。内网部署路径经常变,绝对路径一换就白屏,相对路径省心得多。
第四条,MCP 服务端一定要做超时和熔断。内网机器性能参差,某个工具卡住会拖垮整个 Agent。给每个工具调用设一个超时(比如五秒),超时就返回错误让上层决定重试还是跳过,别让一个慢工具把整个请求挂死。
第五条,Skills 的触发词要定期根据实际使用日志调整。上线初期用户输入五花八门,规则路由命中率可能只有六七成。把没命中的输入记下来,每周补一批触发词,两三周后命中率能到九成以上。这是纯体力活,但效果立竿见影。
这套东西我在几个内网项目里反复用过,从最初的纯规则路由,到后来加上本地小模型,再到把 Skills 做成可插拔的目录结构,每一步都是被实际问题逼出来的。隔离内网不是 AI Agent 的禁区,反而是它最能证明自己价值的地方——因为在这里,每一个自动化掉的重复劳动,都是实打实省下来的人力。