koach 开发者文档

Skill 组件

Skill 是 Keep 把自己产品里跑了十年的能力封装出来的调用单元。 它和「让大模型自由发挥」的区别在于:输入输出是结构化的、行为是确定的、 背后有专门的模型和规则引擎,不是一段提示词。

什么时候用 Skill,什么时候直接调模型

需要一段自然语言回答,用 chat/completions。 需要一个能直接渲染成 UI、能入库、能被下游程序消费的结构化结果,用 Skill。 多数产品两者都要 —— 用 Skill 拿数据,用模型把数据讲成人话。

Skill 目录

GET /v1/skills

列出你的账号已开通的 Skill 及其当前版本。开通范围由合同约定。

Skill ID能力输入典型用途
training.plan.generate 训练计划生成目标 + 体能水平 + 约束 周计划、备赛周期、康复期渐进方案
metrics.interpret 运动数据解读时序指标 心率 / 配速 / 功率 / VO₂max / HRV 的归因与建议
motion.form.analyze 动作与体态分析图像或视频 深蹲、硬拉、跑姿的关节角度与风险评估
diet.image.recognize 饮食识别餐食照片 菜品识别、份量估算、热量与三大营养素
course.aigc.compose AIGC 课程生成素材 + 品牌调性 把品牌内容改造成结构化训练课程
voice.pacer 语音陪跑实时运动状态 跑步中的实时播报与激励,低延迟流式
injury.risk.screen 损伤风险筛查训练负荷史 + 主诉 加量过快、单侧失衡、恢复不足的预警
{
      "object": "list",
      "data": [
        {
          "id": "training.plan.generate",
          "version": "2.4.0",
          "status": "enabled",
          "latency_p95_ms": 1840,
          "billing": "per_call"
        },
        {
          "id": "voice.pacer",
          "version": "1.1.0",
          "status": "trial",
          "latency_p95_ms": 320,
          "billing": "per_minute"
        }
      ]
    }

调用 Skill

POST /v1/skills/{skill_id}/invoke

所有 Skill 共用同一个调用约定,差别只在 input 的结构。

参数类型说明
inputobject必填 该 Skill 的入参,结构见下方各节
profilestring选填 用户在记忆大脑里的 ID。 传入后 Skill 会读取历史数据,结果显著更贴合个体
versionstring默认最新稳定版 锁定 Skill 版本,避免升级导致输出结构变化
streamboolean默认 false 仅部分 Skill 支持,见各节说明
idempotency_keystring选填 24 小时内相同 key 返回首次结果,不重复计费。 重试时务必带上

训练计划生成

plan = client.skills.invoke(
        "training.plan.generate",
        profile="usr_8f2a41",
        input={
            "goal":        {"type": "race", "event": "half_marathon",
                            "target_time_min": 105, "date": "2026-11-08"},
            "level":       "intermediate",
            "days_per_week": 4,
            "constraints": {
                "no_equipment": False,
                "injuries": ["achilles_tendinitis_history"],
                "max_session_min": 90,
            },
            "horizon_weeks": 4,
        },
    )
curl "$KOACH_BASE_URL/skills/training.plan.generate/invoke" \
      -H "Authorization: Bearer $KOACH_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "profile": "usr_8f2a41",
        "input": {
          "goal": {"type": "race", "event": "half_marathon",
                   "target_time_min": 105, "date": "2026-11-08"},
          "level": "intermediate",
          "days_per_week": 4,
          "constraints": {"injuries": ["achilles_tendinitis_history"],
                          "max_session_min": 90},
          "horizon_weeks": 4
        }
      }'
{
      "skill": "training.plan.generate",
      "version": "2.4.0",
      "output": {
        "phase": "build",
        "weeks": [
          {
            "index": 1,
            "load_km": 33,
            "intensity_distribution": {"z1_z2": 0.78, "z3": 0.12, "z4_z5": 0.10},
            "sessions": [
              {"day": 2, "type": "easy",     "distance_km": 6, "target_zone": "z2"},
              {"day": 3, "type": "interval", "distance_km": 8,
               "detail": "6 × 800m @ z4,组间慢跑 2 分钟",
               "caution": "跟腱有既往史,热身延长至 15 分钟"},
              {"day": 5, "type": "tempo",    "distance_km": 8, "target_zone": "z3"},
              {"day": 7, "type": "long",     "distance_km": 11, "target_zone": "z2"}
            ]
          }
        ],
        "rationale": "既往跟腱炎史,本周期把速度课从每周两次降为一次,长距离增幅控制在 8% 以内。",
        "review_at": "2026-08-31"
      },
      "usage": {"calls": 1, "tokens": 2140},
      "request_id": "req_01JQ9H5P7T"
    }
