ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Open Notebook 变更剧本(Change Playbooks)实战指南:跨全栈分步改造与数据库迁移操作手册

Open Notebook 变更剧本(Change Playbooks)实战指南:跨全栈分步改造与数据库迁移操作手册 Open Notebook 变更剧本Change Playbooks实战指南跨全栈分步改造与数据库迁移操作手册【免费下载链接】open-notebookAn Open Source implementation of Notebook LM with more flexibility and features项目地址: https://gitcode.com/GitHub_Trending/op/open-notebook本文档为开源项目Open Notebook一款更灵活、更开源、隐私优先的 NotebookLM 实现开发者维护指南中的实操核心。它按变更类型而非文件顺序组织了一套分步式操作手册Change Playbooks覆盖为领域模型加字段、新增 REST API、新增 LangGraph 工作流、单层/跨层 Bug 修复、SurrealDB 数据库迁移、纯前端改动、后台异步命令与 i18n 多语言更新等九大类常见改造场景。读完本文你将掌握每一类改动先动哪个文件、再动哪个文件、最后测什么的标准流程并能理解其背后由 Next.js 前端、FastAPI 后端与 SurrealDB 数据库构成的三层异步架构为何要如此拆分变更边界。在动手之前必须先明确docs/7-DEVELOPMENT/change-playbooks.md的定位是变更执行手册它与 architecture.md架构总览以及仓库中按目录分布的AGENTS.md根目录、open_notebook/AGENTS.md、frontend/AGENTS.md共同构成开发者的规则与陷阱知识源。文档开篇就给出一条对AI 编程 Agent 尤其重要的告诫For AI agents:Read the relevant playbook BEFORE implementing. Follow the sequence — skipping steps causes incomplete changes that break other layers.即实现前先读对应剧本并按顺序执行——跳步会造成只改一半的不完整变更进而破坏其它分层。一、先看架构为什么变更要分层进行要理解每个 playbook 为什么指定特定的文件顺序先看 Open Notebook 的三层架构详见 architecture.md┌─────────────────────────────────────────┐ │ Next.js Frontend (React 19) :8502 │ UI 内部代理 /api/* 请求 └──────────────────┬──────────────────────┘ │ REST (JSON / SSE) ▼ ┌─────────────────────────────────────────┐ │ FastAPI Backend (Python 3.11) :5055 │ Routers → Services → Domain/Repository │ Router /api/routers/ │ async-first、Pydantic v2 校验 │ Service /api/*_service.py │ │ Schema /api/models.py │ └──────────────────┬──────────────────────┘ ▼ ┌─────────────────────────────────────────┐ │ SurrealDB :8000 │ 图 向量数据库SurrealQL └─────────────────────────────────────────┘关键推论也是所有 playbook 的共同前提数据契约贯穿三层一个字段的生命周期会出现在领域模型Pydantic 字段→ 数据库迁移DEFINE FIELD→ API 请求/响应 Schema → 前端 TypeScript 接口 → 组件 → i18n 文案这一整条链路中。跳过任何一层轻则字段丢失重则前端类型与后端契约不一致导致运行时错误。业务逻辑不许放 Router文档反复强调 router 只做校验 → 调用 service → 返回响应业务编排一律下沉到api/*_service.py。迁移不是自动发现的SurrealDB 迁移文件必须被显式注册到AsyncMigrationManager否则写了也不会执行详见下文「数据库迁移」一节。长任务走后台命令需要异步执行的耗时任务podcast 生成、source 处理、embedding 重建必须通过CommandService.submit_command_job()提交而不是阻塞在 HTTP 请求里。二、本文档的正确使用方式原文档给出了四步使用方法是挑选剧本的入口先判断你的 issue 属于哪种变更类型按剧本一步步执行如果改动跨越多种类型例如新增字段 新增端点把相关剧本组合起来使用拿不准时回代码库找最近的同类改动做参照——用git log查看最新一次相似变更是如何落地的。实际开发中Add a Field新增字段与Database Migration两个剧本通常要合并执行因为多数新增字段都会改变数据库 schema而新增字段 暴露给前端又会牵出New API Endpoint与Frontend-Only Change的内容。下面的各小节可以按需拼装。三、Playbook 1为现有模型新增字段典型场景示例给Source增加一个language字段。步骤文件做什么1open_notebook/domain/model.py添加带类型标注与默认值的字段跟随类中既有写法2open_notebook/database/migrations/N.surrealql创建迁移使用序列中的下一个编号DEFINE FIELD声明新字段需要回填存量数据时用UPDATE随后在AsyncMigrationManagerasync_migrate.py中注册——迁移不会被自动发现3api/models.py把字段加入*Create、*Update此处应为 Optional与*Response三个 Pydantic Schema4frontend/src/lib/types/api.ts在对应 TypeScript 接口*Response、Create*Request、Update*Request中同步添加字段5前端组件若要面向用户在相关组件中展示或编辑该字段6frontend/src/lib/locales/*/若字段有用户可见的标签为所有语言文件补充 i18n 字符串7测试新增/更新覆盖新字段的测试——至少包含 create/read 的 API 测试验证重启 API迁移会自动执行在 Loguru 日志中确认迁移成功再通过/docs交互式接口实测。源码印证三层三份 Schema领域层字段定义参照 open_notebook/domain/notebook.py 中Notebook的写法——每个领域对象继承 open_notebook/domain/base.py 的ObjectModelPydantic BaseModel以ClassVar声明table_name字段直接带类型与默认值需要时用field_validator做业务校验如Notebook.name不允许空白class Notebook(ObjectModel): table_name: ClassVar[str] notebook name: str description: str archived: Optional[bool] False last_viewed_at: Optional[datetime] NoneAPI 层则要求为 Create / Update / Response 各建一份 Pydantic Schema。以 api/models.py 中的NotebookCreate/NotebookUpdate/NotebookResponse为范式Create 用必填 默认值Update 全部字段Optional[...] None只有显式传入才更新Response 输出id、created、updated等数据库回读信息class NotebookCreate(BaseModel): name: str Field(..., descriptionName of the notebook) description: str Field(default, descriptionDescription of the notebook) class NotebookUpdate(BaseModel): name: Optional[str] Field(None, descriptionName of the notebook) archived: Optional[bool] Field(None, descriptionWhether the notebook is archived) class NotebookResponse(BaseModel): id: str name: str description: str archived: bool created: str updated: str source_count: int note_count: int值得注意的细节是ObjectModel.save()base.pyid为空时走repo_create插入并打created时间戳id存在时走repo_update保存前还会model_validate(..., strictTrue)做严格校验。因此领域层新增字段时默认值、必填性必须与迁移文件、API Schema 保持一致——三处任一不一致都会在保存链路上暴露出来。四、Playbook 2新增 API Endpoint典型场景示例新增把 notebook 导出为 PDF的端点。步骤文件做什么1api/models.py定义请求/响应 Pydantic Schema命名遵循FeatureRequest、FeatureResponse2api/routers/resource.py在既有 router 中加端点若是全新资源则新建 router 文件。遵循固定套路校验 → 调 service → 返回响应3api/resource_service.py业务逻辑放这里不许写进 router必要时新建 service 文件4api/main.py若是新建 router 文件用app.include_router()注册5frontend/src/lib/types/api.ts添加与 Pydantic Schema 对齐的 TypeScript 类型6frontend/src/lib/api/resource.ts在对应 API 模块添加方法沿用既有模式axios 调用返回response.data7frontend/src/lib/hooks/use-resource.ts添加 React Query HookGET 用useQueryPOST/PUT/DELETE 用useMutation并包含缓存失效与 toast8前端组件/页面在 UI 中接入该 Hook9测试API 测试状态码、校验、错误分支命名约定必须遵守Router 路径router.get(/resources/{id})—— 复数、小写、多单词用 kebab-caseService 函数async异步函数、描述性命名process_source、generate_podcastHook列表用useResources()单个用useResource(id)变更操作用useCreateResource()。源码印证Router 的装饰器形态api/routers/sources.py 展示了真实 router 的写法——response_model显式声明响应的 Pydantic Schema这是后端契约的单一事实来源router.get(/sources, response_modelList[SourceListResponse]) router.post(/sources, response_modelSourceResponse) router.get(/sources/{source_id}, response_modelSourceResponse)而请求-响应周期architecture.md概括为HTTP Request → Router → Service → Domain/Repository → SurrealDB可选的 AI 环节在 Service 层挂载 LangGraph 图返回时再经 Pydantic 序列化。新增端点时只要沿这条链补位即可不必打破任一环节的既有边界。五、Playbook 3新增 LangGraph 工作流典型场景示例新增一个 summarization摘要工作流。步骤文件做什么1prompts/workflow_name/*.jinja创建 Jinja2 提示词模板使用 ai-prompter 的Prompter2open_notebook/graphs/workflow_name.py定义StateDictTypedDict、节点函数用StateGraph构图用provision_langchain_model()选择模型LLM 调用用classify_error()包裹3api/resource_service.py调用图await graph.ainvoke(state, config)4api/routers/resource.py暴露触发工作流的端点5commands/workflow_commands.py若工作流需要异步执行用CommandInput/CommandOutput创建 command 并注册到 command service6前端集成API 模块 → Hook → 组件7测试用 mock 的 LLM 响应逐个测试图节点关键模式Key patterns节点是同步函数LangGraph 的约束但可通过ThreadPoolExecutor调用异步代码使用classify_error()把裸异常转换为类型化的OpenNotebookError子类使用provision_langchain_model()选择模型——绝不硬编码某家 providerState 是TypedDict不是 Pydantic 模型。源码印证Source 处理图的结构open_notebook/graphs/source.py 是仓库中最典型的一张图。它的 State 定义为class SourceState(TypedDict)来自typing_extensions节点如async def content_process(state)、async def save_source(state)都是模块级函数最终通过StateGraph(SourceState)串联并出口为workflow。从这里可以观察三条与本 playbook 对应的事实状态即数据契约图的输入输出就是 TypedDict与 Pydantic 领域模型解耦跨节点传递临时中间量如 embeddings、topics都放在 dict 中节点职责单一抽取内容、清洗、生成 embedding、保存、触发 transformation 各自成节点便于用 mock LLM 单测模型选择集中管理LLM 实例统一由open_notebook/ai/provision.py的provision_langchain_model()按任务类型与上下文大小提供底层对接 Esperanto 多 provider 抽象见 architecture.md这保证了换模型不换代码。仓库中同类图还包括ask.py、chat.py、transformation.py、prompt.py、source_chat.py都在open_notebook/graphs/下可作为新工作流的对照蓝本。六、Playbook 4Bug 修复单层典型场景示例sources 端点上的order_by参数不生效。步骤做什么1判定问题层。阅读 issue判断它属于前端、API router、service、领域模型、数据库还是 graph2读规则文档读取相应层级的AGENTS.md根目录、open_notebook/或frontend/及docs/7-DEVELOPMENT/中的对应页面——它们记录了规则与坑3复现。用 API 文档/docs、浏览器或一条测试复现该 Bug4修复。做最小改动不要顺手重构周边代码5补测试让测试先复现 Bug、再验证修复6回归运行uv run pytest tests/确认无回归实战案例印证order_by校验恰好有对应源码与测试order_by这个示例并非虚构——open_notebook/domain/base.py 的ObjectModel._validate_order_by()正是为防 SurrealQL 注入而对 ORDER BY 做白名单校验的实现只允许形如field、field asc/desc、field1 dir, field2 dir的写法字段名必须匹配^[a-z_][a-z0-9_]*$方向仅限asc/desc非法输入直接抛InvalidInputError。与之配套的回归测试位于 tests/test_order_by_validation.py。这说明单层 Bug 修复的正确姿势是找到该层承担职责的那段代码做最小改动并用一个能复现问题的测试锁定行为——就像这里用_validate_order_by 专项测试把参数校验与注入防护钉死在领域层。七、Playbook 5Bug 修复跨层典型场景示例通过 URL 创建的 source 没有出现在 notebook 里。步骤做什么1追踪数据流。从用户看到问题的位置前端往回追组件 → Hook → API 调用 → router → service → domain → database2定位断链处。用 API 文档脱离前端单独测后端用 SurrealDB 查询确认数据是否已持久化3在正确的层修复。Bug 若在 service不要在前端打补丁掩盖症状4修复后验证完整链路5在 Bug 所在层补测试跨层 Bug 与单层 Bug 的核心差别在于症状出现的位置通常是 UI往往不是根因所在的位置。文档给的排查纪律是先二分到层、再二分到函数并利用 SurrealDB 直接查询open_notebook/database/repository.py的repo_query是后端执行 SurrealQL 的统一入口配合ensure_record_id()规范化 record id来确认数据到底写没写进去。例如创建 source 的数据链路上URL 抓取 → 抽取 → embedding → 落库 → 建立reference关系source → notebook每一步都可能在 SurrealDB 的图关系上断掉。从领域层看notebook 读取来源靠的是图边查询——notebook.py 中Notebook.get_sources()即通过select in as source from reference where out$id fetch source反查reference边若 source 创建后没有relate这条边notebook 视图自然看不到它而数据库里却查得到 source 记录。这类有记录无关系/无 embedding/无索引的现象正是用 SurrealDB 查询做链路诊断的典型场景。仓库在 tests/ 下提供了大量对应层的回归测试范本如test_sources_api.py、test_crud_404.py等修复后照例补在 Bug 所在层。八、Playbook 6数据库迁移SurrealDB典型场景示例为查询性能在source.notebook_id上加索引。步骤文件做什么1open_notebook/database/migrations/N.surrealql及其N_down.surrealql编写 SurrealQL。使用序列中的下一个编号参照既有迁移文件的写法2open_notebook/database/async_migrate.py把新文件注册进AsyncMigrationManager.__init__——迁移是硬编码注册的不是自动发现的3领域模型若 schema 变化同步更新字段定义4API Schema若字段新增/变化同步更新 Pydantic 模型5验证重启 API 并查看日志迁移在启动时自动执行观察 Loguru 输出是否有错误重要提醒Important迁移按编号顺序执行执行记录维护在_sbl_migrations表中——已执行的不会重跑每个需要迁移的 PR 只写一个迁移按合并顺序编号迁移一旦合入 main 就绝不合并/回改开发镜像会立刻应用它——详见 ADR-006: Migration Granularity破坏性变更如DROP FIELD要考虑数据保留问题测试时用带存量数据的库验证而不是只测空库。源码印证迁移引擎的真实机制open_notebook/database/async_migrate.py 完整实现了文档描述的三件事1) 从文件读取并清理 SQL。AsyncMigration.from_file()async_migrate.py逐行读取.surrealql跳过空行与--注释后拼接执行——这也是为什么迁移文件里可以写大段说明性注释。2) 硬编码注册 up/down 两套列表。AsyncMigrationManager.__init__async_migrate.py显式列出1.surrealql…23.surrealql与对应的*_down.surrealql。新增迁移若忘了在这里追加即使文件存在也不会执行——这正是文档反复强调不自动发现的原因。执行顺序由AsyncMigrationRunner.run_all()async_migrate.py负责它先读当前版本号再从该版本起逐个执行 up 迁移。3) 版本记录在_sbl_migrations表。bump_version()async_migrate.py在执行成功后CREATE type::thing(_sbl_migrations, $version)写入版本与应用时间get_latest_version()async_migrate.py从该表取最大值表不存在视为版本 0全新库。run_one_down()async_migrate.py则执行对应 down 迁移并lower_version()用于手动回滚。真实迁移文件的三种写法仓库里 migrations/22.surrealql 是一个字段清理 回填 删定义的复杂迁移范本展示了三类关键 SurrealQL 用法数据回填用子查询按 provider/name 从model表匹配 record id 并回写引用字段清理存量UPDATE episode_profile UNSET outline_provider, outline_model, ...——先清值再删定义避免脏数据残留删字段定义REMOVE FIELD IF EXISTS outline_provider ON TABLE episode_profile;破坏性操作注意数据保留策略。migrations/23.surrealql 则是只回填不定义字段的另一个典型由于content_settings位于SCHEMALESS的open_notebook表新字段docling_formulas、docling_vision无需DEFINE FIELD只需UPDATE ... SET ... false WHERE ... NONE把显式默认值回填到存量记录让运维在 UI 上能读到当前关闭的状态而非缺失的 key。它对应的回滚文件 migrations/23_down.surrealql 则是UPDATE ... UNSET docling_formulas, docling_vision;。从这两例可以总结出迁移写作的三个判断标准schema 是否有 DEFINED FIELD有则DEFINE FIELDUPDATE回填无则只UPDATE、是否破坏性破坏性先保留数据再降级、是否可回滚每个 up 都配套N_down.surrealql。九、Playbook 7纯前端变更典型场景示例改进 notebook 列表的 loading 状态。步骤文件做什么1定位组件组件位于frontend/src/app/页面或frontend/src/components/共享组件2修改遵循既有模式函数组件、状态用 hooks、样式用 Tailwind3i18n 字符串新增用户可见文案时必须同步到frontend/src/lib/locales/下的所有语言文件4浏览器测试检查响应式布局、暗色模式如适用、loading 态、空态、错误态关键模式使用 hooks 的组件顶部必须加use client指令状态管理三分局部状态用useState全局状态用 Zustand服务端状态用 TanStack Query样式Tailwind 工具类 components/ui/下的 Shadcn/ui 组件类型统一定义在lib/types/api.ts各处 import——即 frontend/src/lib/types/api.ts。纯前端变更如 loading 状态通常不需要后端改动但要守住三条红线文案必须走 i18n否则其它语言直接缺 key、类型必须来自lib/types/api.ts不能散落定义、客户端逻辑必须符合use client边界。Frontend 的架构约束可进一步阅读 docs/7-DEVELOPMENT/frontend.md。十、Playbook 8新增后台命令Background Command典型场景示例为某个 notebook 添加重建全部 embedding 的命令。步骤文件做什么1commands/name_commands.py定义CommandInput、CommandOutputPydantic 类编写命令函数2注册命令加入 command service使其可通过CommandService.submit_command_job()被提交3API 端点新增提交命令并返回 command id 的端点4前端轮询通过/commands/{command_id}端点轮询状态向用户展示进度模式要点命令是fire-and-forget的提交后立即返回 command id重试配置包含max_attempts、stop_on异常列表ValueError不重试瞬时故障采用带抖动的指数退避exponential backoff with jitter。源码印证命令的真实配置形态仓库中的重建 embedding 命令正是 commands/embedding_commands.py含embed_note/embed_source/rebuild_*等它是本 playbook 的活教材CommandInput、CommandOutput直接来自surreal_commands库from surreal_commands import CommandInput, CommandOutput, command, submit_command命令函数统一走EMBED_RETRY_CONFIGembedding_commands.py作为重试配置其结构正是文档所描述的范式EMBED_RETRY_CONFIG { max_attempts: 5, wait_strategy: exponential_jitter, wait_min: 1, wait_max: 60, stop_on: [ ValueError, ConfigurationError, ], # 校验/配置类错误不重试 retry_log_level: debug, }stop_on的语义很清晰把重试也无效的错误类型参数错误、配置错误排除在重试之外而瞬时故障网络、DB 抖动交给指数退避反复尝试。注意文件中还留有一段代码注释提醒stop_on目前在实践层面很难触发因为命令内部捕获了ValueError并以successFalse返回而非抛出——这类注释与实现之间的张力正是阅读既有命令实现时要留意的坑。HTTP 层可通过/commands/{command_id}轮询任务状态详见 architecture.md。十一、Playbook 9i18n / 翻译更新典型场景示例为新设置页添加翻译。步骤文件做什么1frontend/src/lib/locales/en-US/index.ts先加英文字符串按功能分组2其余所有语言文件在pt-BR、zh-CN、zh-TW、ja-JP、ru-RU、bn-IN等文件里添加同样的 key暂无译稿时先用英文占位3组件使用const { t } useTranslation()通过t(section.key)访问语言数量提醒仓库现状原文档写于语言注册仅 7 种的时期7 locales total一个都不能漏。从当前仓库看frontend/src/lib/locales/index.ts 的languages数组实际已注册14 种语言en-US、tr-TR、ca-ES、zh-CN、zh-TW、pt-BR、ja-JP、it-IT、fr-FR、ru-RU、bn-IN、es-ES、de-DE、pl-PL每个目录下都有index.ts。因此实际执行时请以frontend/src/lib/locales/目录与resources对象中的最新清单为准逐一补齐。新增一整套语言步骤文件做什么1frontend/src/lib/locales/code/index.ts复制en-US/index.ts的结构并翻译全部字符串2frontend/src/lib/locales/index.ts注册语言import、加入resources、加入languages数组{ code, label }3frontend/src/lib/utils/date-locale.tsimport 对应的date-fns/locale并加入LOCALE_MAP4测试通过界面语言切换器切换语言缺失的 key 会回退到 en-USlanguages数组中每个语言项的结构就是{ code, label }code 为区域码、label 为本地语言名翻译 key 的类型以typeof enUS为基准type TranslationKeys typeof enUSlocales/index.ts因此en-US 必须先写好——它是其余所有语言文件 key 对齐的类型与内容基准。翻译层与 date-fns 本地化的对接测试可参考 frontend/src/lib/locales/interpolation.test.ts。十二、各分层文件速查表Quick Reference下表是原文档的最终收束也是执行任何剧本时的目录地图。所有路径均以仓库根目录为基准分层位置Schema/类型测试领域模型 Domain modelsopen_notebook/domain/Pydantic 字段tests/数据库 Databaseopen_notebook/database/repository.pySurrealQLtests/迁移 Migrationsopen_notebook/database/migrations/*.surrealqlSurrealQL启动时自动执行AI/LLMopen_notebook/ai/Esperanto 类型tests/图 Graphsopen_notebook/graphs/TypedDict statetests/提示词 Promptsprompts/**/*.jinjaJinja2 context—命令 Commandscommands/CommandInput/Outputtests/API 路由 Routersapi/routers/api/models.pytests/API 服务 Servicesapi/*_service.py—tests/前端类型 Frontend typesfrontend/src/lib/types/TypeScript interfaces—前端 APIfrontend/src/lib/api/——前端 Hooksfrontend/src/lib/hooks/—frontend/src/test/前端组件frontend/src/components/Props interfacesfrontend/src/test/前端页面frontend/src/app/——i18nfrontend/src/lib/locales/——从这张表可以提炼贯穿所有剧本的总规律后端按领域 → 迁移 → AI/图 → 命令 → Router → Service自底向上扩展前端按类型 → API 模块 → Hook → 组件/页面自底向上扩展每补一层就在该层对应的测试目录补一个测试。这正是 Open Notebook 的服务模式Service Pattern与异步优先架构在变更流程上的投影见 architecture.md。十三、动手前后命令清单与验证闭环无论执行哪个剧本以下验证手段贯穿始终后端单测与回归uv run pytest tests/对应文档 Bug 修复剧本中的回归命令前端可运行 Vitest 测试见frontend/src/test/与 frontend/vitest.config.ts交互式 API 验证API 启动后访问/docsSwagger UI可脱离前端直接验证新增端点与字段的请求/响应契约迁移验证重启 API看 Loguru 启动日志中的Running migration N与Migration successful. New version: Nasync_migrate.py——迁移失败会打印Migration failed: ...并中断启动流程数据库直查用 SurrealDB 客户端确认数据与_sbl_migrations版本表用于跨层断链定位变更参照git log查看仓库中最近一次相似改动如何组织文件与测试。结语把改哪里固化成怎么改Open Notebook 的 change-playbooks 之所以有价值是因为它把一个全栈 AI 工作流 图数据库迁移的复杂代码库的变更成本压缩成了先判断类型、再按顺序走步骤、最后按层补测试的可执行流程。对人工开发者而言它消除了跨文件遗漏对 AI Agent 而言它提供了确定性顺序避免跳步导致其它分层损坏。配合 architecture.md 理解分层动机、配合AGENTS.md规避已知陷阱、配合git log参照最近落地案例任何新增字段、新端点、新工作流、新命令或迁移改造都能以最小风险落地并保证前端、API 与数据库三端契约始终一致。【免费下载链接】open-notebookAn Open Source implementation of Notebook LM with more flexibility and features项目地址: https://gitcode.com/GitHub_Trending/op/open-notebook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表