1. 为什么“隔离内网”是 AI Agent 工程化的分水岭
很多人第一次听到“隔离内网下跑 AI Agent”,脑子里冒出来的第一个念头是:不就是把模型部署到一台没有外网的机器上吗,能有多难?我一开始也这么想,直到真正在一个物理隔离的机房里折腾了两周,才发现这件事的复杂度根本不在模型本身,而在于整条工具链的“断网适配”。
所谓隔离内网,通常指与公网完全物理断开、或者仅通过单向通道做数据摆渡的局域网环境。这种环境在金融、制造、能源、科研等行业的内部系统中非常普遍。它的核心特征是:没有外网 DNS、没有公网 IP、无法直接访问任何云端 API、包管理器的默认源不可达、容器镜像需要离线导入。你平时在公网环境下习以为常的pip install、npm install、docker pull,在这里全部失效。
那为什么还要在这种环境里跑 AI Agent?原因很直接:数据不能出去。很多业务场景下的文档、代码、工单、日志,天然带有敏感性,不可能送到公网大模型去推理。但业务方又确实需要 Agent 的自动化能力——比如自动分类工单、自动生成测试用例、自动检索内部知识库、自动执行运维巡检脚本。这就形成了一个矛盾:能力要,数据不能出。
解决这个矛盾的唯一路径,就是把 AI Agent 的整套运行时搬到内网里。这包括模型推理服务、Agent 编排框架、工具调用协议、技能插件、向量数据库、以及最容易被忽视的——依赖包和镜像的离线供给。
我踩过的第一个大坑就在这里。当时我以为只要把模型权重拷进去、装个推理框架就完事了,结果发现 Agent 框架依赖的某个 Python 包又依赖了一个需要编译的 C 扩展,而那个 C 扩展的源码里又引用了外网才能拉取的子模块。一个看似简单的依赖,卡了整整一天。
所以这篇文章不是讲“怎么调模型参数”,而是讲“怎么在断网环境下把一套 AI Agent 工程真正跑起来并且稳定运行”。适合的读者是:需要在隔离环境中落地 Agent 的后端工程师、运维工程师、以及负责内网平台建设的技术负责人。如果你只是想在公网环境玩一玩 Agent,这篇内容可能对你偏重了;但如果你面对的是“机器不能出网”的硬约束,那接下来的内容应该能帮你少走不少弯路。
2. 断网环境下的依赖供给:从“缺什么装什么”到“提前备好一切”
2.1 离线包仓库的搭建逻辑与常见误区
在公网环境下,依赖管理是“按需拉取”;在内网环境下,依赖管理必须变成“提前备货”。这个思维转变听起来简单,但实际操作中有很多细节容易翻车。
最直接的做法是在一台有外网的机器上,把所有需要的 Python 包、Node 包、系统依赖全部下载下来,然后通过物理介质导入内网。但问题在于:你怎么知道“所有需要的包”到底是哪些?
我的做法是分三层来准备。第一层是直接依赖,就是你在requirements.txt或package.json里明确写出来的包。第二层是传递依赖,也就是这些包自己依赖的包。第三层是运行时动态依赖,这类最隐蔽——有些包在特定代码路径下才会 import 某个模块,静态分析根本看不出来。
对于第一层和第二层,可以用pip download配合--platform和--python-version参数做交叉下载。比如目标内网机器是 Linux x86_64 + Python 3.11,那就在外网机器上执行:
pip download -r requirements.txt \ --platform manylinux2014_x86_64 \ --python-version 311 \ --only-binary=:all: \ -d ./offline_packages这里有几个关键点。--platform必须和目标机器的架构完全匹配,否则下载下来的 wheel 包装不上。--only-binary=:all:强制只下载二进制包,避免下载到需要现场编译的源码包——在内网里编译是最容易出问题的环节,因为编译工具链本身可能就不全。如果某个包没有对应平台的 wheel,那就需要单独处理,要么找替代包,要么在外网机器上编译好再打包。
对于第三层动态依赖,我的经验是:不要试图一次性分析清楚,而是采用“迭代补齐”的策略。先在内网跑一遍完整的业务流程,把报错信息收集起来,每遇到一个ModuleNotFoundError就补一个包。通常跑三轮下来,依赖就基本齐了。
注意:
pip download下载的包默认包含所有依赖,但如果你的requirements.txt里有些包是通过-e方式安装的本地包,这些不会被自动打包,需要手动处理。
2.2 容器镜像的离线搬运与层复用技巧
如果 Agent 是跑在容器里的,那镜像的离线搬运就是另一个必须解决的问题。docker pull在内网里是走不通的,需要用docker save和docker load做中转。
基本操作很直接:
# 外网机器上保存镜像 docker save my-agent-image:latest -o agent-image.tar # 内网机器上加载镜像 docker load -i agent-image.tar但这里有个很容易被忽视的问题:镜像体积。一个包含 PyTorch、CUDA 运行时、以及各种 Python 依赖的镜像,动辄好几个 GB。如果每次更新都要完整搬运,效率极低。
我的优化思路是分层搬运。把镜像拆成基础层和业务层:基础层包含操作系统、CUDA、Python 运行时、以及不常变动的核心依赖;业务层只包含 Agent 代码和经常变动的配置。基础层一次搬运到位,后续只更新业务层。这样每次搬运的体积可以从几个 GB 降到几十 MB。
具体做法是写一个多阶段构建的 Dockerfile,把基础环境固化成一个单独的镜像,业务镜像FROM那个基础镜像。更新时只需要重新构建业务层,然后docker save业务镜像即可。不过要注意,docker save默认会保存所有层,如果基础层没有变化,可以用--platform参数配合镜像分层工具做增量导出,但这需要额外的工具支持,在内网环境里不一定方便。更简单的做法是维护一个内网的镜像仓库,比如用 registry 的离线部署方案,把基础镜像推送到内网 registry,业务镜像只推送变更层。
2.3 模型权重的分片传输与校验
模型权重的搬运是另一个大工程。一个 7B 参数的模型,FP16 精度下大约 14GB;如果是 70B 模型,那就直接上百 GB 了。这么大的文件,通过物理介质搬运时,分片和校验是必须的。
分片用split命令就可以:
split -b 2G model_weights.bin model_part_每个分片 2GB,方便拷贝到 U 盘或移动硬盘。到了内网之后用cat合并:
cat model_part_* > model_weights.bin合并之后必须做校验。用sha256sum生成校验值,在外网机器上算一次,内网合并后再算一次,对比一致才能用。我遇到过好几次因为拷贝过程中断导致文件损坏的情况,如果没有校验,后面加载模型时报的错会非常莫名其妙,排查起来很浪费时间。
另外,如果模型是从 Hugging Face 下载的,注意它的权重文件通常是分片的(比如pytorch_model-00001-of-00003.bin),而且有一个pytorch_model.bin.index.json索引文件。搬运时要把整个目录结构保持完整,不能只拷权重文件而漏掉配置文件和 tokenizer 文件。
3. MCP 协议在内网 Agent 中的角色与落地方式
3.1 MCP 到底解决了什么问题
MCP 是 Model Context Protocol 的缩写,它要解决的核心问题是:让 Agent 用一种标准化的方式和外部工具、数据源交互。
在没有 MCP 之前,如果你想让 Agent 调用一个内部 API、查询一个数据库、或者读取一个文件系统,通常的做法是在 Agent 代码里硬编码这些调用逻辑。每接一个新工具,就要改一次 Agent 的代码,耦合度很高。而且不同 Agent 框架之间的工具定义方式还不一样,换一个框架就要重写一遍。
MCP 的思路是把这个交互抽象成一个协议层。工具提供方实现一个 MCP Server,把能力暴露出来;Agent 作为 MCP Client,通过标准协议去发现和调用这些能力。这样 Agent 和工具之间就解耦了,工具可以独立开发、独立部署、独立更新。
在内网环境里,MCP 的价值更加突出。因为内网的工具生态往往是碎片化的——可能有几个不同的内部系统、几套不同的数据库、一些遗留的脚本工具。如果每个都要在 Agent 里单独适配,维护成本会非常高。用 MCP 统一起来之后,Agent 只需要对接 MCP 协议,具体的工具实现由各个团队自己维护。
3.2 内网 MCP Server 的部署要点
在内网部署 MCP Server,首先要确定通信方式。MCP 支持两种主要的传输方式:stdio(标准输入输出)和 SSE(Server-Sent Events)。stdio 方式适合本地进程间通信,Agent 和 MCP Server 在同一台机器上;SSE 方式适合跨机器通信,MCP Server 作为独立的 HTTP 服务运行。
在内网环境里,如果 Agent 和工具在同一台机器上,stdio 是最简单的方式,不需要考虑网络配置和端口开放。但如果工具分布在不同的机器上,那就需要 SSE 方式,这时候要注意几个问题。
第一是服务发现。内网没有公网 DNS,MCP Server 的地址需要手动配置或者通过内网的配置中心下发。我的做法是在 Agent 的配置文件里维护一个 MCP Server 列表,每个 Server 标注名称、地址、端口、以及能力描述。Agent 启动时读取这个列表,逐个建立连接。
第二是认证与鉴权。内网虽然相对封闭,但也不能完全不设防。MCP Server 应该至少有一个简单的 token 认证机制,防止误调用。可以在 HTTP header 里加一个预共享的 token,Agent 和 Server 双方约定好即可。
第三是超时与重试。内网环境虽然延迟低,但服务稳定性不一定比公网好,尤其是那些跑在老旧的内部系统上的工具。MCP Client 端要设置合理的超时时间,并且对可重试的错误做退避重试。我的经验是超时设 30 秒比较合适,太短容易误判,太长会拖慢整个 Agent 的响应。
3.3 用 MCP 封装内部工具的实操示例
假设内网有一个工单系统,提供 REST API 可以查询和创建工单。我们要把它封装成一个 MCP Server,让 Agent 能够调用。
首先定义一个 MCP Server 的基本结构。用 Python 实现的话,可以用mcp这个库:
from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app = Server("ticket-system") @app.list_tools() async def list_tools(): return [ Tool( name="query_ticket", description="根据工单ID查询工单详情", inputSchema={ "type": "object", "properties": { "ticket_id": {"type": "string", "description": "工单编号"} }, "required": ["ticket_id"] } ), Tool( name="create_ticket", description="创建一条新工单", inputSchema={ "type": "object", "properties": { "title": {"type": "string"}, "description": {"type": "string"}, "priority": {"type": "string", "enum": ["low", "medium", "high"]} }, "required": ["title", "description"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "query_ticket": result = await query_ticket_api(arguments["ticket_id"]) return [TextContent(type="text", text=result)] elif name == "create_ticket": result = await create_ticket_api( arguments["title"], arguments["description"], arguments.get("priority", "medium") ) return [TextContent(type="text", text=result)] async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options())这个例子里,list_tools定义了 Agent 可以发现的两个工具,call_tool负责实际执行。query_ticket_api和create_ticket_api是内部实现的函数,负责调用工单系统的 REST API。
部署到内网时,把这个脚本和它的依赖打包,放到目标机器上,然后在 Agent 的配置里注册这个 MCP Server 的启动命令即可。如果是 SSE 方式,把stdio_server换成 SSE 的 server 实现,并指定监听端口。
提示:MCP Server 的工具描述(description)写得越清晰,Agent 选择工具的准确率越高。不要写“查询工单”这种模糊描述,要写“根据工单ID查询工单的详细信息,包括状态、处理人、创建时间”。Agent 是靠这些描述来做决策的。
4. Skills 机制:让 Agent 在内网里“有技能可用”
4.1 Skills 与 MCP 的分工边界
Skills 和 MCP 经常被混为一谈,但它们在 Agent 工程里扮演的角色其实不同。MCP 解决的是“Agent 怎么调用外部能力”的协议问题;Skills 解决的是“Agent 在什么场景下该用什么能力”的知识问题。
打个比方:MCP 像是给 Agent 装了一双手,让它能够操作各种工具;Skills 像是给 Agent 配了一本操作手册,告诉它遇到什么情况该用哪双手、怎么用。
在内网环境里,Skills 的重要性体现在:内网的业务逻辑往往有很强的领域特殊性,通用的 Agent 能力不足以覆盖。比如一个内部的运维 Agent,它需要知道“当磁盘使用率超过 85% 时应该先检查日志目录再清理临时文件”,这种知识不是模型预训练里有的,必须通过 Skills 注入。
Skills 的典型形式是一组结构化的指令文档,包含触发条件、执行步骤、注意事项。Agent 在运行时根据当前上下文匹配相关的 Skill,然后按照 Skill 里的步骤来执行。
4.2 内网 Skills 的编写规范与组织方式
写 Skill 不是写文档,它的读者是 Agent,所以格式和措辞都要针对 Agent 的理解方式来优化。
一个 Skill 通常包含这几个部分:名称、触发条件、前置检查、执行步骤、异常处理、输出格式。触发条件要写得具体,比如“当用户提到‘工单积压’或‘工单超时’时触发”,而不是“当用户需要工单帮助时触发”。执行步骤要写成有序列表,每一步都是一个明确的动作,最好能对应到具体的 MCP 工具调用。
我习惯把 Skills 组织成一个目录结构,每个 Skill 一个 Markdown 文件,文件名就是 Skill 的名称。目录里再放一个索引文件,列出所有 Skill 的名称和一句话描述,Agent 启动时先读索引,需要时再加载具体的 Skill 文件。
# Skill: 工单积压排查 ## 触发条件 - 用户提到"工单积压"、"工单超时"、"待处理工单过多" - 定时任务检测到待处理工单数超过阈值 ## 前置检查 - 确认当前用户有工单系统的查询权限 - 确认工单系统的 MCP Server 连接正常 ## 执行步骤 1. 调用 query_ticket_stats 获取当前各状态工单数量 2. 如果待处理工单数超过 50,调用 query_ticket_list 获取最早的 20 条工单 3. 分析这些工单的优先级和创建时间,识别是否有高优先级工单被积压 4. 调用 generate_report 生成积压分析报告 5. 如果存在高优先级积压,调用 notify 发送告警 ## 异常处理 - 如果 query_ticket_stats 超时,重试一次,仍失败则报告"工单系统暂时不可用" - 如果工单数量为 0,直接返回"当前无积压工单" ## 输出格式 - 积压总数 - 高优先级积压数 - 最早的 5 条工单摘要 - 建议的处理动作这种结构化的写法,Agent 解析起来很顺畅。关键是每一步都要有明确的工具调用对应,不要让 Agent 去“自由发挥”。
4.3 Skills 的版本管理与灰度更新
在内网环境里,Skills 的更新不像公网那么方便,不能随时改随时生效。所以需要一套版本管理机制。
我的做法是给每个 Skill 文件加一个版本号,放在文件头部的元数据里。Agent 加载 Skill 时记录版本号,执行时在日志里带上版本信息。这样出问题的时候可以追溯到具体是哪个版本的 Skill 导致的。
灰度更新方面,可以在 Agent 的配置里加一个 Skill 白名单或黑名单。新版本的 Skill 先只对部分 Agent 实例生效,观察一段时间没问题再全量推送。内网环境里没有公网的 A/B 测试基础设施,这种手动灰度虽然原始,但足够可靠。
另外要注意的是,Skills 的更新往往需要和 MCP Server 的更新配合。比如 Skill 里新增了一个工具调用步骤,但对应的 MCP Server 还没部署新版本,那 Agent 执行到那一步就会失败。所以我的经验是:先更新 MCP Server,确认新工具可用,再更新 Skill。顺序反了就容易出问题。
5. 并发压力下的 Agent 工程调优
5.1 内网 Agent 的并发瓶颈到底在哪
“AI Agent 怎么扛并发”是最近被问得很多的问题。在公网环境里,瓶颈通常在模型 API 的速率限制;但在内网环境里,瓶颈的分布完全不同。
我实测下来,内网 Agent 的并发瓶颈通常出现在三个地方:模型推理服务的吞吐、MCP 工具调用的响应时间、Agent 编排框架本身的调度效率。
模型推理服务方面,如果用的是本地部署的开源模型,并发能力取决于 GPU 显存和推理框架的批处理策略。一个 7B 模型在单张消费级显卡上,如果用 vLLM 这类支持连续批处理的框架,大概能支撑 10-20 路并发;如果用朴素的 Hugging Face pipeline,可能 2-3 路就撑不住了。
MCP 工具调用方面,如果工具背后是内部的老旧系统,响应时间可能很不稳定。一个查询请求正常 200ms 返回,但遇到数据库锁表可能变成 5 秒。这种长尾延迟会拖垮整个 Agent 的并发能力,因为 Agent 的执行线程会被阻塞。
Agent 编排框架方面,很多框架默认是同步执行的,一个请求处理完才处理下一个。要支持并发,必须改成异步或者多线程模式。
5.2 用异步编排提升 Agent 吞吐
解决并发问题的第一步,是把 Agent 的执行链路改成全异步。这包括模型调用异步、MCP 工具调用异步、以及 Agent 主循环异步。
以 Python 为例,如果 Agent 框架本身不支持异步,可以用asyncio包一层。核心思路是把每个请求的处理逻辑写成一个协程,然后用asyncio.gather或者信号量来控制并发数。
import asyncio semaphore = asyncio.Semaphore(10) # 最大并发数 async def handle_request(request): async with semaphore: # 模型推理 model_result = await call_model_async(request.prompt) # 工具调用 tool_result = await call_mcp_tool_async(model_result.tool_name, model_result.args) # 生成最终回复 final = await call_model_async(build_final_prompt(tool_result)) return final async def main(): requests = [handle_request(r) for r in incoming_requests] results = await asyncio.gather(*requests) return results这里的semaphore是关键,它限制了同时进行的请求数,防止把后端服务打垮。具体设多少,要根据模型推理服务和 MCP 工具的实际承载能力来定。我的经验是从 5 开始试,逐步往上加,观察响应时间和错误率,找到拐点。
5.3 MCP 工具调用的超时与降级策略
MCP 工具调用是并发链路里最不可控的一环。内网系统的稳定性参差不齐,必须做好超时和降级。
超时设置上,我建议分两级:软超时和硬超时。软超时比如 10 秒,到了之后 Agent 先给用户返回一个“正在处理中”的中间状态,同时继续等待;硬超时比如 30 秒,到了之后直接放弃这次工具调用,走降级逻辑。
降级逻辑要根据业务场景来设计。比如查询类工具超时了,可以返回缓存的历史数据;创建类工具超时了,可以先把请求写入本地队列,稍后重试。最忌讳的是超时之后直接报错,让用户看到一个失败的结果。
async def call_mcp_tool_with_fallback(tool_name, args): try: result = await asyncio.wait_for( call_mcp_tool_async(tool_name, args), timeout=30 ) return result except asyncio.TimeoutError: # 降级:查缓存 cached = get_from_cache(tool_name, args) if cached: return cached # 降级:写入重试队列 enqueue_retry(tool_name, args) return "请求已提交,正在后台处理,请稍后查看结果"这个模式我在实际项目里用了很久,效果很稳。用户不会因为后端系统的偶发慢响应而看到报错,体验上好了很多。
5.4 推理服务的批处理与显存优化
如果内网 Agent 用的是本地部署的模型,推理服务的优化空间很大。核心思路是批处理:把多个请求攒在一起,一次性送给 GPU 推理,这样能显著提升吞吐。
vLLM 是这方面比较成熟的方案,它支持连续批处理(continuous batching),能在请求不断到来的情况下动态组批。部署方式也很简单:
python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --tensor-parallel-size 1 \ --max-num-seqs 32 \ --gpu-memory-utilization 0.9max-num-seqs控制同时处理的最大请求数,gpu-memory-utilization控制显存使用率。这两个参数需要根据显卡型号和模型大小来调。我的经验是先把gpu-memory-utilization设到 0.9,然后逐步增加max-num-seqs,直到出现显存不足或者响应时间明显上升为止。
如果显存实在不够,可以考虑量化。4-bit 量化能把显存占用降到 FP16 的四分之一左右,代价是推理质量会有一定下降。在内网环境里,如果业务对精度要求不是极高,量化是很划算的选择。
6. 内网 Agent 的可观测性与故障排查
6.1 日志体系的设计:让问题可追溯
内网环境里没有公网的监控 SaaS,可观测性全靠自己搭。日志是最基础也是最重要的一环。
Agent 的日志要覆盖这几个层面:请求级日志(每个用户请求的完整生命周期)、模型调用日志(每次推理的输入输出和耗时)、工具调用日志(每次 MCP 调用的参数、结果、耗时)、错误日志(异常堆栈和上下文)。
我习惯用结构化日志,每条日志是一个 JSON 对象,包含timestamp、level、trace_id、component、message、extra字段。trace_id贯穿一个请求的所有日志,方便串联。
import logging import json import uuid class StructuredLogger: def __init__(self, component): self.component = component self.logger = logging.getLogger(component) def log(self, level, message, trace_id=None, **extra): record = { "timestamp": datetime.utcnow().isoformat(), "level": level, "component": self.component, "trace_id": trace_id or str(uuid.uuid4()), "message": message, "extra": extra } self.logger.info(json.dumps(record, ensure_ascii=False))日志输出到文件之后,可以用jq或者简单的 Python 脚本做查询和分析。内网环境里不一定有 ELK 这样的日志平台,但至少要做到能按trace_id把一次请求的完整链路捞出来。
6.2 常见故障的排查链路
内网 Agent 的故障排查,我总结了一个从外到内的排查顺序。
第一步:确认 Agent 进程是否存活。这个看似简单,但内网环境里进程被 OOM Killer 杀掉、或者因为某个未捕获异常而退出,都是常见情况。用systemctl status或者supervisorctl status先看一眼。
第二步:确认模型推理服务是否可达。用curl直接打推理服务的健康检查接口,看是否返回正常。如果推理服务挂了,Agent 的所有请求都会失败。
第三步:确认 MCP Server 是否可达。逐个检查 Agent 配置里注册的 MCP Server,看连接是否正常。可以用 MCP 协议自带的 ping 机制,或者直接发一个简单的工具调用请求测试。
第四步:看日志里的错误模式。如果前面三步都正常,那问题可能在业务逻辑层面。按trace_id捞一次失败请求的完整日志,看是在哪一步出的错。
第五步:复现问题。如果日志信息不够,就手动构造一个相同的请求,在测试环境里复现。内网环境里复现问题通常比公网容易,因为环境是可控的。
我遇到过最隐蔽的一个问题是:MCP Server 在长时间运行后,文件描述符泄漏,导致新的连接建立失败。日志里只看到“连接超时”,看不出根因。后来用lsof检查进程的文件描述符数量,才发现已经接近上限。解决办法是在 MCP Server 里加一个定时重启机制,或者修复泄漏的代码。
6.3 性能基线与容量规划
在内网环境里做容量规划,不能靠拍脑袋,要有性能基线。
性能基线包括:单请求的平均响应时间、P95 响应时间、最大并发数、以及在不同并发下的错误率。这些数据要通过压测来获取。
压测工具可以用locust或者简单的asyncio脚本。关键是压测的请求要模拟真实场景,不能只发同一种请求。我的做法是从生产日志里采样一批真实请求,脱敏后作为压测输入。
拿到基线数据之后,容量规划就简单了:如果业务预期峰值 QPS 是 10,而单实例在 P95 响应时间可接受的前提下能扛 5 QPS,那就部署 2-3 个实例,留一定余量。
注意:内网环境里的扩容往往比公网慢,因为涉及机器申请、网络配置、镜像分发等流程。所以容量规划要留足够的 buffer,不能卡着上限来。
7. 一些踩坑之后的经验沉淀
7.1 依赖版本锁定比你想的重要
在内网环境里,依赖版本漂移是噩梦。因为不能随时从外网拉最新版本,一旦某个依赖升级引入了不兼容的变更,回滚成本很高。
我的做法是:所有依赖必须锁定到具体版本,包括传递依赖。Python 用pip freeze生成完整的requirements.txt,Node 用package-lock.json,系统包用apt-mark showmanual记录。每次更新依赖时,在外网环境完整测试通过后,再整体打包搬运到内网。
7.2 给 Agent 设一个“最大执行步数”
Agent 在执行复杂任务时,有可能陷入循环——反复调用同一个工具、或者在不同工具之间来回跳转。在内网环境里,这种循环会白白消耗推理资源和工具调用配额。
我的做法是在 Agent 的编排逻辑里加一个最大步数限制,比如 20 步。超过之后强制终止,返回当前已完成的部分结果,并记录一条警告日志。这个限制可以根据业务复杂度调整,但一定要有。
7.3 模型输出的格式约束要前置
内网 Agent 经常需要调用内部工具,而工具对输入参数的格式往往有严格要求。如果让模型自由生成参数,很容易出现格式错误。
解决办法是在 Prompt 里前置格式约束,并且在模型输出之后加一层校验。校验不通过就重新生成,或者用规则做修正。比如要求模型输出 JSON,那就用 JSON Schema 做校验;要求某个字段是枚举值,那就检查是否在枚举范围内。
7.4 内网时间同步问题容易被忽视
这个问题很隐蔽:内网机器的时间如果没有同步,Agent 日志的时间戳就会错乱,排查问题时根本对不上。更严重的是,如果 MCP 协议或者认证机制里涉及时间戳校验,时间偏差会导致认证失败。
确保内网所有机器都配置了 NTP 同步,指向内网的时间服务器。如果没有内网时间服务器,至少手动定期校准。这个小事不注意,后面会带来很多莫名其妙的故障。
7.5 留一条“逃生通道”
最后分享一个我觉得最重要的经验:在内网 Agent 的部署里,一定要留一条不依赖 Agent 的逃生通道。
什么意思?就是当 Agent 完全不可用的时候,业务方还能通过其他方式完成关键操作。比如提供一个简单的 Web 表单或者命令行工具,让用户可以手动触发那些 Agent 负责的任务。这条通道平时不用,但在 Agent 出故障时能保证业务不中断。
我在一个项目里就是因为没留这条通道,Agent 的 MCP Server 出问题之后,整个工单处理流程卡住了半天,业务方意见很大。后来补了一个简单的备用入口,虽然功能简陋,但至少保证了业务连续性。
内网环境里的系统,稳定性永远比先进性重要。Agent 再智能,也得先保证“能用”。