注意 caution 与 rationale 两个字段

它们来自 constraints.injuries 和记忆大脑里的历史负荷。 产品里建议把 rationale 展示给用户 —— 「为什么这周只安排一次速度课」是留存率很高的内容, 而且能显著降低用户擅自加量的比例。

运动数据解读

输入时序数据,输出归因与建议。这是把硬件厂商和 App 手里已有的数据变成用户 看得懂的内容的最短路径。

read = client.skills.invoke(
        "metrics.interpret",
        profile="usr_8f2a41",
        input={
            "activity": {
                "type": "running",
                "started_at": "2026-08-16T06:12:00+08:00",
                "distance_km": 12.4,
                "duration_s": 3720,
            },
            "series": {
                "heart_rate": [132, 141, 148, 155, 161, 166, 171, 168],
                "pace_s_per_km": [340, 322, 310, 305, 301, 298, 296, 305],
                "cadence_spm":   [172, 174, 176, 178, 178, 179, 180, 176],
                "sample_interval_s": 465,
            },
            "context": {"temperature_c": 31, "humidity": 0.74, "sleep_h_last_night": 5.5},
        },
    )
{
      "output": {
        "summary": "配速稳步提升但心率同步走高,末段出现明显心率漂移,主要归因于高温高湿与睡眠不足,不是体能下降。",
        "findings": [
          {
            "metric": "cardiac_drift",
            "value": 0.084,
            "severity": "moderate",
            "note": "后半程心率较前半程上升 8.4%,配速仅提升 1.6%,典型的热应激表现"
          },
          {
            "metric": "cadence",
            "value": 176.6,
            "severity": "ok",
            "note": "步频稳定在 176 左右,落地控制良好"
          }
        ],
        "recommendations": [
          "明天安排恢复跑或休息,避免连续高温下的高强度课",
          "下次同类天气下把目标配速放慢 10 到 15 秒",
          "训练前后各补充 500 毫升含电解质饮品"
        ],
        "trend_vs_profile": {
          "aerobic_efficiency_4w": "+3.1%",
          "note": "剔除天气因素后,近四周有氧效率仍在改善"
        }
      }
    }

动作与体态分析

接受图像或短视频,返回关节角度、风险点与纠正建议。 视频最长 30 秒,会自动抽帧。

form = client.skills.invoke(
        "motion.form.analyze",
        input={
            "movement": "back_squat",
            "media": {"type": "video_url",
                      "url": "https://cdn.example.com/user/squat_20260816.mp4"},
            "view": "side",          # side | front | 45deg
            "load_kg": 60,
        },
    )
{
      "output": {
        "reps_detected": 5,
        "overall_score": 72,
        "issues": [
          {
            "code": "knee_valgus",
            "label": "膝内扣",
            "severity": "high",
            "frames": [42, 51, 88],
            "detail": "第 2、4 次上升阶段右膝内扣约 11°,臀中肌激活不足",
            "correction": "降到 40 公斤重新建立动作模式,加练侧向弹力带走"
          },
          {
            "code": "depth_insufficient",
            "label": "深度不足",
            "severity": "low",
            "detail": "平均最低点髋关节高于膝关节 4.2°,未达平行"
          }
        ],
        "joint_angles": {
          "knee_min_deg": 84.3, "hip_min_deg": 88.5, "ankle_dorsiflex_deg": 27.1
        }
      }
    }
severity 为 high 时建议阻断而不是提示

膝内扣配 60 公斤负重是真实的受伤风险。产品里不要只弹个 toast, 建议直接给出降重量的强提示。这类交互设计我们在 FDE 共创阶段会一起过。

饮食识别

