
Omi 插件体系重构审计实录从重复模型到统一omi-plugin-sdk的迁移决策【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend本篇技术指南以 Omi 开源仓库中plugins/PLUGIN_REFACTOR_AUDIT.md这份历史重构审计记录为核心骨架梳理插件体系从每个插件各自维护一份 webhook 模型副本到统一由omi-plugin-sdk提供 canonical 模型的迁移过程。文章会结合仓库中 SDK 源码、后端兼容层、遗留单体部署文件与测试用例讲清模型收敛的动机、部署与依赖矩阵、相对路径安装的风险以及遗留单体为何被保留的完整决策记录帮助读者理解在单体仓库中做跨服务模型共享重构时需要考虑的部署约束。审计背景与文档定位plugins/PLUGIN_REFACTOR_AUDIT.md是 2026-06-29 针对插件重构工作issue #8559编写的审计记录。它既是一份决策记录decision record也附带了一份2026-09-03 的 Superseded 更新说明文档中提到的 SDKauth.py、webhook.py、fastapi.py三个辅助模块已于 2026-07-13commitf6ac87773f被删除——因为它们是零调用方的 speculative helpers删除后 SDK 变为纯模型包__init__.pymodels.pyplugins/_mem0与plugins/advanced/相关引用也已被删除文档其余部分作为历史决策记录保留。因此阅读这份文档时要注意表格与结论描述的是重构当时的设计意图而仓库当前的实际状态以plugins/README.md、plugins/omi-plugin-sdk/README.md与plugins/LEGACY_MONOLITH.md为准。这篇文章会同时呈现历史决策与当前落地状态两层信息。重构动机被四处复制的Structured/ActionItem/Event审计文档在Shared Model Surfaces一节明确点出了重构前的痛点Structured、ActionItem、Event这几个核心 webhook 模型在仓库里被重复定义于多个位置SurfaceBefore重构前After重构后Structured、ActionItem、Event在后端、根目录plugins/models.py、Dropbox 应用以及future-use应用模型块中重复定义收敛为plugins/omi-plugin-sdk/src/omi_plugin_sdk/models.py中的唯一实现backend/root/plugin 文件改为 re-exportDropbox webhook 解析本地ActionItem/Structured定义与后端漂移plugins/omi-dropbox-app/models.py导入 SDK 的Conversationaction_items通过 SDK 解析根目录插件单体模型本地实现 webhook 模型从 SDK 兼容导入仅保留单体特有的 proactive notification 模型这段描述的实质是webhook 载荷是跨服务共享的契约。后端生成会话结构化结果插件服务消费同一份 JSON 结构如果每个服务各自维护一份 Pydantic 定义字段一旦漂移例如后端给ActionItem增加了source_segment_idsDropbox 的旧副本没有解析就会出现静默失败或类型错误。收敛到单一 canonical 实现是从源头消除这类漂移。落地验证SDK 模型的 canonical 实现当前 SDK 位于plugins/omi-plugin-sdk/src/omi_plugin_sdk/models.py核心模型包括Conversationwebhook 载荷的顶层模型包含id、created_at、transcript_segments、photos、structured、apps_results、plugins_results、discarded等字段Structured会话的结构化摘要title、overview、emoji、category、sections、action_items、eventsActionItem行动项description、completed、due_at、capture_kind、capture_confidence、ownership_confidence、source_segment_ids等TranscriptSegment转录片段text、speaker、is_user、start、end构造时自动从SPEAKER_03解析出speaker_id 3Event、Section、ConversationPhoto、PluginResult、AppResult、EndpointResponse等辅助模型。SDK 的包元数据在plugins/omi-plugin-sdk/pyproject.toml中定义[project] name omi-plugin-sdk version 0.1.0 description Shared Omi plugin webhook models and small integration primitives. readme README.md requires-python 3.10 dependencies [pydantic2.0.0] [tool.setuptools.packages.find] where [src]__init__.py将全部模型作为__all__导出SDK 对外呈现为omi_plugin_sdk.models命名空间应用侧统一from omi_plugin_sdk.models import ...导入。测试佐证canonical 模型的解析契约plugins/omi-plugin-sdk/tests/test_models.py用 4 个用例锁定了模型的契约行为值得逐条看Structured容忍非法 category传入not-a-real-category时set_category_default_on_error校验器把它兜底为CategoryEnum.other同时action_items能正确解析、events默认空列表——保证后端产出的历史载荷不会因枚举漂移而解析崩溃旧版载荷的向后兼容不含photos、apps_results等可选字段的 legacy payload 仍可被Conversation.model_validate接受discarded默认Falsespeaker_id自动推导Dropbox 兼容的辅助方法get_duration()输出0:00:05get_transcript(include_timestampsTrue)输出[0:00:00 - 0:00:02] Speaker 1: helloget_transcript(user_nameUser)输出User: reply——这正是 Dropbox 应用把转录/摘要写入文件的底层格式化逻辑apps_results与plugins_results同步model_validator(modeafter)的sync_plugin_results会在apps_results存在而plugins_results为空时自动把AppResult转成等价的PluginResult——这是兼容历史字段名旧版 webhook 用plugins_results新版引入apps_results的显式桥接。部署与依赖矩阵两种安装方式、三类消费方审计文档的Deploy and Dependency Matrix是整份记录信息密度最高的部分完整继承如下TargetEntrypointDeploy descriptorSDK dependency modeNotes遗留单体plugins/main.py.github/workflows/gcp_plugins.yml-plugins/Dockerfile存在 Datadog 变体plugins/requirements.txt安装./omi-plugin-sdk根目录 Dockerfile 先复制 SDK 再安装找到活跃部署证据作为 legacy 保留omi-dropbox-appmain.pyProcfile、railway.tomlrequirements.txt安装../omi-plugin-sdk已迁移 webhook 解析与EndpointResponse要求仓库 checkout 时 SDK 为同级目录omi-linear-appmain.pyProcfile、railway.tomlrequirements.txt安装../omi-plugin-sdkfuture-use Omi webhook 模型为 SDK re-export业务模型保留本地omi-hive-appmain.pyProcfile、railway.tomlrequirements.txt安装../omi-plugin-sdk同上omi-shopify-appmain.pyProcfile、railway.tomlrequirements.txt安装../omi-plugin-sdk同上omi-shipbob-appmain.pyProcfile、railway.tomlrequirements.txt安装../omi-plugin-sdk同上其他plugins/omi-*-appPython 应用存在则main.py各应用目录中的Procfile/railway.toml/Dockerfile除非导入 SDK-backed 模型否则不新增 SDK 依赖未发现重复的Structured实现这张表揭示了三条关键信息omi-*-app/是独立部署的 FastAPI 服务当前仓库中有 28 个如 notion、github、slack、dropbox、whoop 等各自持有main.py、依赖文件和部署描述符互不依赖、也与单体解耦SDK 的安装方式分为两种单体走plugins/requirements.txt里的./omi-plugin-sdk相对当前目录的路径独立应用走自己requirements.txt里的../omi-plugin-sdk相对上一级目录的路径迁移是分级进行的Dropbox 是已完成迁移的样板Linear/Hive/Shopify/Shipbob 是future-usewebhook 模型已 re-export业务逻辑仍未完全迁走其余应用保持原样。从源码验证plugins/requirements.txt第 8 行确实固定了./omi-plugin-sdk并注明这是uv pip compile生成的解析结果openai 1.x-2.x、langchain-openai-1.1.14 是为修复 #7327 的 SSRF 公告所做的升级plugins/omi-dropbox-app/requirements.txt第 2 行是../omi-plugin-sdkplugins/Dockerfile的 builder 阶段先COPY plugins/omi-plugin-sdk /app/omi-plugin-sdk再pip install -r /tmp/requirements.txt运行阶段COPY plugins/ .后以uvicorn main:app --host 0.0.0.0 --port 8080启动——复制 SDK 先于安装正是为了让相对路径依赖可解析。依赖风险相对路径安装的前提与坑审计文档在Dependency Risk一节专门警告了相对路径安装的约束已迁移的 Railway/Nixpacks 应用使用../omi-plugin-sdk。只有当构建从仓库 checkout 进行、且plugins/omi-plugin-sdk与应用目录互为同级时该依赖才有效。如果某个服务配置了排除同级目录的独立根目录isolated root directory依赖安装就会失败。在验证这种部署模式之前不做破坏性迁移或删除。翻译成实操语言就是两条硬性前提必须整仓 checkout 构建../omi-plugin-sdk意味着 pip 需要从应用目录的上一级找到 SDK 源码目录若 CI 只把某个plugins/omi-xxx-app/目录作为构建上下文例如 Nixpacks 的 subdirectory 构建上一级目录不存在pip install会直接报找不到包相对路径不能发布到索引../omi-plugin-sdk这样的路径只适用于本地/仓库内安装无法被 PyPI 等包索引解析因此这类服务不能依赖常规的包发布流程。仓库为这个问题配套了一个契约检查脚本plugins/scripts/check_plugin_imports.py——它逐个 import 所有omi-*-app/main.py并构建其 OpenAPI schema专门验证isolated-root 构建场景下应用能否独立 import 成功。后端镜像的特殊处理兼容回退而非 SDK 依赖同一个风险在后端侧呈现为另一种形态。审计文档指出backend/Dockerfile只复制backend/目录因此backend/models/structured.py保留了一份本地兼容回退实现只有当omi_plugin_sdk可导入时才使用 SDK 版本本地全仓运行则直接导入 SDK 实现。源码验证backend/models/structured.py文件开头会尝试把plugins/omi-plugin-sdk/src插入sys.path然后try: from omi_plugin_sdk.models import ActionItem, Event, Section, Structured except ModuleNotFoundError: # 回退到 backend 本地实现 models.conversation_enums.CategoryEnum ...这个try/except ModuleNotFoundError就是SDK 可用则用 SDK不可用则回退本地副本的双轨策略。值得注意的是回退副本并不是一个缩小版它完整保留了ActionItem的全部字段capture_kind、capture_confidence、ownership_confidence、source_segment_ids等、Event、Section、Structured以及category兜底校验器与 SDK 实现保持行为一致只是CategoryEnum换成了后端自己的models/conversation_enums。这也印证了审计文档所说的仅用于后端只复制backend/的镜像构建。同理根目录plugins/models.py是单体兼容层它从omi_plugin_sdk.modelsre-export 共享模型同时在文件内保留单体特有的 proactive notification 模型ProactiveNotificationResponse、ProactiveNotificationContextResponse、ProactiveNotificationContextFitlersResponse、RealtimePluginRequest等——这正是审计表中根插件单体模型 SDK 兼容导入 root-only proactive notification 模型的落地形态。存储、认证与 Webhook 辅助刻意保持小而薄审计文档Storage, Auth, and Webhook Helpers一节记录了最初为 SDK 添加的auth.py、webhook.py、fastapi.py三个辅助模块并明确其设计原则这些模块故意做得很小它们不改变 OAuth token 存储、Redis key、卷路径或任何应用专属业务逻辑换言之SDK 只承担共享契约与通用胶水绝不侵入各插件应用自己的状态管理和业务实现。结合文首的 Superseded 说明这三个模块后来因零调用方于 2026-07-13 被删除commitf6ac87773fSDK 从此收敛为纯模型包。plugins/README.md与plugins/omi-plugin-sdk/README.md都明确写着intentionally models-only并强调App-specific OAuth state、persisted settings、provider clients 与 business logic 留在各应用内。这条演进路径本身就是一个值得借鉴的工程决策样本辅助函数如果没有被实际调用就不应该留在共享包里——共享包每多一个模块就多一份维护与版本兼容成本。当前 SDK 的边界非常清晰pydantic2.0.0是唯一运行时依赖模型即全部内容。遗留单体决策为什么不能删plugins/main.py审计文档的Legacy Monolith Decision与仓库里的plugins/LEGACY_MONOLITH.md互为印证核心结论是plugins/main.py、plugins/_multion、plugins/Dockerfile、plugins/Dockerfile.datadog必须保留删除被活跃部署证据阻塞。阻塞原因是具体的.github/workflows/gcp_plugins.yml仍在构建plugins/Dockerfile该 DockerfileCOPY plugins/ .后以uvicorn main:app启动根目录plugins/包在 GCP 插件部署目标退役之前_multion最后一个遗留集成目前处于 dormant 状态不能删除plugins/Dockerfile与plugins/Dockerfile.datadog必须保持与单体决策对齐。plugins/README.md补充了单体现状它是旧的 all-in-one 插件 API由plugins/Dockerfile构建、经gcp_plugins.yml仅workflow_dispatch手动部署仍存活的 router 包括basic/conversation_created、mentor、oauth/、zapier/、chatgpt/、subscription/、notifications/、iq_rating/与_multion/templates/存放其 setup 流程 HTML.env.template列出环境变量。另外plugins/_mem0因是 100% 注释掉的示例代码已于 2026-09-03 删除部署目标只要求保留_multion。LEGACY_MONOLITH.md还给出了明确的维护守则同样值得作为团队约定保留不要向单体新增插件业务逻辑保持根目录plugins/models.py作为omi_plugin_sdk.models的兼容层未先替换或移除 GCP 插件部署目标前不要删除_multion保持plugins/Dockerfile与plugins/Dockerfile.datadog与单体决策一致。这套用活跃部署证据决定删除时机的纪律避免了重构中常见的两类事故一是删了还在被 CI 构建的入口导致线上部署失败二是为了漂亮的架构图牺牲了可运行的旧路径。迁移路径速查从旧代码到 SDK 导入综合审计文档、SDK 与各应用源码一份可落地的迁移清单如下识别共享契约判断应用是否消费 Omi webhook 载荷Conversation/Structured/ActionItem/TranscriptSegment等。只消费这些模型的应用才需要 SDK 依赖纯业务模型如DropboxUserSettings留在本地声明依赖独立应用在自身requirements.txt加入../omi-plugin-sdk单体场景则走plugins/requirements.txt的./omi-plugin-sdk替换导入把本地重复的Structured/ActionItem/Event定义替换为from omi_plugin_sdk.models import ...。参考plugins/omi-dropbox-app/models.py——它只 importActionItem, Conversation, EndpointResponse, Structured, TranscriptSegment自己的DropboxUserSettingsfolder_name、save_summary、save_transcript、save_audio等用户偏好依然本地定义保留业务状态OAuth token 存储、Redis key、卷路径、provider client 与业务逻辑一律不迁移SDK 不碰这些验证构建前提确认部署平台Railway/Nixpacks以整仓 checkout 方式构建、SDK 与应用目录互为同级用plugins/scripts/check_plugin_imports.py做 isolated-root 契约检查本地开发安装pip install -e plugins/omi-plugin-sdk见plugins/omi-plugin-sdk/README.md。总结这次重构留下的三条工程经验第一webhook 模型是跨服务契约必须单一化。Structured/ActionItem/Event从四处副本收敛到omi_plugin_sdk.models一处配合CategoryEnum兜底校验器、apps_results→plugins_results同步器与get_transcript/get_duration等辅助方法既消除了漂移又保留了与历史载荷的兼容。第二共享包要克制部署前提要显式。SDK 经历了模型 auth/webhook/fastapi 辅助到纯模型的瘦身零调用方即删除当前仅依赖pydantic2.0.0而./omi-plugin-sdk与../omi-plugin-sdk两种相对路径安装方式决定了所有消费方都必须满足整仓 checkout、SDK 同级可见的构建前提后端镜像则通过try/except ModuleNotFoundError回退到本地副本绕开该前提。第三删除要由部署证据驱动而非架构洁癖。plugins/main.py单体会一直保留到.github/workflows/gcp_plugins.yml的 Cloud Run 部署目标退役为止——这是对活跃构建入口不可贸然删除这条工程纪律最直接的注解。延伸阅读重构审计原文档插件体系总览SDK / 独立应用 / 遗留单体三部分划分遗留单体决策记录SDK 模型实现 与 SDK 使用说明SDK 契约测试后端兼容回退实现根目录单体兼容层迁移样板Dropbox 应用模型单体构建描述符 与 单体依赖清单【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考