ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

notebooklm-py 的 Session/Kernel 拆分:ADR-0010 三分契约设计与能力组合架构的演进

notebooklm-py 的 Session/Kernel 拆分:ADR-0010 三分契约设计与能力组合架构的演进 notebooklm-py 的 Session/Kernel 拆分ADR-0010 三分契约设计与能力组合架构的演进【免费下载链接】notebooklm-pyUnofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLMs features—including capabilities the web UI doesnt expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.项目地址: https://gitcode.com/GitHub_Trending/no/notebooklm-py本篇文章基于开源仓库 notebooklm-py 的架构决策记录 docs/adr/0010-session-kernel-split.md 展开并结合 ADR-0013、ADR-0014 及当前源码完整还原这场从胖 Session 门面到窄契约 能力组合 直接依赖注入的解耦历程。导读本篇文章以 notebooklm-py 的架构决策记录 ADR-0010Session/Kernel 拆分为主线讲解该库如何把曾经集编排、RPC 编解码、drain 追踪、request-id 分配、cookie 访问、HTTP 生命周期与特性能力面于一身的Session拆分为五个成员的会话契约 三个成员的传输内核契约 单一成员的 drain 钩子契约并进一步演化为当前的能力组合capability composition与特性本地运行时模型。读完本文你将掌握这套契约拆分的完整决策脉络、各阶段契约的精确成员清单、当前源码中Kernel/RpcCaller/LoopGuard三个共享契约的实际形态以及单一消费者能力必须留在特性本地这一条贯穿始终的晋升规则。一、背景一个职责过载的Session在 ADR-0010 被提出时Tier-13 阶段Session同时承担了大量职责。从 ADR 原文看它身兼六项功能编排orchestration统一调度各特性 API 的调用流程RPC 编解码负责请求的编码与响应的解码drain 追踪在关闭时追踪未完成的操作request-id 分配为每次调用分配请求标识cookie 访问持有并暴露认证 cookieHTTP 生命周期管理httpx.AsyncClient的创建与关闭。此外它还对外暴露面向特性的能力面——Tier-12 的中间件链虽然已经隔离了横切关注点transport 层面的重试、刷新等但特性 API 仍然依赖_session_contracts.py中的共享能力协议以及每个特性自己的窄能力协议。同时在代码里还有特性本地运行时feature-local runtimesChatRuntime定义在_chat.py:90ArtifactsRuntime定义在_artifacts.py:154。这个形状直接阻碍了 Tier-13 的分解目标特性 API 需要一个稳定的编排契约transport 代码需要一个更小的、仅涉及 HTTP 的契约Artifacts 需要一个关闭时的钩子注册通道但不应因此膨胀通用Session面。这正是 ADR-0010 决策要解决的问题。二、决策Tier-13 的三分契约ADR-0010 的核心决策是在src/notebooklm/_session_contracts.py中定义三个结构契约以严格的成员数量门禁约束各自的边界契约成员数量成员清单Session: Protocol恰好 5 个rpc_call、transport_post、next_reqid、assert_bound_loop、operation_scopeKernel: Protocol恰好 3 个post、cookies、acloseDrainHookRegistration: Protocol恰好 1 个register_drain_hook这套设计的意图非常清晰特性 API 收敛到单一语义编排面——不再需要把许多窄能力片段组合来拼凑出一个可用的会话对象transport 边界足够小——一个具体的Kernel实现可以独立拥有httpx.AsyncClient生命周期与 cookie而无需同时承担 RPC 编排逻辑Artifacts 获得显式、类型化的 drain 钩子缝——其他特性只需针对Session做类型化不会被 Artifacts 的生命周期需求污染。BuildRequest别名的边界约束值得注意的细节是Session.transport_post接受的是既有公开别名BuildRequest来自src/notebooklm/_request_types.py并且 ADR 明确规定新的会话契约签名中不得暴露_BuildRequest。这是为了在契约层面阻止私有类型泄漏到协议签名里。在今天的源码中这条边界以更精细的形态延续了下来src/notebooklm/_web/transport/request_types.py 定义了AuthSnapshot、BuildRequestCallable[[AuthSnapshot], tuple[str, PostBody, dict[str, str] | None]]、BuildRequestResult与materialize_build_request等五个名字。BuildRequestResult是三元组命名 dataclass 形态被 ADR-0009 的AuthRefreshMiddleware采用materialize_build_request则把旧式的 tuple 回调桥接到Kernel.post所需的命名信封。可以看到ADR-0010 当年签名不得泄漏私有构建类型的原则最终落地成了公共输入类型公开化、内部形态用命名结果类收敛的具体实现。刻意保持的类型化 PRADR-0010 特别强调该 PR仅做类型化改造type-only。它定义契约与文档但不创建具体的_session.py或_kernel.py模块不重命名Session不移动 cookie不移动httpx生命周期不接入新的运行时行为。后续的 Tier-13 PR 才负责删除src/notebooklm/_capabilities.py与各特性的_XCore协议。DrainHookRegistration保持独立于Session正是为了让 Artifacts 能注册关闭时的轮询清理逻辑而无需给五成员Session面增加特性专用生命周期方法。三、后果与备选方案权衡中的取舍想要的后果特性 API 收敛到单一语义编排面transport 边界足够小使具体Kernel能独立持有httpx.AsyncClient生命周期与 cookieArtifacts 获得显式 drain 钩子缝其他特性只面向SessionBuildRequest别名阻止新协议签名泄漏_BuildRequest。不想要的后果迁移窗口期内旧_capabilities.py协议与新契约并存该 PR 中Session尚未结构上满足每个新契约——具体的一致性conformance要等后续抽取与重类型化 PR 落地。被否决的备选方案ADR-0010 明确记录了四个被否决的替代方案及其否决理由方案否决理由保留各特性的_XCore协议终态仍会在各特性子客户端模块重复同一组核心会话操作并使_capabilities.py成为永久协调点把register_drain_hook加进Session会为了 Artifacts 独有的生命周期需求扩大通用特性契约破坏五成员门禁暴露Kernel.stream成员chat 接收的是完整缓冲的httpx.Response流式字节迭代是内部 transport 实现细节不是面向消费者的 Kernel 操作在本 PR 移动具体类PR 13.1 刻意保持类型化_session.py/_kernel.py留给后续具体实现 PR四、契约为什么没能守住从五个成员漂移到八个ADR-0013 的记录显示ADR-0010 的五成员意图并未维持住。到 ADR-0013 撰写时_session_contracts.py中宽泛的Session协议已经长到了八个成员authAuthMetadata属性kernelKernel属性rpc_call(...)transport_post(...)next_reqid(...)assert_bound_loop()operation_scope(...)register_drain_hook(...)漂移的成因在 ADR-0013 中被剖析得很透彻auth与kernel是为上传流程方便而提升为成员的register_drain_hook尽管已有独立的DrainHookRegistration协议覆盖同一形状却仍被加入通用契约导致两个冗余协议携带同一单成员面transport_post与next_reqid只有 chat 一个特性在用但每个面向Session类型化的特性都被迫耦合了它们。根本原因是缺乏一条显式规则来阻止以防万一就先提升promote-it-just-in-case的漂移。这正是 ADR-0013 提出能力组合模型的直接动因。五、能力组合模型ADR-0013共享与特性本地的分界线ADR-0013 采纳了能力组合模型核心规则只有一条却治住了漂移共享能力协议只有当存在 ≥2 个生产消费者时才晋升到共享契约模块当时的_session_contracts.py单一消费者能力留在所属特性模块。依据这条规则审计把能力分为两类SHAREDrpc_call逻辑 RPC 分发被每个特性 API 使用loop 亲和性断言被 chat 与制品轮询使用FEATURE-LOCALtransport_post chat 手工的next_reqid记账只有 chat 需要drain 钩子注册只有制品轮询使用。同时 ADR-0013 还并行推进了两件邻接的边界整理拆分_mind_map.py通用 note 行 CRUD 服务新_note_service.py与 mind-map 适配器NoteBackedMindMapService留在_mind_map.py分离把AskResult存为 note 的所有权从NotesAPI迁到ChatAPI数据的所有者应当拥有持久化调用即ChatAPI.save_answer_as_note(notebook_id, ask_result, *, title: str | None None) - Note旧NotesAPI.create_from_chat转发器在 v0.7.0 被移除。此外 ADR-0013 还确立了不用 mixin 表达依赖下划线前缀模块隐私依旧适用两条纪律能力用Protocol声明抽取的行为由协作对象/服务持有公共面NotebookLMClient与 v0.4.1 可达的每个client.feature.method签名、默认值与返回类型全部保持不变。六、运行时解耦ADR-0014从普遍满足者到直接注入ADR-0013 解决的是接口模型但运行时仍有一个隐患NotebookLMClient.__init__把self._session一个Session实例传给每个特性 APISession充当所有协议的普遍满足者universal satisfier。ADR-0014 记录了这个形态的四个可观察后果Session必须满足所有特性协议的并集——方法数随特性数增长当时约 779 行的类上有约 33 个方法其中约 24 个是一行转发转发是结构上必需的而非偶然——Session.transport_post的存在只是因为ChatRuntime要求它测试 monkeypatchSession而非协作对象ADR-0007 的禁止 monkeypatch 白名单当时约 30 条文件级条目正是这个重力井的体现RpcOwner协议携带下划线前缀的Session内部成员——这是私有面被结构类型化不是窄契约。ADR-0014 因此立下六条实现规则把协作对象各自满足自己的协议落地Rule 1单协作对象协议被直接满足如RpcCaller由RpcExecutor直接满足LoopGuard由ClientLifecycle直接满足OperationScopeProvider与DrainHookRegistration由CallSupervisor直接满足Rule 2复合协议仅在适配器物有所值时引入特性本地适配器意图判断取代了旧的计数启发式否则消费者直接构造注入底层协作对象死协议随迁移一并删除Rule 3NotebookLMClient.__init__成为组合根composition root构造代码读起来像一张显式接线图Rule 4Session只保留编排与文档化的中间件链缝Rule 5协作对象直接接收它们真正依赖的对象RpcExecutor从owner: RpcOwner改为kernel/transport/auth_refresh/metrics关键字参数Rule 6适配器与所属特性同模块。这条路线最终走到终点2026-05-28 的session-elimination-plan把具体Session类与其模块整体删除NotebookLMClient直接持有ClientComposed、SessionCollaborators束、RpcExecutor与公开特性 API生命周期入口__aenter__/__aexit__/close/drain/is_connected直接调用ClientLifecycle与CallSupervisor。ADR-0014 的 Rule 2 适配器ArtifactsRuntimeAdapter、UploadRuntimeAdapter也因只隐藏三个稳定协作对象、且恰好一个生产满足者而被退役特性构造器改为直接接收rpcdrainlifecycle关键字参数。七、当前活态今天的共享契约到底长什么样把时间线拉回现在ADR-0010 标题中的名字大多已进入历史但它的精神——窄契约、明确边界、防泄漏——以更成熟的形式活在源码里。_runtime/contracts.pytransport 中立的共享契约当前文件 src/notebooklm/_runtime/contracts.py 的模块 docstring 本身就是一部演进史曾经的共享契约模块经历了_session_contracts.py→_runtime/contracts.py的重命名ChatRuntime/ArtifactsRuntime复合协议与适配器 dataclass 退役单一消费者的AuthMetadata内联到_web/sources/upload.py无用的AsyncWorkRuntime复合体在 issue #1327 中删除。现在它只导出一个名字class LoopGuard(Protocol): Loop-affinity assertion surface for features that own async work. def assert_bound_loop(self) - None: ..._web/contracts.pyweb 侧的两个契约由于 2026-08-27 的 web/mobile 后端拆分web 独有的Kernel与RpcCaller移到了 src/notebooklm/_web/contracts.pyKernel纯 transport 面由具体 web kernel 实现post(url, headers, body, *, read_timeout, max_response_bytes, expected_epoch) - httpx.Response、get_http_client(...) - httpx.AsyncClient、cookies属性、aclose()。对比 ADR-0010 的三成员 Kernelpost/cookies/aclose今天它多了get_http_client与 epoch 围栏参数——这也印证了 ADR-0014 之后 transport 契约随资源代际epoch控制演进的轨迹RpcCallerweb 特性 API 消费的窄 RPC 分发面rpc_call(method, params, source_path, allow_null, ...)带disable_internal_retries、operation_variant、read_timeout、raise_on_null_status等关键字参数。Kernel的具体实现类在 src/notebooklm/_web/transport/kernel.py第 17 行起继承EpochFencedRpcExecutorsrc/notebooklm/_web/transport/executor.py是RpcCaller的直接满足者——ADR-0014 Rule 1 的终态。drain 钩子与准入CallSupervisorADR-0010 中的DrainHookRegistration协议单成员register_drain_hook如今已被并入 src/notebooklm/_runtime/call_supervisor.py 的CallSupervisor。它的register_drain_hook(name, hook)注册或替换特性拥有的关闭时钩子内部存于_drain_hooks字典run_drain_hooks()并发执行这些钩子并保留进程退出优先级同一对象还通过operation_scope(label, ...)持有跨多次调用的准入代际operation lease通过assert_bound_loop()承担 loop 亲和性检查。也就是说ADR-0010 里分散在Session五成员与DrainHookRegistration单成员中的职责最终都收敛到了由CallSupervisor统一拥有生成准入与 drain 记账这一当前形态见 ADR-0014 的 2026-09-03 runtime cleanup 修订。八、防回归守护这套边界的测试网仓库里有一套专门防止旧架构复活的守护测试是这套契约演变能够持续推进而不回退的工程保障tests/_guardrails/test_session_runtime_boundaries.pyADR-0014 的 2026-05-28 host-protocol-removal 修订引入的回归 lint同时检查删除的模块、删除的辅助名、删除的客户端属性与ClientComposed.collaborators别名不能悄悄回来tests/_guardrails/test_no_session.py直接针对Session不得复现的守卫tests/_guardrails/test_no_facade_reach_in.py、tests/_guardrails/test_public_surface_manifest.py 等从不同角度钉住公开面与门面边界tests/unit/test_concurrency_refresh_race.py 中的 AST 守卫读取RuntimeTransport.terminal与refresh_request_for_current_auth的源码断言materialization 之后、Kernel.post之前不得出现await从语法层面保证并发刷新不会在快照重建与 POST 之间插入悬挂点。对希望在本仓库继续深入的人来说docs/architecture.md 是当前活态的权威描述docs/refactor-history.md 记录了能力重构弧线 Phases 1–7 的落地方案与模块映射ADR-0012 的降级/合并规则则与 ADR-0013 的晋升规则构成一对配套策略晋升上移到共享协议、降级折叠回特性本地协议或合并缝文件都不再需要新 ADR。九、结语这场演进留给我们什么从 ADR-0010 的五成员 Session 三成员 Kernel 单成员 DrainHookRegistration到契约漂移成八个成员再到 ADR-0013 的≥2 消费者才晋升共享规则与 ADR-0014 的直接注入终态notebooklm-py 用一段完整的重构弧线演示了窄契约的维持不能只靠自律必须依赖结构规则。五成员门禁之所以失守正是因为缺少没有第二消费者就不要提升的硬约束而这条约束一旦以代码形式协议晋升规则 回归 lint固化下来同一个Session就被彻底消灭取而代之的是Kernel/RpcCaller/LoopGuard这三个窄共享契约加上每个特性构造器里显式、最小化的关键字参数依赖。对于任何正在为门面对象越来越胖而困扰的 Python 异步库这组 ADR 的决策树与代价记录都是一份值得对照的工程样本。【免费下载链接】notebooklm-pyUnofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLMs features—including capabilities the web UI doesnt expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.项目地址: https://gitcode.com/GitHub_Trending/no/notebooklm-py创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表