news 2026/7/1 2:38:35

OpenHarness源码研究-6-架构全景与设计模式总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenHarness源码研究-6-架构全景与设计模式总结

OpenHarness源码研究-6-架构全景与设计模式总结

前言

把前5篇的东西串起来,看整个项目靠什么设计模式撑起来的

完整数据流

一次oh -p "帮我改个bug"从头到尾经过的路径:

┌─────────────────────────────────────────────────────────────────────┐ │ CLI 层 │ │ cli.py:main() │ │ 解析参数 → typer.Option 声明的全部 flag │ │ └─ run_print_mode("帮我改个bug") │ └──────────────────────────────┬──────────────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────────────┐ │ Runtime 装配层 │ │ ui/runtime.py:build_runtime() │ │ load_settings() → 四层覆盖合并 │ │ _resolve_api_client_from_settings() → 根据配置选 Client │ │ load_plugins() → 扫描并加载插件 │ │ McpClientManager.connect_all() → 连接全部 MCP Server │ │ create_default_tool_registry() → 内置工具 + MCP 工具注册 │ │ build_runtime_system_prompt() → 动态拼装 System Prompt │ │ QueryEngine(...) → 创建引擎实例 │ │ → 打包为 RuntimeBundle │ └──────────────────────────────┬──────────────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────────────┐ │ 输入分发层 │ │ ui/runtime.py:handle_line() │ │ bundle.commands.lookup(line) │ │ ├─ slash命令 → CommandHandler │ │ └─ 普通对话 → engine.submit_message() │ └──────────────────────────────┬──────────────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────────────┐ │ Agent Loop (核心) │ │ engine/query.py:run_query() │ │ while turn_count < max_turns: │ │ ├─ auto_compact_if_needed() ← 上下文压缩检查 │ │ ├─ api_client.stream_message() ← 调 LLM │ │ │ └─ [AnthropicApiClient | OpenAICompatibleClient | ...] │ │ │ └─ HTTP/SSE → ApiStreamEvent │ │ ├─ yield StreamEvent ← 通知 UI 层 │ │ ├─ IF 无 tool_use → return │ │ └─ IF 有 tool_use → _execute_tool_call() │ │ ├─ hook_executor.execute(PRE_TOOL_USE) ← Hook 拦截 │ │ ├─ permission_checker.evaluate() ← 权限决策链 │ │ ├─ tool.execute() ← 真正执行 │ │ └─ hook_executor.execute(POST_TOOL_USE) ← Hook 审计 │ │ → 工具结果作为新 user 消息 → 继续循环 │ └──────────────────────────────┬──────────────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────────────┐ │ UI 渲染层 │ │ ui/app.py: 消费 StreamEvent │ │ AssistantTextDelta → 逐字打印到终端 │ │ ToolExecutionStarted/Completed → 显示工具状态 │ │ AssistantTurnComplete → 换行 + 存档 │ │ ErrorEvent / StatusEvent → stderr 输出 │ └─────────────────────────────────────────────────────────────────────┘

这是一个典型的管道架构——数据单向流动,每层有自己的职责,层与层之间通过定义好的接口通信。

六个核心设计模式

Protocol-策略模式

第3篇分析过的。SupportsStreamingMessages是个 Protocol(结构化子类型),四种 Client 谁也不继承谁,但都能被 QueryEngine 使用。

# 新增供应商的成本 # 如果 API 是 OpenAI 兼容的 → 零代码,只加 ProviderSpec # 如果 API 格式特殊 → 实现 stream_message() 一个方法

对比传统的 ABC + Factory 方案:ABC 要求显式继承,Factory 要求显式注册。Protocol 把这两步都省了。

适用场景:你有多种后端实现、它们之间没有共享代码、你不想引入依赖框架。

声明式注册表

api/registry.py的 PROVIDERS 元组是声明式的极致——42个 Provider,每个是一个 dataclass 实例,没有任何函数调用。检测逻辑是独立的三层扫描(key前缀 → URL关键字 → 模型名),不依赖注册顺序。

同样的模式出现在 Hook(hooks/schemas.py的 HookDefinition)、Skill(声明式 markdown)、Plugin(manifest 文件)。

适用场景:你需要管理大量同类配置项、配置需要被不同子系统按不同维度查询。

分层覆盖

Settings 的四层覆盖:cli arg → env var → settings.json → default。

这不是什么新奇模式,但实现上的细节值得注意——merge_cli_overrides()只覆盖非 None 的值,意味着用户可以不传大部分参数,只传需要覆盖的那一个。

适用场景:你的应用有多个配置来源,需要明确的优先级。

决策链

PermissionChecker 的 evaluate() 是一个顺序决策链。每个环节只检查一件事,拦截了就返回,不拦截就往下走。

敏感路径 → 黑名单 → 白名单 → 路径规则 → 命令规则 → 权限模式

这种写法比一个巨大的 if-elif-else 函数清晰得多。新增一个检查条件只需要在链中插入一个新步骤,不修改现有逻辑。

适用场景:你需要做多重条件判断,每层条件来源不同、优先级明确。

事件总线

引擎产生的 6 种 StreamEvent 是狭义的事件总线。更广义的——Hook 系统也是事件驱动(4 个 HookEvent → 3 种 Handler)。

两个事件系统的共同点:生产者不关心消费者是谁,消费者不关心事件是谁产生的。这让 UI 层可以从 Textual TUI 换成 React TUI(backend_only模式),引擎层一行代码都不需要改。

适用场景:你需要解耦数据生产和消费、或者同一个数据流有多个消费方。

原子文件写入

