上周我把一个开源Agent框架跑通之后,一直在想一个问题:大模型明明什么健身知识都懂,为什么让它当私教还是那么不靠谱?你跟它聊深蹲,它能从解剖学讲到生物力学,可你让它排一周训练计划,它常常前一秒让你练胸,后一秒让你练背,第三天直接给你上高强度间歇,练完你分不清这是增肌还是受刑。
后来我明白了,教练的水平根本不在于知识量,而在于决策的连续性和边界感。同样一个知识库,有人能教出好身材,有人只能制造伤病。于是我开始动手把"私教"这套决策逻辑封装成一个开源的健身教练Skill,挂在Agent框架上,让智能体真的像教练一样按流程工作。这篇文章就是这段时间的完整复盘,从能力拆解到数据结构,从踩坑记录到部署经验,一次性说清楚。
1. 为什么私教这件事不能靠聊天回答凑合
1.1 大模型缺的不是知识,是"训练课表的管理状态"
普通对话里,模型每回答一个问题都是独立的。你问"今天练什么",它给你一个合理回答;你问"明天练什么",它又给你一个合理回答。但两个"合理"拼在一起,可能就不合理了:周一练了胸,周二模型又给你推荐卧推,周三再推荐俯卧撑,你这周的胸部训练量直接超标,其他部位却一次没碰。
真实教练的工作方式完全不是这样。教练脑子里有一个持续更新的状态:你上周练了什么、哪个部位恢复得差不多、这周目标是什么、哪天的状态适合冲大重量。他每天早上翻一遍你的训练记录,再决定今天的动作和强度。这种"带状态的决策",单靠零散对话根本做不出来。
Skill的价值就在这里。它不是让模型更聪明,而是给模型一个结构化的决策流程,让它在固定的框架里做选择,同时把每一次交互都记录到用户档案里。Agent每次调用这个Skill,拿到的不是一个空白对话,而是过去几周的训练历史。这样它给出的计划就是连续的、递进的,不会今天一个思路明天另一个思路。
1.2 为什么选"Skill"而不是写一长串Prompt
可能有人会说,这些要求我写进System Prompt里不就行了吗?我一开始也是这么想的,后来发现行不通。
第一,Prompt做到后面会越来越长。你要约束动作选择、强度计算、休息时间、器械判断、伤病提醒、渐进超负荷……全写进去,一次会话上下文可能就爆了,而且模型越到后面越容易遗漏早期约束。
第二,Prompt没有"程序化保障"。你可以叫模型"按顺序输出周一到周日",但它心情不好可能就从周三开始写了。即便加了严格格式要求,也难免遇到JSON解析失败、字段缺失的情况。
第三,Skill可以挂代码。这是最本质的区别。Skill不只是文本,它是一段可执行逻辑:可以读取用户档案、可以做算术、可以查动作库、可以写训练记录。而Prompt只是一段文字。比如强度计算这种事,我完全可以用代码算清楚"周一胸肌训练量该是多少",不需要依赖模型临场发挥。
一句话总结:Prompt适合处理一次性问答,Skill适合处理需要状态和规则的长期任务。私教恰恰是典型的长期任务。
2. Skill里到底装了什么:一份教练的决策流程图
我做的这个Skill叫健身教练技能,整体思路是模拟一个私教从首次见面到跟进训练的完整流程。它拆成了几个模块,各有各的作用。
2.1 首次沟通模块:先摸清家底再谈计划
真实教练接新学员,第一件事绝对不是上课,而是聊天:你平时运动吗?膝盖有没有旧伤?一周能练几次?家里有什么器械?目标是什么?这个环节我做得比较细,因为后面所有计划都建立在用户画像上。
Skill里定义了一份结构化问卷,字段包括:年龄、身高体重、是否有慢性病或旧伤、健身频率、单次可投入时间、可用器械(健身房/徒手/哑铃/弹力带)、训练目标(增肌/减脂/保持健康/提升力量)、经验水平(新手/初级/进阶)。
这里有个值得注意的细节:问卷里所有可选字段我都给了枚举值,而不是让用户自由输入。比如"训练目标"只允许选增肌、减脂、保持健康、提升力量四类。这么做不是限制用户,而是保证后面生成计划时能走确定性逻辑。自由文本可以让模型去理解,但结构化字段才是程序能依赖的。
2.2 计划生成模块:从目标反推每周课表
拿到用户画像之后,Skill开始生成训练计划。生成逻辑不是让模型一鼓作气写完七个自然段,而是走一套半自动的流程。
第一步是代码决定训练频率和分化方式。比如一周三练,就安排全身训练;一周四练,就安排上下肢分化;一周五练,就安排推拉腿分化。这个规则是写死在代码里的,不交给模型决策,因为"几练用几分化"是运动科学里的成熟套路,不需要大模型来创造。
第二步是代码根据目标分配各肌群训练量。增肌方向,胸部每周安排14到16组;减脂方向,训练量略低,但会叠加低强度有氧;保持健康方向,每组数量不用太激进。这些数值我参考了常见的训练学建议,并用经验系数做了调整。
第三步才轮到模型。模型需要做的是:在指定的动作库中,为每个肌群挑选具体动作,排好顺序,并给每个动作配上组数、次数、休息时间。这一步允许模型发挥,但选择范围已经被动作库锁死了,它不能凭空创造动作,也不能漏掉该练的肌群。
2.3 动作指导模块:文本对话里怎么做"姿势检查"
这个模块一开始困扰我很久。私教的核心价值之一是纠正动作,但你不在用户身边,怎么纠正?
后来我换了个思路:不追求真正的视觉识别,而是用"描述-反馈"的方式模拟纠错。用户练完一组动作后,Skill会主动追问几个关键点。比如做完深蹲,它会问:你的膝盖有没有内扣?下蹲时腰部是挺直还是弓了?脚后跟有没有离地?
根据用户回答,Skill从动作库里的错误模式表找到对应修正指令。膝盖内扣就提示"在脚外侧加一条弹力带,激活臀中肌";脚后跟离地就提示"重心移到全脚掌";腰部弓了就提示"先做靠墙髋铰链练习,找回中立位"。这套逻辑跑起来之后,效果比我想象中好。用户确实能通过几个定向问题发现自己动作的问题,比单纯甩一段标准视频有用得多。
2.4 进度追踪模块:什么情况下要主动干预
训练计划不能生成完就完事,后面还有进度追踪和调整。Skill会记录每次训练的实际完成情况:用户做了哪些动作、完成组数、是否超时、主观疲劳度(1到10打分)。
当数据积累到一定程度,代码会自动判断是否需要干预。我会做几个简单判定:如果连续三次训练都没完成计划组数,可能是计划强度过高,要降载;如果某肌群连续缺席两周,则要重新提醒用户回归计划;如果用户反馈某个关节疼痛,则直接调低相关动作训练量,或者换成退阶动作。
这个模块的意义在于让Agent有"主动性"。它不会等你问才说话,而是到了该调整的时候主动提出建议。这非常关键,因为私教的本职工作就是帮用户守住节奏,而不是被动问答。
3. 开源实现核心:数据结构和生成逻辑拆解
这一节直接上干货,说一下我开源版本里最核心的三块内容:目录结构、训练计划的数据模型、生成逻辑的混合方式。
3.1 整体文件结构长什么样
整个Skill设计成一个独立包,方便挂到不同Agent框架上。目录大概是这样的:
fitness_coach_skill/ ├── skill.py # 技能入口,注册给Agent的主文件 ├── config.json # 技能配置(强度系数、频率规则等) ├── profiles/ │ ├── user_profile.py # 用户画像数据模型 │ └── profile_store.py # 画像持久化存储 ├── planner/ │ ├── weekly_planner.py # 周计划编排逻辑 │ └── plan_schema.json # 训练计划数据结构定义 ├── actions/ │ ├── exercise_library.json # 动作库 │ └── correction_map.json # 动作错误纠错映射表 ├── tracker/ │ └── session_tracker.py # 训练记录与进度判定 └── prompts/ ├── questionnaire_prompt.txt # 首次问卷提示词 ├── plan_generation_prompt.txt # 计划补全提示词 └── session_feedback_prompt.txt # 训练反馈提示词这个目录设计参考了我之前写其他Skill的经验:凡是规则明确的逻辑都放进代码文件,凡是需要语言表达的场合都抽成独立的Prompt文件。动作库单独放JSON,方便后台维护更新,不需要改代码。
3.2 训练计划的数据结构
训练计划的数据结构是整个Skill的地基。我把它设计成多层的:周计划包天数,天包含训练块,训练块包含动作组。
{ "plan_id": "plan_20250217", "user_id": "u_12345", "goal": "muscle_gain", "weekly_frequency": 4, "generated_at": "2025-02-17T08:00:00Z", "days": [ { "day_index": 1, "focus": ["push", "triceps"], "intensity_level": "moderate", "exercises": [ { "name": "杠铃卧推", "muscle_group": "chest", "sets": 4, "reps": 8, "rest_seconds": 90, "equipment": "barbell", "difficulty": 3, "modifications": ["哑铃卧推", "跪姿俯卧撑"] } ] } ] }为什么每个动作都要带一个modifications字段?因为在实际训练中,用户随时可能因为疼痛、器械不足、时间不够而需要退阶。这个字段就是给Agent的备用方案。如果用户说"我今天没杠铃",Agent可以查这个动作的modifications,直接换成哑铃卧推或俯卧撑,不用重新生成整个计划。
3.3 生成逻辑:约束式模板加模型补全的混合流
在写生成逻辑时,我最担心的是模型自由发挥导致计划结构不合法。所以最终采用了一种"约束式模板加模型补全"的混合方式。
先看核心代码的一个片段。这一步先由代码确定分化方向和动作选择范围:
def build_daily_exercise_pool(profile, day_focus): """根据肌群给当天动作候选池""" pool = [] for muscle in day_focus: candidates = [e for e in exercise_library if e["muscle_group"] == muscle and e["difficulty"] <= profile["experience_level"] + 1] pool.extend(candidates) return pool def generate_weekly_template(profile): """根据训练频率确定分化方案,这是确定性逻辑""" frequency = profile["weekly_frequency"] if frequency <= 2: return ["full_body"] * frequency elif frequency == 3: return ["full_body", "upper", "lower", "full_body"] elif frequency == 4: return ["upper", "lower", "upper", "lower"] elif frequency >= 5: return ["push", "pull", "legs", "upper", "lower"][:frequency]确定好当天练什么部位之后,再把一份结构化的Prompt交给大模型,让它在候选池里选动作、补组次和休息时间。Prompt里写死输出规则,比如"必须从候选池中选择动作,不得超过5个动作,每个动作必须包含sets、reps、rest_seconds字段"。生成后回到代码里校验JSON合法性,不合规就重新生成一次。
这套混合流跑起来非常稳。规则的事交给代码,审美和语言表达的事交给模型,两边都不越界。
3.4 与Agent主循环的交互协议
Skill要挂在Agent上,必须定义一套清晰的交互协议。我在实现中定义了三种状态:等待输入、处理中、需要确认。
例如,在首次沟通阶段,Skill的状态是"需要用户输入",Agent收到用户回答后调用Skill的handle_message方法,更新画像字段,返回下一个问题。画像没收集完就一直循环,收集完之后自动跳转到计划生成阶段。
def handle_message(self, user_input: str) -> SkillResponse: if self.state == "PROFILE_COLLECTING": self.profile.update_from_answer(user_input) if self.profile.is_complete(): return self.build_workout_plan() return self.ask_next_question() elif self.state == "ACTIVE": return self.process_session_feedback(user_input)Agent主循环只需要判断SkillResponse里的action字段是reply还是wait_for_input,就能决定是直接回复用户还是继续等待。这套协议简单,但复用性很高,我后来做的其他Skill也沿用了这套写法。
4. 真实测试中翻过的车:三个问题与修复思路
代码写完只是开始,真正磨细节的是测试阶段。这一节记录的三个问题,每一个都是真实跑出来的,不是脑补的。
4.1 过度训练计划:模型太想表现自己了
第一个问题出现在计划生成模块。测试时发现,模型生成计划非常"积极",恨不得把一周七天全排满,每天五六个动作,组数全往高里标。我一看就知道问题出在哪:大模型没有身体疲劳的概念,它不理解"恢复"也是训练的一部分。
后来我在生成逻辑里加了一层硬约束。代码在拿到模型生成的计划后,会做一个总训练量检查:单次训练动作不超过5个,总组数不超过25组,同一肌群一周训练量控制在16到28组之间。如果超出,不是让模型重新生成,而是由代码自动裁剪:删掉多余动作,把超出的组数降回来。
这个修复让我想明白一件事:模型提出的方案要经过规则过滤,而不是直接采信。尤其在健身这种有安全边界的场景里,宁可计划保守一点,也不能让模型把用户练伤。
4.2 误判临时情绪:用户说"不想练了"被当成正式指令
第二个坑更隐蔽。测试时一位用户训练中途说了一句"今天好累,不想练了",Skill直接把它理解为用户要终止本轮训练,调用了结束逻辑。但实际情况是,用户只是嘴上抱怨,他还是希望有人能push他一把。
这个问题的本质是:模型分不清表达情绪和下达指令的区别。我的修复方案是给Skill增加一个"情绪识别前置处理"。凡是收到"不想练了""好累""算了"这类表达,先不直接执行,而是返回一个二次确认的选项:您是确认今天休息,还是需要我把训练量降低一些?
改动之后效果很明显。用户被问到的第一反应往往是"那降低一点吧",而不是真的放弃。这个设计也让Agent更像真正的教练:教练遇到学员说累,从来不会立刻放人走,而是先问一句"是身体不舒服还是今天状态差"。
4.3 动作库膨胀:每个动作都要维护太难了
第三个坑是我自己挖的。一开始我为了丰富计划,拼命往动作库里加动作,很快动作库就膨胀到两百多个词条。结果出现了两个问题:一是模型在候选池里选动作时经常犹豫,生成时间变长;二是每个动作的质量参差不齐,有的动作甚至没有配退阶方案。
后来我做了两个调整:第一,按用户经验水平过滤候选池,新手只看难度1-2的动作,进阶用户才看难度3-4的动作,候选池从两百瞬间缩小到二三十。第二,把动作库从兼职维护改成了结构化维护:每个动作必须有主肌群、器械、难度、退阶动作、常见错误、纠错提示六个字段,缺一不可。质量比数量重要,二十个维护良好的动作,比两百个残缺动作更有用。
5. 多端部署要注意的边界:配置、降级与隐私
5.1 跑通Demo后要补的配置项
很多开发者和我一开始一样,在本地跑通Demo后以为就完事了。真到接入各种Agent平台或部署到服务器时,才发现还差很多配置。
首先要配置的是用户数据的持久化路径。本地跑可以写JSON文件,做真实部署最好换成一个轻量数据库。我把用户画像和训练记录分开存,这样在不同设备间同步时,不会因为一次训练记录写错导致整个画像损坏。
其次是Skill的超时处理。大模型生成计划需要时间,如果用户在对话中长时间没收到回复,可能会重复发送消息,导致Skill被并发调用。我在入口处加了一个简单的单飞锁,同一用户同时只能有一个生成任务,然后再配一个超时清理机制。
还有一个很容易忽略的点:日志。每一轮训练对话我都会记录一条结构化日志,包括用户输入、模型响应、状态转移、耗时。第一次部署时就是靠这些日志定位到上面说的"误判情绪"问题,否则完全无从查起。
5.2 弱网和离线场景的降级方案
Agent应用现在经常跑在各类终端设备上,网络不一定稳定。如果完全依赖云端大模型生成计划,用户在健身房里一断网,整个Skill就瘫了,这就很尴尬。
我的做法是给训练会话设计了降级链路。计划生成依赖大模型,但训练中的"当日课程表查看"和"动作要点查看"不依赖。用户开始训练后,Skill会把当天的计划缓存到本地,断网状态下,用户可以浏览当天的动作列表、组数次数和休息时间。如果用户反馈疲劳度,代码也能先记录到本地缓存,等网络恢复再同步到服务端。
这个降级设计很有必要。真实用户的核心诉求是把这一节课练完,而不是和Agent聊天。让最关键的信息在离线下可用,体感会好特别多。
5.3 数据存储与隐私边界
健身数据属于比较敏感的个人健康信息,这块必须谨慎处理。我开源的版本里,默认配置是全部数据本地存储,不上传任何云端服务。用户照片、体重、病史这些字段,我设计时就直接不让Skill采集,不是采集后加密,而是压根不进数据模型。
如果你要接第三方存储或同步服务,也要注意去标识化。我的建议是:用户ID用哈希值,训练记录不绑定手机号或真实姓名,运动数据与画像数据分库保存。我在文档里给过一份最小数据集建议,原则只有一条:能力范围内能少采集就少采集。
6. 给想复刻这个Skill的开发者的三点建议
第一,先别急着堆功能,先想清楚你的Agent面对的用户和场景。我的版本偏重增肌和居家训练,是因为最开始就用这类用户做测试。如果你要面向康复人群或跑步爱好者,动作库、强度算法和交互逻辑都要重新设计。不要试图做一个通吃的健身教练,垂直才是出路。
第二,测试时一定要用真人,不要用模型自我对话来模拟。我前期偷懒,用一个模型同时扮演用户和教练,结果测出来的对话自然顺畅、毫无问题,一上真人就翻车。真人会说漏嘴、会前后矛盾、会突然抱怨、会记错训练量。这些不确定性才是Skill真正要处理的对象。
第三,数据闭环比生成能力重要。很多人把精力花在让模型生成更精准的计划上,但我实测下来的体会是,真正拉开体验差距的是训练记录和计划调整的逻辑。用户喜欢这个Skill,不是因为它能生成多漂亮的课表,而是因为它记得上次练了什么、这次能接得上。把历史数据用起来,比换更强的模型更管用。
我目前还在继续迭代这个Skill,最近在试着加入动作视频解析的能力,让姿势纠错从"描述式问答"升级成"视觉识别加定向提示"。这中间又有一堆新问题要踩,但方向我认为是对的:让Agent从"会说话"变成"会干活",健身教练Skill只是其中一个例子。如果你也在做类似的Agent技能,欢迎参考这个开源项目,按自己的场景改一版出来,那一刻你会明显感受到,把规则和模型混合起来用,比单靠大模型的临场发挥靠谱太多了。