Hermes Agent系统架构全面拆解:核心子系统与执行路径实践
我做Agent系统维护也三年多了每次接一个新项目第一件事不是看代码不是读文档而是先找一张架构图。没有架构图就自己照着源码画一张。Hermes Agent 这套系统我前前后后部署过十几个环境从裸机到容器从单人调试到团队共享可以说它的价值不在于某个单一模型跑得多快而在于整个子系统之间的协作路径设计得是否顺畅。这篇架构总览说白了就是把我脑子里那张“地图”画给你看。如果你是准备接入 Hermes Agent 做自动化任务的人或者你正在维护一套经常“跑着跑着就卡住”的Agent系统又或者你想搞清楚“一条用户指令进去之后到底经历了什么”这篇内容都合适。我会把子系统拆开讲再把执行路径串起来讲最后补充几个实践里经常踩的坑。尽量用大白话但该严谨的地方不含糊。1. 整体架构设计与设计哲学1.1 Hermes Agent 的定位与核心设计原则先明确一个概念Hermes Agent 不是一个简单的“AI问答机器人”它是一个任务执行型智能体框架。这意味着它的核心产出不是回复文本而是“动作”——调用工具、操作文件、访问网页、控制桌面应用、生成报告等等。所以它的架构设计目标非常明确怎么把一个模糊的自然语言意图稳定地转化成一串可执行的、可回滚、可追踪的动作序列。围绕这个目标Hermes Agent 遵循了几个底层原则。第一个是单一入口原则。所有外部输入不管来自命令行、Web API、桌面输入框还是WebSocket消息最终都会归一化成一个统一的内部事件结构。这个结构里包含用户ID、会话ID、原始消息、时间戳、附带元数据。好处是你不用在业务代码里到处判断“我是被谁唤起的”。我见过不少Agent项目不同入口进来的消息处理逻辑各写一套后期维护成本直接起飞。第二个原则是路径可观测。Hermes Agent 在内部不是一个黑盒而是一个管道每一个环节都会产出结构化的Trace记录。从意图解析开始到规划器给出任务草稿到工具调用返回结果再到记忆更新完成每一个节点都有状态记录。这个设计在故障排查时救过我太多次了。没有路径可观测出了问题你只能猜。第三个原则是记忆与计算分离。这里说的“记忆”不只是数据库存聊天记录而是包括长期知识库、短期上下文、会话状态、工具调用历史在内的一整套状态体系。Hermes Agent 刻意把“思考”的部分和“存储”的部分拆成独立子系统目的就是为了让规划器保持无状态方便水平扩展。你可以在后面执行路径里看到这种设计对系统的稳定运行影响有多大。1.2 子系统划分的底层逻辑Hermes Agent 的子系统划分不是按“功能模块”分的而是按数据流转的阶段分的。这是很多初读源码的人最容易搞混的地方。你以为它有一个“工具库子系统”把所有工具集中管理实际上工具相关的能力被拆到了至少三个地方工具注册表负责索引和元数据工具执行器负责进程级调用工具结果解析器负责把返回内容标准化。这样拆的好处是什么注册和解析可以不依赖执行环境工具执行器崩溃了不影响工具清单的查询。我梳理了一下整个系统大致包括七大子系统子系统核心职责关键接口失败影响输入解析子系统统一入口、格式归一化、意图初判消息接收接口、事件总线所有请求无法进入处理流程规划与决策子系统任务拆解、步骤编排、策略选择规划器接口、步进调度接口任务无法生成或生成错误步骤工具调用子系统注册工具、调用执行器、参数校验工具注册表、调用句柄动作无法落盘系统空转记忆与状态子系统上下文管理、长期记忆存储、会话状态恢复向量库接口、KV存储接口多轮对话失忆状态错乱安全与权限子系统权限校验、命令白名单、资源隔离策略引擎、审计接口越权操作或误操作风险事件总线与调度子系统节点间消息传递、异步调度、重试队列消息队列、调度器子系统间通信断裂任务悬挂可观测性子系统Trace记录、日志聚合、指标上报日志接口、监控面板问题定位变成灾难这套划分逻辑的巧妙之处在于每个子系统的生命周期边界是清晰的。输入解析子系统只在请求进入的那一瞬间被激活任务进入规划器之后它就可以释放了。工具调用子系统在等待外部进程返回时事件总线已经把控制权交回调度器。这种异步解耦的结构让Hermes Agent 在面对慢IO、外部API不稳定时不会像同步阻塞式架构那样整体瘫痪。1.3 为什么模块化边界如此重要我在实践中经常看到有人抱怨“Agent跑起来好慢”打开监控一看瓶颈往往不在模型推理上而在某个子系统的边界模糊地带。比如工具执行器直接把结果写入了记忆库绕过了事件总线导致调度器完全不知道那个步骤已经完成了于是超时重试又执行了一遍。这就是典型的模块化边界失守。Hermes Agent 对模块化边界的强制性要求是“三个不能”不能跨层访问数据、不能绕过调度器通信、不能隐式共享状态。所有子系统之间的交互必须走事件总线所有状态变化必须通过记忆子系统的接口。哪怕你是想快速打个日志也不能直接改全局变量。对于单人调试来说这种限制显得啰嗦但一旦多人协作、功能迭代加速这套纪律能保证系统不会变成一坨互相纠缠的毛线团。从版本演进来看这种边界设计也大大降低了升级风险。比如从 v0.20 升级到 v0.21主要变化集中在规划器内部策略上工具调用子系统的接口完全没有变动。这意味着你升级后之前写的工具配置还能直接复用不需要返工。2. 核心子系统逐个拆解2.1 输入解析与意图理解子系统很多人以为意图理解就是“让模型猜一下用户想干嘛”但在 Hermes Agent 里这个环节做得相当克制。输入解析子系统的第一层是格式标准化它把用户发送的内容转成统一的Message对象。这个对象包含ChannelType来源渠道、SenderID、MessageType文本、音频转写、文件路径引用等、Priority优先级。第二层才是意图初判它不会直接生成完整计划而是先做粗粒度的分类是“查询型任务”“操作型任务”还是“闲聊”。这种设计有个很实际的好处可以针对不同类型的任务分配不同的模型路线。查询型任务走轻量级快速模型操作型任务才需要启动重量级规划模型。我配置过一台性能较弱的机器如果不做这个分流每次闲聊式提问都会触发大模型的完整推理链路资源消耗直接失控。这个子系统还有一个容易忽略的组件叫“参数预填器”。它的作用是从原始消息里提前抽取关键参数比如文件路径、URL、目标端口、指定工具名。预填结果的准确度不需要特别高只要能把候选参数传给规划器做参考就行。这样规划器就不用在很长的上下文里自己翻找这些碎片信息减少了上下文噪声也减少了模型幻觉的概率。2.2 规划与决策子系统规划与决策子系统是整个Hermes Agent 的大脑中枢也是最难调优的部分。它的核心任务是把“意图”变成“步骤”。Hermes Agent 采用了一种混合规划模式先由大模型生成粗粒度的步骤大纲再由规则引擎和约束检查器对大纲进行裁剪和排序。具体来说规划器先输出一个JSON格式的候选计划里面包含多个步骤节点每个节点有type工具调用/信息查询/等待用户确认/子规划、目标工具、输入参数、依赖关系。然后约束检查器开始逐条验证这个工具是否在白名单里这个参数类型是否匹配这一步的前置步骤是否已完成如果验证失败规划器会在限定重试次数内重新生成。这个过程会形成一个“计划生成—校验—修正”的循环。有个非常实用的参数叫max_steps_plan默认值是8。它限制了单个任务的规划步数上限。我在实际调优中建议根据任务的复杂度调整它简单自动化任务设到4就够了复杂的多文件处理任务可以上调到15。但这个值不宜过大否则规划器容易生成无效的多余步骤让任务执行时间成倍增加。限制步数不仅仅是性能考虑更是一种防止系统跑偏的保护机制。2.3 工具调用与执行子系统这个子系统在架构上最有意思的点是“注册与执行分离”。工具注册表里维护着所有可用工具的元数据包括工具名称、描述、参数JSON Schema、调用超时、依赖条件。但注册表不问执行的事。真正干活的是一个薄薄的执行器层它负责把工具调用请求包装成标准格式投递给实际的执行环境可能是本地Shell、Python 子进程、Docker 容器或者远程HTTP服务。这种分离带来了一个杀手级特性动态工具发现。系统在启动时扫描工具目录读取每个工具的Manifest文件然后注册到表中。所以新增一个自定义工具你只需要在目录里放一个带manifest.yaml的文件夹重启或者触发热加载工具就自动可用。不需要改任何主程序代码。我做定时巡检脚本时用这个机制反复添加了十几个小工具全程没有动过主线逻辑。工具调用的失败处理也值得一提。系统保留了“重试、降级、跳过、报告”四档策略。默认情况下非关键步骤工具调用失败会自动重试两次间隔按指数退避1秒、2秒。如果还是失败会根据该步骤在计划中的标签决定是降级换用备用工具还是跳过。只有在任务核心步骤失败时才会整体终止。这个策略需要在配置里明确不是默认就完美适配所有场景的。2.4 记忆与状态管理子系统记忆子系统决定了Agent是“聪明的工作助手”还是“金鱼脑”。Hermes Agent 把记忆分成三层短期上下文、工作记忆和长期记忆。短期上下文指的是当前任务执行过程中的消息片段存在于事件总线里任务结束就释放。工作记忆是会话级别的存在KV数据库中能跨步骤保留状态比如用户偏好、当前目录、最近一次工具调用的结果摘要。长期记忆则存在向量数据库中用于跨会话的信息召回。这套三层设计的核心逻辑是控制上下文窗口的长度。大模型的上下文窗口再长也是有限的如果把所有历史全塞进去响应延迟和成本都会飙升而且视觉注意力会被陈旧信息稀释。Hermes Agent 的策略是每次规划器启动时记忆子系统通过相关性检索从长期记忆里取回Top-K条相关片段加上工作记忆中的重点状态一起组装成“压缩上下文”。我实测下来上下文tokens可以减少30%到50%而且任务准确性没有明显下降。不过这里有个容易踩的坑向量检索的Top-K值不能设得太大。我最初图省事设成10结果检索回很多内容相近但已经过期的旧信息反而干扰了规划器的判断。后来调成5效果好很多。记忆不是越多越好相关且新鲜的信息才有效。2.5 安全与权限控制子系统这可能是最不显眼、但在生产环境中最要命的子系统。Hermes Agent 的安全模块介于规划器和工具执行器之间相当于一个“审批闸口”。每个工具调用在执行前都要过一遍策略引擎校验用户权限级别、工具的合法性、参数中是否包含敏感路径或危险命令。比如用户说“帮我清理掉系统临时目录里一周前的文件”规划器可能会生成调用rm命令的计划。安全子系统会检查这条命令的参数中是否包含受保护目录如果路径匹配到黑名单就会拦截返回“需要更高权限”的提示。我看过很多没配安全策略的Agent一个越权的清理命令直接把数据库备份目录扫掉了。这种事发生一次就能让你理解安全模块存在的必要性。2.6 可观测性与日志子系统最后一个子系统是运维视角的“眼睛”。Hermes Agent 会为每一条完整的任务链路生成一个trace_id从消息进入到任务结束所有子系统的日志都挂在这个ID下面。在排查问题时你只需要拿着trace_id在日志系统里一查就能看到完整的时间线和每一步的耗时。我在本机环境部署时日志级别通常设成INFO把WARN和ERROR单独输出到错误日志文件。DEBUG级别虽然信息详细但会产生大量IO开销建议只在定位问题时临时开。还可以配置指标上报把Token消耗、工具调用次数、规划器推理耗时等数据发到统计面板。没有可观测性你就相当于在半盲状态下开飞机系统崩溃时连失事原因都找不到。3. 一条请求的执行路径全流程分析3.1 从用户指令到任务草稿我们把整个执行流程串起来看一遍。假设用户输入了一条消息“帮忙把 /data/raw 下的CSV文件按大小排序然后挑出最大的三个复制到报告目录里”。消息进入输入解析子系统被包装成标准Message对象随后事件总线把它投递给意图初判模块。初判结果是“操作型任务”于是消息被转发到规划器。规划器拿着消息文本、参数预填结果可能识别出了/data/raw、CSV、报告目录等实体和从记忆子系统取回的短期上下文开始生成任务草稿。这里有个细节值得注意规划器生成的任务草稿首先到的是预演状态不是直接执行。它会被展示给用户确认或者送到安全模块做静态检查。在非交互模式下这个确认环节可以配置为自动通过但系统建议保留确认尤其是当任务涉及文件删除、网络请求或系统配置修改时。3.2 规划器如何拆解任务规划器对上述任务可能给出的计划是这样的list_directory参数/data/raw类型为“信息查询”用于列出全部CSV文件filter_by_extension参数.csv类型为“数据过滤”依赖步骤1的结果sort_by_size参数desc类型为“数据处理”依赖步骤2的结果slice_top参数3类型为“数据处理”依赖步骤3的结果copy_files参数目标目录为“报告目录”类型为“文件操作”依赖步骤4的结果每一步的依赖关系被表示成一个简洁的DAG有向无环图。调度器并不会严格按顺序执行而是会并行处理那些没有依赖关系的步骤。例如步骤1和步骤2本身其实是可以在内存里合并的但规划器不知道目标目录是否包含海量文件所以选择保守的拆法。这种保守拆解在复杂任务里虽然增加了步骤数却让每一步的执行结果都能被观测和追溯。3.3 工具选择与调用循环计划生成并通过校验后进入执行阶段。调度器从步骤1开始把工具调用请求发给工具选择器。工具选择器会基于工具描述和当前步骤的类型做匹配选出最合适的工具。比如步骤1的类型是“信息查询”工具注册表里匹配到了list_directory和ls两个候选选择器会利用工具描述中的语义信息最终选择语义更明确的list_directory因为它提供结构化返回后续的过滤器更好解析。工具调用执行之后返回结果进入结果标准化模块。它会把各种格式混乱的返回值比如Shell输出、文件列表、API响应统一成标准对象比如FileInfo[]数组。标准化后结果被写入事件总线暂存在工作记忆里供下一步骤使用。如果返回结果异常比如目录不存在系统会触发异常路径将错误信息回传给规划器让规划器重新规划相应的后续步骤而不是直接中断。3.4 反馈收敛与任务完成判定任务并不是执行完所有步骤就算结束还有一个非常重要的“完成判定”环节。Hermes Agent 的调度器会在所有计划步骤执行完后汇总每个步骤的输出结果生成一个“执行摘要”。如果某个步骤返回的结果为空或不符合预期摘要会标记该步骤为“存疑状态”。这时系统会触发一次小范围重规划只对存疑步骤之后的子路径做修正而不是整条路径重新生成。当所有步骤都通过状态检查后调度器会向用户输出一轮结构化总结其中包含每个步骤的执行状态、耗时、以及结果摘要的链接如果开了Web界面。同时记忆子系统把当前任务的关键信息写入长期记忆库包括任务类型、使用的工具、结果成功与否、用户有没有额外的反馈。这一步是我非常看重的因为只有完成了这一步Agent才能在下一次类似任务中做得更好。3.5 执行路径中的关键数据流整个流程中关键数据流形成了一个闭环“事件总线分发的消息 - 规划器输出的计划 - 工具执行器返回的结果 - 结果标准化模块生成的中间数据 - 记忆子系统更新的状态 - 下一次规划的上下文”。正是因为事件总线承担了数据流的“高速公路”角色所有子系统无需知道彼此的通信细节。生产环境中事件总线的吞吐和可靠性是整个系统的瓶颈。我用RabbitMQ做默认事件总线承载过一个高频任务流每秒约80条消息的情况下消息积压大概1000条左右任务延迟变得明显。后面换成基于RocksDB的嵌入式事件存储积压问题才缓解。如果条件允许事件总线一定要选支持持久化的否则Agent进程重启一次所有正在执行的任务都会丢失状态这比执行失败更麻烦。4. 不同模式下执行路径的差异4.1 bot mode 下的会话循环Hermes Agent v0.21 提供了一个重点模式叫bot mode。这个模式下执行路径的核心变化是“会话闭环”。系统不再只是被动地等待用户指令而是会周期性地主动评估当前会话中是否有需要跟进的任务。比如你启动bot模式并设定了一个“每小时检查一次收件箱有新邮件就摘要推送”的定时任务规划器会在每次循环中被唤醒生成一个轻量级的检查计划执行完把结果写入工作记忆然后进入休眠等待下一次激活。这个模式的执行路径比单次请求路径多了一个“唤醒器”组件。唤醒器是独立于事件总线的定时调度模块它会按照预设cron表达式创建新任务。注意定时任务同样要走完整的规划和校验流程不会因为它是自动触发的就直接执行。这样的好处在于安全策略保持了一致性不会出现“人工发起的任务被拦截定时任务反而畅通无阻”的漏洞。4.2 CUA 模式下的交互层变化CUAComputer Use Agent模式是 Hermes Agent 在界面交互方面的一个重要场景。它的执行路径比普通命令执行路径多了一层屏幕感知与操作映射。在这种模式下Agent不仅调用底层工具还要操作图形界面应用程序。规划器生成的步骤类型除了代码执行的exec还包括click、type_text、drag、screenshot等UI操作。CUA路径的关键点在“坐标定位”。系统不是直接通过图像识别像素点来定位元素而是先调用辅助功能API获取当前窗口的UI元素树再把规划器要操作的目标映射到元素树中的节点最后通过系统自动化接口触发操作。这个路径一旦堵塞多半是目标应用没有暴露可访问的辅助功能树这个时候Agent会暂时退化成“截屏猜测坐标”模式失败率会高很多。如果你配置了桌面子系统来运行 CUA 模式一定要确保Agent运行账号对目标GUI程序有完整的交互权限否则经常会出现“点击无反应”或“文本无法输入”的问题。这跟目标程序本身是桌面应用还是Web应用无关纯粹是系统权限边界的问题。4.3 路径切换时最容易踩的坑不同模式之间的切换是我运维中踩坑最多的区域。最常见的问题是“模式残留”系统从 bot mode 切回普通交互模式后定时任务挂起的上下文仍然存在而在新一轮普通对话中规划器错误地把定时任务的记忆片段当成当前任务的上下文导致回答牛头不对马嘴。我的经验是在项目初始配置时为每个模式创建独立的会话命名空间。Hermes Agent 的记忆子系统支持命名空间隔离你需要主动设置。比如 bot mode 的会话ID前缀设为cron_CUA 模式的会话ID前缀设为gui_普通交互的会话ID带上用户ID。这样即使事件总线上消息混在一起记忆子系统的召回也不会串味。第二个容易踩的坑是工具白名单没有区分模式。CUA 模式下系统其实只需要UI操作相关的工具但如果你给Agent整体配了Shell执行权限它会尝试在CUA路径里执行Shell命令去模拟UI操作既不安全效率也低。5. 部署与配置中的架构视角5.1 桌面版配置的架构组件Hermes Agent 除了纯服务端形态还有一个桌面版应用。桌面版不是简单的“把服务端装进一个壳里”它带了完整的本地UI层、托盘交互和快捷键监听。从架构上看桌面版相当于额外添加了一个“人机交互子系统”它监听全局快捷键捕获用户指令同时把Agent状态展示在窗口里。桌面版配置时有几个点跟服务端不同。模型API地址可以是本地推理服务比如跑一个Ollama实例也可以是云端API。如果走本地模型建议把推理超时调大桌面交互场景用户容忍度比服务端低一旦超过十几秒没响应用户就会认为系统卡死了。另一个重点是本地缓存目录的权限桌面版会存储会话快照和工具执行缓存路径不要放到系统盘临时目录否则系统重启后容易丢失。5.2 环境依赖与子系统健康检查部署完成后我第一件事不是跑真实任务而是做一轮子系统健康检查。Hermes Agent 启动时会依次前检查事件总线连接是否正常、记忆数据库能否读写、工具注册表是否加载成功、模型API是否响应。但在实际环境里有些子系统是懒加载的比如说CUA模式依赖的桌面自动化库直到第一次UI操作前都不会暴露问题。我建议在部署脚本里加一个“冒烟测试”环节用一组预定义的测试任务覆盖所有可能用到的子系统一个纯文本查询任务验证推理链路一个文件操作任务验证工具调用一个带持久化上下文的对话验证记忆一个定时任务验证调度器。全部通过再接入真实流量。部署后没有冒烟测试等于把你线上环境的稳定性交给运气这个话虽然难听但真的是我用事故换来的教训。5.3 版本升级对执行路径的影响升级版本之前我建议先看清楚升级说明中哪些子系统接口变了。v0.21 版本里变化最大的是规划器的策略配置结构旧的max_steps配置被拆分成了max_plan_steps和max_retry_steps如果你直接沿用旧配置系统会用默认值覆盖。这意味着旧任务可能因为步数限制变小而执行失败。版本升级后最好保留旧版本的工作目录作为备份至少留一周。这周内如果发现新版本的任务成功率明显下降可以迅速回滚不至于因为一个小版本的问题让整个业务流程停摆。另外注意升级后记忆数据库可能需要做一次索引重建否则检索速度会下降。我遇到过升级后向量索引没自动重建的情况检索耗时从50毫秒涨到了800毫秒排查了半天才定位到原因。6. 常见问题与排查技巧实录6.1 任务不执行或执行中断这类问题占了排障工作量的四成。排查路径有先后顺序先看日志里有没有task_rejected标记这表示任务在规划器校验阶段就被拦下了多半是权限或者工具白名单问题。如果看到task_timeout则是某个步骤运行超时。用trace_id找到具体步骤确认是哪一类工具调用超时。如果两者都没有检查事件总线的消费者链路看消息是否被正确投递到调度器。还有种隐性问题任务的确认环节卡住了。如果配置了“需要人工确认”但确认通道没有绑定任何UI或通知模块任务会一直挂在“待确认”状态。这种任务最迷惑人因为它没有报错只是“不前进”。检查规则是在一个任务创建后如果超过20秒没有看到任何调度事件就要怀疑确认通道出了问题。6.2 工具调用失败工具调用失败时第一件事看的是工具返回的原始输出。Hermes Agent 会在结果标准化之前保留原始返回内容的快照包括退出码、stderr、stdout。很多时候问题不是工具本身执行不了而是参数解析时类型不匹配。比如某个Python工具需要接收list类型的参数规划器却生成了逗号分隔的字符串这种情况下工具执行器会直接拒绝调用日志里会看到type_mismatch。解决参数问题的技巧是改进工具描述文件里的paramter_schema。描述越精确规划器生成正确参数的概率越高。比如对于目录参数除了写type: string还可以加上format: path和示例值。我在调试一个文件处理工具时就因为参数Schema里多了一个“文件后缀默认csv”的说明失败率从30%降到了5%。工具描述写得好Agent就少犯病。6.3 记忆混乱与上下文丢失出现“用户说AAgent回答B”的情况先看会话ID是否正常传递。我排查过一个案例用户从桌面版发起对话再切到Web端继续聊结果Agent完全不记得之前的对话就是因为两端使用的是不同的会话命名空间。确认命名空间没问题后再看向量检索的召回结果可以在调试模式下打印出喂给规划器的上下文片段看看是不是被陈旧信息干扰了。如果上下文频繁丢失还要检查记忆持久化的写入频率。Hermes Agent 默认不会对每一个中间步骤都做持久化而是每隔N步或任务结束才写一次。如果在写入之前进程崩溃了这个任务的所有状态都会丢。这种情况需要调整记忆子系统的持久化策略把关键节点写入设为同步模式其他中间结果设为异步模式。6.4 性能瓶颈判断系统响应变慢先不要急着骂模型。把 Trace 里的阶段耗时拎出来看工具执行耗时、规划器推理耗时、记忆检索耗时、事件总线积压四个数据摆在一起瓶颈一目了然。我见过最离谱的一次用户反馈“Agent转圈”查下来发现是日志子系统在DEBUG级别下疯狂写盘把CPU和磁盘IO都吃满了。把日志级别调回INFO整个系统瞬间恢复流畅。性能优化还要关注记忆检索的索引类型。数据量超过十万条向量后默认的扁平索引检索速度会明显下降。建议切换到HNSW索引并设置合适的M和efConstruction参数。我这里用的是M: 32, efConstruction: 200从切换前的平均150毫秒降到了30毫秒左右。这些调优虽然不起眼但对用户体验的提升非常直接。写到这里我想起一个真实的体会架构总览这类内容看起来像是在讲图纸实际上是在讲“取舍”。Hermes Agent 把子系统之间画得那么清晰是为了让运维者知道“问题出在哪一截”把执行路径画得那么细致是为了让人知道“每一步发生了什么”。我自己在维护过程中最大的收获不是记住了多少个配置项而是养成了一个习惯任何一个异常先问它是哪条路径上的哪个环节再问那个环节的输入输出是否符合预期。用这个思路去排查问题绝大多数故障都能在十分钟内定位。希望这篇地图对你也有同样的效果哪怕只是少踩一个坑也值了。