
1. 为什么要在隔离内网里折腾 AI Agent第一次被问到能不能在内网里跑一套 AI Agent的时候我脑子里第一反应是这不是自找麻烦吗。外网环境里各种模型 API、MCP 服务、Skills 库、插件生态一应俱全点几下就能跑起来为什么非要把自己关进一个没有外网、没有公网 IP、甚至连 pip 源都要走内部镜像的环境里后来真正在几个项目里落地之后我才明白这件事的价值。隔离内网通常出现在对数据流向有严格要求的场景代码不能出内网、业务数据不能出内网、日志不能出内网但团队又确实想用 AI Agent 来提升研发效率——比如自动读代码、自动生成测试、自动整理需求文档、自动在内部知识库里检索。这时候你面对的不是要不要用 AI而是怎么在什么都拿不到的环境里把 AI Agent 跑起来。这个约束会逼着你把平时被各种云服务掩盖掉的东西全部想清楚模型从哪来、工具怎么注册、Skills 怎么分发、上下文怎么管理、Agent 的循环怎么控制、失败怎么兜底。外网环境下你可以靠多试几个服务糊过去内网环境下每一步都得有确定答案。所以我把这套东西称为工程实战而不是快速上手——它更像是在有限资源下做一次完整的系统设计。这篇文章适合三类人一是在内网环境里被要求落地 AI 能力的工程师二是想理解 Agent 底层机制、不想只会调 API 的开发者三是负责内部平台建设、需要评估 MCP 和 Skills 这套体系能不能搬进内网的技术负责人。我会把模型接入、MCP 工具层、Skills 组织、Agent 主循环、调试与排错这几块拆开讲尽量给出可以直接抄的配置和踩过的坑。提示本文讨论的所有部署方式都基于内网自建服务与本地模型不涉及任何跨网络访问手段。如果你的场景允许访问外部服务很多步骤可以简化但核心的工程思路是一致的。2. 内网 Agent 的模型层本地推理服务怎么选、怎么接2.1 先想清楚模型能力边界再决定硬件内网 Agent 的第一个卡点永远是模型。外网你可以随手调一个能力很强的大模型内网里你只能用手上有的算力。我见过太多团队一上来就问能不能跑 70B结果机器只有一张 24G 显存的卡最后折腾一周还是回到 7B 或 14B 量化版本。我的建议是先做一次能力盘点把 Agent 需要的能力拆成几类指令遵循、工具调用Function Calling / Tool Use、长上下文理解、代码理解。这四类里工具调用是最容易被低估的。很多小模型能聊天、能写代码但一到结构化输出就开始胡说JSON 格式经常缺字段或者多逗号Agent 主循环直接崩。所以选模型时工具调用的稳定性优先级要高于单纯的聪明程度。下面这张表是我在实际项目里对不同规模模型的粗略定位供参考模型规模典型显存需求量化后适合的 Agent 任务主要风险7B 级别6-10G单步工具调用、简单检索、格式转换多步推理容易跑偏14B 级别12-20G中等复杂度任务、代码解释、文档整理长上下文下注意力衰减32B 级别24-40G多步规划、复杂工具链编排推理速度慢并发低70B 级别多卡或大显存高复杂度规划与代码生成部署成本高延迟明显这张表不是绝对标准量化方式INT8、INT4、上下文长度、批处理大小都会影响实际占用。我一般会留 20% 显存余量因为 Agent 的上下文会随着工具返回结果不断膨胀峰值占用比单轮对话高不少。2.2 本地推理服务的接口要像标准 API内网里跑模型常见做法是用本地推理框架起一个 HTTP 服务然后让 Agent 通过 OpenAI 兼容接口去调。这里有个非常实用的经验尽量让你的本地服务暴露成 OpenAI 兼容的/v1/chat/completions格式。原因很简单绝大多数 Agent 框架、MCP 客户端、Skills 运行时默认都支持这个协议你不需要为每个工具单独写适配层。一个典型的启动配置大概长这样以常见的本地推理服务为例具体参数按你的框架调整# 启动本地推理服务暴露 OpenAI 兼容接口 python -m your_inference_server \ --model /models/your-model-14b-int4 \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 32768 \ --gpu-memory-utilization 0.85 \ --served-model-name internal-agent-model几个参数值得单独说。--max-model-len决定了 Agent 能带多长的上下文内网 Agent 经常要把代码文件、检索结果塞进上下文32K 是比较舒服的起点低于 16K 会很难受。--gpu-memory-utilization控制显存占用比例设太高容易 OOM设太低浪费算力0.85 是我比较常用的值。--served-model-name建议起一个内部统一的名字这样 Agent 配置里不用关心底层换的是哪个模型。2.3 模型层的三个内网专属坑第一个坑是模型文件分发。内网不能直接从外部下载模型权重你得提前把权重文件通过合规渠道导入然后在内网做一次校验。我习惯用哈希校验因为大文件传输过程中损坏的概率比想象中高加载时报错往往很隐晦。第二个坑是Tokenizer 与模板不一致。有些模型权重和 tokenizer 配置是分开的如果 chat template 没配对模型输出会带上奇怪的标记工具调用解析直接失败。判断方法很简单用同样的 prompt 在外网同款模型上跑一遍对比原始输出如果内网输出多了或少了一些特殊 token基本就是模板问题。第三个坑是并发与排队。Agent 一次任务可能触发十几次模型调用如果多个 Agent 同时跑本地推理服务很容易被打满。我的做法是在推理服务前面加一层轻量队列限制同时处理的请求数超出的排队等待而不是直接拒绝。这样 Agent 侧只需要处理超时不需要处理连接被拒。注意内网环境里模型服务的稳定性比外网更重要因为外网你可以随时切换供应商内网你只有这一套。建议给推理服务加健康检查接口Agent 启动前先探活。3. MCP 工具层把内网能力包装成 Agent 能调用的工具3.1 MCP 到底解决了什么问题MCPModel Context Protocol这个词最近出现频率很高但很多人对它的理解停留在又一个协议。我的理解是MCP 解决的是工具和模型之间的标准化对接问题。在没有 MCP 之前你每接一个工具就要写一套适配代码工具多了之后维护成本爆炸。MCP 把工具的描述、参数、调用方式统一成一套协议Agent 只需要会说 MCP就能调用所有实现了 MCP 的工具。在内网环境里MCP 的价值反而更大。因为内网的工具往往是自研的、内部的、接口不统一的MCP 相当于给这些散乱的能力加了一层统一外壳。你可以把内部代码仓库检索、内部文档查询、内部数据库查询、内部构建系统触发全部包装成 MCP 工具Agent 侧只认 MCP 接口。一个 MCP 工具的核心结构包括工具名称、描述、输入参数 schema、执行逻辑。描述这部分特别关键因为模型是靠描述来判断什么时候调用这个工具的。描述写得含糊模型就会乱调或者不调。3.2 内网 MCP 服务的部署形态内网里 MCP 服务一般有两种形态本地进程stdio和独立服务HTTP/SSE。stdio 形态适合单机、工具轻量的场景Agent 直接拉起子进程通信简单直接。HTTP 形态适合工具需要独立部署、多 Agent 共享的场景但要多考虑网络和鉴权。我一般这样选如果工具只是读写本地文件、执行本地命令用 stdio如果工具要访问内部数据库、内部 API、需要独立扩缩容用 HTTP。下面是一个 HTTP 形态 MCP 服务的配置示例{ mcpServers: { internal-code-search: { url: http://mcp-gateway.internal:9100/sse, headers: { X-Internal-Token: your-internal-token } }, internal-doc-query: { url: http://mcp-gateway.internal:9101/sse, headers: { X-Internal-Token: your-internal-token } } } }这里X-Internal-Token是内网自建的简单鉴权不要小看这一步。内网虽然相对封闭但内部服务之间如果没有基本鉴权任何一个能访问网络的进程都能调用你的 MCP 工具风险不小。3.3 工具描述怎么写才不会被模型忽略这是我在实际项目里花时间最多的地方。工具能不能被正确调用八成取决于描述。我总结了几条经验描述里写清楚什么时候用和什么时候不用。比如当需要查询内部代码仓库中某个函数的定义时使用如果只是查询文档请使用 doc-query 工具。模型对边界描述很敏感。参数描述要具体到格式。不要写查询关键词要写查询关键词支持空格分隔的多个词不支持正则。避免工具之间功能重叠。两个工具都能查代码模型就会随机选结果不稳定。要么合并要么把边界写死。我踩过的一个典型坑有两个工具一个叫search一个叫query描述都很模糊结果模型在同一个任务里反复横跳一会儿调 search 一会儿调 query任务永远跑不完。后来把search改成search_code_by_keywordquery改成query_doc_by_semantic描述写清楚各自适用场景问题立刻消失。3.4 MCP 工具返回结果的处理工具返回的内容会直接进入模型上下文所以返回格式很重要。我的原则是结构化、精简、带来源。结构化是为了模型好解析精简是为了不撑爆上下文带来源是为了后续可追溯。一个反例是把整个数据库查询结果原样返回几千行数据塞进上下文模型直接懵。正确做法是在 MCP 工具内部做截断和摘要只返回最相关的若干条并标注共 N 条已返回前 M 条。# MCP 工具内部对返回结果做截断的示意 def format_result(rows, max_items10): total len(rows) shown rows[:max_items] lines [f共 {total} 条结果返回前 {len(shown)} 条] for r in shown: lines.append(f- {r[title]} (来源: {r[source]})) return \n.join(lines)这段逻辑看起来简单但它决定了 Agent 能不能在有限上下文里做出正确判断。我见过太多 Agent 失败案例根因不是模型不行而是工具返回了一堆无关信息把上下文污染了。4. Skills 体系把重复的 Agent 行为沉淀成可复用能力4.1 Skills 和 MCP 的分工很多人会把 Skills 和 MCP 搞混。我的区分方式是MCP 管能做什么Skills 管怎么做。MCP 提供原子能力查代码、查文档、执行命令Skills 把这些原子能力组织成完成特定任务的流程。举个例子MCP 提供读文件和写文件两个工具而一个生成单元测试的 Skill 会定义先读目标源文件再分析函数签名再生成测试用例再写入测试文件最后跑一次测试验证。这个流程就是 Skill。在内网环境里Skills 的价值在于把团队的最佳实践固化下来。外网你可以让模型自由发挥内网里模型能力有限自由发挥容易跑偏用 Skill 把流程约束住反而更稳。4.2 Skill 的目录结构与元数据一个 Skill 通常是一个目录里面包含元数据文件和具体指令。元数据描述这个 Skill 叫什么、什么时候用、需要哪些工具指令部分告诉模型具体步骤。下面是一个典型结构skills/ generate-unit-test/ SKILL.md templates/ test-template.py review-code/ SKILL.md rules/ style-rules.mdSKILL.md是核心一般包含 frontmatter 和正文--- name: generate-unit-test description: 为指定源文件生成单元测试。当用户要求为某个模块补充测试时使用。 tools: - read_file - write_file - run_command --- ## 步骤 1. 读取目标源文件识别所有公开函数。 2. 对每个函数根据参数和返回值设计测试用例。 3. 使用 templates/test-template.py 作为骨架生成测试代码。 4. 写入对应的测试文件路径。 5. 运行测试命令若失败则分析原因并修正一次。这个结构的好处是模型不需要每次从零推理流程只要按 Skill 定义的步骤走稳定性大幅提升。而且 Skill 可以版本化管理团队改了流程就更新 Skill所有 Agent 行为同步变化。4.3 内网 Skills 的分发与更新内网里 Skills 怎么让每个 Agent 都能拿到是个容易被忽略的工程问题。外网你可以从公共库拉内网你得自己搞分发。我的做法是建一个内部 Git 仓库专门放 SkillsAgent 启动时从仓库拉取最新版本到本地缓存目录。# Agent 启动时同步 Skills git clone --depth 1 http://git.internal/skills-repo.git /opt/agent/skills # 或者增量更新 cd /opt/agent/skills git pull --rebase这里有个细节Skills 更新后正在运行的 Agent 不会自动感知。我的处理方式是给 Skills 加一个版本号Agent 每轮任务开始前检查版本版本变了就重新加载。不要每轮都重新拉取那样开销太大。另一个细节是 Skills 的权限。不是所有 Agent 都应该能执行所有 Skill比如部署到生产环境这种 Skill 应该只对特定 Agent 开放。我在 Skill 元数据里加了一个allowed_agents字段加载时做过滤。4.4 写 Skill 的几个实战心得第一步骤要具体到可执行。不要写分析代码质量要写检查函数长度是否超过 50 行、是否有未处理的异常、是否有硬编码的密钥。模型对模糊指令的解读每次都不一样。第二给 Skill 留失败出口。任何步骤都可能失败Skill 里要写清楚失败后怎么办是重试、是跳过、还是终止并报告。我一般要求每个 Skill 最后一步是输出执行摘要包含成功和失败的步骤。第三Skill 不要写太长。一个 Skill 超过 200 行指令模型执行时容易丢步骤。长流程拆成多个 Skill用主 Skill 串联。第四用模板减少模型自由发挥。生成代码、生成文档这类任务给一个模板让模型填空比让模型从零写稳定得多。模板放在 Skill 目录里指令里引用模板路径。5. Agent 主循环内网环境下的规划、执行与兜底5.1 主循环的基本结构Agent 主循环说白了就是一个思考-行动-观察的循环模型根据当前上下文决定下一步做什么调用工具拿到结果更新上下文再决定下一步直到任务完成或达到终止条件。听起来简单但内网环境下每个环节都有坑。一个最小可用的主循环大概是这样def run_agent(task, tools, skills, max_steps20): context build_initial_context(task, skills) for step in range(max_steps): response call_model(context, tools) if response.is_final: return response.content tool_result execute_tool(response.tool_call, tools) context append_observation(context, tool_result) if is_stuck(context): context append_hint(context, 检测到重复行为请换一种思路) return 达到最大步数任务未完成以下是当前进展 summarize(context)这段伪代码里有几个关键设计max_steps防止无限循环is_stuck检测重复行为summarize在超步数时给出进展而不是直接报错。这些在外网可能不重要因为模型聪明内网模型能力有限必须靠工程手段兜底。5.2 上下文管理是内网 Agent 的生死线内网模型上下文窗口通常比外网小而 Agent 任务又特别吃上下文所以上下文管理必须精细。我的策略是分层系统层Agent 的角色、可用工具、可用 Skills 的简要说明。这部分固定不随任务变化。任务层当前任务描述、已完成步骤的摘要。这部分随任务推进更新但只保留摘要不保留原始工具返回。工作层最近几步的完整工具调用和返回。这部分保留原始内容但只保留最近 N 步。关键操作是压缩。每完成几步就把之前的详细记录压缩成摘要。压缩可以用模型做也可以用规则做。规则压缩更快更稳比如读取了文件 X发现包含函数 A、B、C。def compress_history(history, keep_recent3): if len(history) keep_recent: return history old history[:-keep_recent] recent history[-keep_recent:] summary rule_based_summary(old) # 规则压缩不调模型 return [{role: system, content: f历史摘要{summary}}] recent我试过用模型做压缩效果确实好一些但内网模型压缩时经常丢关键信息反而不如规则压缩可靠。规则压缩虽然粗糙但至少不会丢事实。5.3 工具调用的失败处理内网工具失败率比外网高原因很多内部服务不稳定、权限配置错误、参数格式不对、超时。Agent 主循环必须能处理这些失败而不是一失败就整个任务崩掉。我的处理原则是区分可重试失败和不可重试失败。超时、连接错误可以重试参数错误、权限错误重试没用要把错误信息返回给模型让它改参数或换工具。def execute_tool(tool_call, tools, max_retry2): for attempt in range(max_retry 1): try: return tools[tool_call.name].run(tool_call.args) except RetryableError as e: if attempt max_retry: return f工具 {tool_call.name} 重试 {max_retry} 次后仍失败{e} time.sleep(1) except NonRetryableError as e: return f工具 {tool_call.name} 调用失败请调整参数或换用其他工具{e}把错误信息返回给模型而不是直接抛异常是 Agent 能自我修正的关键。模型看到参数 X 格式错误应为整数下一轮就会改。如果直接抛异常任务就断了。5.4 防止 Agent 陷入死循环内网模型能力有限很容易陷入死循环反复调同一个工具、反复生成同样的内容、在两个工具之间来回横跳。我用了几个检测手段重复调用检测连续 N 次调用同一个工具且参数相同判定为卡住。无进展检测连续 N 步上下文没有新增有效信息判定为卡住。步数上限硬性限制最大步数超过就终止并输出进展。检测到卡住后不要直接终止而是往上下文里注入提示比如你已经连续三次调用同一个工具请重新审视任务考虑换一种方法或直接给出当前结论。很多时候模型看到提示就能跳出来。6. 内网 Agent 的调试与排错实战6.1 日志要记到什么粒度内网 Agent 出问题时你没法像外网那样快速对比多个服务只能靠日志。我的日志策略是每次模型调用、每次工具调用、每次上下文变更都记。日志格式用结构化 JSON方便后续检索。{ step: 5, event: tool_call, tool: read_file, args: {path: /src/main.py}, result_summary: 读取成功120 行, duration_ms: 45, context_tokens: 8200 }context_tokens这个字段特别有用它能让你看到上下文是怎么膨胀的。我遇到过任务跑到一半突然失败查日志发现上下文从 8K 涨到 30K超过模型窗口模型开始输出乱码。有了这个字段一眼就能定位。6.2 常见故障的排查链路内网 Agent 的故障大致分几类排查顺序我一般这样走第一类模型不调用工具。表现是模型一直输出文本不触发工具调用。排查先看工具描述是否清晰再看模型是否支持工具调用最后看 prompt 里工具定义格式是否正确。我遇到过模型支持工具调用但格式要求特殊换了个格式就好了。第二类工具调用参数错误。表现是工具报参数错误。排查看模型生成的参数和工具 schema 是否匹配常见问题是模型把数字写成字符串、把数组写成逗号分隔字符串。解决方法是把 schema 写得更明确或者在工具侧做参数容错。第三类任务跑不完。表现是达到最大步数还没完成。排查看日志里是否有重复调用看上下文是否被无关信息污染看 Skill 步骤是否太模糊。这类问题八成是上下文管理或 Skill 设计的问题不是模型的问题。第四类结果不对但流程跑完了。表现是 Agent 说完成了但结果明显错误。排查看工具返回是否准确看模型是否误解了工具返回。这类问题最隐蔽我的做法是给关键 Skill 加验证步骤比如生成代码后跑一次测试测试不过就不算完成。6.3 一个真实的排查案例有个任务让 Agent 从内部代码仓库找某个接口的所有调用点然后生成一份影响面报告。Agent 跑完了报告里只列了 3 个调用点但人工检查发现有 12 个。排查过程先看工具调用日志发现 Agent 只调了一次代码检索工具返回了 3 条结果。再看工具返回发现工具默认只返回前 3 条而 Agent 没有意识到结果被截断了。根因是工具返回里没有标注共 N 条返回前 M 条模型以为就 3 条。修复方式在工具返回里明确标注总数和截断信息并在 Skill 里加一步如果结果被截断调整查询条件或分页获取。改完之后同样的任务能正确列出 12 个调用点。这个案例说明内网 Agent 的很多问题不在模型而在工具和 Skill 的设计。工具返回的信息不完整模型再聪明也没用。6.4 性能调优的几个方向内网 Agent 跑得慢是常态因为本地模型推理速度有限。优化方向有几个减少模型调用次数能用一个 Skill 一步做完的不要拆成多步。每多一步就多一次模型调用。工具结果缓存同一个文件被读多次缓存起来。内网工具调用虽然快但累积起来也影响体验。并行工具调用如果模型支持一次返回多个工具调用且这些调用之间没有依赖可以并行执行。上下文精简前面说的压缩策略不仅省 token也加快推理速度因为注意力计算量和上下文长度相关。我实测下来上下文从 16K 压到 8K推理速度大概能快 30% 到 40%。对于交互式场景这个提升很明显。7. 把整套东西串起来一个内网 Agent 的最小落地清单7.1 部署顺序建议如果你要从零在内网搭一套 Agent我建议按这个顺序来每一步都能独立验证出问题好定位先跑通模型服务。用最简单的 prompt 验证模型能正常响应再验证工具调用格式。再接一个 MCP 工具。选最简单的工具比如读文件验证 Agent 能调用并拿到结果。然后写一个 Skill。把读文件分析输出串成一个 Skill验证流程能跑通。最后加主循环和兜底。加上步数限制、失败处理、上下文压缩验证长任务能稳定完成。补日志和监控。在每一步都加上结构化日志方便后续排查。不要一上来就把所有东西堆在一起那样出问题你根本不知道是哪一层的问题。7.2 配置清单下面是我在实际项目里用的配置清单可以直接作为起点配置项建议值说明模型上下文窗口≥32K低于 16K 长任务很难跑最大步数15-25按任务复杂度调整单步超时60s工具调用超时时间上下文压缩阈值70% 窗口超过就压缩工具重试次数2仅对可重试错误日志级别INFO关键事件全记Skills 同步间隔任务开始时不要每步都同步7.3 几个容易忽略的细节时间同步。内网机器时间不同步会导致日志时间戳错乱排查时很痛苦。部署前确认所有节点时间一致。字符编码。内网环境里文件编码可能不统一工具读取文件时要显式指定编码否则中文内容会乱码模型看到乱码直接懵。路径规范。内网工具访问文件时路径要统一用绝对路径相对路径在不同工作目录下行为不一致容易出问题。资源隔离。Agent 跑任务时可能占用大量 CPU 和内存如果和推理服务在同一台机器要限制 Agent 的资源使用避免把推理服务拖垮。版本锁定。内网环境里依赖升级很麻烦所有组件版本要锁定并记录避免某次更新导致整个链路不可用。8. 一些个人体会这套东西我在几个项目里反复搭过最大的感受是内网 Agent 的难点从来不是模型本身而是工程约束下的系统设计。外网环境里你可以用各种现成服务把问题绕过去内网里你只能正面解决。模型能力不够就用 Skill 把流程约束住上下文不够就用压缩策略腾空间工具不稳定就用重试和兜底扛住。另一个体会是MCP 和 Skills 这套体系在内网里的价值比外网更大。外网工具生态丰富MCP 只是锦上添花内网工具都是自研的、散乱的MCP 提供的统一抽象能省掉大量适配工作。Skills 则把团队经验固化下来让 Agent 的行为可预期、可复现。最后分享一个小技巧内网 Agent 上线前一定要用一批已知答案的任务做回归测试。比如让它找某个已知函数的调用点、生成某个已知模块的测试、整理某份已知文档的摘要。每次改 Skill 或换模型都跑一遍这批任务对比结果。这样能在上线前发现大部分回归问题比等用户反馈再修要主动得多。