meal = client.skills.invoke(
        "diet.image.recognize",
        profile="usr_8f2a41",
        input={
            "media": {"type": "image_url", "url": "https://cdn.example.com/meal.jpg"},
            "meal_type": "lunch",
            "reference_object": "chopsticks",   # 用于估算份量的参照物
        },
    )
{
      "output": {
        "items": [
          {"name": "米饭",     "portion_g": 180, "kcal": 234,
           "protein_g": 5.4, "fat_g": 0.5, "carb_g": 52.0, "confidence": 0.94},
          {"name": "清炒西兰花", "portion_g": 120, "kcal": 68,
           "protein_g": 3.6, "fat_g": 4.1, "carb_g": 4.8, "confidence": 0.89},
          {"name": "煎鸡胸肉",   "portion_g": 110, "kcal": 198,
           "protein_g": 33.2, "fat_g": 6.4, "carb_g": 0.9, "confidence": 0.91}
        ],
        "total": {"kcal": 500, "protein_g": 42.2, "fat_g": 11.0, "carb_g": 57.7},
        "comment": "蛋白质充足,这一餐对你今晚的力量课是合适的。碳水略偏低,如果练后还饿可以再补一份主食。"
      }
    }

AIGC 课程生成

把品牌已有的内容素材改造成结构化训练课程。这是 L3 场景合作里用得最多的 Skill —— 一条广告片的生命周期是一个月,一节用户愿意反复练的课程可以用一年。

course = client.skills.invoke(
        "course.aigc.compose",
        input={
            "source": {
                "type": "brand_assets",
                "video_urls": ["https://cdn.brand.com/campaign_2026_fall.mp4"],
                "brand_kit":  {"tone": "专业克制", "spokesperson": "李某某",
                               "forbidden_words": ["瘦身", "速成"]},
            },
            "target": {
                "duration_min": 20,
                "level": "beginner",
                "equipment": ["yoga_mat"],
                "focus": ["core", "mobility"],
            },
            "output_format": ["structure", "script", "shot_list"],
        },
    )

输出包含课程结构(热身 / 主体 / 拉伸的动作序列与时长)、口播脚本、 以及可交给拍摄团队的分镜表。forbidden_words 会在生成阶段就被排除,不需要事后人工筛查。

语音陪跑

实时性要求最高的一个 Skill,走 WebSocket 而不是 HTTP。 运动中每隔一段时间或触发特定条件时推送一句播报。

WSS /v1/skills/voice.pacer/stream P95 延迟 320ms
const ws = new WebSocket(
      "wss://api.koach.ai/v1/skills/voice.pacer/stream?profile=usr_8f2a41",
      ["bearer", KOACH_API_KEY]
    );

    ws.onopen = () => {
      ws.send(JSON.stringify({
        type: "session.start",
        session: { activity: "running", target: { type: "pace", value_s_per_km: 300 },
                   voice: "female_calm", language: "zh-CN" },
      }));
    };

    // 每 5 秒上报一次运动状态
    setInterval(() => {
      ws.send(JSON.stringify({
        type: "state.update",
        state: { elapsed_s: 620, distance_km: 2.1,
                 pace_s_per_km: 312, heart_rate: 158, cadence_spm: 174 },
      }));
    }, 5000);

    ws.onmessage = (e) => {
      const msg = JSON.parse(e.data);
      if (msg.type === "cue.audio") {
        // msg.audio 为 base64 编码的 PCM,msg.text 为对应文字
        play(msg.audio);
      }
    };
{
      "type": "cue.audio",
      "text": "配速慢了 12 秒,但心率已经到 158 了,先稳住别硬提,前面有个上坡。",
      "trigger": "pace_deviation",
      "audio": "UklGRiQAAABXQVZFZm10IBAAAAABAAEA…",
      "format": {"encoding": "pcm_s16le", "sample_rate": 24000}
    }

计费

Skill 的计费与模型 token 分开结算,两种方式:

方式适用 Skill示例单价
per_call 按次 计划生成、数据解读、动作分析、饮食识别、课程生成 0.08 – 0.60 元 / 次
per_minute 按时长 语音陪跑 0.12 元 / 分钟
以上为示例价格

Skill 通常打包在平台年度订阅里,超出包量部分才按次结算。 具体以商务报价单为准。

错误

Skill 除了通用错误,还有几个自己的错误码: skill_not_enabled(未开通)、 skill_input_invalid(入参不符合 schema)、 media_unprocessable(图像或视频无法解析,如画面中没有人体)。 完整列表见错误码与重试

本页内容属于 koach 商业化合作方案的一部分。接口定义可能在正式签约前调整, 以合同附件中的版本为准。

koach · AI by Keep 隐私政策 用户协议 联系我们