
1. 为什么隔离内网里的 AI Agent 工程是另一套玩法先把场景说清楚。所谓隔离内网就是一台或者一批机器物理上或者逻辑上跟公网断开没有外网出口装不了在线包连不上任何云端大模型 API甚至连 pip、npm 这类包管理器的默认源都访问不了。很多做 AI Agent 的朋友第一反应是那还玩什么Agent 不就是靠调云端大模型活着的吗这个反应恰恰暴露了一个思维惯性——把 Agent 等同于调 API 的壳。实际上Agent 的核心是任务分解、工具调用、状态管理、结果校验这一整套控制流大模型只是其中的推理引擎。引擎可以换控制流才是工程价值所在。在隔离内网里做 Agent 工程你要解决的不是怎么调模型而是怎么在没有外部依赖的前提下把一整套推理加执行的闭环跑起来。我接触这个方向是因为一个很现实的需求某些业务数据、代码资产、内部文档出于合规和保密要求只能待在隔离环境里但团队又确实想用 Agent 来自动化一些重复劳动比如代码审查、日志分析、文档问答、批量数据处理。这些活儿如果全靠人工效率低得让人抓狂如果拿到公网去做数据又出不去。于是隔离内网 本地化 Agent就成了唯一解。这里要提前说清楚一个关键点隔离内网不等于低配。很多人以为内网就是几台老服务器其实不少隔离环境里的机器配置相当能打有 GPU 卡、有大内存、有高速内网互联。真正的约束不是算力而是依赖获取和模型部署这两件事。你没法pip install一个包就完事没法直接调一个云端接口就出结果所有东西都得提前搬进来、离线装好、本地跑通。关键词里提到的 MCP、Skills、内网穿透这些概念在这个场景下都有对应的落地形态。MCP 是模型和工具之间的协议层Skills 是可复用的能力封装内网穿透则是解决隔离环境怎么和外部有限交互的问题——注意这里的穿透指的是合规的、受控的数据摆渡和调试通道不是绕过管控。下面我会一层层拆开讲。这篇文章适合三类人看一是在隔离环境里被装不上依赖折磨过的工程师二是想把 Agent 能力落到内网业务里的技术负责人三是对 Agent 架构感兴趣、想理解去掉云端依赖后 Agent 还剩什么的开发者。不管你是哪种我都会尽量把每一步的为什么讲透而不是甩一堆命令让你照抄。2. 隔离环境下的依赖困境与离线工程基线2.1 依赖获取的真实痛点不是装不上是装不全在公网环境里pip install langchain一条命令搞定背后是 PyPI 上几百个依赖包的自动解析和下载。到了隔离内网这条命令会直接超时。你可能会想那我提前在有网的机器上pip download打包不就行了理论上对实操上坑很多。第一个坑是平台差异。你在 Mac 上下载的 wheel 包拿到 Linux 服务器上装不了因为很多包带 C 扩展是平台相关的。第二个坑是依赖树爆炸。一个看似简单的 Agent 框架依赖树可能有上百个包其中某个包又依赖特定版本的底层库版本冲突在离线环境下极难排查因为你没法临时去源上找另一个版本。第三个坑是系统级依赖。有些 Python 包依赖系统里的libssl、libffi、gcc这些光打包 Python 层没用系统层的东西也得对齐。我的做法是用容器做依赖基线。在一台有网的机器上用和目标内网机器尽可能一致的镜像同发行版、同架构、同 Python 版本构建一个完整环境把所有依赖装好然后docker save导出成 tar 包搬进内网docker load。这样依赖树、系统库、Python 版本全部锁死内网里直接起容器就行。这个方法的代价是镜像体积大动辄几个 G但换来的是一次构建、处处运行的确定性在隔离环境里这个确定性比什么都值钱。提示构建镜像时一定要记录基础镜像的 digest不要只记 tag。tag 会变digest 不会。内网复现时用 digest 拉取能避免同样的 tag 装出不同环境的诡异问题。2.2 离线包仓库的搭建把 PyPI 搬回家如果内网机器不方便全用容器那就得搭一个离线包仓库。常见方案是用devpi或者bandersnatch做 PyPI 镜像但完整镜像太大动辄几百 G。更务实的做法是按需镜像先在内网跑一遍安装流程把报错缺的包记下来然后在外网把这些包及其依赖下载下来放进一个本地目录用pip install --no-index --find-links/path/to/packages安装。这里有个经验pip download的时候加上--platform、--python-version、--only-binary:all:这几个参数能强制下载指定平台的二进制包避免下到源码包还得在内网编译。命令大概长这样pip download \ --dest ./offline_packages \ --platform manylinux2014_x86_64 \ --python-version 311 \ --only-binary:all: \ -r requirements.txtmanylinux2014_x86_64是绝大多数 Linux 服务器兼容的 wheel 标签311对应 Python 3.11。这两个参数一定要和内网目标环境对齐否则下下来的包照样装不上。对于 npm 生态思路类似用npm pack或者verdaccio搭私有源。对于系统包用apt-get download或者yumdownloader把 deb/rpm 包拉下来内网用dpkg -i或rpm -ivh装。这些操作本身不复杂复杂的是依赖完整性校验——你永远不知道漏了哪个间接依赖直到内网安装报错。所以我的习惯是在内网做一次全新安装演练把所有报错收集齐再回外网补包反复几轮直到干净通过。2.3 模型权重的搬运与本地推理服务Agent 的推理引擎得本地化。可选路径有几条一是用开源模型权重比如 Qwen、Llama 系列下载下来在内网用 vLLM、Ollama 或者 llama.cpp 起服务二是如果内网有 GPU用 TensorRT-LLM 做加速三是如果对推理质量要求不高用小参数模型跑在 CPU 上。模型权重的搬运是个体力活。一个 7B 的模型FP16 大概 14G量化到 INT4 大概 4G。搬运方式看内网管控策略有的允许用移动介质有的走专门的数据摆渡通道。不管哪种校验哈希是必须的权重文件损坏是内网调试最隐蔽的坑之一模型能加载但输出乱码排查半天才发现是文件传坏了。本地推理服务起来之后Agent 框架通过 OpenAI 兼容接口去调它。vLLM 和 Ollama 都提供 OpenAI 兼容的/v1/chat/completions接口这意味着你的 Agent 代码几乎不用改只要把base_url指向本地服务就行。这是隔离内网 Agent 工程里最省心的一环——接口协议的统一让上层逻辑和底层模型解耦。推理方案适用场景显存要求部署复杂度Ollama快速验证、小模型低低vLLM生产级、高并发中高中llama.cppCPU 环境、极致量化无中TensorRT-LLMGPU 加速、低延迟高高选哪个取决于你的硬件和并发需求。如果只是团队内部几个人用Ollama 足够如果要支撑几十上百的并发请求vLLM 的连续批处理能力是刚需。3. MCP 与 Skills 在断网环境里的落地形态3.1 MCP 协议的本质模型和工具之间的插座MCP 全称 Model Context Protocol说白了就是一套让模型和外部工具、数据源对话的标准协议。你可以把它理解成 USB 接口——模型是电脑工具是各种外设MCP 就是那个统一的插口标准。有了它你换模型不用改工具换工具不用改模型两边都按协议来就行。在隔离内网里MCP 的价值反而更突出。因为内网工具五花八门有查数据库的、有读内部文档的、有调内部 API 的如果每个工具都写一套对接逻辑维护成本会爆炸。用 MCP 把这些工具统一封装成 serverAgent 作为 client 去调用整个架构就清爽了。MCP 的通信方式主要有两种stdio 和 SSE。stdio 是本地进程间通信适合工具和 Agent 跑在同一台机器上SSE 是 HTTP 长连接适合工具分布在不同的内网机器上。隔离环境里如果所有工具都在一台机器stdio 最简单没有网络配置的麻烦如果工具分散就用 SSE但要注意内网防火墙策略确保端口通。一个典型的 MCP server 长这样Python 示例from mcp.server import Server from mcp.server.stdio import stdio_server app Server(internal-tools) app.tool() async def query_internal_db(sql: str) - str: 查询内部数据库返回结果 # 这里接内网的数据库连接逻辑 result execute_sql(sql) return format_result(result) 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())这个 server 暴露了一个query_internal_db工具Agent 通过 MCP 协议调用它就能查内网数据库。注意app.tool()装饰器会自动把函数的 docstring 和参数类型转成模型能理解的工具描述这是 MCP 的便利之处——你写普通函数协议帮你做适配。3.2 Skills 的工程化封装把重复劳动变成可复用能力Skills 这个词在不同语境下含义不太一样。在 Agent 工程里我把它理解为可复用的能力单元——一段封装好的逻辑有明确的输入输出能被 Agent 按需调用。它和 MCP 工具的区别在于粒度MCP 工具通常是对接外部系统的原子操作Skills 更像是编排好的工作流可能内部调用了多个 MCP 工具。举个例子。内网里有个常见需求给定一个代码仓库路径做一轮代码审查输出问题清单。这个需求拆开是读代码文件、分析代码质量、检查安全漏洞、生成报告。如果每次都让 Agent 从头规划效率低且不稳定。把它封装成一个 Skill输入是仓库路径输出是结构化报告Agent 直接调用就行。Skills 的封装方式有很多种。简单的是写一个 Python 函数注册到 Agent 的工具列表里复杂的是用配置文件定义工作流比如 YAML 描述步骤和条件分支。我倾向于用代码封装用配置暴露——核心逻辑写在 Python 里保证可控对外暴露的参数用配置描述方便非开发人员调整。class CodeReviewSkill: def __init__(self, mcp_client): self.mcp mcp_client async def run(self, repo_path: str, rules: list[str]) - dict: files await self.mcp.call(list_code_files, {path: repo_path}) issues [] for f in files: content await self.mcp.call(read_file, {path: f}) for rule in rules: result await self._check_rule(content, rule) if result: issues.append(result) return {repo: repo_path, issues: issues}这个 Skill 内部调了两个 MCP 工具对外只暴露run方法。Agent 不需要知道内部细节只要知道给我仓库路径和规则我还你问题清单。3.3 内网 Skills 的发现与版本管理Skills 多了之后管理就成了问题。哪个 Skill 是哪个版本、依赖哪些 MCP 工具、输入输出格式是什么这些信息如果散落在各处用起来会非常痛苦。我的做法是建一个内网 Skill 注册表用一个简单的 JSON 或者 SQLite 记录所有 Skill 的元信息。{ name: code_review, version: 1.2.0, description: 对代码仓库做规则化审查, inputs: {repo_path: string, rules: list[string]}, outputs: {issues: list[object]}, dependencies: [list_code_files, read_file], entry: skills.code_review:CodeReviewSkill }Agent 启动时读这个注册表动态加载所有 Skill。版本管理用语义化版本号升级时保留旧版本新版本用新名字注册避免正在跑的任务被中断。这套机制在公网环境里可能显得多余因为有各种现成的包管理工具但在隔离内网里自己搭一套轻量注册表是最省事的方案。注意Skill 的依赖一定要显式声明。我踩过一次坑一个 Skill 内部偷偷调了一个没在注册表里声明的 MCP 工具结果那个工具没启动Skill 运行时报错排查了半天才发现是依赖漏了。从那以后我要求所有 Skill 的依赖必须写进注册表启动时做一次依赖检查。4. 内网穿透与数据摆渡的合规边界4.1 内网穿透要解决的真实问题隔离内网不是完全与世隔绝它总得和外部有某种形式的交互否则数据进不来、结果出不去。内网穿透要解决的就是这个有限交互的问题。但这里必须划清边界穿透的目的是合规的数据摆渡和调试不是绕过管控。实际场景里穿透需求通常有两类。一类是调试通道内网服务出了问题外部工程师需要临时连进去看日志、调接口。另一类是数据同步内网的产出需要定期同步到外部系统或者外部的更新需要推送到内网。这两类需求都有成熟的合规方案比如通过堡垒机跳转、通过专门的数据交换平台、通过审批后的临时通道。技术实现上常见的有反向代理、端口转发这些手段。但我要强调的是任何穿透方案都必须经过安全审批并且有完整的审计日志。在内网环境里绕过管控的穿透是红线碰不得。我见过有人图省事在内网机器上偷偷起了一个反向隧道结果被安全扫描发现整个项目组被通报。这种教训不值得重复。4.2 数据摆渡的工程化做法合规的数据摆渡核心是单向、受控、可审计。单向是指数据只能按既定方向流动不能双向随意传受控是指每次摆渡都要经过审批和检查可审计是指所有操作都有日志能追溯。工程上我推荐用文件摆渡 内容校验的模式。内网侧把要传出的数据打包成文件计算哈希通过审批通道传出外部侧收到文件后校验哈希确认完整性再使用。反过来也一样。这个模式的好处是简单、可靠、易审计不依赖复杂的网络配置。# 内网侧打包并计算哈希 tar czf output_$(date %Y%m%d).tar.gz ./results sha256sum output_$(date %Y%m%d).tar.gz output.sha256 # 外部侧校验 sha256sum -c output.sha256如果数据量大、频率高可以考虑用专门的数据交换中间件但核心逻辑不变校验、审批、审计三件套一个都不能少。4.3 调试通道的最小权限原则调试通道的配置要遵循最小权限原则。具体来说只开放必要的端口只允许必要的源 IP只授予必要的操作权限通道用完立即关闭。不要图方便开一个长期存在的全权限通道那是安全隐患。我通常的做法是调试通道按需申请有效期以小时计到期自动关闭。通道内只允许 SSH 到指定机器且只能执行预定义的一组诊断命令不能随意操作。这套机制配置起来麻烦一点但换来的是安全上的确定性。通道类型适用场景权限粒度有效期临时调试通道故障排查单机只读小时级数据摆渡通道定期同步指定目录读写按需服务调用通道跨网服务指定 API长期但受限这张表是我在实际项目里总结的不同场景用不同通道不要混用。混用的结果是权限失控审计也做不清楚。5. 并发压力下的 Agent 工程调优5.1 Agent 并发和普通服务并发的区别普通 Web 服务的并发瓶颈通常在数据库连接和网络 IO。Agent 的并发瓶颈更复杂因为每个请求背后是一次甚至多次模型推理而模型推理是计算密集型的。一个 Agent 请求可能触发 5 到 10 次模型调用每次调用占用 GPU 几百毫秒到几秒。如果并发上来GPU 就是第一瓶颈。更麻烦的是Agent 的请求时长差异极大。简单任务可能几秒完成复杂任务可能跑几分钟。这意味着你不能用固定的线程池或者连接池来管理得用异步 队列的模式。请求进来先入队Worker 从队列取任务执行执行完回调通知。这样能平滑突发流量避免 GPU 被瞬间打满。5.2 推理服务的批处理与限流vLLM 这类推理服务支持连续批处理能把多个请求合并成一批一起推理显著提升 GPU 利用率。但批处理有代价单个请求的延迟会上升因为要等批次凑齐。所以要在吞吐和延迟之间找平衡。我的经验是设置一个最大批次大小和最大等待时间。比如最多凑 8 个请求最多等 50 毫秒。超过 8 个就立即执行不足 8 个但等了 50 毫秒也执行。这样在负载高时吞吐优先负载低时延迟优先。限流方面Agent 层要做请求准入控制。不是所有请求都立即执行而是根据当前 GPU 负载动态调整。负载低时全放行负载高时排队或者拒绝低优先级请求。优先级可以按任务类型分交互式的优先批量的靠后。import asyncio from collections import deque class AgentScheduler: def __init__(self, max_concurrent: int): self.semaphore asyncio.Semaphore(max_concurrent) self.queue deque() async def submit(self, task, priority: int 0): if priority 0: self.queue.appendleft(task) else: self.queue.append(task) async with self.semaphore: return await task()这个调度器用信号量控制并发数用双端队列支持优先级插队。实际生产里还要加超时、重试、熔断这些机制但核心思路就是这个。5.3 状态管理与断点续跑Agent 任务跑得久中途失败是常态。如果每次失败都从头再来浪费算力也浪费时间。所以状态管理是必须的。每个任务要有唯一 ID执行过程中的中间状态要持久化失败后能从最近的检查点恢复。持久化用什么内网环境里Redis 或者 SQLite 都行。Redis 快但需要额外部署SQLite 简单但并发写性能差。如果任务量不大SQLite 足够如果并发高上 Redis。状态内容至少包括任务 ID、当前步骤、已完成步骤的结果、剩余步骤、失败原因。class TaskState: def __init__(self, task_id: str, steps: list): self.task_id task_id self.steps steps self.current 0 self.results {} def save(self, store): store.set(ftask:{self.task_id}, self.to_json()) classmethod def load(cls, task_id: str, store): data store.get(ftask:{task_id}) return cls.from_json(data) if data else None断点续跑的逻辑是任务启动时先查状态如果有未完成的状态从current步骤继续如果没有从头开始。每完成一步就更新状态。这样即使进程崩溃重启后也能接着跑。提示状态持久化要注意序列化格式的兼容性。我吃过一次亏用 pickle 存状态后来代码重构改了类结构旧状态反序列化直接报错。后来改用 JSON虽然麻烦点但兼容性好得多。6. 从零搭一个内网 Agent 的完整链路6.1 环境准备清单与检查脚本动手之前先把环境清单列清楚。我通常用一个检查脚本把所有前置条件过一遍避免装到一半发现缺东西。#!/bin/bash # check_env.sh - 内网 Agent 环境检查 echo 系统信息 uname -a cat /etc/os-release | head -2 echo Python 版本 python3 --version echo GPU 状态 nvidia-smi 2/dev/null || echo 无 GPU 或驱动未安装 echo 关键依赖 python3 -c import fastapi; print(fastapi, fastapi.__version__) 2/dev/null || echo 缺 fastapi python3 -c import mcp; print(mcp ok) 2/dev/null || echo 缺 mcp echo 模型文件 ls -lh /models/ 2/dev/null || echo 模型目录不存在 echo 端口占用 ss -tlnp | grep -E 8000|11434|6379 || echo 关键端口空闲这个脚本跑一遍环境状况一目了然。缺什么补什么别等到跑起来才发现。6.2 最小可运行 Agent 的代码骨架环境齐了之后先搭一个最小可运行的 Agent验证链路通不通。不要一上来就搞复杂功能先让模型能调、工具能用、结果能出这三件事跑通。import asyncio from openai import AsyncOpenAI from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client class MinimalAgent: def __init__(self, model_url: str, model_name: str): self.llm AsyncOpenAI(base_urlmodel_url, api_keyinternal) self.model model_name self.tools [] self.session None async def connect_tools(self, server_cmd: str, args: list): params StdioServerParameters(commandserver_cmd, argsargs) self.read, self.write await stdio_client(params).__aenter__() self.session await ClientSession(self.read, self.write).__aenter__() tools_result await self.session.list_tools() self.tools [ {type: function, function: { name: t.name, description: t.description, parameters: t.inputSchema }} for t in tools_result.tools ] async def run(self, user_input: str) - str: messages [{role: user, content: user_input}] while True: resp await self.llm.chat.completions.create( modelself.model, messagesmessages, toolsself.tools if self.tools else None ) msg resp.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for call in msg.tool_calls: result await self.session.call_tool( call.function.name, eval(call.function.arguments) ) messages.append({ role: tool, tool_call_id: call.id, content: str(result.content) })这个骨架包含了 Agent 的核心循环调模型、判断是否有工具调用、执行工具、把结果喂回模型、继续循环直到模型给出最终答案。跑通这个后面的复杂功能都是在这个基础上加。6.3 工具注册与调用的调试技巧工具调用是 Agent 最容易出问题的地方。模型可能调错工具、传错参数、或者该调不调。调试的时候我习惯把每次工具调用的原始请求和原始响应都打日志包括模型返回的tool_calls结构和工具返回的内容。import logging logging.basicConfig(levellogging.DEBUG) # 在调用工具前后加日志 logging.debug(fTool call: {call.function.name}, args: {call.function.arguments}) result await self.session.call_tool(...) logging.debug(fTool result: {result.content})日志级别开到 DEBUG能看到完整的交互链路。常见问题有几个一是工具描述写得不清楚模型不知道什么时候该调二是参数 schema 定义有误模型传的参数对不上三是工具返回的内容格式模型理解不了。这三个问题都能通过看日志定位。提示工具描述要写得像给新人看的文档说清楚这个工具做什么、什么时候用、参数是什么含义。模型对工具描述的理解能力直接决定了工具调用的准确率。7. 踩过的坑与内网特有的经验7.1 模型加载失败的那些隐蔽原因内网部署模型加载失败的原因往往很隐蔽。我遇到过几次表面看是模型加载报错实际原因五花八门。一次是权重文件传输不完整文件大小对但内容损坏模型加载到一半报错。排查方法是比对哈希一比对就发现传坏了。另一次是显存不够但报错信息说的是CUDA out of memory看起来像代码问题实际是模型太大得换量化版本。还有一次是模型格式不对下载的是 safetensors 格式但加载代码按 PyTorch bin 格式读自然读不出来。这些坑的共同点是报错信息不直接指向根因。所以我的习惯是模型加载失败时先做三件事校验文件哈希、确认显存够不够、确认格式和加载代码匹配。这三件事能解决八成以上的加载问题。7.2 内网时间同步与证书过期内网机器如果时间不同步会引发一堆诡异问题。最典型的是 HTTPS 证书校验失败——证书有有效期机器时间不对证书就被判定为过期或未生效。内网服务之间如果用 HTTPS 通信时间不同步直接导致服务调不通。解决办法是内网部署一个 NTP 服务所有机器定期同步。如果内网完全隔离没法连外部 NTP就在内网选一台机器做时间源其他机器跟它同步。这个配置一次就好但漏配的代价很大。证书方面内网自签证书是常态。自签证书要自己维护有效期过期了服务就挂。我的做法是给证书设置一个较长的有效期同时在日历上标记到期前一个月提醒续期。别笑我见过不止一个项目因为证书过期导致内网服务集体不可用。7.3 日志与可观测性的内网方案内网环境里可观测性工具的选择受限。Prometheus、Grafana 这些可以离线部署但配置起来比公网麻烦。如果不想搞太重用结构化日志 简单聚合也能满足基本需求。结构化日志就是每条日志都是 JSON 格式包含时间戳、级别、模块、消息、上下文。这样可以用jq这类工具做过滤和统计不需要复杂的日志系统。import json, logging, time class JsonFormatter(logging.Formatter): def format(self, record): return json.dumps({ ts: time.time(), level: record.levelname, module: record.module, msg: record.getMessage(), extra: getattr(record, extra, {}) }) logger logging.getLogger(agent) handler logging.StreamHandler() handler.setFormatter(JsonFormatter()) logger.addHandler(handler) logger.setLevel(logging.INFO)日志输出到文件后用jq做查询# 查所有 ERROR 级别的日志 cat agent.log | jq select(.levelERROR) # 统计各模块的日志量 cat agent.log | jq -r .module | sort | uniq -c | sort -rn这套方案轻量、无依赖、够用。等规模大了再上完整的可观测性栈也不迟。7.4 内网 Agent 的测试策略内网环境没法像公网那样随时拉测试数据、调外部服务所以测试策略要调整。我的做法是分层测试 模拟依赖。单元测试测纯逻辑不依赖外部服务这部分在内网随便跑。集成测试测 Agent 和工具的交互用 mock 的模型和 mock 的工具验证调用链路正确。端到端测试才用真实模型和真实工具但这类测试跑得少通常只在版本发布前跑一轮。# 用 mock 模型做集成测试 class MockLLM: async def chat(self, messages, toolsNone): # 根据输入返回预设的响应 if 查数据库 in messages[-1][content]: return MockResponse(tool_calls[MockToolCall(query_internal_db, {sql:SELECT 1})]) return MockResponse(content完成)Mock 的好处是快、稳定、可重复。真实模型有随机性同样的输入可能给不同输出测试断言很难写。用 Mock 把不确定性隔离掉测试才能可靠。8. 一些个人体会隔离内网做 Agent 工程最大的感受是约束反而逼出了更扎实的架构。公网环境里遇到问题可以随时换个库、调个 API、加个依赖很多设计缺陷被外部资源掩盖了。内网环境里每个依赖都要自己搬每个服务都要自己部署每个问题都要自己排查这种什么都得自己来的压力反而让你把每个环节都想清楚。MCP 和 Skills 这套组合在内网里的价值比公网更大。因为内网工具分散、协议不统一MCP 提供的标准化接口层能省掉大量胶水代码。Skills 把重复的工作流封装起来让 Agent 的能力可以积累和复用而不是每次都从头规划。并发这块我的建议是先跑通再优化。一开始不要想着支撑多少并发先把单请求链路跑稳把状态管理做好把日志打全。等单请求稳定了再逐步加压看瓶颈在哪针对性优化。上来就搞高并发架构往往是在优化一个还不存在的瓶颈。最后说一个心态问题。内网环境里很多在公网看起来理所当然的东西都没有这会让人烦躁。但换个角度想这恰恰是锻炼工程能力的机会。当你能在什么都没有的环境里把一套 Agent 系统搭起来、跑稳定回到公网环境时你会发现那些现成的工具和框架用起来得心应手因为你已经理解了它们底层在解决什么问题。