
1. 隔离内网下的 AI Agent 工程实战从零搭建到稳定运行很多做企业级交付的朋友都遇到过这种场景客户现场是一套完全物理隔离的内网没有外网出口没有公网镜像源甚至连 pip install 都得靠离线包。偏偏这时候甲方提了个需求——你们这个系统能不能加个 AI Agent帮我们自动处理工单、查知识库、跑数据分析。这就是我最近半年反复在踩的坑也是这篇文章想聊透的事。隔离内网下的 AI Agent 工程实战核心要解决的不是Agent 能不能跑起来而是在一个没有外网、没有现成工具链、算力受限、运维窗口极窄的环境里怎么把 Agent 从 demo 做成能扛住业务的生产系统。它涉及模型本地化部署、MCP 协议在内网的适配、Skills 能力的离线封装、工具调用的权限收敛、以及一整套不依赖公网的工程化流程。适合谁看适合正在做 ToB/ToG 交付的后端、算法、运维工程师也适合想搞清楚Agent 落地到底难在哪的技术负责人。如果你只在外网环境玩过 LangChain 或者扣子这类平台那内网这一套会让你重新认识什么叫工程。我下面讲的所有内容都基于一个真实项目的抽象某单位内网约 200 台服务器Agent 需要对接 3 个业务系统、1 个知识库、1 套报表工具日均调用量峰值在 3000 次左右全程无外网。所有方案我都实际跑过踩过的坑会明确标出来。2. 内网 Agent 的整体架构设计与选型逻辑2.1 为什么不能照搬外网那套架构外网做 Agent大家习惯的套路是调 OpenAI 或者 Claude 的 API用 LangChain 编排工具通过 MCP 或者 Function Calling 挂上去向量库用云服务日志丢到云端可观测平台。这套东西在内网里几乎全部失效——API 调不通、云服务连不上、依赖装不上。所以内网 Agent 的第一原则是所有组件必须能离线自持。模型要本地跑向量库要本地部署工具调用要本地闭环连日志都得自己搭一套。这不是能不能简化的问题而是少了任何一环整个链路就断的问题。我当时的架构决策是这样的层级外网常见方案内网替代方案选型理由模型层GPT-4/Claude APIQwen2.5-14B-Instruct 本地部署中文能力强14B 在单张 A100 上能跑量化后 4090 也能扛编排层LangChain/LangGraphLangGraph 自研状态机LangGraph 支持离线安装状态机便于审计工具协议MCP over HTTPMCP over stdio内网无稳定 HTTP 服务发现stdio 更可靠向量库Pinecone/云 MilvusMilvus 单机版离线部署简单支持本地磁盘可观测LangSmith自研日志 Prometheus无外网只能自建这张表看着简单但每一行的选择背后都是被坑出来的。比如模型层一开始想用 7B 省资源结果工具调用的准确率惨不忍睹经常把参数填错最后还是上了 14B。再比如 MCP一开始想走 HTTP结果内网的服务注册发现机制不健全Agent 经常找不到工具服务改成 stdio 之后稳定多了。2.2 MCP 协议在内网里的定位MCPModel Context Protocol这两年被聊得很多但很多人对它的理解停留在让模型调用工具这个层面。在内网环境里MCP 的真正价值是把工具能力和模型解耦。什么意思假设你的 Agent 需要查工单、查知识库、跑报表三个能力。如果不用 MCP你得在 Agent 代码里硬编码这三个工具的调用逻辑模型换了、工具改了代码就得重写。用了 MCP 之后每个工具是一个独立的 MCP ServerAgent 只负责发现工具、调用工具工具内部怎么实现、用什么语言写Agent 完全不关心。在内网里这一点尤其重要因为内网的业务系统往往是异构的——有的用 Java有的用 Python有的甚至是老掉牙的 C# 系统。MCP 让每个团队可以独立维护自己的工具服务Agent 侧只需要一份统一的协议描述。提示内网部署 MCP Server 时优先选 stdio 模式而不是 SSE/HTTP 模式。stdio 不依赖网络端口进程生命周期由 Agent 管理出问题好排查。HTTP 模式在内网里经常因为防火墙策略、端口占用、服务发现失败而翻车。2.3 Skills 的离线封装思路Skills 这个概念最近很火Claude 的 Agent Skills、Codex 的 Skills 都在推。本质上 Skills 就是预封装的能力包——把一段提示词、一组工具、一套流程打包成一个可复用的单元。内网里做 Skills 封装我的经验是按业务场景切不要按技术能力切。比如工单处理是一个 Skill知识库问答是一个 Skill报表生成是一个 Skill。每个 Skill 内部包含触发条件、所需工具、提示词模板、输出格式约束、失败兜底逻辑。这样做的好处是业务方提需求的时候可以直接说我要一个 XX Skill而不是我要一个能查数据库又能调 API 的 Agent。交付边界清晰测试也好做。3. 核心组件在内网环境下的落地细节3.1 模型本地化部署显存、量化与推理框架模型部署是内网 Agent 的第一道坎。我实测下来Qwen2.5-14B-Instruct 用 vLLM 部署单张 A100 80G 可以跑 FP16吞吐大概在 1500 tokens/s 左右如果用 4090 24G必须上 AWQ 4bit 量化吞吐掉到 400 tokens/s 左右但够用。部署命令大概是这样python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-14B-Instruct-AWQ \ --quantization awq \ --dtype float16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000 \ --served-model-name qwen-agent几个关键参数的解释--max-model-len 8192Agent 场景下上下文经常很长系统提示词 工具描述 历史对话8192 是底线能上 16384 更好但显存会吃紧。--gpu-memory-utilization 0.9留 10% 给 KV Cache 之外的显存开销设太高容易 OOM。--quantization awq4bit 量化精度损失在 Agent 场景下可以接受但工具调用的参数格式偶尔会出错需要在提示词里加强约束。注意内网部署模型时一定要提前把模型权重、tokenizer、配置文件全部拷贝到本地。vLLM 启动时会去 HuggingFace 拉配置内网拉不到会直接报错。用--model指向本地目录并且确保目录里有完整的config.json、tokenizer.json、tokenizer_config.json。3.2 MCP Server 的离线开发与注册MCP Server 的开发本身不复杂官方有 Python 和 TypeScript 的 SDK。内网里的难点在于依赖安装和服务注册。依赖安装这块我的做法是在一台有外网的机器上用pip download把所有依赖下成 wheel 包然后拷贝到内网用pip install --no-index --find-links./wheels安装。MCP SDK 的依赖不多主要是mcp、pydantic、httpx这几个。一个最简单的 MCP Server 长这样from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app Server(ticket-server) app.list_tools() async def list_tools(): return [ Tool( namequery_ticket, description根据工单号查询工单详情, inputSchema{ type: object, properties: { ticket_id: {type: string, description: 工单编号} }, required: [ticket_id] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name query_ticket: ticket_id arguments[ticket_id] # 这里调用内网业务系统的接口 result query_internal_api(ticket_id) return [TextContent(typetext, textresult)] async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())服务注册这块内网没有 Consul 或者 Nacos 的话最简单的办法是用配置文件管理。Agent 启动时读取一个mcp_servers.json里面列出所有 MCP Server 的启动命令和参数{ mcpServers: { ticket: { command: python, args: [/opt/mcp/ticket_server.py] }, knowledge: { command: python, args: [/opt/mcp/knowledge_server.py] } } }Agent 侧用 MCP Client 逐个拉起这些进程通过 stdio 通信。这种方式的缺点是进程管理要自己做好处是简单、可控、不依赖任何外部服务。3.3 向量库与知识库的离线构建知识库问答是 Agent 最常见的场景。内网里做 RAG向量库我选的是 Milvus 单机版用 Docker 部署数据落本地磁盘。docker run -d --name milvus-standalone \ -p 19530:19530 \ -p 9091:9091 \ -v /data/milvus:/var/lib/milvus \ milvusdb/milvus:v2.4.0 standaloneEmbedding 模型用的是 BGE-M3本地部署通过 FastAPI 包一层 HTTP 接口给 Agent 调用。这里有个坑BGE-M3 的模型文件也不小内网拷贝的时候记得把sentence_transformers的缓存目录一起拷过去否则第一次加载会去外网拉。知识库的构建流程是文档解析PDF/Word/Excel→ 分块 → 向量化 → 入库。分块策略我试过几种最后定的是按语义分块 512 token 上限 128 token 重叠。纯按固定长度切会把一句话切断检索出来的片段读起来很别扭。提示内网知识库更新是个麻烦事。我的做法是做一个知识库同步的定时任务每天凌晨扫描指定目录有新文档就增量入库同时记录文档指纹避免重复。这个任务本身不依赖外网纯本地跑。4. 实操过程从零到跑通一个内网 Agent4.1 环境准备清单在动手之前先把这些东西准备好缺一个都会卡住模型权重文件Qwen2.5-14B-Instruct-AWQ约 9GBvLLM 及其依赖的离线 wheel 包Milvus Docker 镜像提前docker save成 tar 包BGE-M3 模型文件及 sentence_transformers 缓存MCP SDK 及依赖 wheel 包Python 3.10 运行时内网机器自带或离线安装各业务系统的接口文档和访问凭证这些东西加起来大概 30GB 左右用一个移动硬盘拷进去就行。我建议做一个内网部署包把所有东西按目录组织好附一份 README这样下次交付直接复用。4.2 Agent 主程序的编排逻辑Agent 主程序用 LangGraph 写核心是一个状态机。状态定义如下from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END import operator class AgentState(TypedDict): messages: Annotated[list, operator.add] next_action: str tool_calls: list final_answer: str节点包括understand理解用户意图、plan规划下一步、call_tool调用工具、reflect反思结果、respond生成最终回答。边的关系是understand → plan → 判断是否需要工具 → 需要则 call_tool → reflect → 回到 plan不需要则直接 respond → END。这个状态机的好处是每一步都可审计。内网环境里甲方往往要求Agent 做的每个决策都要有日志状态机天然满足这个需求。4.3 工具调用的权限收敛内网 Agent 最容易被忽视的是权限问题。Agent 能调用的工具本质上就是它能访问的系统资源。如果不做收敛一个提示词注入就可能让 Agent 去查它不该查的数据。我的做法是三层收敛工具白名单Agent 只能调用注册在mcp_servers.json里的工具其他一律拒绝。参数校验每个 MCP Server 内部对参数做严格校验比如工单号必须符合特定格式SQL 查询必须带租户 ID 条件。调用审计每次工具调用都记录到日志包括调用者、参数、返回结果摘要、耗时。注意千万不要让 Agent 直接执行 SQL 或者 shell 命令。我见过有项目为了灵活给 Agent 开了数据库直连结果模型幻觉生成了一个DROP TABLE虽然最后被权限拦住了但惊出一身冷汗。工具要封装成业务语义的接口而不是技术语义的接口。4.4 并发与稳定性处理热词里有个ai agent 怎么扛并发这确实是内网 Agent 的痛点。我的实测数据是单张 A100 跑 14B 模型并发 8 的时候响应时间还在 2s 以内并发 16 就开始排队并发 32 直接超时。扛并发的思路有几个请求队列 限流用 Redis 或者内存队列做缓冲超过阈值的请求排队等待而不是直接打爆模型。模型实例横向扩展如果有多张卡起多个 vLLM 实例前面挂一个负载均衡。结果缓存对于高频重复的查询比如XX 工单状态缓存结果减少模型调用。异步化Agent 的思考过程异步执行前端先返回处理中完成后推送结果。我最后用的是队列 缓存 双实例的组合峰值 3000 次/天的调用量下P99 响应时间控制在 5s 以内。5. 常见问题与排查技巧实录5.1 模型输出格式错误这是最高频的问题。Agent 需要模型输出结构化的工具调用参数但模型经常输出多余的说明文字或者 JSON 格式不对。排查思路先看提示词里有没有明确要求只输出 JSON不要任何其他内容再看有没有给 few-shot 示例。如果还不行就在解析层做容错——用正则提取 JSON 部分解析失败就重试一次。我的经验是提示词约束 输出解析容错 重试机制三件套缺一不可。重试的时候把上一次的错误信息也塞进提示词模型往往能自我纠正。5.2 MCP Server 启动失败内网里 MCP Server 启动失败90% 是依赖问题。排查步骤手动执行启动命令看报错信息。如果是ModuleNotFoundError检查 wheel 包是否装全。如果是权限问题检查 Python 解释器路径和文件权限。如果是端口占用HTTP 模式换端口或者改 stdio。我建议在 Agent 启动时加一个健康检查环节逐个拉起 MCP Server 并调用一个ping工具确认所有工具可用后再开始服务。5.3 知识库检索不准RAG 检索不准通常是三个原因分块不合理、Embedding 模型不匹配、检索策略单一。我的优化顺序是先调分块语义分块 合适的大小再换 Embedding 模型BGE-M3 比 BGE-large 在中文上强不少最后加混合检索向量检索 BM25 关键词检索结果融合。混合检索的代码大概是这样def hybrid_search(query, top_k5): vector_results milvus_search(query, top_ktop_k*2) bm25_results bm25_search(query, top_ktop_k*2) # RRF 融合 scores {} for rank, doc in enumerate(vector_results): scores[doc.id] scores.get(doc.id, 0) 1/(60 rank) for rank, doc in enumerate(bm25_results): scores[doc.id] scores.get(doc.id, 0) 1/(60 rank) return sorted(scores.items(), keylambda x: -x[1])[:top_k]5.4 常见问题速查表问题现象可能原因排查方法解决方案模型加载 OOM显存不足nvidia-smi看显存降量化精度或换小模型工具调用超时业务系统响应慢看 MCP Server 日志加超时 重试 降级回答答非所问提示词不清晰打印完整 prompt优化系统提示词检索结果重复分块重叠过大检查分块参数减小 overlapAgent 死循环状态机无终止条件看状态转移日志加最大步数限制并发下响应慢模型排队看 vLLM 队列长度加实例或限流6. 内网 Agent 工程化的几点个人体会做了几个内网 Agent 项目之后我最大的体会是内网 Agent 的难点从来不是 AI而是工程。模型能力再强如果依赖装不上、服务起不来、日志查不到整个项目就是空中楼阁。第二个体会是收敛比灵活重要。外网做 Agent 追求什么都能干内网做 Agent 追求该干的干好不该干的碰都别碰。工具白名单、参数校验、调用审计这三样东西看着笨但能救命。第三个体会是离线部署包要标准化。我现在的做法是维护一个内网 Agent 部署模板包含所有依赖、配置、脚本、文档新项目直接复制改配置。这样交付周期从两周压缩到三天。最后分享一个小技巧内网环境里Agent 的日志一定要打到本地文件并且按天切割。我见过有项目日志只打 stdout结果服务一重启日志全没了出问题根本没法排查。日志格式建议用 JSON方便后续用脚本分析。这个方向后续还能扩展的地方很多比如把 Agent 的执行过程做成可视化面板、把 Skills 做成可热插拔的插件、把 MCP Server 做成容器化部署。但这些都是锦上添花把基础链路跑稳才是内网 Agent 工程实战的第一要务。