ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

隔离内网AI Agent工程化落地:MCP Tools与Skills编排及审批机制实践

隔离内网AI Agent工程化落地:MCP Tools与Skills编排及审批机制实践 1. 项目缘起与整体设计思路1.1 为什么要在隔离内网里折腾 AI Agent先说清楚这个项目的背景。我所在的团队负责一套内部业务系统的智能化改造这套系统跑在完全隔离的内网环境里——没有外网出口没有公网依赖所有服务都在本地机房闭环运行。业务方希望引入 AI Agent 来接管一部分重复性的工单处理、数据核对和审批流转工作但前提是所有推理、工具调用、状态管理必须在内网完成不能有任何数据出网的路径。这个约束直接砍掉了一大半常规方案。市面上大部分 AI Agent 框架默认假设你能访问云端大模型 API能拉取远程工具市场能动态下载 Skills 包。但在隔离内网里这些假设全部不成立。你得自己解决模型部署、工具注册、技能分发、审批链路这一整套问题。我接手这个项目时的核心判断是隔离内网做 AI Agent难点不在模型本身而在工程链路的闭环设计。模型可以用开源权重本地部署但 Agent 要真正“干活”需要工具调用MCP Tools、技能编排Skills、状态持久化、人工审批介入这些环节在无外网条件下每一个都需要重新设计。适合读这篇内容的人正在或即将在受限网络环境里落地 AI Agent 的工程师、架构师以及对 MCP Tools、Skills 机制、审批机制感兴趣的技术同学。如果你只是想在公网环境跑个 demo这篇的很多约束对你可能过重但工程思路依然有参考价值。1.2 整体架构选型与取舍逻辑我把整个系统拆成四层从下往上分别是模型推理层、工具接入层、技能编排层、审批交互层。这个分层不是拍脑袋定的而是根据隔离内网的实际约束倒推出来的。模型推理层选的是本地部署的开源模型通过兼容 OpenAI 接口的推理服务暴露能力。为什么不用更轻量的方案因为 Agent 场景对模型的指令遵循能力和工具调用格式要求很高太小的模型在解析 MCP Tools 的 JSON Schema 时错误率飙升反而增加调试成本。实测下来参数量在可接受范围内的模型配合量化部署能在内网单卡环境跑出可用的响应速度。工具接入层是整个项目的核心。我采用MCPModel Context ProtocolTools作为工具注册和调用的统一协议。选 MCP 的理由很直接它把工具的输入输出用 JSON Schema 描述清楚模型只需要按 schema 生成调用参数工程侧只需要按 schema 做参数校验和路由。在隔离内网里这意味着我可以用一套标准协议把内部系统的各种接口数据库查询、工单创建、审批提交包装成工具而不需要为每个工具写一套适配代码。技能编排层对应的是Skills机制。Skills 在我的理解里不是简单的函数封装而是面向业务场景的工具组合与流程编排。比如“工单自动分类并派发”这个 Skill内部可能依次调用“读取工单内容”“查询分类规则”“匹配处理人”“提交派发请求”四个 MCP Tools。Skills 的价值在于把原子化的工具调用组织成有业务语义的完整动作让 Agent 的决策粒度更接近真实工作流。审批交互层是隔离内网场景下必须单独设计的一层。原因很简单内网业务系统对操作合规性要求极高Agent 不能自主执行所有动作关键节点必须有人工审批。我的设计是在 Skill 执行链路中插入审批断点当某个 Skill 标记为“需审批”时Agent 执行到该节点会挂起生成审批任务推送给对应人员审批通过后从断点恢复执行。这个机制后面会详细展开。注意隔离内网环境下所有依赖必须提前离线准备。模型权重、Python 包、工具依赖库、Skills 包全部需要在有网环境打包后导入。我踩过的坑是低估了依赖的传递性第一次导入时漏了一个底层库导致工具调用链在运行时才报错。2. 核心细节解析与实操要点2.1 MCP Tools 在内网环境下的注册与发现MCP Tools 的核心价值是用标准协议描述工具能力让模型知道有哪些工具可用、每个工具需要什么参数、返回什么结构。在公网环境里工具发现可能是动态的但在隔离内网里我采用的是静态注册 启动时加载的方式。具体做法是在项目配置目录下维护一个tools_registry.json文件每个工具条目包含工具名称、描述、输入参数的 JSON Schema、输出结构说明、以及实际调用的后端服务地址。Agent 服务启动时读取这个文件把所有工具注册到内存中的工具表中。模型在推理时系统会把工具表转换成模型能理解的格式注入到提示词里。这里有个关键细节工具描述的质量直接决定模型调用准确率。我试过把工具描述写得非常技术化比如“执行 SQL 查询并返回结果集”结果模型经常在不需要查询的时候也去调这个工具。后来改成“根据工单编号查询该工单的历史处理记录仅在需要了解工单背景时使用”调用准确率明显提升。工具描述要回答三个问题这个工具做什么、什么时候该用、什么时候不该用。参数 Schema 的设计也有讲究。我最初把所有参数都设为必填结果模型在信息不全时强行编造参数值。后来改成区分必填和可选并在描述里说明缺省行为模型的表现稳定了很多。比如“处理人 ID”设为可选描述里写明“不填则按规则自动匹配”模型在不确定时就会选择不填而不是瞎猜一个。2.2 Skills 的编排设计与版本管理Skills 在我的项目里是一组有业务含义的工具调用序列加上执行条件、审批标记、异常处理策略。一个 Skill 的定义包含以下字段字段说明示例skill_id技能唯一标识ticket_auto_dispatchskill_name业务名称工单自动派发trigger触发条件描述当新工单创建且分类为“网络故障”时steps工具调用步骤列表见下方步骤说明approval_required是否需要审批trueapproval_role审批角色网络组组长fallback异常回退策略转人工处理steps 字段是一个有序列表每个步骤指定调用哪个 MCP Tool、参数如何从上一步结果或上下文中提取、失败时是否中断。这里的设计难点在于参数传递的表达。我采用了一种简单的模板语法用{{step_1.output.ticket_id}}这样的占位符引用前序步骤的输出。解析器在执行时按顺序替换占位符再调用对应工具。版本管理是隔离内网里容易被忽视的问题。Skills 定义会随业务规则变化而更新但内网环境没有 Git 服务器我采用的是文件版本号 变更日志的方式。每个 Skill 文件头部记录版本号和修改说明加载时校验版本兼容性。如果新版本 Skill 依赖了旧版本不存在的工具启动时会直接报错避免运行时才发现问题。实操心得Skills 的步骤不要设计得太长。我最初有一个 Skill 包含 12 个步骤结果调试时极难定位问题。后来拆成多个小 Skill每个不超过 5 步通过 Skill 之间的调用来组合可维护性大幅提升。2.3 审批机制的断点设计与状态恢复审批机制是隔离内网 AI Agent 项目里最容易被低估的部分。公网环境的 Agent 可以比较激进地自主执行但内网业务系统往往有严格的操作审计要求关键动作必须有人工确认。我的设计思路是把审批看作 Skill 执行过程中的一个特殊步骤。当 Skill 执行到标记了approval_required的步骤时系统不会直接调用工具而是做三件事第一把当前执行上下文已完成步骤的结果、待执行步骤的参数序列化第二生成一条审批任务记录包含操作描述、影响范围、待执行参数第三把任务推送到审批队列Agent 执行状态置为“等待审批”。审批人处理任务时看到的是结构化的操作预览可以选择通过、驳回或修改参数后通过。审批结果回写后系统根据审批结论决定是继续执行、终止还是带修改参数重新执行。这里的关键是状态序列化和恢复的可靠性。我采用的是把执行上下文存到内网数据库的审批任务表中恢复时从表中读取并反序列化。实测下来只要序列化格式稳定跨服务重启的恢复也能正常工作。审批超时是另一个需要处理的场景。我设置了审批超时时间超时后自动驳回并触发 fallback 策略。fallback 通常是转人工处理同时记录一条告警日志。这个设计避免了审批任务被遗忘导致 Agent 长时间挂起。3. 实操过程与核心环节实现3.1 内网模型服务的部署与接口适配模型部署是整个链路的第一步。我选用的推理服务支持兼容 OpenAI 的接口格式这样 Agent 框架侧不需要做特殊适配。部署时需要注意几个参数上下文长度Agent 场景下提示词里要注入工具表、Skill 定义、历史对话上下文消耗比普通对话大得多。我最初设的 4K 上下文结果工具表一注入就满了。后来调到 16K才勉强够用。如果模型支持更长上下文建议直接上 32K。并发数内网单卡环境下并发数不能设太高否则显存溢出。我实测下来量化后的模型在单卡上设 4 到 8 个并发比较稳。再高会出现请求排队超时。超时时间Agent 调用工具后需要把结果送回模型继续推理这个往返时间要算进超时里。我把单次推理超时设为 60 秒工具调用超时单独设为 30 秒。接口适配层我写了一个轻量的代理服务负责把 Agent 框架的请求转发到本地推理服务同时做请求日志记录和限流。限流是为了防止某个 Skill 异常循环调用把模型服务打满。代理服务用 Python 的 FastAPI 实现代码量不大但起到了关键的隔离作用。# 简化的推理代理核心逻辑 from fastapi import FastAPI, HTTPException import httpx app FastAPI() MODEL_ENDPOINT http://localhost:8000/v1/chat/completions SEMAPHORE asyncio.Semaphore(6) # 并发控制 app.post(/agent/infer) async def infer(request: dict): async with SEMAPHORE: async with httpx.AsyncClient(timeout60.0) as client: resp await client.post(MODEL_ENDPOINT, jsonrequest) if resp.status_code ! 200: raise HTTPException(status_code502, detailmodel service error) return resp.json()3.2 工具调用的参数校验与错误处理工具调用是 Agent 真正“干活”的环节也是最容易出问题的地方。我的经验是永远不要相信模型生成的参数是合法的。必须在工具执行前做严格的参数校验。校验分两层。第一层是 Schema 校验检查参数类型、必填项、枚举值范围。这一层用 JSON Schema 校验库自动完成。第二层是业务校验比如工单编号格式是否正确、处理人 ID 是否在有效范围内。这一层需要针对每个工具写校验逻辑。错误处理策略我定了三条规则参数校验失败时把错误信息返回给模型让它重新生成参数最多重试两次工具执行超时时记录日志并返回超时错误由 Skill 的 fallback 策略决定后续动作工具返回业务错误时把错误码和错误描述返回给模型让它判断是重试还是放弃。这里有个细节值得展开模型收到错误信息后重新生成参数的能力很大程度上取决于错误信息的质量。我最初返回的错误信息是“参数错误”模型完全不知道该怎么改。后来改成“ticket_id 格式应为 ‘TK-’ 开头加 8 位数字你提供的是 ‘12345’”模型第二次就能生成正确格式。错误信息要具体到可操作的程度。3.3 Skill 执行引擎的实现与审批断点插入Skill 执行引擎是我自己写的一个轻量状态机。核心逻辑是按顺序遍历 steps对每个 step 解析参数模板、调用工具、保存结果。遇到审批断点时序列化上下文并挂起。参数模板解析我用的是简单的字符串替换没有引入复杂的模板引擎。原因是内网环境依赖越少越好而且我的模板语法很简单就是{{step_N.output.field}}和{{context.field}}两种形式。解析时用正则匹配占位符然后从执行上下文中取值替换。审批断点的插入逻辑是这样的在执行每个 step 之前检查该 step 是否标记了approval_required。如果是先检查当前执行是否已经获得了该 step 的审批通过记录。如果没有就创建审批任务并挂起。审批通过后恢复执行时跳过审批检查直接执行该 step。# Skill 执行引擎核心逻辑简化 def execute_skill(skill_def, context): for step in skill_def[steps]: if step.get(approval_required) and not context.get(fapproved_{step[id]}): task_id create_approval_task(skill_def, step, context) save_execution_state(context, task_id) return {status: pending_approval, task_id: task_id} params resolve_params(step[params], context) validate_params(step[tool], params) result call_tool(step[tool], params) context[fstep_{step[index]}_output] result return {status: completed, context: context}审批任务的推送我走的是内网消息队列审批人通过内部工单系统处理。审批通过的回调会触发执行引擎从保存的状态恢复。这里要注意状态恢复的幂等性如果回调重复触发不能导致 Skill 重复执行。我的做法是在恢复执行前检查该审批任务是否已经被处理过已处理则直接返回。3.4 内网环境下的日志与可观测性建设隔离内网没有现成的可观测性平台但 Agent 系统的调试又极度依赖日志。我的做法是结构化日志 本地日志文件 简易查询接口。每条日志记录包含时间戳、会话 ID、Skill ID、步骤序号、事件类型推理请求、工具调用、审批创建、状态恢复、耗时、结果摘要。日志以 JSON Lines 格式写入本地文件按天滚动。同时提供一个简单的 HTTP 接口支持按会话 ID 或 Skill ID 查询日志。这个简易方案在排查问题时非常有用。有一次一个 Skill 在生产环境执行失败我通过会话 ID 查到日志发现是某个工具返回的数据结构在特定条件下多了一层嵌套导致参数模板解析失败。如果没有结构化日志这种问题很难定位。提示日志中不要记录完整的模型输入输出数据量太大且可能包含敏感信息。我记录的是输入输出的哈希值和长度需要详细内容时再通过会话 ID 去专门的调试日志里查。4. 常见问题与排查技巧实录4.1 模型不调用工具或乱调用工具怎么办这是 Agent 项目里最高频的问题。表现有两种该调用工具的时候模型直接编造答案或者不该调用的时候频繁调用。排查思路分三步。第一步检查工具描述是否清晰。我遇到过一次模型不调用查询工具原因是工具描述写的是“查询数据”模型不知道查的是什么数据。改成“根据工单编号查询工单的详细信息和历史处理记录”后调用率正常了。第二步检查提示词里是否明确要求了工具使用。我在系统提示词里加了一句“当需要获取实时数据时必须调用工具不要依赖已有知识编造”乱编答案的情况明显减少。第三步检查模型能力是否足够。如果前两步都排查了还是不行可能是模型本身的指令遵循能力不足需要考虑换更大的模型或做微调。乱调用工具的情况通常是工具描述里没有说明“什么时候不该用”。我在每个工具描述末尾都加了一句使用限制比如“仅在用户明确要求查询工单时使用不要主动调用”。这个改动效果很好。4.2 审批断点恢复后执行状态错乱这个问题我踩过坑。表现是审批通过后Skill 恢复执行时从错误的步骤开始或者重复执行了已完成的步骤。根因是状态序列化和反序列化不一致。我最初序列化时只保存了已完成步骤的结果没有保存当前执行到第几步。恢复时引擎不知道从哪继续就从头开始执行了。修复方法是序列化时显式保存current_step_index恢复时从该索引继续。另一个坑是审批期间上下文被其他操作修改。比如审批等待期间同一个工单被人工处理了Agent 恢复执行时基于的是旧上下文。我的处理方式是在恢复执行前做一次前置校验检查关键业务数据是否发生变化如果变化则终止执行并记录冲突日志。4.3 工具调用超时与重试策略内网环境下后端服务响应时间波动较大工具调用超时是常态。我的策略是区分超时类型连接超时服务不可达直接失败不重试读取超时服务处理慢重试一次重试仍超时则失败。重试时要考虑幂等性。查询类工具重试没有副作用但创建类工具重试可能导致重复创建。我的做法是在工具定义里标记idempotent字段只有幂等工具才允许自动重试。非幂等工具超时后返回错误由 Skill 的 fallback 策略处理。下面是我整理的常见问题速查表问题现象可能原因排查方法解决措施模型不调用工具工具描述模糊检查工具描述是否说明用途和时机补充具体的使用场景描述模型乱调用工具缺少使用限制查看工具描述有无“不该用”的说明添加使用限制语句参数校验失败模型生成参数格式错误查看错误日志中的参数值优化错误信息让模型能自我纠正审批恢复后状态错乱序列化不完整检查保存的执行上下文显式保存步骤索引恢复前做前置校验工具调用超时后端服务响应慢查看工具调用耗时日志区分超时类型幂等工具才重试Skill 执行卡住审批任务未处理检查审批队列设置审批超时超时自动 fallback4.4 内网依赖导入的避坑清单隔离内网项目最烦人的就是依赖问题。我整理了一份避坑清单都是实际踩过的坑Python 包依赖不要只导出requirements.txt要用pip download把所有包及其依赖下载到本地目录再整体导入。我漏过一次间接依赖运行时才报错。模型权重文件确认推理框架的版本和模型权重的格式匹配。我遇到过一次框架升级后权重格式不兼容重新转换花了半天。工具依赖的系统库有些工具依赖系统级的库如数据库驱动、加密库这些不在 Python 包管理范围内需要单独确认。Skills 包的版本兼容Skills 定义中引用的工具名称和参数 Schema 必须与工具注册表一致。我建议在启动时做一次全量校验不一致直接报错。实操心得我现在的做法是在有网环境维护一个完整的离线包目录每次变更后重新打包并记录版本号。导入内网后先跑一遍自检脚本确认所有依赖可加载、所有工具可注册、所有 Skill 可解析再启动服务。这个自检脚本省了我大量排查时间。5. 一些关于扩展方向的个人想法这套系统跑稳定之后我陆续做了一些扩展尝试。一个是把 Skills 的触发条件从固定规则改成模型判断让 Agent 根据当前上下文自主决定调用哪个 Skill而不是等外部触发。这个改动让系统的主动性提升了不少但也带来了新的问题模型选错 Skill 的情况时有发生。我的应对是给每个 Skill 加上适用场景描述并在模型选择后增加一层规则校验明显不匹配的直接拦截。另一个扩展方向是审批机制的粒度细化。目前是 Skill 级别的审批后来我尝试做到步骤级别的审批即同一个 Skill 里不同步骤可以有不同的审批要求。这个改动让审批更灵活但也增加了配置复杂度。我的建议是先从 Skill 级别做起等业务方对审批节奏有明确诉求后再细化。还有一个正在验证的方向是多 Agent 协作。把不同业务域的 Skill 分给不同的 Agent 实例通过内网消息队列做任务分发和结果汇总。这个方案在理论上能提升吞吐但实际调试中发现 Agent 之间的状态同步和冲突处理比预想复杂目前还在小范围试点。最后分享一个我在内网环境做 AI Agent 最深的体会约束越多设计越要简单。公网环境可以靠丰富的生态和动态发现来解决问题内网环境里每一个额外依赖都是风险。把工具协议定死、把 Skill 编排做简单、把审批链路做可靠比追求花哨的 Agent 自主性重要得多。我见过太多项目在演示阶段很惊艳一到内网落地就因为依赖问题、审批问题、状态问题卡住。工程上的稳比智能上的炫更有价值。
返回列表