Mailbox(swarm/mailbox.py)和记忆系统都用了同样的模式:

# 先写 .tmp,再 rename tmp_path.write_text(payload, encoding="utf-8") os.replace(tmp_path, final_path) # 原子操作

os.replace在 POSIX 系统上是原子的——读取方要么看到旧文件,要么看到新文件,绝不会看到写了一半的残缺文件。

Mailbox 还加了一层文件锁(exclusive_file_lock),防止并发写入冲突。这在多 Agent 协作场景下是必须的。

适用场景:多进程/多 Agent 并发读写同一文件系统,需要保证数据一致性。

工程启发

减少依赖,用标准库

OpenHarness 没有引入 langchain、没有用任何 LLM 抽象框架。它的 API 层全部是直接封装原生 SDK 或裸 HTTP。代价是 4 个 Client 类加起来约 1300 行代码,收益是不会被第三方框架的版本升级绑架,也不会有"这个框架不支持某 API 的某个特性"的困境。

对于个人项目而言,引入一个依赖的决策门槛应该很高。尤其是当依赖做的事情你可以用 200 行代码自己写完的时候。

数据类优于字典

整个项目大量使用 dataclass 和 Pydantic BaseModel 做数据传递。从ApiMessageRequestTeammateSpawnConfig,没有看到裸 dict 在模块间传递。

dict 的问题是:没有类型提示,没有字段校验,拼错了 key 要到运行时才发现。dataclass 的成本几乎为零,但能让 IDE 帮你检查字段名。

不可变数据

ApiMessageRequestStreamEventResolvedAuth全部是frozen=True。不可变数据消除了数据在传递过程中被意外修改的可能性。这在异步代码中尤其重要——你不会想知道一个协程在 await 期间,另一个协程改了你手上的对象。

延迟导入

cli.py 的函数体内部到处是from openharness.xxx import ...。这是 CLI 工具的常见优化——oh --help不需要加载 anthropic SDK 和所有工具实现。启动时间从可能的好几秒降到毫秒级。

错误分类

API Client 层的错误不是一把抓的Exception,而是分类为AuthenticationFailureRateLimitFailureRequestFailure。重试逻辑据此决定是否重试——认证错误不重试(没用),限流错误要等一等再重试,网络错误可以立即重试。

这个思路可以用在任何需要调外部 API 的项目里。哪怕你只分了"可重试"和"不可重试"两类,也比统一重试所有错误要好。

总结

  • 数据流是单向管道:CLI → Runtime → Engine → API Client → LLM → Tool → 循环
  • 六个核心模式中,Protocol 策略模式和决策链是最有实用价值的两个
  • 减少依赖、用 dataclass 代替 dict、不可变数据、延迟导入、错误分类——这些不是创新,但组合在一起构成了工程上的可靠性
  • 197 个文件但耦合度低的本质原因是:层间接口清晰(Protocol + 数据类),模块内部高内聚,模块间通过事件解耦

写到最后

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 2:36:20

四川大学《微积分I-1》期末试卷及答案2016-2025学年PDF

四川大学《微积分I-1》期末试卷及答案2016-2025学年PDF 包括&#xff1a; 四川大学《微积分(I&#xff09;-1》2016-2017学年第一学期期末试卷.pdf 四川大学《微积分&#xff08;I&#xff09;-1》2017-2018学年第一学期期末试卷及答案.pdf 四川大学《微积分&#xff08;I&…

作者头像 李华
网站建设 2026/7/1 2:35:17

问题求解策略期末复习

“廉颇老矣&#xff0c;尚能饭否&#xff1f;”&#xff0c;事隔两个月&#xff0c;为了备战期末重出江湖啦&#xff01;和我一起一晚上速成C吧~问题 A: 区间素数个数统计题目描述给定两个整数 L 和 R&#xff08;1 ≤ L ≤ R ≤ 10000&#xff09;&#xff0c;请你统计区间 [L…

作者头像 李华
网站建设 2026/7/1 2:34:00

Java毕设选题推荐:基于 SpringBoot 的校园外卖骑手调度管理系统的设计与实现 基于 SpringBoot 的高校餐饮线上订购配送系统【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/7/1 2:33:21

Shizuku:不 Root 也能调系统 API

文章目录Shizuku&#xff1a;不 Root 也能调系统 API1、它在解决什么问题2、开发者怎么接入3、需要注意的几个点4、适合什么场景Shizuku&#xff1a;不 Root 也能调系统 API Shizuku 在 GitHub 上拿到了 26,807 Star。 这个工具解决了一个 Android 开发里的老问题&#xff1a…

作者头像 李华
网站建设 2026/7/1 2:33:03

LeWorldModel:1GB显存跑通JEPA世界模型,AI预测学习从入门到实践

上周在 GitHub 上看到一个项目&#xff0c;叫 LeWorldModel&#xff0c;短短几天就冲到了 4k star。点进去一看&#xff0c;介绍里写着“基于 JEPA 框架的世界动作模型&#xff0c;1GB 显存可运行”。说实话&#xff0c;看到“世界模型”和“1GB 显存”这两个词放在一起&#x…

作者头像 李华
网站建设 2026/7/1 2:31:23

Memtest86+ 终极指南:3步快速诊断内存故障,保障系统稳定运行

Memtest86 终极指南&#xff1a;3步快速诊断内存故障&#xff0c;保障系统稳定运行 【免费下载链接】memtest86plus Official repo for Memtest86 项目地址: https://gitcode.com/gh_mirrors/me/memtest86plus 当电脑频繁蓝屏、系统无故重启&#xff0c;或是重要数据莫名…

作者头像 李华