news 2026/9/16 19:23:11

基于 Omi 的 Whoop 集成插件:OAuth 授权、Chat Tools 部署与健身数据查询实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 Omi 的 Whoop 集成插件:OAuth 授权、Chat Tools 部署与健身数据查询实战指南

基于 Omi 的 Whoop 集成插件:OAuth 授权、Chat Tools 部署与健身数据查询实战指南

【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend

本文以仓库中的 plugins/omi-whoop-app 插件为核心,系统讲解如何构建并部署一个接入 Whoop 开发者 API 的 Omi 聊天工具插件:从 Whoop 开发者应用创建、Railway 部署、OAuth2 授权回调,到七类 Chat Tools 端点与指标解读的完整实现。读完本文,你将掌握 Whoop 数据通过自然语言对话接入 Omi 的完整链路,并能基于源码定位每个端点的底层数据流。

一、插件定位与功能总览

plugins/omi-whoop-app是 Omi 生态中的第三方数据集成插件,解决的核心问题是:把 Whoop 手环产生的恢复、压力、睡眠与训练数据,通过 Omi 的 AI 对话能力直接以自然语言查询。用户在聊天中输入"我今天恢复得怎么样?",Omi 的 Chat Tools 机制即可路由到该插件的 HTTP 端点,返回结构化的健身数据摘要。

其功能清单在 README 中定义如下:

  • Recovery Score(恢复评分):查询每日恢复分数与 HRV(心率变异性)
  • Strain Score(压力评分):查看每日压力水平
  • Sleep Data(睡眠数据):查看睡眠时长、睡眠阶段与睡眠质量
  • Workouts(训练记录):回顾最近的训练会话
  • Weekly Summary(周度总结):获取趋势与平均值
  • Body Measurements(身体测量数据):查看身高、体重、最大心率
  • Profile(个人资料):访问 Whoop 账户资料信息

从实现角度看,这是一个基于 FastAPI 的独立 Web 服务(见 main.py),通过 OAuth2 授权码模式(Authorization Code Flow)获取 Whoop 用户授权,再用访问令牌调用 Whoop Developer API,最终以 Omi Chat Tools Manifest 协议对外暴露能力。

二、整体架构与 OAuth 数据流

2.1 组件拓扑

插件由四个核心文件组成,职责清晰:

文件职责
main.pyFastAPI 应用:OAuth 路由、Chat Tools 端点、数据格式化、Manifest 生成
db.py令牌与用户设置存储层(Redis 优先,本地 JSON 文件兜底)
models.pyPydantic 响应模型ChatToolResponse
railway.tomlRailway 平台部署配置(Nixpacks 构建 + uvicorn 启动命令)
requirements.txt依赖清单:fastapiuvicornpython-dotenvrequestspydanticredis

2.2 OAuth2 授权码流程

整个授权链路在 main.py 中实现,包含三个阶段:

