OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略? 整理
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?
问题
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?
标准回答
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?NEW中等AIOpenClaw大模型应用开发AI应用开发Agent开发标记分享1144面试问答做这层抽象的根本原因是Context 管理没有万能方案。通俗理解:Context Engine 就像手机的存储管理。有的人喜欢自动清理旧照片,有的人喜欢只删大文件,有的人喜欢全部存云端按需下载。不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大。把 Context 管理抽象成接口(定义好”做什么”),让策略实现(“怎么做”)可以独立替换,既方便内部迭代,也方便社区扩展。这就是经典的策略模式。ContextEngine接口定义在src/context-engine/types.ts,覆盖了完整的生命周期:这套接口覆盖了上下文管理的完整生命周期,核心有三大操作加上若干生命周期钩子:阶段方法做什么初始化bootstrap会话首次创建时做初始化(比如导入历史)存消息ingest/ingestBatch新消息进来时怎么存、要不要做额外处理(比如向量化)挑消息assemble发给模型前,在 token 预算内挑出最合适的一组消息压消息compact历史太长时怎么压缩(摘要、裁剪、归档…)轮后处理afterTurn每轮对话结束后的收尾工作(持久化、触发后台压缩等)子 AgentprepareSubagentSpawn/onSubagentEnded管理子 Agent 的上下文隔离与回收销毁dispose释放引擎持有的资源核心调度逻辑只依赖这套接口,压根不关心背后的具体实现。通过registerContextEngine(id, factory)注册新引擎,在配置里通过plugins.slots.contextEngine一行就能切换,完全不用动核心代码。这就是典型的策略模式 + 插件化。有了这层抽象,至少能支撑以下几种完全不同的策略方向:默认的 legacy 策略:全部塞进去,塞不下了线性压缩最早的消息,简单粗暴但够用基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景分层存储策略:类似冷热分离,最近几轮放内存、摘要放本地、更早的扔云端,按需拉取任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,同样的 token 预算质量更高自定义压缩策略:通过ownsCompaction标记接管压缩,可以实现树状摘要、按话题分支压缩等高级方式
扩展知识
内置的 legacy 引擎当前 OpenClaw 默认内置了一个legacy引擎,代码在src/context-engine/legacy.ts。它的实现非常直白:ingest是 no-op,因为消息持久化由 SessionManager 负责;assemble直接透传消息列表,不做任何筛选;compact委托给compactEmbeddedPiSessionDirect()做线性压缩。这个引擎能跑,但策略非常粗糙,基本就是”全部塞进去,塞不下了就压缩最早的”。对于简单的短对话够用,但一旦对话超过几十轮,或者涉及多个不同话题的长期交互,效果就明显不行了。可以实现的高级策略Context Engine 抽象真正的价值在于它能支撑完全不同的上下文管理思路:基于检索的 Context Engine这种引擎走 RAG 的路子。ingest的时候把每条消息向量化,存进向量库。到assemble阶段,不是按时间顺序把最近的消息拼进去,而是根据当前 query 做语义检索,把最相关的历史片段捞出来。对于那种跨好几天、中间换过好几个话题的长对话,这种方式比线性截断有效得多。分层存储引擎思路类似数据库的冷热分离。热数据就是最近 3-5 轮对话,直接放内存;温数据是最近几个 compaction 周期的摘要,放本地文件;冷数据是更早的历史,可以扔到外部存储甚至云端。assemble的时候按需从不同层拉取,既能保证最近的上下文完整,又不会因为历史太长撑爆内存。任务感知引擎根据当前任务类型动态调整 Context 组装策略。比如检测到用户在写代码,就优先保留代码相关的历史、文件路径、报错信息;检测到是闲聊,就优先保留情感偏好和个人信息。同样的 token 预算,塞进去的内容质量完全不一样。自定义 Compaction 引擎接口里有个ownsCompaction: true的标记,设了这个标记的引擎可以完全接管压缩策略。默认的线性压缩是把最早的消息一坨压成摘要,但你可以换成树状摘要,把对话按话题分支组织,每个分支独立压缩,保留更多结构化信息。插件注册机制整个切换过程对核心代码零侵入。写一个新引擎只需要实现ContextEngine接口,然后调用registerContextEngine(“my-rag-engine”, factory)注册进去。配置文件里把plugins.slots.contextEngine指向你的引擎 ID 就行了。这跟 Webpack 的 plugin 体系、VS Code 的扩展机制是一个思路,都是约定好接口,实现随便换。
面试官追问
- 提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?回答:legacy 引擎的消息持久化是 SessionManager 在做,ingest 啥也不干纯粹是因为职责没划到 Context Engine 这边。换成 RAG 引擎的话,ingest 就得真正接管消息的处理了,至少要做向量化和入库。迁移的关键是 SessionManager 得把”写消息”这个动作让出来,或者两边做好协调,不能重复写。实际上这也是为什么要抽象成接口的原因之一,职责边界可以随着引擎实现灵活调整。- 提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?
- 回答:legacy 引擎很暴力,直接从最早的消息开始砍,砍到塞得下为止。RAG 引擎好一些,它本来就是按相关性排序的,预算紧就少捞几条,质量衰减比较平滑。分层存储引擎会优先砍冷数据层,保住热数据。任务感知引擎最灵活,它可以根据任务权重动态决定哪些类型的历史先丢掉,比如写代码的时候闲聊记录优先级最低,第一个被砍。- 提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?
- 回答:OpenClaw 的压缩触发分两层。不设ownsCompaction的话,底层的 Pi runtime 有内置的 auto-compaction,它自己监控 token 用量,超过阈值就自动压缩,外层 Runner 不需要手动介入。设了ownsCompaction: true之后,Pi 的内置 auto-compaction 会被禁用,改由引擎自己通过afterTurn等生命周期钩子决定什么时候压、怎么压。但不管设没设这个标记,Runner 还有一层溢出压缩兜底,当上下文真的塞不下时,Runner 会直接调contextEngine.compact()做紧急压缩。简单说就是:ownsCompaction控制的是”日常谁来管压缩”,但”快炸了”的时候 Runner 一定会兜底。这种设计对有自己一套存储和索引体系的引擎特别重要,因为通用的压缩逻辑不了解引擎内部的数据组织方式,日常由引擎自主管理更合理。作者:Yes面试鸭官方 不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大
把 Context 管理抽象成接口,让策略实现可以独立替换,方便内部迭代,也方便社区扩展。
有了这层抽象,能支持以下几种完全不同的策略方向:
1.展开新页面打开2026-03-17 22:2900回复晚夜微雨问海棠特训营一、OpenClaw 抽象 Context Engine 的核心原因OpenClaw 将上下文管理从硬编码逻辑升级为可插拔的 Context Engine 插件化架构,本质是软件架构设计中「关注点分离(Separation of Concerns)」与「策略模式(Strat展开新页面打开2026-03-15 10:2600回复添加回答编辑预览请输入回答内容…(支持使用 Markdown )xMarkdown 语法一级标题# 标题二级标题## 标题三级标题### 标题粗体粗体文本**斜体斜体文本引用> 引用文本链接链接描述图片
代码代码块编程语言↵无序列表- 项目有序列表1. 项目分割线---删除线~~文本~~任务列表- [ ] 待办事项行内公式$公式$块级公式$$↵公式↵$$Mermaid图表mermaid快捷键粗体Ctrl-B斜体Ctrl-I链接Ctrl-K图片Shift-Ctrl-I代码Shift-Ctrl-K代码块Shift-Ctrl-C无序列表Shift-Ctrl-U有序列表Shift-Ctrl-O目录字数:0行数:1回到顶部提交目录内置的 legacy 引擎可以实现的高级策略插件注册机制
提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?热门面试题目榜更多说说 Java 中 HashMap 的原理?9130Java 中的序列化和反序列化是什么?6255MySQL 索引的最左前缀匹配原则是什么?5662Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?5067Java 中有哪些集合类?请简单介绍4854MySQL 的索引类型有哪些?4845详细描述一条 SQL 语句在 MySQL 中的执行过程。4218什么是 RAG?RAG 的主要流程是什么?4151MySQL 的存储引擎有哪些?它们之间有什么区别?4092数据库的脏读、不可重复读和幻读分别是什么?3900推荐教程更多AI 超级智能体亿级流量点赞系统教程智能协同云图库项目教程预览用户交流一起刷题学习、求职交流、反馈建议、获取更新通知面试鸭《用户协议》《隐私政策》友情链接编程导航老鱼简历代码小抄剪切助手联系我们商务合作站长:程序员鱼皮关注我们扫码关注面试鸭公众号
答案
解释「短期记忆」和「长期记忆」在 Agent 系统中的区别,分别适合怎么存储和检索?OpenClaw 是什么?它要解决什么问题?它的核心能力有哪些?上次浏览:2026-03-16 15:12:52OpenClaw 的核心组件有哪些?请描述它们之间的关系上次浏览:2026-03-16 15:15:28在 OpenClaw 中,一条用户消息从进入系统到收到回复,完整链路是怎样的?OpenClaw 的 Agent Runner 是如何工作的?一次 Agent 运行经历了哪些阶段?LLM 的 Context Window 有上限,长对话时如何保证 Agent 仍然能正常工作?OpenClaw 是怎么做的?Agent 调用工具可能返回超大结果(比如代码搜索返回 50KB),这会带来什么问题?你会怎么处理?OpenClaw 是怎么做的?当对话历史实在太长、裁剪也不够用时,还有什么办法?什么是 Compaction?OpenClaw 的 Compaction 策略是怎样的?OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?如果一个 Agent 系统要同时接入 Telegram、飞书、钉钉等渠道,你会怎么设计抽象层?OpenClaw 的 Channel Plugin 接口是怎么设计的?13232. OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?NEW中等AIOpenClaw大模型应用开发AI应用开发Agent开发标记分享1144面试问答做这层抽象的根本原因是Context 管理没有万能方案。通俗理解:Context Engine 就像手机的存储管理。有的人喜欢自动清理旧照片,有的人喜欢只删大文件,有的人喜欢全部存云端按需下载。不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大。把 Context 管理抽象成接口(定义好”做什么”),让策略实现(“怎么做”)可以独立替换,既方便内部迭代,也方便社区扩展。这就是经典的策略模式。ContextEngine接口定义在src/context-engine/types.ts,覆盖了完整的生命周期:这套接口覆盖了上下文管理的完整生命周期,核心有三大操作加上若干生命周期钩子:阶段方法做什么初始化bootstrap会话首次创建时做初始化(比如导入历史)存消息ingest/ingestBatch新消息进来时怎么存、要不要做额外处理(比如向量化)挑消息assemble发给模型前,在 token 预算内挑出最合适的一组消息压消息compact历史太长时怎么压缩(摘要、裁剪、归档…)轮后处理afterTurn每轮对话结束后的收尾工作(持久化、触发后台压缩等)子 AgentprepareSubagentSpawn/onSubagentEnded管理子 Agent 的上下文隔离与回收销毁dispose释放引擎持有的资源核心调度逻辑只依赖这套接口,压根不关心背后的具体实现。通过registerContextEngine(id, factory)注册新引擎,在配置里通过plugins.slots.contextEngine一行就能切换,完全不用动核心代码。这就是典型的策略模式 + 插件化。有了这层抽象,至少能支撑以下几种完全不同的策略方向:默认的 legacy 策略:全部塞进去,塞不下了线性压缩最早的消息,简单粗暴但够用基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景分层存储策略:类似冷热分离,最近几轮放内存、摘要放本地、更早的扔云端,按需拉取任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,同样的 token 预算质量更高自定义压缩策略:通过ownsCompaction标记接管压缩,可以实现树状摘要、按话题分支压缩等高级方式
内置的 legacy 引擎当前 OpenClaw 默认内置了一个legacy引擎,代码在src/context-engine/legacy.ts。它的实现非常直白:ingest是 no-op,因为消息持久化由 SessionManager 负责;assemble直接透传消息列表,不做任何筛选;compact委托给compactEmbeddedPiSessionDirect()做线性压缩。这个引擎能跑,但策略非常粗糙,基本就是”全部塞进去,塞不下了就压缩最早的”。对于简单的短对话够用,但一旦对话超过几十轮,或者涉及多个不同话题的长期交互,效果就明显不行了。可以实现的高级策略Context Engine 抽象真正的价值在于它能支撑完全不同的上下文管理思路:基于检索的 Context Engine这种引擎走 RAG 的路子。ingest的时候把每条消息向量化,存进向量库。到assemble阶段,不是按时间顺序把最近的消息拼进去,而是根据当前 query 做语义检索,把最相关的历史片段捞出来。对于那种跨好几天、中间换过好几个话题的长对话,这种方式比线性截断有效得多。分层存储引擎思路类似数据库的冷热分离。热数据就是最近 3-5 轮对话,直接放内存;温数据是最近几个 compaction 周期的摘要,放本地文件;冷数据是更早的历史,可以扔到外部存储甚至云端。assemble的时候按需从不同层拉取,既能保证最近的上下文完整,又不会因为历史太长撑爆内存。任务感知引擎根据当前任务类型动态调整 Context 组装策略。比如检测到用户在写代码,就优先保留代码相关的历史、文件路径、报错信息;检测到是闲聊,就优先保留情感偏好和个人信息。同样的 token 预算,塞进去的内容质量完全不一样。自定义 Compaction 引擎接口里有个ownsCompaction: true的标记,设了这个标记的引擎可以完全接管压缩策略。默认的线性压缩是把最早的消息一坨压成摘要,但你可以换成树状摘要,把对话按话题分支组织,每个分支独立压缩,保留更多结构化信息。插件注册机制整个切换过程对核心代码零侵入。写一个新引擎只需要实现ContextEngine接口,然后调用registerContextEngine(“my-rag-engine”, factory)注册进去。配置文件里把plugins.slots.contextEngine指向你的引擎 ID 就行了。这跟 Webpack 的 plugin 体系、VS Code 的扩展机制是一个思路,都是约定好接口,实现随便换。
- 提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?回答:legacy 引擎的消息持久化是 SessionManager 在做,ingest 啥也不干纯粹是因为职责没划到 Context Engine 这边。换成 RAG 引擎的话,ingest 就得真正接管消息的处理了,至少要做向量化和入库。迁移的关键是 SessionManager 得把”写消息”这个动作让出来,或者两边做好协调,不能重复写。实际上这也是为什么要抽象成接口的原因之一,职责边界可以随着引擎实现灵活调整。- 提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?
- 回答:legacy 引擎很暴力,直接从最早的消息开始砍,砍到塞得下为止。RAG 引擎好一些,它本来就是按相关性排序的,预算紧就少捞几条,质量衰减比较平滑。分层存储引擎会优先砍冷数据层,保住热数据。任务感知引擎最灵活,它可以根据任务权重动态决定哪些类型的历史先丢掉,比如写代码的时候闲聊记录优先级最低,第一个被砍。- 提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?
- 回答:OpenClaw 的压缩触发分两层。不设ownsCompaction的话,底层的 Pi runtime 有内置的 auto-compaction,它自己监控 token 用量,超过阈值就自动压缩,外层 Runner 不需要手动介入。设了ownsCompaction: true之后,Pi 的内置 auto-compaction 会被禁用,改由引擎自己通过afterTurn等生命周期钩子决定什么时候压、怎么压。但不管设没设这个标记,Runner 还有一层溢出压缩兜底,当上下文真的塞不下时,Runner 会直接调contextEngine.compact()做紧急压缩。简单说就是:ownsCompaction控制的是”日常谁来管压缩”,但”快炸了”的时候 Runner 一定会兜底。这种设计对有自己一套存储和索引体系的引擎特别重要,因为通用的压缩逻辑不了解引擎内部的数据组织方式,日常由引擎自主管理更合理。作者:Yes面试鸭官方 不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大
把 Context 管理抽象成接口,让策略实现可以独立替换,方便内部迭代,也方便社区扩展。
有了这层抽象,能支持以下几种完全不同的策略方向:
1.展开新页面打开2026-03-17 22:2900回复晚夜微雨问海棠特训营一、OpenClaw 抽象 Context Engine 的核心原因OpenClaw 将上下文管理从硬编码逻辑升级为可插拔的 Context Engine 插件化架构,本质是软件架构设计中「关注点分离(Separation of Concerns)」与「策略模式(Strat展开新页面打开2026-03-15 10:2600回复添加回答编辑预览请输入回答内容…(支持使用 Markdown )xMarkdown 语法一级标题# 标题二级标题## 标题三级标题### 标题粗体粗体文本**斜体斜体文本引用> 引用文本链接链接描述图片
代码代码块编程语言↵无序列表- 项目有序列表1. 项目分割线---删除线~~文本~~任务列表- [ ] 待办事项行内公式$公式$块级公式$$↵公式↵$$Mermaid图表mermaid快捷键粗体Ctrl-B斜体Ctrl-I链接Ctrl-K图片Shift-Ctrl-I代码Shift-Ctrl-K代码块Shift-Ctrl-C无序列表Shift-Ctrl-U有序列表Shift-Ctrl-O目录字数:0行数:1回到顶部提交目录内置的 legacy 引擎可以实现的高级策略插件注册机制
提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?热门面试题目榜更多说说 Java 中 HashMap 的原理?9130Java 中的序列化和反序列化是什么?6255MySQL 索引的最左前缀匹配原则是什么?5662Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?5067Java 中有哪些集合类?请简单介绍4854MySQL 的索引类型有哪些?4845详细描述一条 SQL 语句在 MySQL 中的执行过程。4218什么是 RAG?RAG 的主要流程是什么?4151MySQL 的存储引擎有哪些?它们之间有什么区别?4092数据库的脏读、不可重复读和幻读分别是什么?3900推荐教程更多AI 超级智能体亿级流量点赞系统教程智能协同云图库项目教程预览用户交流一起刷题学习、求职交流、反馈建议、获取更新通知面试鸭《用户协议》《隐私政策》友情链接编程导航老鱼简历代码小抄剪切助手联系我们商务合作站长:程序员鱼皮关注我们扫码关注面试鸭公众号
来源: OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?.mhtml
关键点
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?
- 这个设计能支持哪些不同的策略?
- NEW中等AIOpenClaw大模型应用开发AI应用开发Agent开发标记分享1144面试问答做这层抽象的根本原因是Context 管理没有万能方案。
备注
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?NEW中等AIOpenClaw大模型应用开发AI应用开发Agent开发标记分享1144面试问答做这层抽象的根本原因是Context 管理没有万能方案。通俗理解:Context Engine 就像手机的存储管理。有的人喜欢自动清理旧照片,有的人喜欢只删大文件,有的人喜欢全部存云端按需下载。不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大。把 Context 管理抽象成接口(定义好”做什么”),让策略实现(“怎么做”)可以独立替换,既方便内部迭代,也方便社区扩展。这就是经典的策略模式。ContextEngine接口定义在src/context-engine/types.ts,覆盖了完整的生命周期:这套接口覆盖了上下文管理的完整生命周期,核心有三大操作加上若干生命周期钩子:阶段方法做什么初始化bootstrap会话首次创建时做初始化(比如导入历史)存消息ingest/ingestBatch新消息进来时怎么存、要不要做额外处理(比如向量化)挑消息assemble发给模型前,在 token 预算内挑出最合适的一组消息压消息compact历史太长时怎么压缩(摘要、裁剪、归档…)轮后处理afterTurn每轮对话结束后的收尾工作(持久化、触发后台压缩等)子 AgentprepareSubagentSpawn/onSubagentEnded管理子 Agent 的上下文隔离与回收销毁dispose释放引擎持有的资源核心调度逻辑只依赖这套接口,压根不关心背后的具体实现。通过registerContextEngine(id, factory)注册新引擎,在配置里通过plugins.slots.contextEngine一行就能切换,完全不用动核心代码。这就是典型的策略模式 + 插件化。有了这层抽象,至少能支撑以下几种完全不同的策略方向:默认的 legacy 策略:全部塞进去,塞不下了线性压缩最早的消息,简单粗暴但够用基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景分层存储策略:类似冷热分离,最近几轮放内存、摘要放本地、更早的扔云端,按需拉取任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,同样的 token 预算质量更高自定义压缩策略:通过ownsCompaction标记接管压缩,可以实现树状摘要、按话题分支压缩等高级方式
内置的 legacy 引擎当前 OpenClaw 默认内置了一个legacy引擎,代码在src/context-engine/legacy.ts。它的实现非常直白:ingest是 no-op,因为消息持久化由 SessionManager 负责;assemble直接透传消息列表,不做任何筛选;compact委托给compactEmbeddedPiSessionDirect()做线性压缩。这个引擎能跑,但策略非常粗糙,基本就是”全部塞进去,塞不下了就压缩最早的”。对于简单的短对话够用,但一旦对话超过几十轮,或者涉及多个不同话题的长期交互,效果就明显不行了。可以实现的高级策略Context Engine 抽象真正的价值在于它能支撑完全不同的上下文管理思路:基于检索的 Context Engine这种引擎走 RAG 的路子。ingest的时候把每条消息向量化,存进向量库。到assemble阶段,不是按时间顺序把最近的消息拼进去,而是根据当前 query 做语义检索,把最相关的历史片段捞出来。对于那种跨好几天、中间换过好几个话题的长对话,这种方式比线性截断有效得多。分层存储引擎思路类似数据库的冷热分离。热数据就是最近 3-5 轮对话,直接放内存;温数据是最近几个 compaction 周期的摘要,放本地文件;冷数据是更早的历史,可以扔到外部存储甚至云端。assemble的时候按需从不同层拉取,既能保证最近的上下文完整,又不会因为历史太长撑爆内存。任务感知引擎根据当前任务类型动态调整 Context 组装策略。比如检测到用户在写代码,就优先保留代码相关的历史、文件路径、报错信息;检测到是闲聊,就优先保留情感偏好和个人信息。同样的 token 预算,塞进去的内容质量完全不一样。自定义 Compaction 引擎接口里有个ownsCompaction: true的标记,设了这个标记的引擎可以完全接管压缩策略。默认的线性压缩是把最早的消息一坨压成摘要,但你可以换成树状摘要,把对话按话题分支组织,每个分支独立压缩,保留更多结构化信息。插件注册机制整个切换过程对核心代码零侵入。写一个新引擎只需要实现ContextEngine接口,然后调用registerContextEngine(“my-rag-engine”, factory)注册进去。配置文件里把plugins.slots.contextEngine指向你的引擎 ID 就行了。这跟 Webpack 的 plugin 体系、VS Code 的扩展机制是一个思路,都是约定好接口,实现随便换。
- 提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?回答:legacy 引擎的消息持久化是 SessionManager 在做,ingest 啥也不干纯粹是因为职责没划到 Context Engine 这边。换成 RAG 引擎的话,ingest 就得真正接管消息的处理了,至少要做向量化和入库。迁移的关键是 SessionManager 得把”写消息”这个动作让出来,或者两边做好协调,不能重复写。实际上这也是为什么要抽象成接口的原因之一,职责边界可以随着引擎实现灵活调整。- 提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?
- 回答:legacy 引擎很暴力,直接从最早的消息开始砍,砍到塞得下为止。RAG 引擎好一些,它本来就是按相关性排序的,预算紧就少捞几条,质量衰减比较平滑。分层存储引擎会优先砍冷数据层,保住热数据。任务感知引擎最灵活,它可以根据任务权重动态决定哪些类型的历史先丢掉,比如写代码的时候闲聊记录优先级最低,第一个被砍。- 提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?
- 回答:OpenClaw 的压缩触发分两层。不设ownsCompaction的话,底层的 Pi runtime 有内置的 auto-compaction,它自己监控 token 用量,超过阈值就自动压缩,外层 Runner 不需要手动介入。设了ownsCompaction: true之后,Pi 的内置 auto-compaction 会被禁用,改由引擎自己通过afterTurn等生命周期钩子决定什么时候压、怎么压。但不管设没设这个标记,Runner 还有一层溢出压缩兜底,当上下文真的塞不下时,Runner 会直接调contextEngine.compact()做紧急压缩。简单说就是:ownsCompaction控制的是”日常谁来管压缩”,但”快炸了”的时候 Runner 一定会兜底。这种设计对有自己一套存储和索引体系的引擎特别重要,因为通用的压缩逻辑不了解引擎内部的数据组织方式,日常由引擎自主管理更合理。作者:Yes面试鸭官方 不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大
把 Context 管理抽象成接口,让策略实现可以独立替换,方便内部迭代,也方便社区扩展。
- 有了这层抽象,能支持以下几种完全不同的策略方向:
1.展开新页面打开2026-03-17 22:2900回复晚夜微雨问海棠特训营一、OpenClaw 抽象 Context Engine 的核心原因OpenClaw 将上下文管理从硬编码逻辑升级为可插拔的 Context Engine 插件化架构,本质是软件架构设计中「关注点分离(Separation of Concerns)」与「策略模式(Strat展开新页面打开2026-03-15 10:2600回复添加回答编辑预览请输入回答内容…(支持使用 Markdown )xMarkdown 语法一级标题# 标题二级标题## 标题三级标题### 标题粗体粗体文本**斜体斜体文本引用> 引用文本链接链接描述图片
代码代码块编程语言↵无序列表- 项目有序列表1. 项目分割线---删除线~~文本~~任务列表- [ ] 待办事项行内公式$公式$块级公式$$↵公式↵$$Mermaid图表mermaid快捷键粗体Ctrl-B斜体Ctrl-I链接Ctrl-K图片Shift-Ctrl-I代码Shift-Ctrl-K代码块Shift-Ctrl-C无序列表Shift-Ctrl-U有序列表Shift-Ctrl-O目录字数:0行数:1回到顶部提交目录内置的 legacy 引擎可以实现的高级策略插件注册机制
提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?热门面试题目榜更多说说 Java 中 HashMap 的原理?9130Java 中的序列化和反序列化是什么?6255MySQL 索引的最左前缀匹配原则是什么?5662Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?5067Java 中有哪些集合类?请简单介绍4854MySQL 的索引类型有哪些?4845详细描述一条 SQL 语句在 MySQL 中的执行过程。4218什么是 RAG?RAG 的主要流程是什么?4151MySQL 的存储引擎有哪些?它们之间有什么区别?4092数据库的脏读、不可重复读和幻读分别是什么?3900推荐教程更多AI 超级智能体亿级流量点赞系统教程智能协同云图库项目教程预览用户交流一起刷题学习、求职交流、反馈建议、获取更新通知面试鸭《用户协议》《隐私政策》友情链接编程导航老鱼简历代码小抄剪切助手联系我们商务合作站长:程序员鱼皮关注我们扫码关注面试鸭公众号
解释「短期记忆」和「长期记忆」在 Agent 系统中的区别,分别适合怎么存储和检索?OpenClaw 是什么?它要解决什么问题?它的核心能力有哪些?上次浏览:2026-03-16 15:12:52OpenClaw 的核心组件有哪些?请描述它们之间的关系上次浏览:2026-03-16 15:15:28在 OpenClaw 中,一条用户消息从进入系统到收到回复,完整链路是怎样的?OpenClaw 的 Agent Runner 是如何工作的?一次 Agent 运行经历了哪些阶段?LLM 的 Context Window 有上限,长对话时如何保证 Agent 仍然能正常工作?OpenClaw 是怎么做的?Agent 调用工具可能返回超大结果(比如代码搜索返回 50KB),这会带来什么问题?你会怎么处理?OpenClaw 是怎么做的?当对话历史实在太长、裁剪也不够用时,还有什么办法?什么是 Compaction?OpenClaw 的 Compaction 策略是怎样的?OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?如果一个 Agent 系统要同时接入 Telegram、飞书、钉钉等渠道,你会怎么设计抽象层?OpenClaw 的 Channel Plugin 接口是怎么设计的?13232. OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?NEW中等AIOpenClaw大模型应用开发AI应用开发Agent开发标记分享1144面试问答做这层抽象的根本原因是Context 管理没有万能方案。通俗理解:Context Engine 就像手机的存储管理。有的人喜欢自动清理旧照片,有的人喜欢只删大文件,有的人喜欢全部存云端按需下载。不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大。把 Context 管理抽象成接口(定义好”做什么”),让策略实现(“怎么做”)可以独立替换,既方便内部迭代,也方便社区扩展。这就是经典的策略模式。ContextEngine接口定义在src/context-engine/types.ts,覆盖了完整的生命周期:这套接口覆盖了上下文管理的完整生命周期,核心有三大操作加上若干生命周期钩子:阶段方法做什么初始化bootstrap会话首次创建时做初始化(比如导入历史)存消息ingest/ingestBatch新消息进来时怎么存、要不要做额外处理(比如向量化)挑消息assemble发给模型前,在 token 预算内挑出最合适的一组消息压消息compact历史太长时怎么压缩(摘要、裁剪、归档…)轮后处理afterTurn每轮对话结束后的收尾工作(持久化、触发后台压缩等)子 AgentprepareSubagentSpawn/onSubagentEnded管理子 Agent 的上下文隔离与回收销毁dispose释放引擎持有的资源核心调度逻辑只依赖这套接口,压根不关心背后的具体实现。通过registerContextEngine(id, factory)注册新引擎,在配置里通过plugins.slots.contextEngine一行就能切换,完全不用动核心代码。这就是典型的策略模式 + 插件化。有了这层抽象,至少能支撑以下几种完全不同的策略方向:默认的 legacy 策略:全部塞进去,塞不下了线性压缩最早的消息,简单粗暴但够用基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景分层存储策略:类似冷热分离,最近几轮放内存、摘要放本地、更早的扔云端,按需拉取任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,同样的 token 预算质量更高自定义压缩策略:通过ownsCompaction标记接管压缩,可以实现树状摘要、按话题分支压缩等高级方式
内置的 legacy 引擎当前 OpenClaw 默认内置了一个legacy引擎,代码在src/context-engine/legacy.ts。它的实现非常直白:ingest是 no-op,因为消息持久化由 SessionManager 负责;assemble直接透传消息列表,不做任何筛选;compact委托给compactEmbeddedPiSessionDirect()做线性压缩。这个引擎能跑,但策略非常粗糙,基本就是”全部塞进去,塞不下了就压缩最早的”。对于简单的短对话够用,但一旦对话超过几十轮,或者涉及多个不同话题的长期交互,效果就明显不行了。可以实现的高级策略Context Engine 抽象真正的价值在于它能支撑完全不同的上下文管理思路:基于检索的 Context Engine这种引擎走 RAG 的路子。ingest的时候把每条消息向量化,存进向量库。到assemble阶段,不是按时间顺序把最近的消息拼进去,而是根据当前 query 做语义检索,把最相关的历史片段捞出来。对于那种跨好几天、中间换过好几个话题的长对话,这种方式比线性截断有效得多。分层存储引擎思路类似数据库的冷热分离。热数据就是最近 3-5 轮对话,直接放内存;温数据是最近几个 compaction 周期的摘要,放本地文件;冷数据是更早的历史,可以扔到外部存储甚至云端。assemble的时候按需从不同层拉取,既能保证最近的上下文完整,又不会因为历史太长撑爆内存。任务感知引擎根据当前任务类型动态调整 Context 组装策略。比如检测到用户在写代码,就优先保留代码相关的历史、文件路径、报错信息;检测到是闲聊,就优先保留情感偏好和个人信息。同样的 token 预算,塞进去的内容质量完全不一样。自定义 Compaction 引擎接口里有个ownsCompaction: true的标记,设了这个标记的引擎可以完全接管压缩策略。默认的线性压缩是把最早的消息一坨压成摘要,但你可以换成树状摘要,把对话按话题分支组织,每个分支独立压缩,保留更多结构化信息。插件注册机制整个切换过程对核心代码零侵入。写一个新引擎只需要实现ContextEngine接口,然后调用registerContextEngine(“my-rag-engine”, factory)注册进去。配置文件里把plugins.slots.contextEngine指向你的引擎 ID 就行了。这跟 Webpack 的 plugin 体系、VS Code 的扩展机制是一个思路,都是约定好接口,实现随便换。
- 提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?回答:legacy 引擎的消息持久化是 SessionManager 在做,ingest 啥也不干纯粹是因为职责没划到 Context Engine 这边。换成 RAG 引擎的话,ingest 就得真正接管消息的处理了,至少要做向量化和入库。迁移的关键是 SessionManager 得把”写消息”这个动作让出来,或者两边做好协调,不能重复写。实际上这也是为什么要抽象成接口的原因之一,职责边界可以随着引擎实现灵活调整。- 提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?
- 回答:legacy 引擎很暴力,直接从最早的消息开始砍,砍到塞得下为止。RAG 引擎好一些,它本来就是按相关性排序的,预算紧就少捞几条,质量衰减比较平滑。分层存储引擎会优先砍冷数据层,保住热数据。任务感知引擎最灵活,它可以根据任务权重动态决定哪些类型的历史先丢掉,比如写代码的时候闲聊记录优先级最低,第一个被砍。- 提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?
- 回答:OpenClaw 的压缩触发分两层。不设ownsCompaction的话,底层的 Pi runtime 有内置的 auto-compaction,它自己监控 token 用量,超过阈值就自动压缩,外层 Runner 不需要手动介入。设了ownsCompaction: true之后,Pi 的内置 auto-compaction 会被禁用,改由引擎自己通过afterTurn等生命周期钩子决定什么时候压、怎么压。但不管设没设这个标记,Runner 还有一层溢出压缩兜底,当上下文真的塞不下时,Runner 会直接调contextEngine.compact()做紧急压缩。简单说就是:ownsCompaction控制的是”日常谁来管压缩”,但”快炸了”的时候 Runner 一定会兜底。这种设计对有自己一套存储和索引体系的引擎特别重要,因为通用的压缩逻辑不了解引擎内部的数据组织方式,日常由引擎自主管理更合理。作者:Yes面试鸭官方 不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大
把 Context 管理抽象成接口,让策略实现可以独立替换,方便内部迭代,也方便社区扩展。
- 有了这层抽象,能支持以下几种完全不同的策略方向:
1.展开新页面打开2026-03-17 22:2900回复晚夜微雨问海棠特训营一、OpenClaw 抽象 Context Engine 的核心原因OpenClaw 将上下文管理从硬编码逻辑升级为可插拔的 Context Engine 插件化架构,本质是软件架构设计中「关注点分离(Separation of Concerns)」与「策略模式(Strat展开新页面打开2026-03-15 10:2600回复添加回答编辑预览请输入回答内容…(支持使用 Markdown )xMarkdown 语法一级标题# 标题二级标题## 标题三级标题### 标题粗体粗体文本**斜体斜体文本引用> 引用文本链接链接描述图片
代码代码块编程语言↵无序列表- 项目有序列表1. 项目分割线---删除线~~文本~~任务列表- [ ] 待办事项行内公式$公式$块级公式$$↵公式↵$$Mermaid图表mermaid快捷键粗体Ctrl-B斜体Ctrl-I链接Ctrl-K图片Shift-Ctrl-I代码Shift-Ctrl-K代码块Shift-Ctrl-C无序列表Shift-Ctrl-U有序列表Shift-Ctrl-O目录字数:0行数:1回到顶部提交目录内置的 legacy 引擎可以实现的高级策略插件注册机制
提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?热门面试题目榜更多说说 Java 中 HashMap 的原理?9130Java 中的序列化和反序列化是什么?6255MySQL 索引的最左前缀匹配原则是什么?5662Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?5067Java 中有哪些集合类?请简单介绍4854MySQL 的索引类型有哪些?4845详细描述一条 SQL 语句在 MySQL 中的执行过程。4218什么是 RAG?RAG 的主要流程是什么?4151MySQL 的存储引擎有哪些?它们之间有什么区别?4092数据库的脏读、不可重复读和幻读分别是什么?3900推荐教程更多AI 超级智能体亿级流量点赞系统教程智能协同云图库项目教程预览用户交流一起刷题学习、求职交流、反馈建议、获取更新通知面试鸭《用户协议》《隐私政策》友情链接编程导航老鱼简历代码小抄剪切助手联系我们商务合作站长:程序员鱼皮关注我们扫码关注面试鸭公众号
来源: OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?.mhtml
-
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?
-
本文已做格式统一与噪声清理,保留原始语义。
-
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?
-
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?
-
- OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?NEW中等AIOpenClaw大模型应用开发AI应用开发Agent开发标记分享1144面试问答做这层抽象的根本原因是Context 管理没有万能方案。通俗理解:Context Engine 就像手机的存储管理。有的人喜欢自动清理旧照片,有的人喜欢只删大文件,有的人喜欢全部存云端按需下载。不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大。把 Context 管理抽象成接口(定义好”做什么”),让策略实现(“怎么做”)可以独立替换,既方便内部迭代,也方便社区扩展。这就是经典的策略模式。ContextEngine接口定义在src/context-engine/types.ts,覆盖了完整的生命周期:这套接口覆盖了上下文管理的完整生命周期,核心有三大操作加上若干生命周期钩子:阶段方法做什么初始化bootstrap会话首次创建时做初始化(比如导入历史)存消息ingest/ingestBatch新消息进来时怎么存、要不要做额外处理(比如向量化)挑消息assemble发给模型前,在 token 预算内挑出最合适的一组消息压消息compact历史太长时怎么压缩(摘要、裁剪、归档…)轮后处理afterTurn每轮对话结束后的收尾工作(持久化、触发后台压缩等)子 AgentprepareSubagentSpawn/onSubagentEnded管理子 Agent 的上下文隔离与回收销毁dispose释放引擎持有的资源核心调度逻辑只依赖这套接口,压根不关心背后的具体实现。通过registerContextEngine(id, factory)注册新引擎,在配置里通过plugins.slots.contextEngine一行就能切换,完全不用动核心代码。这就是典型的策略模式 + 插件化。有了这层抽象,至少能支撑以下几种完全不同的策略方向:默认的 legacy 策略:全部塞进去,塞不下了线性压缩最早的消息,简单粗暴但够用基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景分层存储策略:类似冷热分离,最近几轮放内存、摘要放本地、更早的扔云端,按需拉取任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,同样的 token 预算质量更高自定义压缩策略:通过ownsCompaction标记接管压缩,可以实现树状摘要、按话题分支压缩等高级方式
内置的 legacy 引擎当前 OpenClaw 默认内置了一个legacy引擎,代码在src/context-engine/legacy.ts。它的实现非常直白:ingest是 no-op,因为消息持久化由 SessionManager 负责;assemble直接透传消息列表,不做任何筛选;compact委托给compactEmbeddedPiSessionDirect()做线性压缩。这个引擎能跑,但策略非常粗糙,基本就是”全部塞进去,塞不下了就压缩最早的”。对于简单的短对话够用,但一旦对话超过几十轮,或者涉及多个不同话题的长期交互,效果就明显不行了。可以实现的高级策略Context Engine 抽象真正的价值在于它能支撑完全不同的上下文管理思路:基于检索的 Context Engine这种引擎走 RAG 的路子。ingest的时候把每条消息向量化,存进向量库。到assemble阶段,不是按时间顺序把最近的消息拼进去,而是根据当前 query 做语义检索,把最相关的历史片段捞出来。对于那种跨好几天、中间换过好几个话题的长对话,这种方式比线性截断有效得多。分层存储引擎思路类似数据库的冷热分离。热数据就是最近 3-5 轮对话,直接放内存;温数据是最近几个 compaction 周期的摘要,放本地文件;冷数据是更早的历史,可以扔到外部存储甚至云端。assemble的时候按需从不同层拉取,既能保证最近的上下文完整,又不会因为历史太长撑爆内存。任务感知引擎根据当前任务类型动态调整 Context 组装策略。比如检测到用户在写代码,就优先保留代码相关的历史、文件路径、报错信息;检测到是闲聊,就优先保留情感偏好和个人信息。同样的 token 预算,塞进去的内容质量完全不一样。自定义 Compaction 引擎接口里有个ownsCompaction: true的标记,设了这个标记的引擎可以完全接管压缩策略。默认的线性压缩是把最早的消息一坨压成摘要,但你可以换成树状摘要,把对话按话题分支组织,每个分支独立压缩,保留更多结构化信息。插件注册机制整个切换过程对核心代码零侵入。写一个新引擎只需要实现ContextEngine接口,然后调用registerContextEngine(“my-rag-engine”, factory)注册进去。配置文件里把plugins.slots.contextEngine指向你的引擎 ID 就行了。这跟 Webpack 的 plugin 体系、VS Code 的扩展机制是一个思路,都是约定好接口,实现随便换。
-
提问:legacy 引擎的 ingest 是 no-op,那消息是谁在管?如果换成 RAG 引擎,这块职责怎么迁移?回答:legacy 引擎的消息持久化是 SessionManager 在做,ingest 啥也不干纯粹是因为职责没划到 Context Engine 这边。换成 RAG 引擎的话,ingest 就得真正接管消息的处理了,至少要做向量化和入库。迁移的关键是 SessionManager 得把”写消息”这个动作让出来,或者两边做好协调,不能重复写。实际上这也是为什么要抽象成接口的原因之一,职责边界可以随着引擎实现灵活调整。- 提问:assemble 的时候有个 token 预算参数,如果预算给得很紧,不同引擎的降级策略会有什么差异?
-
回答:legacy 引擎很暴力,直接从最早的消息开始砍,砍到塞得下为止。RAG 引擎好一些,它本来就是按相关性排序的,预算紧就少捞几条,质量衰减比较平滑。分层存储引擎会优先砍冷数据层,保住热数据。任务感知引擎最灵活,它可以根据任务权重动态决定哪些类型的历史先丢掉,比如写代码的时候闲聊记录优先级最低,第一个被砍。- 提问:ownsCompaction 这个标记具体怎么生效的?不设这个标记的话压缩是谁触发的?
-
回答:OpenClaw 的压缩触发分两层。不设ownsCompaction的话,底层的 Pi runtime 有内置的 auto-compaction,它自己监控 token 用量,超过阈值就自动压缩,外层 Runner 不需要手动介入。设了ownsCompaction: true之后,Pi 的内置 auto-compaction 会被禁用,改由引擎自己通过afterTurn等生命周期钩子决定什么时候压、怎么压。但不管设没设这个标记,Runner 还有一层溢出压缩兜底,当上下文真的塞不下时,Runner 会直接调contextEngine.compact()做紧急压缩。简单说就是:ownsCompaction控制的是”日常谁来管压缩”,但”快炸了”的时候 Runner 一定会兜底。这种设计对有自己一套存储和索引体系的引擎特别重要,因为通用的压缩逻辑不了解引擎内部的数据组织方式,日常由引擎自主管理更合理。作者:Yes面试鸭官方- 不同的应用场景、不同的模型上下文窗口大小、不同的任务类型,最优的 Context 策略差异巨大
-
把 Context 管理抽象成接口,让策略实现可以独立替换,方便内部迭代,也方便社区扩展。
-
本文已做格式统一与噪声清理,保留原始语义。
Share Article
If this article helped you, please share it with others!