- 后端
- 前端
- 移动开发
【免费下载链接】SparkyFitness
SparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.
导读
本文围绕 SparkyFitnessServer/prompts/chatbot-core-checkin.md 这份系统提示词片段展开,剖析 SparkyFitness 的 AI 营养与健康教练(Sparky)在"每日打卡(check-in)"域的两条核心提示规则:将用户输入精确匹配到自定义测量类别,以及在确认/描述 mood 时必须保留关联 Emoji。阅读本文后,你将理解该提示词在服务端提示词组合架构中的加载位置、变量注入的数据来源、底层工具与共享数据结构的实现细节,以及测试用例如何锁定这些行为。
一、提示词文件在系统中的定位:按域动态拼接的架构
chatbot-core-checkin.md不是独立成篇的提示词,而是 Sparky 系统提示词按功能域拼接体系中的"check-in 片段"。完整的拼接逻辑位于 SparkyFitnessServer/services/chatService.ts 的getSystemPrompt():
if (categories.has('checkin')) { const checkinPath = path.join( __dirname, '../prompts', `chatbot-${suffix}-checkin.md` ); if (existsSync(checkinPath)) { content += '\n\n' + readFileSync(checkinPath, 'utf-8').trim(); } }关键机制:
- 后缀(suffix)决定版本:
chatbot-core-checkin.md与chatbot-full-checkin.md同属 check-in 域,suffix由ChatToolProfile('full'或'core')决定,二者对应不同粒度的模型配置,提示词措辞随之切换。core 版强调"Compare inputs to the custom categories. If you find a match, use the exact category name.",full 版则扩展为"compare user inputs to the list above. If you find a match or variations (synonyms, capitalization), use the exact category name"。 - 域由分类器决定:同一文件中的意图分类器会把用户消息归入
checkin域,匹配关键词包括weight/height/waist/hips/neck/body fat/checkin/scale/bmi/mood/sleep/fasting/measurements/progress photo/sleep debt/chronotype等(见 chatService.ts 的category: 'checkin'正则),只有命中时才注入该片段。 - 占位符运行时替换:拼接完成后统一执行
content.replace(/\${customCategories}/g, customCategoriesList)与content.replace(/\${today}/g, todayInZone(chatTz)),将模板变量替换为用户级真实数据。
基座提示词 SparkyFitnessServer/prompts/chatbot-core.md 定义了整体人格与"直接执行工具、成功即确认"的对话策略,check-in 片段在其之上追加域专属约束。
二、${customCategories}变量注入:用户自定义测量类别的数据链路
提示词开头的${customCategories}是模板占位符,其数据来源是用户在应用中自行创建的自定义测量类别(如血压、腰围、自定义身体指标等)。
2.1 数据获取
SparkyFitnessServer/models/measurementRepository.ts 中的getCustomCategories(userId)从数据库读取该用户的全部自定义类别;SparkyFitnessServer/services/measurementService.ts 在getCustomCategories上封装了"代理用户(on-behalf-of)"语义:当targetUserId与当前认证用户不同时,优先返回目标用户的数据。
2.2 格式化为提示词列表
chatService.ts 使用带 60 秒 TTL 的缓存(chatContextInputsCache)并发加载类别与用户时区,随后把类别格式化为 Markdown 列表注入提示词:
customCategoriesList: customCategories.length > 0 ? customCategories .map( (cat: DatabaseCustomCategories) => `- ${cat.name} (${cat.measurement_type}, ${cat.frequency})` ) .join('\n') : 'None'即最终注入形如:
- Blood Pressure (mmHg, Daily) - Waist Circumference (cm, Weekly)每条包含name、measurement_type(单位)与frequency(频率),帮助 LLM 既知道类别名,也知道其语义单位与采集频次。
2.3 为什么需要这份上下文
sparky_manage_checkin工具的log_custom_metricaction 要求category_name与用户已存在的类别名精确一致。由于自定义类别完全由用户命名,LLM 若不被告知确切名称,很容易自创近似名称导致写入失败或产生重复类别。将完整清单注入提示词,是让"用户说 '血压' → 工具收到 'Blood Pressure'"这一对齐成为可能的前提。
三、精确类别名匹配的底层实现与测试锁定
提示词要求"if you find a match, use the exact category name"。这一约束并非只靠提示词约束,底层工具在匹配时也做了归一化兜底。查看 SparkyFitnessServer/ai/tools/checkinTools.ts 的log_custom_metric分支,服务端会基于用户类别列表做大小写不敏感的名称匹配,命中后使用类别内部 ID 写入。
测试 SparkyFitnessServer/tests/chatbotToolsCheckin.test.ts 明确锁定了这一行为:
describe('log_custom_metric', () => { it('logs against a case-insensitively matched category', async () => { vi.mocked(measurementService.getCustomCategories).mockResolvedValue([ { id: 'c1', name: 'Blood Pressure', measurement_type: 'mmHg', frequency: 'Daily' }, ]); // 用户输入 'blood pressure'(小写)也能命中类别 'Blood Pressure' await tools.sparky_manage_checkin.execute!({ action: 'log_custom_metric', category_name: 'blood pressure', value: 120, // ... }); }); });由此可见:提示词层要求 LLM 输出"exact category name"(保证数据规范),工具层再以大小写不敏感匹配做工程兜底(容忍用户口语差异),full 版提示词中的 "variations (synonyms, capitalization)" 正是对这一兜底语义的提示级复述。
四、Mood Emoji 保真规则:从提示词到共享数据模型
提示词后半段是强约束(MUST):
When confirming a logged mood or describing the user's mood for today, you MUST always include the smileys/emojis associated with the mood tags (for example: "😰 Anxious", "Irritable 😤", "🥺 Sensitive", "😴 Tired"). Do not strip them out or write them as plain text only.
4.1 内置 mood 与 Emoji 的权威映射
mood 标签与 Emoji 的权威映射定义在共享包 shared/src/mood/index.ts 的BUILT_IN_MOODS常量中。它分为两类:
带数值区间(band)的 9 个强度型 mood——与遗留 0-100 MoodMeter 刻度一一对应,用于回填旧数据和从设备同步的数值 mood 推导标签:
| name | displayName | emoji | band(0-100 上限) |
|---|---|---|---|
| sad | Sad | 😢 | 15 |
| angry | Angry | 😠 | 25 |
| worried | Worried | 😟 | 35 |
| neutral | Neutral | 😐 | 45 |
| thoughtful | Thoughtful | 🤔 | 55 |
| calm | Calm | 🙂 | 65 |
| confident | Confident | 😎 | 75 |
| happy | Happy | 😀 | 85 |
| excited | Excited | 😍 | 100 |
6 个纯描述型 tag(无 band)——与强度评分并列使用:
| name | displayName | emoji |
|---|---|---|
| energetic | Energetic | ⚡ |
| sensitive | Sensitive | 🥺 |
| tired | Tired | 😴 |
| low | Low energy | 🔋 |
| anxious | Anxious | 😰 |
| irritable | Irritable | 😤 |
提示词示例中的"😰 Anxious"、"Irritable 😤"、"🥺 Sensitive"、"😴 Tired"均可在上表中找到对应条目,属于anxious、irritable、sensitive、tired四个内置 tag。每条 MoodDef 还携带icon与color(如mood-calm/sky),供前端渲染使用。
4.2 服务端格式化:formatMoodTags是保真的工程底座
提示词要求 LLM 输出 Emoji,服务端则在两个出口用同一个formatMoodTags函数强制保真(checkinTools.ts):
function formatMoodTags(tags?: string[] | null): string { if (!tags || !tags.length) return ''; const formatted = tags.map((t) => { const matched = BUILT_IN_MOODS.find( (m) => m.name.toLowerCase() === t.toLowerCase() ); if (matched) { return `${matched.emoji} ${matched.displayName}`; } return t; // fallback if it is a custom tag or not found }); // ... }- 日志确认出口:
log_moodaction 写入成功后返回Mood logged for ${date}: ${value}/10${tagsStr} — ${notes},tagsStr 即格式化结果。 - 日记查询出口:
list_checkin_diary渲染## Mood区块时同样调用该函数,保证历史回看也带 Emoji。
formatMoodTags对内置 tag 做大小写不敏感匹配并输出emoji + displayName,对用户自定义 tag 则原样回退,与提示词的保真要求互为表里。
4.3 测试如何锁定 Emoji 保真
chatbotToolsCheckin.test.ts 对两个出口都做了断言:
// 日记渲染出口 expect(result).toBe( '### Check-in Diary: 2026-06-01\n\n' + '## Mood\n' + '- 7/10 [😰 Anxious, 😟 Worried] — Feeling okay\n\n' ); // 日志确认出口 expect(result).toBe( '✅ Mood logged for 2026-06-01: 8/10 [😰 Anxious, 😴 Tired] — Feeling good.' );两个断言精确到 Emoji 字符,意味着任何"剥离 Emoji、只写纯文本"的回归都会导致测试失败——提示词中的 MUST 由此获得可执行的工程保障。
五、提示词工程视角:为什么"不剥离 Emoji"是硬约束
从交互设计看,mood 是高度情绪化的数据:Emoji 是用户选择 tag 时看到的视觉符号(前端基于BUILT_IN_MOODS的 emoji/icon 渲染),若 LLM 确认回复里只写 "Anxious" 而不写 "😰",用户会感到确认与自身选择脱节。让确认文本与服务端渲染、前端展示三者使用同一套emoji + displayName格式,才能形成"看到即反馈、反馈即所见"的闭环。
这也解释了为什么提示词同时出现smileys/emojis与具体示例:LLM 需要既知道"必须输出 Emoji",又知道"以emoji 空格 名称的形式输出",二者缺一不可。
六、实践要点与配置小结
针对 SparkyFitness 服务端开发者或提示词维护者,可沉淀以下 checklist:
- 维护
${customCategories}注入格式:chatService.ts中的格式化决定了 LLM 能看到什么。若调整measurement_type或frequency的展示语义,需同步确认getCustomCategories返回字段与DatabaseCustomCategories类型一致。 - 新增内置 mood 时同步三处:在
shared/src/mood/index.ts的BUILT_IN_MOODS添加条目(含 emoji、displayName、icon、color、可选 band),formatMoodTags自动生效;若属于强度型(band),需确认moodValueToTag/representativeMoodValue的区间推导仍正确。 - 不要绕过工具直接拼文本:所有 mood 相关回复都应基于
log_mood/list_checkin_diary返回的格式化结果,避免 LLM 自造 Emoji 与BUILT_IN_MOODS不一致。 - 回归保护:
chatbotToolsCheckin.test.ts中的 Emoji 断言与log_custom_metric的大小写不敏感匹配测试,是这两条提示规则的守护网,修改提示词措辞不应破坏对应工具行为。 - core 与 full 版本保持语义一致:
chatbot-core-checkin.md与chatbot-full-checkin.md措辞有简繁差异(full 版补充了 synonyms/capitalization 说明),但核心规则一致;修改其一需同步评估另一版本,避免同域两种模型行为漂移。
参考文件索引
- 提示词片段:SparkyFitnessServer/prompts/chatbot-core-checkin.md、SparkyFitnessServer/prompts/chatbot-full-checkin.md
- 拼接与变量注入:SparkyFitnessServer/services/chatService.ts
- 工具实现:SparkyFitnessServer/ai/tools/checkinTools.ts
- 共享 mood 数据模型:shared/src/mood/index.ts
- 数据获取:SparkyFitnessServer/models/measurementRepository.ts、SparkyFitnessServer/services/measurementService.ts
- 测试锁定:SparkyFitnessServer/tests/chatbotToolsCheckin.test.ts
- 后端
- 前端
- 移动开发
【免费下载链接】SparkyFitness
SparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.
相关推荐
Quivr提示工程:自定义提示词模板和优化技巧
Quivr提示工程:自定义提示词模板和优化技巧 引言:为什么提示工程对AI助手至关重要 在人工智能助手领域,提示工程(Prompt Engineering)是决
人工智能AI 应用大模型RAG后端前端goose 系统提示词(system.md)全解析:模板结构、渲染机制与自定义实战
goose 系统提示词(system.md)全解析:模板结构、渲染机制与自定义实战 goose 是一个开源、可扩展的通用 AI Agent,支持安装、执行、编辑
人工智能大模型AI AgentAI 应用本地部署MCP ClientsMCP 服务工具调用桌面应用CLIChatGPT AutoExpert 自定义指令机制全解析:相关性分级、双文本框设计与提示词工程实战
ChatGPT AutoExpert 自定义指令机制全解析:相关性分级、双文本框设计与提示词工程实战 本篇技术指南以 ChatGPT AutoExpert 仓库
提示工程AI 应用AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考