阶段一:发起授权(GET /auth/whoop?uid=<uid>

state = secrets.token_urlsafe(16) store_oauth_state(state, uid) params = { "client_id": WHOOP_CLIENT_ID, "redirect_uri": WHOOP_REDIRECT_URI, "response_type": "code", "scope": " ".join(WHOOP_SCOPES), "state": state } auth_url = f"{WHOOP_AUTH_URL}?{urlencode(params)}" return RedirectResponse(url=auth_url)

服务端生成随机state并与用户uid绑定存储(10 分钟过期,见 db.py),用于回调时校验,防止 CSRF 攻击。同时携带七项 scopes 发起跳转:

read:recovery read:cycles read:sleep read:workout read:profile read:body_measurement offline

其中offline作用域用于换取refresh_token,保证长期免重新授权。

阶段二:回调换令牌(GET /auth/whoop/callback

response = requests.post( WHOOP_TOKEN_URL, data={ "client_id": WHOOP_CLIENT_ID, "client_secret": WHOOP_CLIENT_SECRET, "code": code, "grant_type": "authorization_code", "redirect_uri": WHOOP_REDIRECT_URI } )

用授权码交换access_tokenrefresh_tokenexpires_in,计算expires_at后经store_whoop_tokens落库,随后展示连接成功页。

阶段三:令牌自动续期

每次访问 Whoop API 前都会先经get_valid_access_token检查过期时间(提前 5 分钟触发刷新),过期则调用refresh_access_tokenrefresh_token换取新令牌并回写存储(main.py)。这就是 README 中"只需授权一次"的底层保障。

2.3 令牌存储的双模式设计

db.py 实现了生产/开发双存储策略:

  • Redis 模式:读取REDIS_URL(或REDIS_PRIVATE_URLREDIS_PUBLIC_URL)建立连接;令牌以whoop:tokens:{uid}为键存储,过期时间 90 天;OAuth state 键whoop:oauth_state:{state}过期 10 分钟;用户设置以whoop:settings:{uid}存储。
  • 文件兜底模式:Redis 不可用时自动降级为data/目录下的tokens.jsonoauth_states.jsonuser_settings.json三个 JSON 文件,便于本地开发。

三、从零部署:Whoop 开发者应用 + Railway

3.1 创建 Whoop 开发者应用

  1. 打开 Whoop 开发者门户(Whoop Developer Portal),创建新应用;
  2. 填写应用信息:名称(如 "Omi Integration")、描述,以及回调地址(Redirect URI,见下文);
  3. 申请以下 scopes:read:recoveryread:cyclesread:sleepread:workoutread:profileread:body_measurementoffline
  4. 复制生成的Client IDClient Secret

注意:Whoop 对state参数有至少 8 个字符的长度要求。源码使用secrets.token_urlsafe(16)生成 16 字节随机串,并额外做了state -> uid的映射存储(db.py 注释明确说明),这比直接透传 uid 更安全。

3.2 部署到 Railway

按 README 的步骤:

  1. 在 Railway 创建新项目,连接 GitHub 仓库或直接从插件目录部署;
  2. 为项目添加一个Redis服务;
  3. 设置环境变量:
WHOOP_CLIENT_ID=your_client_id WHOOP_CLIENT_SECRET=your_client_secret WHOOP_REDIRECT_URI=https://your-app.up.railway.app/auth/whoop/callback
  1. 触发部署。Railway 会自动完成三件事:
    • 依据requirements.txt安装依赖;
    • 依据 railway.toml 启动服务:uvicorn main:app --host 0.0.0.0 --port $PORT,并使用/health作为健康检查路径(超时 100s,失败重启最多 3 次);
    • 自动注入PORTREDIS_URL环境变量。

部署完成后,回到 Whoop 开发者应用后台,将 Redirect URI 更新为:

https://your-app.up.railway.app/auth/whoop/callback

3.3 环境变量参考表

变量说明是否必填
WHOOP_CLIENT_IDWhoop OAuth Client ID
WHOOP_CLIENT_SECRETWhoop OAuth Client Secret
WHOOP_REDIRECT_URIOAuth 回调 URL
PORT服务端口(默认 8080)
REDIS_URLRedis 连接 URL(未设置时降级为文件存储)

从源码看,PORT默认值 8080 与WHOOP_REDIRECT_URI默认值http://localhost:8080/auth/whoop/callback在 main.py 中定义,本地开发无需显式设置。此外 db.py 还会读取REDIS_PRIVATE_URL/REDIS_PUBLIC_URL作为备选连接串,兼容不同托管平台的 Redis 注入方式。

四、Omi App 配置:Chat Tools 接入的关键三 URL

在 Omi 后台创建/更新应用时,需要把以下三个 URL 填入对应字段(来自 README):

字段
Setup URLhttps://your-app.up.railway.app/?uid={{uid}}
Setup Completed URLhttps://your-app.up.railway.app/setup/whoop?uid={{uid}}
Chat Tools Manifest URLhttps://your-app.up.railway.app/.well-known/omi-tools.json

这三个 URL 的底层语义对应三条 GET 路由:

  • /:带uid访问时渲染连接/已连接页面(无uid时返回 JSON 服务信息,见 main.py);
  • /setup/whoop?uid=<uid>:返回{"is_setup_completed": true/false},Omi 据此判断用户是否已完成授权(main.py);
  • /.well-known/omi-tools.json:Chat Tools Manifest,Omi 的 AI 层据此发现可用工具及其参数 schema(main.py)。

4.1 Manifest 如何驱动对话路由

Manifest 中每个工具都声明了namedescriptionendpointmethodparametersauth_required。以恢复评分工具为例:

{ "name": "get_recovery", "description": "Get the user's recovery score and metrics from Whoop. Use this when the user asks about their recovery, readiness, HRV, or how recovered they are.", "endpoint": "/tools/get_recovery", "method": "POST", "parameters": { "properties": { "date": { "type": "string", "description": "Date in YYYY-MM-DD format. Defaults to today." } }, "required": [] }, "auth_required": true, "status_message": "Getting your recovery data..." }

description是给 LLM 的"意图匹配说明书"——它显式罗列了用户说 recovery、readiness、HRV、"how recovered they are" 时应触发该工具,这就是自然语言查询能被正确路由的关键机制。status_message则在工具执行期间向用户展示过程反馈。

五、API 端点全览

5.1 Chat Tools(POST,供 Omi 调用)

端点功能可传参数
/tools/get_recovery恢复评分与 HRVdate(YYYY-MM-DD,默认今天)
/tools/get_strain每日压力评分date
/tools/get_sleep睡眠数据date(获取"该日结束"的睡眠)
/tools/get_workouts近期训练记录days(默认 7,上限 30)、max_results(默认 10,上限 50)
/tools/get_weekly_summary近 7 天周度总结
/tools/get_body_measurements身体测量数据
/tools/get_profileWhoop 资料

所有工具端点都遵循统一处理模式:读取请求体中的uid→ 获取有效访问令牌(未授权则返回"Please connect your Whoop first in the app settings.")→ 构造 Whoop API 请求 → 格式化结果 → 返回ChatToolResponseresulterror二选一,见 models.py)。

5.2 OAuth 与设置(GET)

端点功能
/首页 / 设置 UI(带uid时渲染 HTML 页面)
/auth/whoop?uid=<uid>发起 OAuth 流程
/auth/whoop/callbackOAuth 回调
/setup/whoop?uid=<uid>检查设置完成状态
/disconnect?uid=<uid>解除 Whoop 账号绑定(删除令牌并重定向回首页)
/health健康检查(Railway 探活用)
/.well-known/omi-tools.jsonChat Tools Manifest

5.3 底层 Whoop API 映射

从工具端点的实现可整理出插件调用的 Whoop Developer API(基础地址https://api.prod.whoop.com/developer/v1):

插件端点Whoop API
/tools/get_recoveryGET /recovery
/tools/get_strainGET /cycle
/tools/get_sleepGET /activity/sleep
/tools/get_workoutsGET /activity/workout
/tools/get_body_measurementsGET /body_measurement
/tools/get_profileGET /user/profile/basic

日期过滤统一构造为{date}T00:00:00.000Z{date}T23:59:59.999Z的时间窗口,并设置limit取最新一条记录(main.py)。

六、本地开发

按 README 的步骤:

  1. .env.example复制为.env并填入凭据;
  2. 设置WHOOP_REDIRECT_URI=http://localhost:8080/auth/whoop/callback
  3. 在 Whoop 开发者应用后台的 Redirect URI 列表中加入该地址;
  4. 安装依赖:pip install -r requirements.txt
  5. 启动服务:python main.py

python main.py会读取PORT(默认 8080)与HOST(默认0.0.0.0)并启动 uvicorn(reload=True便于开发调试,见 main.py)。本地开发时未配置REDIS_URL会自动走 JSON 文件存储,无需额外基础设施。

七、示例聊天命令

部署并授权完成后,可在 Omi 聊天中直接输入:

  • "What's my recovery today?"
  • "How did I sleep last night?"
  • "What's my strain level?"
  • "Show my recent workouts"
  • "Give me my weekly summary"
  • "What's my HRV?"

八、Whoop 指标解读

8.1 Recovery Score(0-100%)

区间状态含义
67-100%绿色恢复充分,可承受训练压力
34-66%黄色恢复一般,谨慎安排训练
0-33%红色恢复不足,优先休息

源码中的分区间逻辑与文档一致:>= 67绿、>= 34黄、其余红(main.py),并在结果中附带hrv_rmssd_milli(HRV)、resting_heart_rate(静息心率)、spo2_percentage(血氧)、skin_temp_celsius(皮肤温度)。

8.2 Strain Score(0-21)

区间等级
0-9轻度(Light day)
10-13中度(Moderate strain)
14-17高强度(High strain)
18-21过度训练(Overreaching,极高)

对应源码分档:>= 18极高、>= 14高、>= 10中、其余低(main.py),并附带千焦(自动换算为 kcal,系数 0.239006)、平均心率与最大心率。

8.3 关键指标

  • HRV(心率变异性):通常越高越好
  • RHR(静息心率):通常越低越好
  • Sleep Performance(睡眠表现):满足睡眠需求的程度
  • Sleep Efficiency(睡眠效率):实际睡眠时间与在床时间之比

睡眠结果中还包含分阶段统计(浅睡/深睡/REM,单位为小时)、呼吸频率等,由format_sleepstage_summary中的毫秒值换算(main.py)。

九、源码中的工程化细节

9.1 分页兜底:周度总结的完整性保证

get_weekly_summary需要聚合近 7 天四种数据,源码专门实现了whoop_fetch_all_records分页函数(main.py):循环跟随next_token/nextToken翻页,单集合最多 20 页兜底;任一页失败即返回(None, error),使调用方能区分"空数据"与"拉取不完整"。

对应的回归测试在 test_weekly_summary.py 中验证了三种行为:

  1. 翻页聚合:两页 recovery(50、70)平均为 60%,两页 workout(7+3)总数为 10;
  2. 无续页令牌即停止:单页返回即结束;
  3. 分页失败标记为不可用:第二页失败时输出**Workouts:** Temporarily unavailable而非错误的 7 条,避免把部分结果当最终结果。

9.2 分页失败时的降级表现

当某类数据拉取失败时,周度总结不会整体报错,而是对单项输出Temporarily unavailable,其余维度照常汇总(main.py),提升了对话场景下的容错性。

9.3 统一的 API 请求封装

whoop_api_request统一注入Authorization: Bearer <token>头并处理错误(main.py),任何工具端点都无需重复鉴权逻辑;get_valid_access_token的 5 分钟提前刷新窗口则降低了对话过程中令牌过期的概率。

9.4 多运动类型映射

format_workout内置了常见sport_id到运动名称的映射(1=Running、16=Cycling、32=HIIT、33=Strength Training、48=Swimming、71=Walking、82=Yoga 等),未知 ID 显示为Activity {id}(main.py)。

十、常见问题与排查建议

  • Please connect your Whoop first:用户未完成 OAuth 授权。检查get_whoop_tokens(uid)是否返回空,引导用户访问/页面完成连接。
  • 回调报错 / Token exchange failed:核对WHOOP_CLIENT_IDWHOOP_CLIENT_SECRETWHOOP_REDIRECT_URI是否与 Whoop 开发者后台配置完全一致(含协议与路径)。
  • Redis 连接失败:服务会打印Redis connection failed: ... falling back to file storage并降级为文件存储;Railway 上建议确认 Redis 服务已绑定到应用并注入了REDIS_URL
  • 本地 OAuth 无法回调:确认.envWHOOP_REDIRECT_URIhttp://localhost:8080/auth/whoop/callback,且该地址已加入 Whoop 后台的 Redirect URI 白名单。

结语

plugins/omi-whoop-app完整演示了 Omi 第三方集成插件的标准范式:OAuth2 授权码 + 刷新令牌续期、Chat Tools Manifest 声明式工具路由、Redis/文件双模式存储、以及面向对话场景的健壮数据聚合。通过本文的部署步骤与源码对照,你可以将同样的模式复用到任意具备开放 API 的可穿戴设备或健康平台,让健身数据真正"开口说话"。

【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

用Python和贪心算法构建自动行程规划器:从建模到实地测试

去年年底开始规划西班牙假期的时候&#xff0c;我干了件很程序员的事情&#xff1a;给自己写了一个自动化的逐日行程规划器&#xff0c;把每天几点去哪、怎么串联、在哪个城市停留几天&#xff0c;全部交给算法去算。这件事做完之后&#xff0c;最大的感受是——我以前手动做行…

作者头像 李华
网站建设 2026/9/16 19:22:34

CUTLASS GEMM 怎么用:跑通第一个 GPU 矩阵乘法的完整流程

CUTLASS GEMM 怎么用&#xff1a;跑通第一个 GPU 矩阵乘法的完整流程 【免费下载链接】cutlass CUDA Templates and Python DSLs for High-Performance Linear Algebra 项目地址: https://gitcode.com/GitHub_Trending/cu/cutlass CUTLASS 是 NVIDIA 的 header-only CUD…

作者头像 李华
网站建设 2026/9/16 19:22:23

UltraEdit 的 Ctrl+A 全选失效?把 TaoToken 的 Key 给 Codex 查 alt+a 行模式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 19:22:19

Kimi K2 Thinking 执行 300 轮 Agent 任务,Key 用 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 19:22:02

AI Agent技能层:TypeScript+NX构建可治理的skills工程范式

1. “agent-skills”不是库名&#xff0c;而是AI工程中一个被严重低估的抽象层你点开 GitHub 搜索agent-skills&#xff0c;大概率会看到零星几个冷门仓库&#xff0c;Star 数个位数&#xff0c;文档页空白&#xff0c;README 里只有一行// TODO: add description。这很反常——…

作者头像 李华