
1. 隔离内网下的 AI Agent 工程为什么值得认真做第一次被问到“能不能在完全断网的内网里跑一套 AI Agent 工程”时我的直觉是这不是自找麻烦吗大模型调用、依赖下载、包管理、模型权重拉取哪一样离得开公网但真正在几个涉密研发、工业控制、金融后台的场景里落地之后我的看法彻底变了。隔离内网下的 AI Agent 工程不是把公网方案硬塞进内网而是一套完全不同的设计哲学所有外部依赖必须提前“搬”进来所有运行时行为必须可预测、可审计、可离线复现。这套东西解决的核心问题很具体内网机器不能访问外网但团队又希望用 AI Agent 来辅助代码理解、数据库查询、文档检索、前端页面生成等日常研发工作。适合谁来参考三类人最需要一是被派去内网环境做交付的工程师二是负责企业内部 AI 平台建设的技术负责人三是想搞清楚 Agent 工程底层原理、不想只会调 API 的开发者。哪怕你暂时用不到内网这套“离线优先”的思路也能帮你在公网环境里把工程做得更稳。我下面要聊的是一套我实际搭过、跑过、踩过坑的完整方案。核心组件包括AI Agent 运行时、MCP 协议层、Skills 技能包、SQLite 本地存储、Vue 前端界面。这几个词看着散其实是一条完整的链路Agent 负责决策MCP 负责连接工具Skills 负责封装能力SQLite 负责记忆和状态Vue 负责给人看。下面逐层拆。2. 整体架构设计与选型思路拆解2.1 为什么是“Agent MCP Skills”这套组合先说清楚这三个东西各自是什么不然后面全是空中楼阁。AI Agent你可以理解成一个会自己决定“下一步干什么”的程序它接收目标拆解任务调用工具观察结果再决定下一步。MCPModel Context Protocol是一套标准化的协议规定了 Agent 怎么和外部工具、数据源对话——就像 USB 接口不管你是键盘还是鼠标插上就能用。Skills则是把某一类具体能力比如查 SQLite、生成 Vue 组件、解析日志打包成一个可复用的技能单元。为什么选这套组合而不是自己写一堆 if-else因为内网环境下可维护性比性能更重要。你不可能每次加个新工具就改 Agent 核心代码。MCP 把“工具接入”标准化了Skills 把“能力封装”模块化了Agent 只负责编排。这样带来的直接好处是新增一个数据库查询能力只需要写一个 MCP Server 加一个 Skill 描述文件Agent 主逻辑一行不用动。我试过纯手写工具调用的方案一开始很快两周后代码就烂成一团——每个工具的入参格式、错误处理、超时逻辑都不一样。换成 MCP Skills 之后虽然前期多花了两天搭框架但后面每加一个能力平均只要半天。这个投入产出比在内网项目里非常划算因为内网调试成本极高你不可能频繁重启服务试错。2.2 隔离内网带来的三个硬约束内网不是“网速慢一点”那么简单它带来的是三个根本性约束直接决定架构长什么样。第一个约束是依赖必须全量预置。公网环境里pip install一行搞定的事内网里你得提前把所有 wheel 包、npm 包、模型文件、甚至字体和图标库都下载好打成离线包搬进去。这意味着你的技术选型要尽量“依赖少、体积小、纯本地”。这也是我最终选 SQLite 而不是 PostgreSQL 的原因之一——SQLite 就是一个文件零服务、零配置、零网络。第二个约束是运行时不能有任何外部回调。很多 Agent 框架默认会做遥测、版本检查、云端同步这些在内网里全是定时炸弹轻则卡住重则直接崩溃。所以选型时必须确认这个库能不能完全离线运行有没有隐藏的网络请求我的做法是在测试环境用抓包工具跑一遍全流程确认没有任何出站连接才敢上内网。第三个约束是可审计、可复现。内网项目往往有合规要求你得能说清楚 Agent 每一步为什么这么决策、调用了什么、返回了什么。这就要求所有交互都落盘到本地存储SQLite 在这里又赢了一局——单文件、可备份、可用 DB Browser for SQLite 直接打开查看审计的时候把 .db 文件一交清清楚楚。2.3 前端为什么用 Vue 而不是别的Agent 跑起来之后总得有个界面给人用。内网环境里前端选型的核心考量是构建产物要能塞进一个静态目录最好还能直接嵌进后端服务里。Vue 在这点上非常合适npm run build出来的 dist 目录就是一堆静态文件扔进任何 HTTP 服务都能跑甚至可以打包进 Spring Boot 的static目录里一起发布。我对比过几个方案React 生态更重构建配置更复杂纯 HTML 原生 JS 在功能一多之后就难以维护Vue 的模板语法对后端工程师友好上手快而且 Vue Router、Pinia 这些配套在内网离线包里都能一次性备齐。热词里出现的“vue打包放进springboot中”“vue安装及环境配置”“vue路由”“vue插槽”这些其实都是内网前端落地时的真实痛点——因为不能随时查文档、不能随时装依赖每一步都得提前想清楚。3. 核心组件细节解析与实操要点3.1 AI Agent 运行时选型与离线化改造Agent 运行时是整个系统的大脑。公网上流行的框架不少但内网选型要看三个指标是否支持本地模型、是否支持 MCP、是否零网络依赖。我最终用的方案是“本地推理 自研轻量编排层”因为现成框架要么太重要么偷偷联网。本地模型这块内网里通常用 7B 到 14B 量级的量化模型跑在带 GPU 的机器上。模型文件提前下载好放在本地目录推理框架用支持离线加载的那种。这里有个关键细节模型的 tokenizer 和配置文件必须一起搬进去很多人只搬了权重文件结果加载时报错又得重新走一遍审批流程非常痛苦。编排层我自己写了一个几百行的核心循环逻辑很朴素接收用户输入 → 拼装系统提示词和 Skills 描述 → 调用本地模型 → 解析模型输出中的工具调用意图 → 执行对应 MCP 工具 → 把结果塞回上下文 → 继续循环直到模型给出最终答案。这个循环看着简单但有几个坑必须提前处理。注意本地模型的工具调用能力通常弱于云端大模型经常出现格式错误、参数缺失、幻觉工具名。所以编排层必须做严格的输出校验和重试不能假设模型一定听话。我的做法是给工具调用定义一个严格的 JSON Schema模型输出后先做 schema 校验不通过就把错误信息塞回去让它重试最多重试三次。实测下来7B 模型在明确 schema 约束下工具调用成功率能从 60% 提到 90% 以上。3.2 MCP 协议层把工具接入标准化MCP 的价值在于“一次定义处处调用”。在内网里我把它当成 Agent 和外部世界之间的唯一通道。每个能力都做成一个独立的 MCP Server比如SQLite MCP Server负责执行查询、返回结果、管理连接文件系统 MCP Server负责读写内网共享目录里的文件代码检索 MCP Server负责在本地代码库里做语义或关键词搜索Vue 组件生成 MCP Server负责根据描述生成符合团队规范的 Vue 组件骨架每个 Server 都是独立进程通过标准输入输出或本地 socket 和 Agent 通信。这样做的好处是隔离性好——某个 Server 崩了不影响主流程重启即可。而且每个 Server 可以单独测试不用把整个 Agent 拉起来。MCP Server 的实现语言可以灵活选。热词里提到“基于 rust 语言 ai agent”Rust 写 MCP Server 确实有优势单二进制、无运行时依赖、启动快、内存安全非常适合内网部署。但如果你团队更熟 Python 或 Node用它们也完全没问题关键是打包成离线可运行的形式。我有个项目就是用 Python 写的 MCP Server用 PyInstaller 打成单文件可执行程序搬进内网直接跑效果也很好。提示MCP Server 的日志一定要写到本地文件并且带上时间戳和请求 ID。内网排查问题时日志是你唯一的朋友。3.3 Skills 技能包让 Agent 知道“自己能干什么”Skills 和 MCP 的关系容易混淆。我的理解是MCP 是“手”Skills 是“说明书”。MCP Server 提供了实际执行能力但 Agent 怎么知道什么时候该用哪个工具靠的就是 Skills 描述。一个 Skill 本质上是一段结构化的文本告诉模型这个能力叫什么、什么场景下用、入参是什么、返回什么、有什么注意事项。比如一个“查询 SQLite 表结构”的 Skill描述里会写清楚当用户问“某张表有哪些字段”时使用入参是表名返回字段列表和类型。Skills 的质量直接决定 Agent 的智商。我踩过的最大坑是Skill 描述写得太笼统模型就乱用工具。比如把“查询数据库”写成一个 Skill模型会在任何跟数据沾边的问题上都去查库哪怕用户只是问“数据库是什么”。后来我把 Skill 拆细查表结构、查数据、统计行数、导出结果每个都写清楚触发条件和边界准确率立刻上来了。热词里“claude agent skills: a first principles deep dive”“agent skills 测试”“codex skills”“find skills”这些反映的正是大家在探索怎么把 Skills 这套机制用好。我的经验是Skills 要像写 API 文档一样写甚至更严格因为你的“调用方”是一个理解力有限、还容易幻觉的模型。3.4 SQLite内网里最靠谱的本地存储SQLite 在内网 Agent 工程里的角色被严重低估了。它不只是存数据还承担了三个关键职责对话历史、工具调用记录、Agent 长期记忆。对话历史好理解每次用户和 Agent 的交互都落一张表字段包括会话 ID、角色、内容、时间戳。工具调用记录更关键每次 MCP 调用都记一条调了哪个工具、入参是什么、返回什么、耗时多少、成功还是失败。这张表在排查问题时价值极高你可以精确还原 Agent 的决策链路。长期记忆是我后来加的功能。Agent 可以把一些稳定的事实比如“这个项目的数据库叫 order_db”“前端用 Vue3 Vite”存进一张记忆表下次对话时先检索相关记忆塞进上下文。这样 Agent 就不会每次都问同样的问题体验提升明显。SQLite 的实操要点有几个。第一开启 WAL 模式允许多读单写并发Agent 一边写日志一边查历史不会锁死。第二定期 VACUUM因为频繁写入会产生碎片文件会膨胀。第三用 DB Browser for SQLite 做人工审查这个工具在内网里也能离线安装打开 .db 文件就能看表结构和数据审计时特别方便。-- 开启 WAL 模式 PRAGMA journal_modeWAL; -- 建一张工具调用记录表 CREATE TABLE tool_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, tool_name TEXT NOT NULL, arguments TEXT, result TEXT, duration_ms INTEGER, success INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 建索引加速按会话查询 CREATE INDEX idx_session ON tool_calls(session_id);热词里“sqlite 修改字段的类型”“sqlite callback 怎么触发的”“linux 下 sqlite 安装命令”这些都是实操中会遇到的。SQLite 改字段类型比较麻烦因为它不支持直接 ALTER COLUMN标准做法是建新表、导数据、删旧表、改名。callback 触发则跟具体语言的驱动有关Python 的 sqlite3 模块里callback 通常指自定义函数注册后在被 SQL 调用时触发。3.5 Vue 前端给人看的那个窗口前端这块内网环境的核心诉求是“构建一次到处能跑”。Vue 项目在公网机器上开发、构建把 dist 目录搬进内网用任意静态服务器托管即可。如果后端是 Spring Boot直接把 dist 内容放进src/main/resources/static打包成一个 jar部署最省事。界面功能不用花哨够用就行一个对话框让用户输入任务一个区域展示 Agent 的思考过程和工具调用一个侧边栏列出历史会话。Vue 的响应式系统让这些状态同步变得很简单ref和reactive管数据v-for渲染列表v-if控制显隐。有个细节值得说Agent 的输出是流式的一个字一个字往外蹦。前端要用 SSE 或 WebSocket 接收流式数据然后增量更新界面。Vue 里处理这个很自然每收到一个 chunk 就 push 到响应式数组里界面自动刷新。热词里“使用 mcp 工具流式输出内容到文件”说的也是类似场景只不过输出目标是文件而不是界面。注意内网里 Vue 的依赖安装是个坎。建议在公网机器上把node_modules整个打包或者用npm ci配合离线缓存。Vue Router 如果用 history 模式记得后端要配置 fallback 到 index.html否则刷新页面会 404。4. 完整实操流程与关键环节实现4.1 环境准备把公网的东西“搬”进内网这一步是整个工程里最繁琐、最容易出错的环节。我的标准流程是在公网机器上准备一个“离线包”包含所有运行时、依赖、模型、工具然后通过合规渠道搬进内网。离线包的结构大概是这样offline-bundle/ ├── runtime/ # Python 或 Node 运行时 ├── wheels/ # Python 依赖 wheel 包 ├── node_modules/ # 前端依赖 ├── models/ # 本地模型权重和配置 ├── mcp-servers/ # 各 MCP Server 可执行文件 ├── skills/ # Skills 描述文件 ├── frontend-dist/ # Vue 构建产物 └── install.sh # 一键安装脚本install.sh是关键它要在内网机器上完成解压运行时、pip install --no-index --find-linkswheels、拷贝模型、启动 MCP Server、启动 Agent、启动前端服务。这个脚本我改了十几版每次内网部署遇到新问题就补一条。提示离线包一定要带校验文件比如 SHA256 清单搬进去之后先校验完整性。我遇到过一次传输过程中文件损坏排查了半天才发现是包的问题。4.2 Agent 核心循环的实现细节Agent 的核心循环我前面提过这里展开讲实现。伪代码大概是这样def agent_loop(user_input, session_id): context load_history(session_id) context.append({role: user, content: user_input}) for step in range(MAX_STEPS): # 拼装系统提示词包含所有 Skills 描述 prompt build_prompt(context, skills_registry) # 调用本地模型 response local_model.generate(prompt) # 解析是否有工具调用意图 tool_call parse_tool_call(response) if tool_call is None: # 没有工具调用说明是最终答案 save_history(session_id, context, response) return response # 校验工具调用参数 if not validate_schema(tool_call): context.append({role: system, content: 参数格式错误请重试}) continue # 执行 MCP 工具 result mcp_client.call(tool_call.name, tool_call.arguments) # 记录到 SQLite log_tool_call(session_id, tool_call, result) # 把结果塞回上下文 context.append({role: tool, content: result}) return 达到最大步数限制任务未完成这里有几个参数需要根据实际情况调。MAX_STEPS我一般设 10太少复杂任务做不完太多容易陷入死循环烧算力。build_prompt里 Skills 描述的顺序也有讲究把最常用的放前面模型注意力更集中。4.3 MCP Server 的编写与注册以一个 SQLite 查询 MCP Server 为例核心就是暴露几个方法list_tables、describe_table、query。每个方法接收参数、执行操作、返回结果。用 Python 写的话大概长这样import sqlite3 import json class SQLiteMCPServer: def __init__(self, db_path): self.db_path db_path def list_tables(self): conn sqlite3.connect(self.db_path) cursor conn.execute( SELECT name FROM sqlite_master WHERE typetable ) tables [row[0] for row in cursor.fetchall()] conn.close() return {tables: tables} def describe_table(self, table_name): conn sqlite3.connect(self.db_path) cursor conn.execute(fPRAGMA table_info({table_name})) columns [ {name: row[1], type: row[2], nullable: not row[3]} for row in cursor.fetchall() ] conn.close() return {columns: columns} def query(self, sql): # 只允许 SELECT防止误删数据 if not sql.strip().upper().startswith(SELECT): return {error: 只允许 SELECT 查询} conn sqlite3.connect(self.db_path) cursor conn.execute(sql) rows cursor.fetchall() columns [desc[0] for desc in cursor.description] conn.close() return {columns: columns, rows: rows}写完 Server 之后要在 Agent 的配置里注册它同时写对应的 Skill 描述。Skill 描述我一般写成 Markdown 格式包含名称、用途、触发条件、参数说明、示例。4.4 前端流式展示的实现前端接收 Agent 输出用 SSE 最省事。后端提供一个/api/chat/stream接口返回text/event-stream每产生一个 token 就推一个 event。前端用EventSource接收const eventSource new EventSource(/api/chat/stream?session sessionId); eventSource.onmessage (event) { const data JSON.parse(event.data); if (data.type token) { currentAnswer.value data.content; } else if (data.type tool_call) { toolCalls.value.push(data); } else if (data.type done) { eventSource.close(); } };Vue 的响应式让这段代码非常直观currentAnswer一变界面就更新。工具调用展示我用一个折叠面板默认收起点开能看到完整的入参和返回方便调试和审计。4.5 打包与部署让整套系统在内网跑起来部署顺序很重要先起 SQLite其实就是确认文件路径可写再起各个 MCP Server再起 Agent 主服务最后起前端。我用一个 supervisor 脚本统一管理每个组件一个进程崩了自动重启。前端如果嵌进 Spring Boot构建流程是Vue 项目npm run build生成 dist把 dist 内容拷到 Spring Boot 的static目录然后mvn package打成 jar。这样只需要部署一个 jar 文件运维最省心。热词里“vue 打包放进 springboot 中”说的就是这个流程实测下来确实是最适合内网的部署方式。5. 常见问题与排查技巧实录5.1 模型不调用工具或乱调用工具这是最高频的问题。表现是明明有对应的 Skill模型就是不调用或者调用了错误的工具。排查思路分三步。第一步检查 Skill 描述是否清晰。把 Skill 描述单独拿出来读一遍问自己一个不了解背景的人看了能不能准确判断什么时候用如果描述里有“等等”“相关”这种模糊词模型大概率会懵。第二步检查系统提示词里 Skills 的呈现方式。我试过把所有 Skill 塞进一个超长提示词结果模型注意力被稀释效果很差。后来改成先给一个 Skill 目录只有名称和一句话描述模型决定用哪个之后再加载详细描述准确率明显提升。第三步检查模型本身的能力。7B 模型在工具调用上确实弱如果条件允许上 14B 或更大。如果只能用 7B那就把工具数量控制在 5 个以内每个工具的职责尽量单一。5.2 SQLite 并发写入锁死Agent 一边写日志一边查历史如果没开 WAL很容易遇到database is locked。解决办法就是开头提到的PRAGMA journal_modeWAL。另外写操作尽量批量提交不要每条都 commit减少锁竞争。还有一个隐蔽的坑如果多个 MCP Server 同时连同一个 SQLite 文件连接数太多也会出问题。我的做法是让所有数据库操作走一个统一的 MCP Server其他组件通过它间接访问避免多进程直接竞争。5.3 前端构建产物在内网白屏白屏通常有三个原因。一是资源路径问题Vue 默认打包用绝对路径/assets/...如果部署在子路径下就会 404需要在vite.config.js里设base: ./。二是路由模式问题history 模式需要后端 fallbackhash 模式则不用内网图省事可以直接用 hash。三是浏览器版本问题内网机器可能装着老版本浏览器Vue3 需要较新的浏览器支持必要时降级到 Vue2 或加 polyfill。5.4 常见问题速查表问题现象可能原因排查方向解决办法Agent 不调用工具Skill 描述模糊单独读 Skill 描述细化触发条件和边界工具调用参数错误模型能力不足看工具调用日志加 schema 校验和重试SQLite 锁死未开 WAL查 journal_mode开启 WAL批量提交前端白屏资源路径错误看浏览器控制台设 base 为相对路径MCP Server 启动失败依赖缺失看启动日志补全离线依赖模型加载报错配置文件缺失看加载日志补齐 tokenizer 和 config流式输出卡顿缓冲区设置不当看网络面板调整 chunk 大小和 flush 频率内存持续增长上下文未清理看进程内存限制历史长度定期清理5.5 几个只有踩过才知道的坑第一个坑内网机器的时钟可能不准。SQLite 的CURRENT_TIMESTAMP依赖系统时钟如果时钟漂移日志时间全乱。部署前一定先校时。第二个坑模型文件路径不要有中文和空格。很多推理框架对路径处理不严谨带中文的路径会加载失败而且报错信息很隐晦能查到你怀疑人生。第三个坑Skills 描述文件用 UTF-8 无 BOM 保存。带 BOM 的文件在某些解析器里会在开头多出一个不可见字符导致 Skill 名称匹配失败。第四个坑前端和后端的接口协议要提前定死。内网里前后端联调成本高最好在公网阶段就把接口文档、字段类型、错误码全部确定搬进去之后只做部署不做修改。6. 一些实操心得与后续扩展方向这套系统我在几个项目里迭代下来最大的体会是内网 Agent 工程的难点不在 AI而在工程。模型能力固然重要但真正决定项目成败的是依赖管理、离线部署、日志审计、错误恢复这些“脏活累活”。把 MCP 和 Skills 这套标准化机制用起来能省下大量重复劳动。后续可以扩展的方向有几个。一是多 Agent 协作让不同 Agent 分别负责代码、数据库、文档通过 MCP 互相调用。二是本地知识库增强把内网文档向量化后存进 SQLite 的扩展或本地向量库让 Agent 检索更精准。三是Skills 市场内部化团队内部维护一个 Skills 仓库新项目直接复用避免重复造轮子。最后分享一个小技巧在内网部署前先在公网环境用完全相同的离线包跑一遍全流程把所有网络请求都断掉看能不能正常工作。这个“断网演练”能提前暴露 90% 的离线化问题比搬进内网之后再排查高效得多。我现在的习惯是任何要进内网的组件先在本地用防火墙规则模拟断网跑通确认无误才打包。这个习惯帮我省了至少三次返工。