Skill 组件
Skill 是 Keep 把自己产品里跑了十年的能力封装出来的调用单元。 它和「让大模型自由发挥」的区别在于:输入输出是结构化的、行为是确定的、 背后有专门的模型和规则引擎,不是一段提示词。
需要一段自然语言回答,用 chat/completions。 需要一个能直接渲染成 UI、能入库、能被下游程序消费的结构化结果,用 Skill。 多数产品两者都要 —— 用 Skill 拿数据,用模型把数据讲成人话。
Skill 目录
列出你的账号已开通的 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
所有 Skill 共用同一个调用约定,差别只在 input 的结构。
| 参数 | 类型 | 说明 | |
|---|---|---|---|
input | object | 必填 | 该 Skill 的入参,结构见下方各节 |
profile | string | 选填 | 用户在记忆大脑里的 ID。 传入后 Skill 会读取历史数据,结果显著更贴合个体 |
version | string | 默认最新稳定版 | 锁定 Skill 版本,避免升级导致输出结构变化 |
stream | boolean | 默认 false | 仅部分 Skill 支持,见各节说明 |
idempotency_key | string | 选填 | 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"
}
它们来自 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
}
}
}
膝内扣配 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。 运动中每隔一段时间或触发特定条件时推送一句播报。
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(图像或视频无法解析,如画面中没有人体)。
完整列表见错误码与重试。