1. 为什么要在隔离内网里折腾 AI Agent先把场景说清楚。所谓“隔离内网”就是一台或者一批机器物理上或者逻辑上跟公网断开只能在内网里互相通信外部的 API、模型服务、包管理仓库统统够不着。很多做金融、制造、政务信息化的团队都是这种环境代码不能出网数据不能出网连pip install都得走内部镜像源。在这种地方谈 AI Agent第一反应往往是“不可能”因为大家习惯了 Agent 背后挂一个云端大模型靠 function calling 去调各种在线工具。但真实需求是存在的。比如内网里有一套工单系统、一套审批流、一堆数据库表运维和业务人员每天要重复查状态、走流程、填表单。你完全可以让一个 Agent 在内网里把这些活儿接过去模型跑在内网的推理服务上工具通过 MCP 协议或者本地 Skills 注册进来整个链路不出网。这就是“隔离内网下 AI Agent 工程实战”要解决的核心问题在没有任何外网依赖的前提下把 Agent 的感知、决策、执行三段打通并且让它稳定跑在生产环境里。这篇文章适合谁看如果你是有一定后端基础、正在或者准备在内网环境落地 Agent 的工程师那基本可以照着抄作业如果你只是听说过 Agent 想了解它到底怎么落地也能从里面看到真实的工程细节而不是 demo 级别的玩具。我会把选型逻辑、目录结构、MCP Tools 和 Skills 的写法、审批流的接入、并发处理、以及一堆踩过的坑都摊开讲。核心关键词就几个AI Agent、MCP Tools、Skills、内网系统、审批后面所有内容都围绕它们展开。先说一个我自己的判断内网 Agent 的难点从来不是模型本身而是工具怎么注册、权限怎么收口、审批怎么卡点、并发怎么扛住。模型能力再强工具调不通、审批绕不过这个 Agent 就是废的。所以下面的内容重心会放在工程侧而不是提示词调优。2. 整体架构设计与选型思路2.1 内网 Agent 的分层结构我最终落地的架构分四层从下往上依次是模型推理层、Agent 编排层、工具与技能层、业务接入层。这个分层不是拍脑袋定的而是被内网环境逼出来的。模型推理层跑在内网的 GPU 机器上用 vLLM 或者类似的推理框架起一个 OpenAI 兼容的接口。为什么强调“OpenAI 兼容”因为绝大多数 Agent 框架默认对接的就是这套协议你只要把 base_url 指到内网地址框架代码几乎不用改。这一步是整个方案能成立的前提模型必须内网自持。Agent 编排层是大脑负责接收用户输入、规划步骤、决定调哪个工具、处理工具返回、生成最终答复。这一层我选的是 LangGraph 思路的编排方式原因后面细说。工具与技能层是手脚MCP Tools 负责对接外部系统数据库、HTTP 接口、消息队列Skills 负责封装可复用的能力单元比如“查审批状态”“生成周报”。业务接入层就是内网里那些真实系统工单、审批、CMDB、监控告警。提示分层的目的不是好看而是让每一层可以独立替换。模型换了不影响工具工具换了不影响编排逻辑这在长期维护里能省大量返工。2.2 为什么选 MCP Tools 而不是硬编码函数早期我试过最土的办法在 Agent 代码里直接写一堆 Python 函数每个函数对应一个内网操作然后把这些函数塞进工具的列表里。跑 demo 没问题一上规模就崩。问题有三个第一工具和 Agent 代码耦合死加一个工具要改主流程第二工具的入参出参没有统一描述模型经常传错参数第三权限控制没法做任何工具一旦注册就全局可用。MCPModel Context Protocol解决的正是这几个问题。它把工具的定义标准化成一份带 schema 的描述Agent 通过协议去发现和调用工具工具本身可以独立部署、独立鉴权。在内网里你可以起一个 MCP Server 专门管数据库工具再起一个管审批工具Agent 只认协议不认实现。这样加工具就是加一个 Server主流程一行不改。选 MCP 还有一个现实原因生态在往这个方向收敛。不管是哪家的 Agent 框架对 MCP 的支持都在变好你现在按 MCP 写工具将来换框架迁移成本最低。内网项目最怕的就是技术债选一个正在成为事实标准的协议比选一个当下最顺手的私有方案要稳。2.3 Skills 的定位把“会做的事”沉淀下来MCP Tools 解决的是“能调什么”Skills 解决的是“会做什么”。这两者容易混我一开始也混了。举个具体例子查审批状态是一个 Tool它只负责发一个请求拿回原始数据而“判断一个审批是否超时、超时了该催谁、催的时候话术怎么组织”这一整套逻辑是一个 Skill。Tool 是原子能力Skill 是编排好的能力包。Skills 的价值在于复用和可维护。内网里很多操作是重复的比如“根据工单号拉详情、判断优先级、决定是否升级、生成通知”。如果每次都让模型现场规划既慢又不稳定。把它固化成一个 Skill模型只需要决定“现在该用这个 Skill”剩下的步骤由 Skill 内部确定性执行。这样既降低了模型负担又提高了结果一致性。我用的 Skills 组织方式是每个 Skill 一个目录里面放一份描述文件说明这个 Skill 干什么、什么时候用、需要什么参数和一份执行逻辑。Agent 启动时扫描 Skills 目录把描述注入到系统提示里模型就能知道有哪些技能可用。这套机制在多个框架里都有对应实现核心思想一致。2.4 审批环节为什么必须单独设计内网系统里审批是绕不开的。Agent 可以自动查数据、自动生成内容但一旦涉及“改状态”“发通知”“提交申请”这类有副作用的操作就必须过审批。这不是技术问题是合规问题。我见过有团队图省事让 Agent 直接调写接口结果误操作把一批工单状态改了事后追责都找不到人。我的做法是把审批做成 Agent 执行链路里的一个强制卡点。具体来说凡是标记为“需审批”的 ToolAgent 调用时不会直接执行而是生成一条待审批记录推给审批人审批通过后才真正执行。审批流本身用内网已有的工作流引擎Java 1.8 环境下可用的开源审批工作流就够用Agent 只负责发起和查询不负责审批逻辑。这样职责清晰也符合内网的既有规范。3. 核心细节解析与实操要点3.1 内网模型服务的对接细节模型服务这块坑主要集中在接口兼容性和上下文长度上。内网推理服务起好之后先确认它暴露的是不是标准的/v1/chat/completions接口请求体和返回体字段是否和主流协议一致。有些自研推理框架字段名会不一样比如把max_tokens写成max_new_tokens这种就得在 Agent 侧做一层适配。上下文长度要提前算清楚。内网模型往往是量化过的上下文窗口可能只有 8K 或者 16K。Agent 的系统提示、工具描述、历史对话、工具返回结果全都要塞进这个窗口。我的经验是工具描述尽量精简只保留模型决策必需的信息工具返回结果做截断比如数据库查询只返回前 20 行加一个总数。不然跑几轮对话就爆窗口模型开始胡言乱语。还有一个细节是超时设置。内网推理服务如果并发高响应会变慢。Agent 调用模型的超时不能设太短否则正常请求也被判失败也不能太长否则一个卡住的请求会拖垮整个会话。我一般设 60 秒配合重试一次。重试要幂等也就是同样的输入重发不会产生副作用这点在调用模型时天然满足。3.2 MCP Tools 的注册与鉴权写一个 MCP Tool 的流程大概是定义工具名和描述、定义入参 schema、实现执行逻辑、注册到 MCP Server。描述这块要下功夫模型是靠描述来决定用不用这个工具的。描述里要写清楚“这个工具做什么”“什么场景下用”“参数是什么含义”。我见过描述写得太简略模型该调的时候不调不该调的时候乱调。鉴权是内网 Agent 的重点。我的方案是每个 Tool 调用都带一个身份标识这个标识来自当前会话的用户而不是 Agent 自己的服务账号。这样工具侧可以根据用户身份做权限判断Agent 无法越权。实现上Agent 在发起工具调用时把用户 token 透传下去MCP Server 校验 token 后再执行。这样即使 Agent 被诱导去调一个它不该调的工具工具侧也会拒绝。注意千万不要给 Agent 配一个万能的服务账号。一旦这个账号泄露或者被滥用整个内网系统就裸奔了。透传用户身份是底线。3.3 Skills 的编写规范Skills 写得好不好直接决定 Agent 稳不稳。我的规范是三条单一职责、确定性执行、可观测。单一职责是说一个 Skill 只干一件事。“处理工单”这种太宽泛应该拆成“查询工单详情”“判断工单优先级”“生成工单通知”三个 Skill。拆细了模型更容易选对出问题也更容易定位。确定性执行是说 Skill 内部的步骤要写死不要在里面再让模型自由发挥。Skill 是编排好的流程输入确定、输出确定。如果 Skill 内部还要模型决策那就失去了固化的意义不如让主 Agent 直接规划。可观测是说每个 Skill 执行都要打日志记录输入、输出、耗时、成功失败。内网排查问题全靠日志没有日志的 Skill 等于黑盒。我一般会在 Skill 执行前后各打一条日志中间关键步骤也打点出问题时能快速定位是哪一步挂了。3.4 审批卡点的实现方式审批卡点的实现核心是“拦截 挂起 恢复”。Agent 调用一个需审批的 Tool 时不直接执行而是生成审批单把这次调用的上下文谁发起的、要调什么工具、参数是什么存下来然后返回一个“已提交审批”的结果给 Agent。Agent 告诉用户“操作已提交等待审批”。审批通过后需要一个机制把这次调用恢复执行。我的做法是审批系统在通过时回调 Agent 的一个接口Agent 根据存下来的上下文重新发起工具调用这次带上“已审批”标记工具侧放行。整个链路里Agent 是无状态的状态存在审批单里这样 Agent 重启也不影响待审批的任务。这里有个容易忽略的点审批超时怎么办。审批单不能无限期挂着得设一个过期时间过期自动作废并通知发起人。不然一堆僵尸审批单堆在那里既占资源又容易误批。4. 实操过程与核心环节实现4.1 环境准备与目录结构内网环境准备的第一步是确认 Python 版本和依赖。我用的 Python 3.10因为很多 Agent 框架对 3.8 支持在减弱3.10 是比较稳的选择。依赖全部走内网镜像源提前把需要的包同步进来。这一步没什么技巧就是耐心把requirements.txt里的包一个个确认内网源里有。目录结构我按功能划分大致是这样agent/ config/ # 配置文件模型地址、超时、日志级别 core/ # Agent 编排主逻辑 mcp_servers/ # 各个 MCP Server db_server/ approval_server/ skills/ # 各个 Skill query_ticket/ judge_priority/ adapters/ # 模型接口适配层 logs/ # 日志输出这个结构的好处是每一块都能独立测试。MCP Server 可以单独起起来用 curl 测Skill 可以单独写单测跑不用每次都把整个 Agent 拉起来。内网调试成本高能单独测的绝不整体测。4.2 模型适配层的代码实现适配层的职责是把内网模型的接口包装成 Agent 框架认识的格式。核心是一个 chat 方法接收消息列表返回模型回复。下面是一个简化版的实现import requests class InternalLLM: def __init__(self, base_url, model_name, timeout60): self.base_url base_url.rstrip(/) self.model_name model_name self.timeout timeout def chat(self, messages, toolsNone): payload { model: self.model_name, messages: messages, temperature: 0.1, } if tools: payload[tools] tools resp requests.post( f{self.base_url}/v1/chat/completions, jsonpayload, timeoutself.timeout, ) resp.raise_for_status() return resp.json()[choices][0][message]temperature设 0.1 是为了让输出稳定Agent 场景不需要创意需要的是可预测。tools参数是可选的有些内网模型不支持 function calling那就得走提示词里描述工具、让模型输出特定格式再由代码解析的路子。这两种方式我都用过支持原生 function calling 的优先用原生稳定得多。4.3 一个完整的 MCP Tool 实现以“查询工单详情”为例走一遍完整实现。工具定义部分TOOL_DEFINITION { name: query_ticket, description: 根据工单号查询工单的详细信息包括状态、优先级、创建时间、处理人。当用户询问某个工单的情况时使用。, input_schema: { type: object, properties: { ticket_id: { type: string, description: 工单编号格式如 TK-20240101-001 } }, required: [ticket_id] } }执行逻辑部分关键是鉴权和错误处理def execute(ticket_id, user_token): if not check_permission(user_token, ticket:read): return {error: 无权限查询工单} try: row db.query_one( SELECT * FROM tickets WHERE ticket_id %s, (ticket_id,) ) if not row: return {error: f工单 {ticket_id} 不存在} return { ticket_id: row[ticket_id], status: row[status], priority: row[priority], created_at: str(row[created_at]), assignee: row[assignee], } except Exception as e: log.error(fquery_ticket failed: {e}) return {error: 查询失败请稍后重试}注意返回结果里不要带敏感字段比如内部备注、客户联系方式。工具返回什么模型就能看到什么也会原样转述给用户。返回前做一层字段过滤是必要的。4.4 审批流的接入代码审批接入分两步发起和执行。发起部分Agent 检测到工具需要审批时不执行工具而是调审批接口def request_approval(tool_name, params, user_token): approval_id approval_client.create( titlefAgent 请求执行 {tool_name}, detailjson.dumps(params, ensure_asciiFalse), applicantget_user_from_token(user_token), expire_hours24, ) return { status: pending_approval, approval_id: approval_id, message: 操作已提交审批请等待审批人处理 }执行部分审批通过后的回调def on_approval_passed(approval_id): record approval_store.get(approval_id) if not record or record[status] ! pending: return result execute_tool( record[tool_name], record[params], record[user_token], approvedTrue, ) notify_user(record[user], result) approval_store.mark_done(approval_id)这里approvedTrue是关键工具侧看到这个标记才放行。整个链路里审批单是唯一的状态载体Agent 本身不存状态这样水平扩展 Agent 实例时不会有状态同步问题。4.5 并发处理的实际做法“AI Agent 怎么扛并发”是个高频问题。我的答案是Agent 本身要无状态把并发压力分散到各个无状态实例上前面挂一个负载均衡。会话状态存 Redis工具调用走异步审批走队列。具体来说每个用户请求进来负载均衡分给某个 Agent 实例实例从 Redis 拉会话历史处理完写回 Redis。这样任何一个实例挂了请求转到别的实例照样能继续。工具调用如果是耗时的不要同步等扔到消息队列里异步处理处理完再回调通知。审批本身就是异步的天然适合队列。实测下来单实例在 4 核 8G 的机器上配合内网模型服务能稳定支撑几十路并发会话。瓶颈通常不在 Agent 代码而在模型推理服务的吞吐。所以并发上不去的时候先看模型服务是不是排队了别急着优化 Agent。5. 常见问题与排查技巧实录5.1 模型不调工具或者乱调工具这是最高频的问题。表现是模型该调工具的时候不调或者调了一个完全不相关的工具。排查顺序是这样先看工具描述是不是太模糊模型看不懂什么时候该用再看工具数量是不是太多一次给模型几十个工具它选择困难最后看系统提示里有没有明确告诉模型“遇到 X 情况必须用 Y 工具”。我的经验是工具数量控制在 10 个以内超过就分组按场景动态加载。比如用户问工单相关的问题只加载工单类工具别把审批、监控的工具全塞进去。这样模型选择范围小准确率明显提升。5.2 工具返回结果太长导致上下文爆炸数据库查询返回几百行模型上下文直接爆掉。解决办法是在工具侧做截断和摘要。查询类工具默认返回前 20 行同时返回总数让模型知道“还有更多”。如果模型需要更多再让它带分页参数重新查。这样既控制了上下文又保留了继续查的能力。5.3 审批卡住导致会话挂起用户提交了审批然后就没下文了。排查发现是审批回调没配好审批通过了但 Agent 不知道。解决办法是加一个轮询兜底Agent 定期扫描自己发起的、状态还是 pending 的审批单发现已通过的主动恢复执行。回调是快路径轮询是慢路径两条路都通才稳。5.4 内网依赖缺失导致启动失败内网装包是个老大难。我的做法是提前把所有依赖打成离线包包括间接依赖放到内网源里。启动前先跑一个依赖检查脚本缺什么一目了然。另外注意有些包在安装时会去外网拉东西这种要在内网源里提前准备好对应的资源或者找替代包。下面这张表是我整理的高频问题速查问题现象可能原因排查动作解决方式模型不调工具描述模糊/工具过多检查工具描述和数量精简描述分组加载上下文爆炸工具返回过长看工具返回体大小截断分页审批不恢复回调未配/失败查审批回调日志加轮询兜底启动失败依赖缺失跑依赖检查脚本补离线包并发上不去模型服务排队看模型服务指标扩推理实例权限报错token 未透传查工具侧鉴权日志透传用户身份5.5 几个只有踩过才知道的坑第一个坑内网模型的 function calling 支持不完整。有些模型声称支持但实际调用时参数格式不对或者多轮工具调用会丢上下文。遇到这种退回提示词解析方案虽然土但稳。第二个坑审批单的并发。同一个用户短时间内提交多个审批如果审批系统没做幂等可能重复执行。我的做法是审批单带一个业务唯一键重复提交直接返回已有单子。第三个坑日志里别打敏感数据。工具参数里可能有用户信息、内部编号日志脱敏要做在前面别等出事再补。6. 一些关于内网 Agent 的个人体会内网 Agent 这个方向技术上的天花板其实不高难的是把工程细节磨平。我做了几个内网项目下来最大的感受是别追求 Agent 多智能先追求它多可靠。一个只会查工单、走审批的“笨” Agent只要稳定不出错业务方就愿意用一个什么都能聊但时不时调错工具的“聪明” Agent没人敢让它碰生产系统。Skills 的沉淀是长期价值所在。每解决一类问题就把它固化成一个 Skill日积月累Agent 的能力边界是靠 Skills 撑起来的不是靠模型。模型会换Skills 不会白写。最后分享一个小技巧内网 Agent 上线前一定要做一轮“对抗测试”故意用模糊的、带歧义的、甚至诱导性的输入去试它看它会不会越权、会不会调错工具、会不会绕过审批。这轮测试暴露的问题比正常测试多得多。我每次上线前都跑一遍基本每次都能揪出几个隐患。