diff --git a/.gitignore b/.gitignore index 801d93c757ef32be7342402d6158cc620bb90fcf..dbe9c82b3610ccd58d1c681848dcd322e500051e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -.vscode/ -data/nutrition/raw/ +.vscode/ \ No newline at end of file diff --git a/__pycache__/loop.cpython-311.pyc b/__pycache__/loop.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..085e15b01373f2a7f8633a17acf19e6ae794764e Binary files /dev/null and b/__pycache__/loop.cpython-311.pyc differ diff --git a/__pycache__/loop.cpython-313.pyc b/__pycache__/loop.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b48df87597f1eb51c5e16b096974c90e08db6713 Binary files /dev/null and b/__pycache__/loop.cpython-313.pyc differ diff --git a/app.py b/app.py index 2d58c727497581be1e6407fc3d4983446c2c7536..365a5aa286b80e36b4aa637e48ad8c3c8a024527 100644 --- a/app.py +++ b/app.py @@ -1,12 +1,13 @@ """ PregoPal - 孕期陪护AI助手 ========================= -主入口:启动 Gradio 应用。 +主入口:启动 Gradio 应用 + 每日 Loop。 架构: - app.py ← 薄入口(仅启动) + app.py ← 薄入口(启动 Gradio + Loop) config.py ← 全局配置 - core/ ← AI 核心层(等待 MiniCPM-o 部署) + loop.py ← 核心循环引擎(状态机驱动) + plugins/ ← Loop 插件 modules/ ← 业务逻辑层 ui/ ← 表现层(Gradio 界面) data/ ← 数据存储 @@ -14,9 +15,14 @@ PregoPal - 孕期陪护AI助手 import gradio as gr from ui.app_builder import create_app +from loop import check_and_run_loop if __name__ == "__main__": - demo = create_app() + # 启动时检查并运行每日 Loop + loop = check_and_run_loop() + + # 创建并启动 Gradio 应用 + demo = create_app(loop) demo.launch( theme=gr.themes.Soft( primary_hue="pink", diff --git a/c b/c new file mode 100644 index 0000000000000000000000000000000000000000..49721fb5307f7f7c4cd9e9f9930a078de09b3dbd --- /dev/null +++ b/c @@ -0,0 +1,502 @@ +# 🌸 PregoPal 项目理解与技术架构(v4 - Loop 驱动 + 三文档操作) + +> 本文档已完成重构,每个小节末尾附有批注区,便于你边看边标注意见。 + +--- + +## 一、一句话定位 + +**一个基于每日生命周期 Loop 驱动的孕期陪护 AI 助手**:每天首次启动时自动总结昨日饮食、对比国家营养标准(DRIs/膳食指南/体重标准),生成今日简报注入对话上下文;白天与家庭成员(通过声纹区分)自然交互,实时记录饮食、推荐食谱;次日循环往复。 + +> **批注:** ________ + +--- + +## 二、三个营养文档的操作方法 + +你已经下载到 `data/nutrition/raw/` 的三个文件,各自承担不同角色: + +| 文档 | 文件 | 用途 | PregoPal 的操作方式 | +|------|------|------|-------------------| +| **① 体重增长标准** | `妊娠期妇女体重增长推荐值标准.md` | 体重监测基准 | **主动询问体重** — 每天 Loop 中检查是否需要询问,对照 BMI 分类给出评价 | +| **② 膳食指南** | `中国孕期妇女膳食指南2022图片转md版.md` | 食谱推荐基准 | **作为推荐食谱的约束基础** — 根据孕期阶段用宝塔推荐量生成模板 | +| **③ DRIs** | `备孕&孕早中晚三期和哺乳期的所有膳食营养素参考摄入量.md` | 营养分析基准 | **Agent 定期思考** — 每天自动对比 RNI,分析不足,写入次日预设 | + +> **批注:** ________ + +### 2.1 体重增长标准 → 主动询问体重 + +**数据来源**:WS/T 801-2022 表1 + +``` +每天早上 Loop 的 weight_check 插件: + ├→ 检查 data/logs/ 中是否有近期的体重记录 + ├→ 若无记录或记录超过 7 天 + │ └→ 在今日简报中标记「需询问体重」 + │ (UI 弹出体重卡片,或语音询问:「今天称体重了吗?」) + └→ 若有记录 + ├→ 计算当前孕周、孕前 BMI、当前总增长值 + ├→ 对照标准表: + + | 孕前 BMI 分类 | 总增长范围 | 每周增长(中+晚) | + |----------------------|------------|----------------| + | 低体重 BMI<18.5 | 11.0~16.0 | 0.46(0.37~0.56) | + | 正常 18.5≤BMI<24.0 | 8.0~14.0 | 0.37(0.26~0.48) | + | 超重 24.0≤BMI<28.0 | 7.0~11.0 | 0.30(0.22~0.37) | + | 肥胖 BMI≥28.0 | 5.0~9.0 | 0.22(0.15~0.30) | + + ├→ 在范围内 → ✅ 鼓励「体重增长很好,继续保持!」 + └→ 超出范围 → ⚠️ 提醒「增长偏快/偏慢,建议调整饮食/咨询医生」 +``` + +> **批注:** ________ + +### 2.2 膳食指南 → 食谱推荐基础 + +**数据来源**:中国孕期妇女平衡膳食宝塔(备孕/孕中期/孕晚期/哺乳期) + +``` +推荐食谱时,AI 的约束条件: + ├→ 孕中期宝塔: + │ 奶类 300-500g,鱼禽蛋肉类 150-200g,谷类 200-250g + │ 蔬菜 400-500g,水果 200-300g,薯类 75g + │ 大豆 20g,坚果 10g,油 25g,盐 5g + │ + ├→ 孕晚期宝塔: + │ 奶类 300-500g,鱼禽蛋肉类 175-225g,谷类 225-275g + │ 蔬菜 400-500g,水果 200-350g,薯类 75g + │ 大豆 20g,坚果 10g,油 25g,盐 5g + │ + └→ 膳食指南六条核心建议(嵌入系统提示词): + 1. 调整孕前体重至正常范围 + 2. 常吃含铁食物,选用碘盐,补充叶酸和维生素D + 3. 孕吐严重者少量多餐 + 4. 孕中晚期增加奶、鱼、禽、蛋、瘦肉 + 5. 经常户外活动,禁烟酒 + 6. 愉快孕育新生命,积极准备母乳喂养 +``` + +**原有 MEAL_TEMPLATES 的改造**:不再硬编码模板字符串,而是将上述宝塔约束条作为结构化 JSON 传递给 AI(或作为本地 fallback 兜底),由 AI 根据宝塔约束 + 用户偏好生成个性化食谱。 + +> **批注:** ________ + +### 2.3 DRIs → Agent 定期分析(自动运行 → 写入次日预设) + +**数据来源**:表15(孕早期)/ 表16(孕中期)/ 表17(孕晚期)/ 表18(哺乳期) + +``` +每天 Loop 的 dri_analysis 插件: + ├→ 读取昨日饮食记录(data/logs/饮食日志_YYYY-MM-DD.md) + ├→ 根据当前孕期阶段选择对应 DRIs 表 + ├→ 关键营养素逐项对比(重点标记 RNI 与摄入量差异): + + ┌─────────────────────────────────────────────┐ + │ 营养素 RNI(孕中期) 昨日摄入 差异 │ + ├─────────────────────────────────────────────┤ + │ 能量(MJ) 9.84(PAL II) 8.2 -17% │ + │ 蛋白质(g) 70 55 -21% │ + │ 钙(mg) 800 420 -48% │ + │ 铁(mg) 25 12 -52% ← 重点关注 │ + │ 叶酸(μg DFE) 600 350 -42% ← 重点关注 │ + │ 维生素D(μg) 10 2.1 -79% ← 重点关注 │ + └─────────────────────────────────────────────┘ + + ├→ 生成分析结论(自然语言): + │ 「昨天钙、铁、叶酸摄入偏低,建议今天多吃: + │ ✓ 深绿色蔬菜(菠菜、西兰花) + │ ✓ 瘦肉/动物肝脏 + │ ✓ 豆制品/奶制品」 + │ + └→ 写入 data/presets/今日简报_YYYY-MM-DD.json + ├ 今日重点关注营养素列表 + ├ 今日推荐食物类别与份量 + ├ 体重提醒(如需) + └ 与孕妇及家人交谈的建议开场话题 +``` + +> **批注:** ________ + +--- + +## 三、核心架构:Loop 驱动(状态机 + 插件注册) + +### 3.1 设计理念 + +PregoPal 不是一个「用户点按钮 → 出结果」的被动应用,而是**有每日生命周期的主动伴侣**。 + +参考你提供的 `nanobot_core/agent/loop.py` 中的**状态机事件驱动模式**,PregoPal 的 Loop 提取两个核心设计: + +1. **TurnState 状态机**(类比 `_process_message` 的 `_state_*` 状态链)— 管理每日生命周期的阶段流转 +2. **可注册的扩展能力**(类比 ToolRegistry)— 每个阶段的具体逻辑由 Plugin 实现 + +> **批注:** ________ + +### 3.2 每日生命周期 + +``` + ┌─────────────┐ + │ LAUNCH │ ← 首次启动检查 + │ (启动) │ + └──────┬──────┘ + │ + ┌───────────┴───────────┐ + │ │ + 首次运行今日 已运行过今日 + │ │ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ SUMMARIZE │ │ │ + │ (昨日总结) │ │ │ + │ ├ 读取饮食日志 │ │ │ + │ └ 检查体重 │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ │ │ + ┌──────────────┐ │ │ + │ ANALYZE │ │ │ + │ (营养分析) │ │ │ + │ └ 对比DRIs │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ │ │ + ┌──────────────┐ │ │ + │ BRIEF │ │ │ + │ (生成简报) │ │ │ + │ └ 写JSON │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ ▼ │ + ┌──────────────────────────────────┐ │ + │ INTERACT │◄─┘ + │ (白天交互模式) │ + │ ├ 声纹识别 → 区分说话人 │ + │ ├ 对话理解 → 记录饮食/推荐/闲聊 │ + │ └ 简报注入 → AI 知道今日关注点 │ + └──────────────┬───────────────────┘ + │ + 用户结束今日 + │ + ▼ + ┌──────────────┐ + │ CONSOLIDATE │ + │ (晚间整理) │ ← 可选(关闭页面时触发) + │ ├ 汇总今日 │ + │ └ 写明日预设 │ + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ DONE │ + └──────────────┘ +``` + +> **批注:** ________ + +### 3.3 状态机定义 + +```python +# 状态枚举 +class LoopState(Enum): + LAUNCH = "launch" # 启动检查:是否是今日首次运行 + SUMMARIZE = "summarize" # 昨日总结:分析昨日饮食/体重 + ANALYZE = "analyze" # 营养分析:对比 DRIs + BRIEF = "brief" # 生成今日简报 + INTERACT = "interact" # 白天交互模式(等待用户操作) + CONSOLIDATE = "consolidate" # 晚间整理 + DONE = "done" + +# 状态转移表(类比 nanobot_core 的 _TRANSITIONS) +_TRANSITIONS = { + (LoopState.LAUNCH, "first_run_today"): LoopState.SUMMARIZE, + (LoopState.LAUNCH, "already_run"): LoopState.INTERACT, + (LoopState.SUMMARIZE, "ok"): LoopState.ANALYZE, + (LoopState.ANALYZE, "ok"): LoopState.BRIEF, + (LoopState.BRIEF, "ok"): LoopState.INTERACT, + (LoopState.INTERACT, "day_ended"): LoopState.CONSOLIDATE, + (LoopState.CONSOLIDATE, "ok"): LoopState.DONE, +} +``` + +> **批注:** ________ + +### 3.4 状态处理器 + +每个状态对应一个 handler(类比 `_state_restore`, `_state_compact`, `_state_run` 等): + +| 状态 | Handler | 功能 | +|------|---------|------| +| `LAUNCH` | `_state_launch()` | 检查 `data/presets/` 中是否有今日简报 → 决定首次/已运行 | +| `SUMMARIZE` | `_state_summarize()` | 遍历执行所有注册在 SUMMARIZE 阶段的 Plugin | +| `ANALYZE` | `_state_analyze()` | 遍历执行所有注册在 ANALYZE 阶段的 Plugin | +| `BRIEF` | `_state_brief()` | 遍历执行所有注册在 BRIEF 阶段的 Plugin | +| `INTERACT` | `_state_interact()` | 等待用户交互(从 Gradio event queue 消费消息) | +| `CONSOLIDATE` | `_state_consolidate()` | 遍历执行所有注册在 CONSOLIDATE 阶段的 Plugin | +| `DONE` | `_state_done()` | 清理资源,标记今日完成 | + +> **批注:** ________ + +### 3.5 主循环入口 + +```python +# loop.py 核心逻辑 +class PregoPalLoop: + def __init__(self): + self.plugins = PluginRegistry() + self.state = LoopState.LAUNCH + self._register_default_plugins() + + async def run(self) -> None: + """主循环入口""" + while self.state is not LoopState.DONE: + handler_name = f"_state_{self.state.value}" + handler = getattr(self, handler_name) + event = await handler() + next_state = _TRANSITIONS.get((self.state, event)) + if next_state is None: + raise RuntimeError(f"No transition from {self.state} on {event}") + self.state = next_state + + async def _state_summarize(self) -> str: + """SUMMARIZE 阶段:执行所有相关插件""" + for plugin in self.plugins.get_plugins(LoopStage.SUMMARIZE): + await plugin.run(self.context) + return "ok" +``` + +> **批注:** ________ + +--- + +## 四、插件系统设计(可扩展性) + +### 4.1 插件基类 + +```python +# plugins/base.py +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from enum import Enum + +class LoopStage(Enum): + """插件可注册的阶段""" + SUMMARIZE = "summarize" + ANALYZE = "analyze" + BRIEF = "brief" + CONSOLIDATE = "consolidate" + +@dataclass +class LoopContext: + """插件上下文:共享数据容器""" + briefing: dict = field(default_factory=dict) # 今日简报数据 + weight_data: dict = field(default_factory=dict) # 体重记录 + diet_records: list = field(default_factory=list) # 饮食记录 + analysis_results: dict = field(default_factory=dict) # 分析结果 + errors: list = field(default_factory=list) # 错误收集 + +@dataclass +class PluginResult: + """插件执行结果""" + success: bool + data: dict = field(default_factory=dict) + message: str = "" + +class LoopPlugin(ABC): + """Loop 扩展插件基类 — 所有插件继承此类""" + + @abstractmethod + def stage(self) -> LoopStage: + """此插件注册到哪个阶段""" + + @abstractmethod + def name(self) -> str: + """插件唯一名称""" + + @abstractmethod + async def run(self, ctx: LoopContext) -> PluginResult: + """执行插件逻辑""" +``` + +> **批注:** ________ + +### 4.2 内置插件清单 + +| 插件 | 类名 | 注册阶段 | 功能 | 数据源 | +|------|------|---------|------|--------| +| 昨日饮食总结 | `DietSummaryPlugin` | SUMMARIZE | 读取昨日饮食日志,汇总三餐 | `data/logs/*.md` | +| 体重检查 | `WeightCheckPlugin` | SUMMARIZE | 检查体重记录,对比 WS/T 801 | `data/logs/*.md` + 用户输入 | +| DRIs 分析 | `DRIAnalysisPlugin` | ANALYZE | 对比营养素参考摄入量 | DRIs 表 + 昨日饮食 | +| 简报生成 | `BriefingGeneratorPlugin` | BRIEF | 汇总分析为结构化 JSON | `LoopContext` | +| 预设写入 | `PresetWriterPlugin` | CONSOLIDATE | 生成明日预设文件 | `data/presets/` | + +> **批注:** ________ + +### 4.3 扩展方式 + +```python +# 后续新增功能(如血糖监测)只需两步: + +# 1. 写一个新插件 +class BloodSugarPlugin(LoopPlugin): + def stage(self) -> LoopStage: return LoopStage.SUMMARIZE + def name(self) -> str: return "blood_sugar" + async def run(self, ctx: LoopContext) -> PluginResult: + # 读取血糖记录 → 分析趋势 → 写入 ctx + ... + +# 2. 注册到 loop +loop.plugins.register(BloodSugarPlugin()) + +# 不需要修改 loop.py 的任何状态机代码 +``` + +> **批注:** ________ + +--- + +## 五、整体文件结构 + +``` +PregoPal/ +├── app.py # 主入口:启动 Gradio + Loop +├── config.py # 全局配置(路径/枚举/系统提示词模板) +├── utils.py # 工具函数 +├── loop.py ← 🆕 新增 # 核心循环引擎(状态机驱动) +│ +├── plugins/ ← 🆕 新增 # Loop 插件 +│ ├── __init__.py +│ ├── base.py # LoopPlugin 基类 + PluginRegistry +│ ├── diet_summary.py # 昨日饮食总结 +│ ├── weight_check.py # 体重检查(WS/T 801) +│ ├── dri_analysis.py # DRIs 营养素对比分析 +│ ├── briefing_generator.py # 今日简报生成器 +│ └── preset_writer.py # 预设文件写入 +│ +├── core/ # AI 核心层(等待 MiniCPM-o 部署) +│ ├── __init__.py +│ ├── model_loader.py # 模型加载器(空接口) +│ ├── voice_processor.py # 语音处理器(空接口) +│ ├── vision_processor.py # 视觉处理器(空接口) +│ └── conversation_manager.py # 对话管理器(空接口) +│ +├── modules/ # 业务逻辑层 +│ ├── __init__.py +│ ├── voiceprint.py # 声纹识别 +│ ├── meal_recommender.py # 菜品推荐 +│ ├── diet_logger.py # 饮食记录 + Markdown 生成 +│ └── nutrition_analyzer.py # 营养分析 + 可视化 +│ +├── ui/ # 表现层 +│ ├── __init__.py +│ └── app_builder.py # Gradio 界面 + 事件绑定 +│ +├── data/ # 数据存储 +│ ├── nutrition/ +│ │ └── raw/ # ← 你已下载的三个 MD 文件 +│ │ ├── 妊娠期妇女体重增长推荐值标准.md +│ │ ├── 中国孕期妇女膳食指南2022图片转md版.md +│ │ └── 备孕&孕早中晚三期和哺乳期的所有膳食营养素参考摄入量.md +│ ├── presets/ ← 🆕 新增 # 每日简报 / 预设文件 +│ ├── voices/ # 声纹数据 +│ ├── logs/ # Markdown 饮食日志 +│ └── reports/ # 导出报告 +│ +├── docs/ +│ ├── 项目理解_技术架构.md # ← 本文档 +│ └── 开发日志.md +│ +├── requirements.txt # 依赖清单 +└── README.md +``` + +> **批注:** ________ + +--- + +## 六、数据流全景 + +``` + data/nutrition/raw/ + ├── 体重标准.md ─────────────┐ + ├── 膳食指南.md ─────────────┤ + └── DRIs.md ─────────────────┤ + │ + ┌───────────────────────▼──────────────────────┐ + │ data/presets/ │ + │ (缓存编译后的结构化数据) │ + │ 体重标准.json 膳食指南.json DRIs.json │ + └──────────────┬────────────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────────────┐ + │ loop.py │ + │ ┌────────────────────────────────────────────────┐ │ + │ │ 状态机驱动 (LAUNCH→SUMMARIZE→ANALYZE→BRIEF) │ │ + │ │ ↓ 依次调用 Plugin │ │ + │ │ diet_summary → weight_check → dri_analysis │ │ + │ │ ↓ │ │ + │ │ briefing_generator │ │ + │ │ ↓ │ │ + │ │ data/presets/今日简报.json │ │ + │ └────────────────────────────────────────────────┘ │ + └──────────────────────┬───────────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────────────┐ + │ ui/app_builder.py (Gradio 界面) │ + │ ┌────────────────────────────────────────────────┐ │ + │ │ Tab1 声纹识别 │ Tab2 菜品推荐 │ Tab3 饮食记录 │ │ + │ │ Tab4 营养报告 │ Tab5 关于 │ │ + │ │ │ │ + │ │ 今日简报注入对话上下文 │ │ + │ │ AI 知道:今天该关注什么营养素 │ │ + │ │ 该问谁体重?该聊什么话题? │ │ + │ └────────────────────────────────────────────────┘ │ + └──────────────────────┬───────────────────────────────┘ + │ + ▼ + modules/diet_logger.py (记录新数据) + modules/meal_recommender.py (膳食指南约束) + modules/nutrition_analyzer.py (DRIs 对比) + │ + ▼ + 次日 loop.py 再次启动 → 循环 +``` + +> **批注:** ________ + +--- + +## 七、技术栈一览 + +| 层级 | 技术 | 用途 | +|------|------|------| +| 核心模型 | **MiniCPM-o 4.5**(统一多模态模型) | 全双工推理:视觉+语音+文本+语音输出 | +| 前端框架 | **Gradio 6.x** | Web 界面,实时对话交互 | +| 声纹识别 | Whisper-medium encoder embedding(优先)| 说话人身份识别 | +| 频谱方案 | NumPy + SoundFile(备选 fallback) | 声纹识别兜底 | +| 数据存储 | JSON + Markdown | 饮食记录 + 报告存档 + 每日简报 | +| 营养标准 | 三个 MD 文件(已下载) | 体重标准/膳食指南/DRIs | +| 循环引擎 | 自研状态机 + 插件注册 | 每日生命周期管理 | +| 部署平台 | HuggingFace Spaces(目前)+ Modal(后续) | 云端部署 | + +> **批注:** ________ + +--- + +## 八、待讨论问题清单 + +> 📝 下表汇总了本文档中需要你决策的问题。请在 `____` 处标注你的意见(如需更多讨论,在对应小节批注区补充)。 + +| # | 问题 | 你的选择 / 意见 | +|---|------|----------------| +| 1 | **Loop 启动时机**:Gradio 启动时先跑完 Loop 总结阶段再开服务(A)/ 用户打开页面时才触发(B) | ________ | +| 2 | **简报承载形式**:JSON 文件落盘作为中介(A)/ 仅内存变量不落盘(B) | ________ | +| 3 | **Loop 复杂度边界**:只需管好每日生命周期 3~5 个阶段,约 200~300 行(同意)/ 需要更复杂(请说明) | ________ | +| 4 | **插件优先级**:DietSummary P0 / WeightCheck P0 / DRIAnalysis P0 / BriefingGenerator P0 / PresetWriter P1(需要调整?) | ________ | +| 5 | **体重数据来源**:用户手动输入(A)/ 语音对话 AI 询问记录(B)/ 蓝牙同步未来扩展(C) | ________ | +| 6 | **core/ 层处理**:现阶段 Loop 直接调用 modules/ 规则引擎,core/ 保持空接口等 MiniCPM-o(同意?) | ________ | +| 7 | **还有什么我没考虑到的问题?** | ________ | + +--- + +*文档生成时间:2026-06-08 | v4 - 引入 Loop 驱动架构 + 三文档操作方案* diff --git a/data/diet_logs.json b/data/diet_logs.json new file mode 100644 index 0000000000000000000000000000000000000000..ff18a367700e6700071470d27cee3c67e04e629a --- /dev/null +++ b/data/diet_logs.json @@ -0,0 +1,53 @@ +{ + "schema_version": "1.0", + "records": [ + { + "id": "rec001", + "member_name": "小红", + "member_relation": "孕妇", + "date": "2026-06-07", + "meals": { + "早餐": "全麦面包+鸡蛋+牛奶", + "午餐": "番茄牛腩+米饭", + "晚餐": "鲫鱼豆腐汤+小馒头", + "加餐": "苹果+酸奶" + }, + "mood": "心情不错", + "notes": "今天胃口很好", + "extensions": {}, + "created_at": "2026-06-07T20:00:00" + }, + { + "id": "rec002", + "member_name": "小红", + "member_relation": "孕妇", + "date": "2026-06-08", + "meals": { + "早餐": "小米粥+包子", + "午餐": "清炒西兰花+糙米饭", + "晚餐": "番茄炒蛋+米饭", + "加餐": "坚果+橙子" + }, + "mood": "有点孕吐", + "notes": "晚上有点不舒服", + "extensions": {}, + "created_at": "2026-06-08T20:00:00" + }, + { + "id": "rec003", + "member_name": "小红", + "member_relation": "孕妇", + "date": "2026-06-09", + "meals": { + "早餐": "燕麦粥+鸡蛋", + "午餐": "清蒸鲈鱼+杂粮饭", + "晚餐": "红枣枸杞鸡汤", + "加餐": "猕猴桃+牛奶" + }, + "mood": "挺好", + "notes": "想吃酸的", + "extensions": {}, + "created_at": "2026-06-09T12:00:00" + } + ] +} diff --git a/data/family/memory.md b/data/family/memory.md new file mode 100644 index 0000000000000000000000000000000000000000..dfb1c8235e1f23adb73135670524e2d0a0bbaef7 --- /dev/null +++ b/data/family/memory.md @@ -0,0 +1,21 @@ +# 家庭记忆 + +> 记录家庭关系、重要事件、日常点滴。 + +## 家庭关系 + +- **小红**: 孕妇,孕中期 +- **小明**: 丈夫,负责做饭 +- **张阿姨**: 婆婆,帮忙照顾 + +## 重要事件 + +- **2026-06-07**: 丈夫做了番茄牛腩,孕妇说很好吃 +- **2026-06-08**: 婆婆煲了鲫鱼豆腐汤,孕妇喝了两碗 +- **2026-06-09**: 孕妇说想吃清蒸鲈鱼 + +## 日常记录 + +- **2026-06-07**: 孕妇今天心情不错,吃了很多水果 +- **2026-06-08**: 孕妇有点孕吐,但坚持吃了晚饭 +- **2026-06-09**: 孕妇说最近想吃酸的 diff --git a/data/family/preferences.md b/data/family/preferences.md new file mode 100644 index 0000000000000000000000000000000000000000..1f21a8ff9ec8b347b66415393c6290e8d70baa42 --- /dev/null +++ b/data/family/preferences.md @@ -0,0 +1,23 @@ +# 家庭成员饮食偏好 + +> 记录所有家人的饮食偏好、忌口、过敏信息。 + +## 成员列表 + +### 成员:小红(孕妇) +- **偏好**: 爱吃水果、爱吃鱼、爱吃清淡 +- **忌口**: 不吃辣、不吃油腻 +- **过敏**: 芒果 +- **备注**: 孕中期,需要补钙 + +### 成员:小明(丈夫) +- **偏好**: 爱吃肉、爱吃面食 +- **忌口**: 不吃香菜 +- **过敏**: +- **备注**: + +### 成员:张阿姨(婆婆) +- **偏好**: 会做各种汤 +- **忌口**: +- **过敏**: +- **备注**: 擅长煲汤 diff --git a/data/family/recipes.md b/data/family/recipes.md new file mode 100644 index 0000000000000000000000000000000000000000..da143b676e5560df178acd5b64480e13289d3e41 --- /dev/null +++ b/data/family/recipes.md @@ -0,0 +1,35 @@ +# 家庭菜谱 + +> 记录家人会做的菜。 + +## 菜谱列表 + +### 番茄牛腩 +- **制作人**: 丈夫 +- **难度**: 中等 +- **食材**: 番茄, 牛腩, 土豆, 胡萝卜 +- **备注**: 孕妇爱吃 + +### 清蒸鲈鱼 +- **制作人**: 丈夫 +- **难度**: 简单 +- **食材**: 鲈鱼, 葱, 姜, 蒸鱼豉油 +- **备注**: 清淡健康 + +### 鲫鱼豆腐汤 +- **制作人**: 婆婆 +- **难度**: 简单 +- **食材**: 鲫鱼, 豆腐, 姜, 葱 +- **备注**: 下奶 + +### 番茄炒蛋 +- **制作人**: 丈夫 +- **难度**: 简单 +- **食材**: 番茄, 鸡蛋, 葱 +- **备注**: + +### 红枣枸杞鸡汤 +- **制作人**: 妈妈 +- **难度**: 中等 +- **食材**: 鸡, 红枣, 枸杞, 姜 +- **备注**: 补气血 diff --git "a/data/nutrition/raw/\344\270\255\345\233\275\345\255\225\346\234\237\345\246\207\345\245\263\350\206\263\351\243\237\346\214\207\345\215\2272022\345\233\276\347\211\207\350\275\254md\347\211\210.md" "b/data/nutrition/raw/\344\270\255\345\233\275\345\255\225\346\234\237\345\246\207\345\245\263\350\206\263\351\243\237\346\214\207\345\215\2272022\345\233\276\347\211\207\350\275\254md\347\211\210.md" index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6b3646b336155970fd04457e96833086b2ced2fa 100644 --- "a/data/nutrition/raw/\344\270\255\345\233\275\345\255\225\346\234\237\345\246\207\345\245\263\350\206\263\351\243\237\346\214\207\345\215\2272022\345\233\276\347\211\207\350\275\254md\347\211\210.md" +++ "b/data/nutrition/raw/\344\270\255\345\233\275\345\255\225\346\234\237\345\246\207\345\245\263\350\206\263\351\243\237\346\214\207\345\215\2272022\345\233\276\347\211\207\350\275\254md\347\211\210.md" @@ -0,0 +1,124 @@ +# 中国备孕妇女平衡膳食宝塔 + +## 备孕核心建议 +- 叶酸补充剂0.4毫克/天 +- 贫血者在医生指导下补充铁剂 +- 每天30分钟以上中等强度运动 +- 监测体重,调整体重至适宜范围 +- 愉悦心情,充足睡眠 +- 饮洁净水,少喝含糖饮料 +- 不吸烟,远离二手烟 +- 不饮酒 + +--- + +## 食物推荐量 +1. **加碘食盐**:5克 +2. **油**:25克 +3. **奶类**:300克 +4. **大豆/坚果**:15克 / 10克 +5. **肉食蛋鱼类**:130-180克 + - 畜禽肉:40-65克 + - 鱼虾类:40-65克 + - 蛋类:50克(每天一次,约1个鸡蛋) +6. **蔬菜类**:300-500克(可每天至少一次深绿色蔬菜) +7. **水果类**:200-300克 +8. **谷类**:200-250克 + - ——全谷物和杂豆:75-130克 +9. **薯类**:50克 +10. **水**:1500-1700毫升 + + +--- + + +# 中国孕期妇女平衡膳食宝塔 + +## 左侧通用建议 +- 叶酸补充剂0.4毫克/天 +- 贫血严重者在医生指导下补充铁剂 +- 适度运动,经常户外活动 +- 每周测量体重,维持孕期适宜增重 +- 愉悦心情,充足睡眠 +- 饮洁净水,少喝含糖饮料 +- 准备母乳喂养 +- 不吸烟,远离二手烟 +- 不饮酒 + +--- + +## 右侧食物推荐量 +| 食物类别 | 孕中期 | 孕晚期 | +| :--- | :--- | :--- | +| 加碘食盐 | 5克 | 5克 | +| 油 | 25克 | 25克 | +| 奶类 | 300-500克 | 300-500克 | +| 大豆/坚果 | 20克/10克 | 20克/10克 | +| 鱼禽蛋肉类 | 150-200克 | 175-225克 | +| - 畜禽肉类 | 50-75克 | 50-75克 | +| - 鱼虾类 | 50-75克(每周2-3次,其中1次为深海鱼) | 75-100克 | +| - 蛋类 | 50克 | 50克 | +| 蔬菜类 | 400-500克(每天至少一次深绿色蔬菜) | 400-500克 | +| 水果类 | 200-300克 | 200-350克 | +| 谷类 | 200-250克 | 225-275克 | +| - 全谷物和杂豆 | 75-100克 | 75-125克 | +| 薯类 | 75克 | 75克 | +| 水 | 1700毫升 | 1700毫升 | + + +1. **调整孕前体重至正常范围,保证孕期体重适宜增长** +2. **常吃含铁丰富的食物,选用碘盐,合理补充叶酸和维生素D** +3. **孕吐严重者,可少量多餐,保证摄入含必需量碳水化合物的食物** +4. **孕中晚期适量增加奶、鱼、禽、蛋、瘦肉的摄入** +5. **经常户外活动,禁烟酒,保持健康生活方式** +6. **愉快孕育新生命,积极准备母乳喂养** + +--- + + + +# 中国哺乳期妇女平衡膳食宝塔 + +--- + +## 左侧生活建议 +- 坚持哺乳 +- 适当增加鱼禽肉蛋和海产品 +- 愉悦心情,充足睡眠 +- 足量饮水,适当多喝粥、汤 +- 适度运动 +- 每周测量体重,逐步恢复适宜体重 +- 不吸烟,远离二手烟 +- 不饮酒 + +--- + +## 右侧食物推荐量 +| 食物类别 | 每日推荐摄入量 | 补充说明 | +| :--- | :--- | :--- | +| 加碘食盐 | 5克 | - | +| 油 | 25克 | - | +| 奶类 | 300-500克 | - | +| 大豆/坚果 | 25克/10克 | - | +| 鱼禽蛋肉类 | 175-225克 | - | +| - 畜禽肉类 | 50-75克 | - | +| - 鱼虾类 | 75-100克 | 每周1-2次食物中包含50g以上的鱼类或贝类 | +| - 蛋类 | 50克 | - | +| 蔬菜类 | 400-500克 | 每天至少一次深绿色蔬菜 | +| 水果类 | 200-350克 | - | +| 谷类 | 225-275克 | - | +| - 全谷物和杂豆 | 75-125克 | - | +| 薯类 | 75克 | - | +| 水 | 2100毫升 | - | + +- 注:月子膳食亦适用 + + +1. **产褥期食物多样不过量,坚持整个哺乳期营养均衡** +2. **适量增加富含优质蛋白质及维生素A的动物性食物和海产品,选用碘盐,合理补充维生素D** +3. **家庭支持,愉悦心情,充足睡眠,坚持母乳喂养** +4. **增加身体活动,促进产后恢复健康体重** +5. **多喝汤和水,限制浓茶和咖啡,忌烟酒** + +--- + diff --git "a/data/nutrition/raw/\345\244\207\345\255\225&\345\255\225\346\227\251\344\270\255\346\231\232\344\270\211\346\234\237\345\222\214\345\223\272\344\271\263\346\234\237\347\232\204\346\211\200\346\234\211\350\206\263\351\243\237\350\220\245\345\205\273\347\264\240\345\217\202\350\200\203\346\221\204\345\205\245\351\207\217.md" "b/data/nutrition/raw/\345\244\207\345\255\225&\345\255\225\346\227\251\344\270\255\346\231\232\344\270\211\346\234\237\345\222\214\345\223\272\344\271\263\346\234\237\347\232\204\346\211\200\346\234\211\350\206\263\351\243\237\350\220\245\345\205\273\347\264\240\345\217\202\350\200\203\346\221\204\345\205\245\351\207\217.md" new file mode 100644 index 0000000000000000000000000000000000000000..08fe34541c8f2cafc2ab65c90d9b86438723af46 --- /dev/null +++ "b/data/nutrition/raw/\345\244\207\345\255\225&\345\255\225\346\227\251\344\270\255\346\231\232\344\270\211\346\234\237\345\222\214\345\223\272\344\271\263\346\234\237\347\232\204\346\211\200\346\234\211\350\206\263\351\243\237\350\220\245\345\205\273\347\264\240\345\217\202\350\200\203\346\221\204\345\205\245\351\207\217.md" @@ -0,0 +1,291 @@ +下面将**四张表格全部拆分为「能量与宏量营养素、矿物质、维生素」三张独立标准 Markdown 表**,语法通用、无超宽合并列,主流编辑器/文档工具均可正常渲染,可直接全选复制使用。 + +# 表15 中国18-49岁孕期女性(孕早期)膳食营养素参考摄入量 +### 缩略词说明 +- EAR:平均需要量 +- RNI:推荐摄入量 +- AI:适宜摄入量 +- PI:预防非传染性慢性病建议摄入量 +- UL:可耐受最高摄入量 +- AMDR:宏量营养素可接受范围 +- PAL:身体活动水平 + +### 一、能量与宏量营养素 +| 能量/营养素 | 单位 | EAR | RNI | AMDR | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| 能量 PAL(I) | MJ/d | - | 7.11 | - | - | - | +| 能量 PAL(II) | MJ/d | - | 8.79 | - | - | - | +| 能量 PAL(III) | MJ/d | - | 10.25 | - | - | - | +| 蛋白质 | g/d | 55 | - | 10~20% | - | - | +| 总碳水化合物 | %E | 130# | - | 50~65% | - | - | +| — 添加糖 | %E | - | - | <10 | - | - | +| 膳食纤维 | g/d | - | - | 25~30(AI) | - | - | +| 总脂肪 | %E | - | - | 20~30 | - | - | +| — 饱和脂肪酸 | %E | - | - | <10 | - | - | +| — n-6多不饱和脂肪酸 | %E | - | - | 2.5~9 | - | - | +| — 亚油酸 | %E | - | - | 4.0(AI) | - | - | +| — n-3多不饱和脂肪酸 | %E | - | - | 0.5~2.0 | - | - | +| — α-亚麻酸 | %E | - | - | 0.60(AI) | - | - | +| — DHA | %E | - | - | 0.25(0.2%E)(AI) | - | - | +| 水 | mL/d | - | 1500/2700 | - | - | - | + +### 二、矿物质 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 钙 | mg/d | 650 | 800 | - | 2000 | +| 磷 | mg/d | 600 | 720 | - | 3500 | +| 钾 | mg/d | - | 2000(AI) | 3600 | - | +| 钠 | mg/d | - | 1500(AI) | ≤2000 | - | +| 镁 | mg/d | 300 | 370 | - | - | +| 氯 | mg/d | - | 2300(AI) | - | - | +| 铁 | mg/d | 12 | 18 | - | 42 | +| 碘 | μg/d | 120 | 230 | - | 500 | +| 锌 | mg/d | 8.6 | 10.5 | - | 40 | +| 硒 | μg/d | 54 | 65 | - | 400 | +| 铜 | mg/d | 0.72 | 0.91 | - | 8.0 | +| 氟 | mg/d | - | 1.5(AI) | - | 3.5 | +| 铬 | μg/d | - | 30(AI) | - | - | +| 锰 | mg/d | - | 4.0(AI) | - | 11 | +| 钼 | μg/d | 20 | 25 | - | 900 | + +### 三、维生素 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 维生素A | μgRAE/d | 470 | 660 | - | 3000 | +| 维生素D | μg/d | 8 | 10 | - | 50 | +| 维生素E | mgα-TE/d | - | 14(AI) | - | 700 | +| 维生素K | μg/d | - | 80(AI) | - | - | +| 维生素B₁ | mg/d | 1.0 | 1.2 | - | - | +| 维生素B₂ | mg/d | 1.0 | 1.2 | - | - | +| 烟酸 | mg NE/d | 10 | 12 | - | 35/310# | +| 维生素B₆ | mg/d | 1.9 | 2.2 | - | 60 | +| 叶酸 | μg DFE/d | 520 | 600 | - | 1000# | +| 维生素B₁₂ | μg/d | 2.4 | 2.9 | - | - | +| 泛酸 | mg/d | - | 6.0(AI) | - | - | +| 生物素 | μg/d | - | 50(AI) | - | - | +| 胆碱 | mg/d | - | 460(AI) | - | 3000 | +| 维生素C | mg/d | 85 | 100 | 200 | 2000 | + +### 备注 +- PAL:身体活动水平(I:低强度;II:中等强度;III:高强度) +- 未制定参考值用“—”表示;#标注项为特殊限定数值 +- 水总摄入量包含饮用水及食物中的水分 + +--- + +# 表16 中国18-49岁孕期女性(孕中期)膳食营养素参考摄入量 +### 缩略词说明 +- EAR:平均需要量 +- RNI:推荐摄入量 +- AI:适宜摄入量 +- PI:预防非传染性慢性病建议摄入量 +- UL:可耐受最高摄入量 +- AMDR:宏量营养素可接受范围 +- PAL:身体活动水平 + +### 一、能量与宏量营养素 +| 能量/营养素 | 单位 | EAR | RNI | AMDR | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| 能量 PAL(I) | MJ/d | - | 8.16 | - | - | - | +| 能量 PAL(II) | MJ/d | - | 9.84 | - | - | - | +| 能量 PAL(III) | MJ/d | - | 11.30 | - | - | - | +| 蛋白质 | g/d | 60 | 70 | 10~20% | - | - | +| 总碳水化合物 | %E | 140 | - | 50~65% | - | - | +| — 添加糖 | %E | - | - | <10 | - | - | +| 膳食纤维 | g/d | - | - | 29~34(AI) | - | - | +| 总脂肪 | %E | - | - | 20~30 | - | - | +| — 饱和脂肪酸 | %E | - | - | <10 | - | - | +| — n-6多不饱和脂肪酸 | %E | - | - | 2.5~9 | - | - | +| — 亚油酸 | %E | - | - | 4.0(AI) | - | - | +| — n-3多不饱和脂肪酸 | %E | - | - | 0.5~2.0 | - | - | +| — α-亚麻酸 | %E | - | - | 0.60(AI) | - | - | +| — DHA+EPA | g/d | - | - | 0.25(0.2%E)(AI) | - | - | +| 水 | mL/d | - | 1700/3000 | - | - | - | + +### 二、矿物质 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 钙 | mg/d | 650 | 800 | - | 2000 | +| 磷 | mg/d | 600 | 720 | - | 3500 | +| 钾 | mg/d | - | 2000(AI) | 3600 | - | +| 钠 | mg/d | - | 1500(AI) | ≤2000 | - | +| 镁 | mg/d | 300 | 370 | - | - | +| 氯 | mg/d | - | 2300(AI) | - | - | +| 铁 | mg/d | 19 | 25 | - | 42 | +| 碘 | μg/d | 160 | 230 | - | 500 | +| 锌 | mg/d | 8.6 | 10.5 | - | 40 | +| 硒 | μg/d | 54 | 65 | - | 400 | +| 铜 | mg/d | 0.72 | 0.9 | - | 8.0 | +| 氟 | mg/d | - | 1.5(AI) | - | 3.5 | +| 铬 | μg/d | - | 33(AI) | - | - | +| 锰 | mg/d | - | 4.0(AI) | - | 11 | +| 钼 | μg/d | 20 | 25 | - | 900 | + +### 三、维生素 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 维生素A | μgRAE/d | 520 | 730 | - | 3000 | +| 维生素D | μg/d | 8 | 10 | - | 50 | +| 维生素E | mgα-TE/d | - | 14(AI) | - | 700 | +| 维生素K | μg/d | - | 80(AI) | - | - | +| 维生素B₁ | mg/d | 1.1 | 1.4 | - | - | +| 维生素B₂ | mg/d | 1.1 | 1.3 | - | - | +| 烟酸 | mg NE/d | 10 | 12 | - | 35/310# | +| 维生素B₆ | mg/d | 1.7 | 2.2 | - | 60 | +| 叶酸 | μg DFE/d | 520 | 600 | - | 1000# | +| 维生素B₁₂ | μg/d | 2.4 | 2.9 | - | - | +| 泛酸 | mg/d | - | 6.0(AI) | - | - | +| 生物素 | μg/d | - | 50(AI) | - | - | +| 胆碱 | mg/d | - | 460(AI) | - | 3000 | +| 维生素C | mg/d | 95 | 115 | 200 | 2000 | + +### 备注 +- PAL:身体活动水平(I:低强度;II:中等强度;III:高强度) +- 未制定参考值用“—”表示;#标注项为特殊限定数值 +- 水总摄入量包含饮用水及食物中的水分 + +--- + +# 表17 中国18-49岁孕期女性(孕晚期)膳食营养素参考摄入量 +### 缩略词说明 +- EAR:平均需要量 +- RNI:推荐摄入量 +- AI:适宜摄入量 +- PI:预防非传染性慢性病建议摄入量 +- UL:可耐受最高摄入量 +- AMDR:宏量营养素可接受范围 +- PAL:身体活动水平 + +### 一、能量与宏量营养素 +| 能量/营养素 | 单位 | EAR | RNI | AMDR | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| 能量 PAL(I) | MJ/d | - | 8.78 | - | - | - | +| 能量 PAL(II) | MJ/d | - | 10.46 | - | - | - | +| 能量 PAL(III) | MJ/d | - | 11.92 | - | - | - | +| 蛋白质 | g/d | 75 | 85 | 10~20% | - | - | +| 总碳水化合物 | %E | 155 | - | 50~65% | - | - | +| — 添加糖 | %E | - | - | <10 | - | - | +| 膳食纤维 | g/d | - | - | 29~34(AI) | - | - | +| 总脂肪 | %E | - | - | 20~30 | - | - | +| — 饱和脂肪酸 | %E | - | - | <10 | - | - | +| — n-6多不饱和脂肪酸 | %E | - | - | 2.5~9 | - | - | +| — 亚油酸 | %E | - | - | 4.0(AI) | - | - | +| — n-3多不饱和脂肪酸 | %E | - | - | 0.5~2.0 | - | - | +| — α-亚麻酸 | %E | - | - | 0.60(AI) | - | - | +| — DHA+EPA | g/d | - | - | 0.25(0.2%E)(AI) | - | - | +| 水 | mL/d | - | 1700/3000 | - | - | - | + +### 二、矿物质 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 钙 | mg/d | 650 | 800 | - | 2000 | +| 磷 | mg/d | 600 | 720 | - | 3500 | +| 钾 | mg/d | - | 2000(AI) | 3600 | - | +| 钠 | mg/d | - | 1500(AI) | ≤2000 | - | +| 镁 | mg/d | 300 | 370 | - | - | +| 氯 | mg/d | - | 2300(AI) | - | - | +| 铁 | mg/d | 22 | 29 | - | 42 | +| 碘 | μg/d | 160 | 230 | - | 500 | +| 锌 | mg/d | 8.6 | 10.5 | - | 40 | +| 硒 | μg/d | 54 | 65 | - | 400 | +| 铜 | mg/d | 0.72 | 0.9 | - | 8.0 | +| 氟 | mg/d | - | 1.5(AI) | - | 3.5 | +| 铬 | μg/d | - | 35(AI) | - | - | +| 锰 | mg/d | - | 4.0(AI) | - | 11 | +| 钼 | μg/d | 20 | 25 | - | 900 | + +### 三、维生素 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 维生素A | μgRAE/d | 520 | 730 | - | 3000 | +| 维生素D | μg/d | 8 | 10 | - | 50 | +| 维生素E | mgα-TE/d | - | 14(AI) | - | 700 | +| 维生素K | μg/d | - | 80(AI) | - | - | +| 维生素B₁ | mg/d | 1.2 | 1.4 | - | - | +| 维生素B₂ | mg/d | 1.2 | 1.5 | - | - | +| 烟酸 | mg NE/d | 10 | 12 | - | 35/310# | +| 维生素B₆ | mg/d | 1.9 | 2.2 | - | 60 | +| 叶酸 | μg DFE/d | 520 | 600 | - | 1000# | +| 维生素B₁₂ | μg/d | 2.4 | 2.9 | - | - | +| 泛酸 | mg/d | - | 6.0(AI) | - | - | +| 生物素 | μg/d | - | 50(AI) | - | - | +| 胆碱 | mg/d | - | 460(AI) | - | 3000 | +| 维生素C | mg/d | 95 | 115 | 200 | 2000 | + +### 备注 +- PAL:身体活动水平(I:低强度;II:中等强度;III:高强度) +- 未制定参考值用“—”表示;#标注项为特殊限定数值 +- 水总摄入量包含饮用水及食物中的水分 + +--- + +# 表18 中国18-49岁哺乳期女性膳食营养素参考摄入量 +### 缩略词说明 +- EAR:平均需要量 +- RNI:推荐摄入量 +- AI:适宜摄入量 +- PI:预防非传染性慢性病建议摄入量 +- UL:可耐受最高摄入量 +- AMDR:宏量营养素可接受范围 +- PAL:身体活动水平 + +### 一、能量与宏量营养素 +| 能量/营养素 | 单位 | EAR | RNI | AMDR | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| 能量 PAL(I) | MJ/d | - | 8.78 | - | - | - | +| 能量 PAL(II) | MJ/d | - | 10.46 | - | - | - | +| 能量 PAL(III) | MJ/d | - | 11.92 | - | - | - | +| 蛋白质 | g/d | 70 | 80 | 10~20% | - | - | +| 总碳水化合物 | %E | 170 | - | 50~65% | - | - | +| — 添加糖 | %E | - | - | <10 | - | - | +| 膳食纤维 | g/d | - | - | 29~34(AI) | - | - | +| 总脂肪 | %E | - | - | 20~30 | - | - | +| — 饱和脂肪酸 | %E | - | - | <10 | - | - | +| — n-6多不饱和脂肪酸 | %E | - | - | 2.5~9 | - | - | +| — 亚油酸 | %E | - | - | 4.0(AI) | - | - | +| — n-3多不饱和脂肪酸 | %E | - | - | 0.5~2.0 | - | - | +| — α-亚麻酸 | %E | - | - | 0.60(AI) | - | - | +| — DHA+EPA | g/d | - | - | 250(200)(AI) | - | - | +| 水 | mL/d | - | 2100/3800 | - | - | - | + +### 二、矿物质 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 钙 | mg/d | 650 | 800 | - | 2000 | +| 磷 | mg/d | 600 | 720 | - | 3500 | +| 钾 | mg/d | - | 2400(AI) | 3600 | - | +| 钠 | mg/d | - | 1500(AI) | ≤2000 | - | +| 镁 | mg/d | 270 | 330 | - | - | +| 氯 | mg/d | - | 2300(AI) | - | - | +| 铁 | mg/d | 18 | 24 | - | 42 | +| 碘 | μg/d | 170 | 240 | - | 500 | +| 锌 | mg/d | 11 | 13.0 | - | 40 | +| 硒 | μg/d | 65 | 78 | - | 400 | +| 铜 | mg/d | 1.12 | 1.5 | - | 8.0 | +| 氟 | mg/d | - | 1.5(AI) | - | 3.5 | +| 铬 | μg/d | - | 35(AI) | - | - | +| 锰 | mg/d | - | 4.2(AI) | - | 11 | +| 钼 | μg/d | 24 | 30 | - | 900 | + +### 三、维生素 +| 营养素 | 单位 | EAR | RNI | PI | UL | +| ---- | ---- | ---- | ---- | ---- | ---- | +| 维生素A | μgRAE/d | 870 | 1260 | - | 3000 | +| 维生素D | μg/d | 8 | 10 | - | 50 | +| 维生素E | mgα-TE/d | - | 14(AI) | - | 700 | +| 维生素K | μg/d | - | 85(AI) | - | - | +| 维生素B₁ | mg/d | 1.2 | 1.5 | - | - | +| 维生素B₂ | mg/d | 1.4 | 1.7 | - | - | +| 烟酸 | mg NE/d | 13 | 16 | - | 35/310# | +| 维生素B₆ | mg/d | 1.4 | 1.7 | - | 60 | +| 叶酸 | μg DFE/d | 450 | 550 | - | 1000# | +| 维生素B₁₂ | μg/d | 2.6 | 3.2 | - | - | +| 泛酸 | mg/d | - | 7.0(AI) | - | - | +| 生物素 | μg/d | - | 50(AI) | - | - | +| 胆碱 | mg/d | - | 500(AI) | - | 3000 | +| 维生素C | mg/d | 125 | 150 | 200 | 2000 | + +### 备注 +- PAL:身体活动水平(I:低强度;II:中等强度;III:高强度) +- 未制定参考值用“—”表示;#标注项为特殊限定数值 +- 水总摄入量包含饮用水及食物中的水分 \ No newline at end of file diff --git "a/data/nutrition/raw/\345\246\212\345\250\240\346\234\237\345\246\207\345\245\263\344\275\223\351\207\215\345\242\236\351\225\277\346\216\250\350\215\220\345\200\274\346\240\207\345\207\206.md" "b/data/nutrition/raw/\345\246\212\345\250\240\346\234\237\345\246\207\345\245\263\344\275\223\351\207\215\345\242\236\351\225\277\346\216\250\350\215\220\345\200\274\346\240\207\345\207\206.md" new file mode 100644 index 0000000000000000000000000000000000000000..6eb581a767eaa49e429366a5eb43ab5885cd2842 --- /dev/null +++ "b/data/nutrition/raw/\345\246\212\345\250\240\346\234\237\345\246\207\345\245\263\344\275\223\351\207\215\345\242\236\351\225\277\346\216\250\350\215\220\345\200\274\346\240\207\345\207\206.md" @@ -0,0 +1,92 @@ +# WS/T 801-2022 妊娠期妇女体重增长推荐值标准 +*Standard of recommendation for weight gain during pregnancy period* + +--- + +## 1 范围 +本标准规定了我国妇女单胎自然妊娠体重增长推荐值。 +本标准适用于对我国妇女单胎自然妊娠体重增长的指导。 +本标准不适用于身高低于140cm,或妊娠前体重高于125kg的妇女。妊娠期合并症和并发症患者应结合临床意见进行个体化评价。 + +## 2 规范性引用文件 +本标准没有规范性引用文件。 + +## 3 术语和定义 +下列术语和定义适用于本标准。 + +### 3.1 妊娠期 pregnancy period +从末次月经的第一日开始计算,约为280日(40周)。 +注:临床上将妊娠全程分为妊娠早期、妊娠中期、妊娠晚期3个时期。 + +### 3.2 妊娠早期 first trimester +妊娠未达14周。 + +### 3.3 妊娠中期 second trimester +妊娠第14周~27+6周。 + +### 3.4 妊娠晚期 third trimester +妊娠第28周及其后。 + +### 3.5 体质指数 body mass index; BMI +一种计算身高别体重的指数,计算方法是体重(kg)与身高(m)的平方的比值。 + +### 3.6 妊娠前体重 pre-pregnancy weight +妊娠之前三个月内的平均体重。 + +### 3.7 分娩前体重 pre-delivery weight +分娩前一周内最后一次称量的体重。 + +### 3.8 妊娠期体重总增长值 gestational weight gain; GWG +分娩前体重(kg)减去妊娠前体重(kg)所得数值。 + +### 3.9 妊娠早期体重增长值 gestational weight gain in first trimester +妊娠13周末体重(kg)减去妊娠前体重(kg)所得数值。 + +### 3.10 妊娠中期体重增长值 gestational weight gain in second trimester +妊娠27周末体重(kg)减去妊娠13周末体重(kg)所得数值。 + +### 3.11 妊娠晚期体重增长值 gestational weight gain in third trimester +分娩前体重(kg)减去妊娠27周末体重(kg)所得数值。 + +## 4 妊娠期妇女体重增长范围及妊娠中期和妊娠晚期每周体重增长推荐值 +按中国成人体质指数切点,分别给予在不同妊娠前体质指数情况下,单胎妊娠妇女体重增长范围及妊娠中期和妊娠晚期每周体重增长推荐值,见表1。 + +**表1 妊娠期妇女体重增长范围及妊娠中期和妊娠晚期每周体重增长推荐值** +| 妊娠前体质指数分类 | 总增长值范围(kg) | 妊娠早期增长值范围(kg) | 妊娠中期和妊娠晚期每周体重增长值及范围(kg/week) | +| ---- | ---- | ---- | ---- | +| 低体重(BMI < 18.5 kg/m²) | 11.0 ~ 16.0 | 0 ~ 2.0 | 0.46(0.37 ~ 0.56) | +| 正常体重(18.5 kg/m² ≤ BMI < 24.0 kg/m²) | 8.0 ~ 14.0 | 0 ~ 2.0 | 0.37(0.26 ~ 0.48) | +| 超重(24.0 kg/m² ≤ BMI < 28.0 kg/m²) | 7.0 ~ 11.0 | 0 ~ 2.0 | 0.30(0.22 ~ 0.37) | +| 肥胖(BMI ≥ 28.0 kg/m²) | 5.0 ~ 9.0 | 0 ~ 2.0 | 0.22(0.15 ~ 0.30) | + +## 5 测量方法 +妊娠期妇女身高、体重的测量方法参见附录A。 + +--- + +## 附录A(资料性)妊娠期妇女身高、体重的测量方法 +### A.1 身高测量 +#### A.1.1 测量条件 +测量时妊娠妇女应免冠、赤足,解开发髻,室温25℃左右。 + +#### A.1.2 测量工具 +立柱式身高计,分度值0.1cm,有抵墙装置。滑测板应与立柱垂直,滑动自如。 + +#### A.1.3 测量方法 +妊娠妇女取立正姿势,站在踏板上,挺胸收腹,两臂自然下垂,脚跟靠拢,脚尖分开约60度。双膝并拢挺直,两眼平视正前方,眼眶下缘与耳廓上缘保持在同一水平。脚跟、臀部和两肩胛角间三个点同时接触立柱,头部保持正立位置。测量者手扶滑测板轻轻向下滑动,直到底面与头颅顶点相接触,此时观察妊娠妇女姿势正确后读取数值。 + +#### A.1.4 读数与记录 +读数时测量者的眼睛与滑测板底面在同一个水平面上,读取滑板底面对应立柱所示数值,以厘米为单位,精确到0.1cm。 + +### A.2 体重测量 +#### A.2.1 测量条件 +测量应在清晨、空腹、排泄完毕的状态下进行,室温25℃左右。 + +#### A.2.2 测量工具 +经校准的电子体重秤,分度值 ≤0.1 kg,测量时体重计放置平稳并调零。 + +#### A.2.3 测量方法 +妊娠妇女平静站立于体重秤踏板中央,两腿均匀负重,免冠、赤足、穿贴身薄内衣裤,注意安全。 + +#### A.2.4 读数与记录 +准确记录体重秤读数,精确到0.1kg。 \ No newline at end of file diff --git a/data/presets/.daily_status.json b/data/presets/.daily_status.json new file mode 100644 index 0000000000000000000000000000000000000000..4b410deccb6bcae41e11ff87c3475aee990ed7b6 --- /dev/null +++ b/data/presets/.daily_status.json @@ -0,0 +1,6 @@ +{ + "2026-06-09": { + "summary_done": true, + "completed_at": "2026-06-09T00:31:27.292878" + } +} \ No newline at end of file diff --git "a/data/presets/\344\273\212\346\227\245\347\256\200\346\212\245_2026-06-10.json" "b/data/presets/\344\273\212\346\227\245\347\256\200\346\212\245_2026-06-10.json" new file mode 100644 index 0000000000000000000000000000000000000000..1b09f5db41cf8e391337607d386ca2a035a5d0a8 --- /dev/null +++ "b/data/presets/\344\273\212\346\227\245\347\256\200\346\212\245_2026-06-10.json" @@ -0,0 +1,15 @@ +{ + "date": "2026-06-10", + "generated_from": "2026-06-09", + "generated_at": "2026-06-09T00:31:27.292878", + "trimester": "孕中期", + "nutrient_focus": [], + "recommended_foods": [], + "need_ask_weight": false, + "need_ask_recipe": false, + "conversation_topics": [ + "今天称体重了吗?" + ], + "family_memory_summary": "{'relationships': ['- **小红**: 孕妇,孕中期', '- **小明**: 丈夫,负责做饭', '- **张阿姨**: 婆婆,帮忙照顾'], 'recent_events': ['- **2026-06-07**: 丈夫做了番茄牛腩,孕妇说很好吃', '- **2026-06-08**: 婆婆煲了鲫鱼豆腐汤,孕妇喝了两碗', '- **2026-06-09**: 孕妇说想吃清蒸鲈鱼'], 'recent_daily': ['- **2026-06-07**: 孕妇今天心情不错,吃了很多水果', '- **2026-06-08**: 孕妇有点孕吐,但坚持吃了晚饭', '- **2026-06-09**: 孕妇说最近想吃酸的']}", + "three_day_summary": {} +} \ No newline at end of file diff --git a/debug2.txt b/debug2.txt new file mode 100644 index 0000000000000000000000000000000000000000..0a22f0215539f6c3daec77d1457583ac4a0e2638 --- /dev/null +++ b/debug2.txt @@ -0,0 +1,3 @@ +add: '[OK] 已添加菜谱: test' +load: [{'name': 'test', 'cook': 't', 'difficulty': '', 'ingredients': '', 'notes': ''}] +len: 1 diff --git a/debug4.txt b/debug4.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ed64b2b12140f626880785e89c717133a4e74c3 --- /dev/null +++ b/debug4.txt @@ -0,0 +1,2 @@ +result: [] +len: 0 diff --git a/debug_out.txt b/debug_out.txt new file mode 100644 index 0000000000000000000000000000000000000000..a1e7229c0a59c39db754ff56402dd73318e996c6 --- /dev/null +++ b/debug_out.txt @@ -0,0 +1,3 @@ +add: '[WARN] 菜谱已存在: test' +load: [{'name': '菜名', 'cook': '', 'difficulty': '', 'ingredients': '', 'notes': ''}, {'name': 'test', 'cook': 't', 'difficulty': '', 'ingredients': '', 'notes': ''}] +len: 2 diff --git "a/docs/\351\241\271\347\233\256\347\220\206\350\247\243_\346\212\200\346\234\257\346\236\266\346\236\204.md" "b/docs/\351\241\271\347\233\256\347\220\206\350\247\243_\346\212\200\346\234\257\346\236\266\346\236\204.md" index 9d7ff36374cd2917b6d56642cae5a0b564a7256b..022c47918752bb1422b31f36944c2019a02c6adc 100644 --- "a/docs/\351\241\271\347\233\256\347\220\206\350\247\243_\346\212\200\346\234\257\346\236\266\346\236\204.md" +++ "b/docs/\351\241\271\347\233\256\347\220\206\350\247\243_\346\212\200\346\234\257\346\236\266\346\236\204.md" @@ -1,300 +1,656 @@ -# 🌸 PregoPal 项目理解与技术架构(v3 - 根据批注修正) +# 🌸 PregoPal 项目理解与技术架构(v5 - 整合批注,细化实现) -> 本文档用于对齐对项目的整体认知。v3 已整合你所有批注。 +> 本文档已整合你所有批注意见,每个小节保留批注区供继续讨论。 --- ## 一、一句话定位 -**一个基于 MiniCPM-o 4.5 全双工多模态大模型的 AI 孕期陪护助手**:家人(丈夫/婆婆/妈妈/孕妇本人)打开摄像头和麦克风,与 AI 实时语音对话,AI 能「看见」和「听见」——通过视觉识别食物、通过声纹区分身份、通过对话理解需求,自动记录饮食、推荐食谱、分析营养并输出报告。 +**一个基于每日生命周期 Loop 驱动的孕期陪护 AI 助手**:每天首次启动时自动总结昨日饮食、对比国家营养标准(DRIs/膳食指南/体重标准),生成今日简报注入对话上下文;白天与家庭成员(通过声纹区分)自然交互,实时记录饮食、推荐食谱;次日循环往复。 -> **✅ 已确认。** +> **批注(已采纳):** 实时记录饮食 = 主动提取孕妇的饮食习惯。场景包括:①孕妇自己说想吃什么 ②家人说孕妇之前想吃什么 → 都记录到孕妇的饮食习惯 md 文件。同时主动询问家人会做什么菜 → 记录到家庭菜单。通过 system prompt + 正则化提取实现。 --- -## 二、四大模块详解 +## 二、三个营养文档的操作方法 + +你已经下载到 `data/nutrition/raw/` 的三个文件,各自承担不同角色: + +| 文档 | 文件 | 用途 | PregoPal 的操作方式 | +|------|------|------|-------------------| +| **① 体重增长标准** | `妊娠期妇女体重增长推荐值标准.md` | 体重监测基准 | **主动询问体重** — 每天 Loop 中检查是否需要询问,对照 BMI 分类给出评价 | +| **② 膳食指南** | `中国孕期妇女膳食指南2022图片转md版.md` | 食谱推荐基准 | **作为推荐食谱的约束基础** — 根据孕期阶段用宝塔推荐量生成模板 | +| **③ DRIs** | `备孕&孕早中晚三期和哺乳期的所有膳食营养素参考摄入量.md` | 营养分析基准 | **Agent 定期思考** — 每天自动对比 RNI,分析不足,写入次日预设 | + +> **批注(已采纳):** 所有 md 文件都需要我们自己写函数读取,而不是让大模型自己读。数据类的需要写全面的读取比较函数接口,文本类的需要设计好自动提取输出的函数。每日第一次对话前 PregoPal 自动整理当天的前置 system prompt 时,要考虑:营养、体重、家庭菜谱、营养素需求、核心记忆文件(家庭关系、谁和谁亲近、家里发生过什么)。 + +### 2.1 体重增长标准 → 主动询问体重 + +**实现方式**:写一个 `WeightStandardParser` 类,将 WS/T 801-2022 表1 编译为结构化数据,提供查询接口。 + +```python +# modules/nutrition_standards.py(新增) +class WeightStandardParser: + """解析体重增长标准 md 文件,提供查询接口""" + + @staticmethod + def load() -> dict: + """读取 md 文件,返回结构化数据""" + # 返回: + # { + # "bmi_standards": [ + # {"category": "低体重", "bmi_range": (0, 18.5), + # "total_gain": (11.0, 16.0), "weekly_gain": 0.46, + # "weekly_range": (0.37, 0.56)}, + # ... + # ] + # } + + @staticmethod + def evaluate(bmi_before_pregnancy: float, current_week: int, + total_gain: float) -> dict: + """评估体重增长是否在推荐范围内""" + # 返回 {"status": "ok"/"warning", "message": "...", "suggestion": "..."} +``` + +**调用时机**:每天早上 Loop 的 `weight_check` 插件调用此函数,结果写入今日简报。 -### 模块 1:🔊 声纹识别 + 多模态身份感知 +> **批注(已采纳):** 整理成函数,不要期待 32B 小参数模型去做这些,而是自动放到上下文中。 -**核心变化:从「单独录音注册」变为「对话中自然感知」** +### 2.2 膳食指南 → 食谱推荐基础 -**业务需求:** -- 用户在实时对话过程中,AI 自动识别说话人身份(丈夫/婆婆/妈妈/孕妇…) -- 无需单独「注册」步骤,在自然对话中完成声纹采集和识别 -- 结合摄像头画面辅助确认(视觉+语音多模态) +**实现方式**:写一个 `DietaryGuideParser` 类,将平衡膳食宝塔编译为结构化数据。 -**技术选型(最终方案):** -1. **主要方案:Whisper-medium encoder embedding** - - MiniCPM-o 4.5 内置了 Whisper-medium 作为语音编码器 - - 当音频经过 Whisper encoder 时,中间层隐藏状态包含了说话人的声学特征(音色/语速/口音) - - 做法:提取 encoder 输出的 hidden states,做时间维度的平均池化,得到固定维度的 **speaker embedding** - - 新音频与该 embedding 做余弦相似度比对,判断是否为同一人 - - 这是业界成熟做法,比当前 6 维统计特征鲁棒许多 +```python +# modules/nutrition_standards.py +class DietaryGuideParser: + """解析膳食指南 md 文件,提供各孕期推荐摄入量""" + + @staticmethod + def get_recommendations(trimester: str) -> dict: + """返回指定孕期的食物推荐量""" + # 返回孕中期/孕晚期/哺乳期的宝塔数据 + + @staticmethod + def get_core_principles() -> list[str]: + """返回六条核心建议""" +``` -2. **备选方案:保留当前频谱相似度方案** - - 作为 fallback 兜底 - - 也可作为置信度参考,与 embedding 方案加权融合 +**与家庭菜谱结合**: +- `data/family/recipes.md` — 记录家人会做的菜(主动询问后记录) +- `data/family/preferences.md` — 记录所有家人的饮食偏好、忌口、过敏 +- 推荐食谱时:膳食指南约束 + 家庭菜谱过滤 + 个人偏好排序 + +> **批注(已采纳):** 需要设计好如何与用户家庭菜谱结合,而不是去提出做一些不可能的菜。饮食偏好应该记录所有家人(不只是孕妇),分模块记录,设计好提取的函数接口。注意记录家里所有人是否有忌口、过敏。 + +### 2.3 DRIs → Agent 定期分析(自动运行 → 写入次日预设) + +**改进方案**: + +```python +# modules/nutrition_standards.py +class DRIsParser: + """解析 DRIs md 文件(表15-18),提供营养素参考摄入量查询""" + + @staticmethod + def get_rni(stage: str) -> dict: + """返回指定阶段的 RNI 数据 + stage: "pre_pregnancy" / "early" / "mid" / "late" / "lactation" + """ + + @staticmethod + def compare_with_intake(stage: str, intake: dict) -> dict: + """对比实际摄入与 RNI,返回差异分析""" + # 返回每个营养素的差异百分比 + 重点关注标记 +``` -**部署说明:** -- MiniCPM-o 4.5 是一个统一的多模态模型,不是多个独立组件的拼装 -- 需要用 MiniCPM-o 官方推理框架(OpenBMB 提供)部署 -- **llama.cpp 对 MiniCPM-o 的全双工多模态支持可能不完整**,后续会根据实际情况选择:A) OpenBMB 官方推理 B) Modal GPU 部署 C) llama.cpp(如果兼容) +**分析流程**: +1. 读取昨日饮食日志(`data/logs/饮食日志_YYYY-MM-DD.md`) +2. 用 `DRIsParser.compare_with_intake()` 对比 +3. 结合家庭菜谱,给出「今天建议多吃哪些家里能做的菜」 +4. 写入 `data/presets/今日简报_YYYY-MM-DD.json` -> **✅ 已根据你的批注修正:优先开发 Whisper-medium embedding 方案,频谱相似度保留为备选。** +> **批注(待改进):** 请基于其它批注改进方案。✅ 已改进:结合家庭菜谱,建议具体可做的菜。 --- -### 模块 2:🍳 智能菜品推荐(由 MiniCPM-o 4.5 驱动) +## 三、核心架构:Loop 驱动(状态机 + 插件注册) -**核心变化:从「模板随机选」变为「AI 实时对话推荐」** +### 3.1 设计理念 -**业务需求:** -- 用户和 AI 对话:「今天想吃鱼」「有点没胃口」「嘴里发苦」… -- AI 理解上下文 → 生成个性化推荐 -- 输出三餐+加餐方案 + 理由 + 注意事项 +PregoPal 不是一个「用户点按钮 → 出结果」的被动应用,而是**有每日生命周期的主动伴侣**。 -**技术选型:** -- **MiniCPM-o 4.5(统一模型)**:直接用对话能力做推荐 -- **营养数据本地嵌入**:把中国孕期营养数据整理成 JSON,在系统提示词中提供给模型 -- **不需要 RAG**(你说得对,过度设计了),只需要把营养数据放在上下文里 -- **规则兜底**:`MEAL_TEMPLATES` 保留作为离线 fallback(无模型时可用) +参考你提供的 `nanobot_core/agent/loop.py` 中的**状态机事件驱动模式**,PregoPal 的 Loop 提取两个核心设计: -**注意:** MiniCPM-o 4.5 本身是一个统一的端到端多模态模型,不是「全家桶」——SigLip2、Whisper、Qwen3、CosyVoice2 是其内部组件,不是独立安装的库。 +1. **TurnState 状态机** — 管理每日生命周期的阶段流转 +2. **可注册的扩展能力** — 每个阶段的具体逻辑由 Plugin 实现 -> **✅ 已根据你的批注修正:去掉 RAG,营养数据本地嵌入,规则兜底保留。** +> **批注(已采纳):** 具体实现不能太复杂,达到目的,具有可拓展性,具有稳定性(不会因为模型偶尔的犯错或者输出不稳定而完全逻辑混乱)。 ---- +### 3.2 每日生命周期(v5 改进版) + +根据你的批注,关键变更: +- **触发时机**:每晚 00:00 自动总结(或程序启动时检查状态位) +- **新增阶段**:`FAMILY_QUIZ`(主动询问家庭菜谱/偏好)、`THREE_DAY_SUMMARY`(每三天总结) +- **状态位机制**:`data/presets/.daily_status.json` 记录每日完成状态 -### 模块 3:📝 饮食记录 — 对话中自动记录,存 Markdown - -**核心变化:从「手动填表单」变为「AI 自动从对话中提取」** - -**业务需求:** -- AI 在对话中记录:「今天中午吃了番茄牛腩和米饭」 -- 用户确认后自动存档 -- 生成结构化数据(JSON)+ 可读文档(Markdown) -- 对历史记录支持查询 - -**技术选型(修正后):** - -**1. 数据提取方式(关键设计)** -MiniCPM-o 4.5 **没有 toolcall 功能**,所以不能直接调用 Python 函数。 -改为 **「标记格式输出 + 正则解析」** 方案: -- 在系统提示词中告诉模型:当你需要记录饮食时,请输出以下格式—— - ``` - [DIET_RECORD] - 日期: 2026-06-07 - 早餐: 全麦面包+鸡蛋+牛奶 - 午餐: 清蒸鱼+糙米饭+炒时蔬 - 晚餐: - 加餐: 酸奶+坚果 - 心情: 挺好 - 备注: - [/DIET_RECORD] - ``` -- 后端 Python 用正则表达式解析 `[DIET_RECORD]...[/DIET_RECORD]` 之间的内容 -- 解析后存入 JSON + 生成 Markdown - -**2. Markdown 模板引擎是什么?** -就是当前 `diet_logger.py` 中的 `_generate_markdown()` 方法——**用 Python f-string 拼接 Markdown 文本**。不是什么复杂东西,就是一个字符串模板函数。保留不变,只是数据来源从「用户填表单」变为「AI 对话中提取」。 - -**3. 为什么不需要向量检索?** -你直觉正确——**确实冗余了**。之前我提出向量检索(ChromaDB)是为了支持语义搜索「上周三吃了什么?」,但实际上直接用 JSON 按日期过滤就能实现。去掉此方案,保持简单。 - -**数据流向:** ``` -MiniCPM-o 回复 (含 [DIET_RECORD] 标记) - → Python 正则解析 - → 存储到 data/diet_logs.json (结构化) - → 同时生成 data/logs/饮食日志_YYYY-MM-DD.md (可读) + ┌─────────────┐ + │ LAUNCH │ ← 检查今日状态位 + │ (启动) │ 若已总结 → 直接 INTERACT + └──────┬──────┘ 若未总结 → 进入总结流程 + │ + ┌───────────┴───────────┐ + │ │ + 今日未总结 今日已总结 + │ │ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ FAMILY_QUIZ │ │ │ + │ (家庭问卷) │ │ │ + │ ├ 检查是否需要 │ │ │ + │ │ 询问新菜谱 │ │ │ + │ └ 检查是否需要 │ │ │ + │ 询问体重 │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ │ │ + ┌──────────────┐ │ │ + │ SUMMARIZE │ │ │ + │ (昨日总结) │ │ │ + │ ├ 读取饮食日志 │ │ │ + │ ├ 读取体重记录 │ │ │ + │ └ 读取家庭记忆 │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ │ │ + ┌──────────────┐ │ │ + │ ANALYZE │ │ │ + │ (营养分析) │ │ │ + │ └ 对比DRIs │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ │ │ + ┌──────────────┐ │ │ + │ BRIEF │ │ │ + │ (生成简报) │ │ │ + │ └ 写JSON │ │ │ + └──────┬───────┘ │ │ + │ │ │ + ▼ ▼ │ + ┌──────────────────────────────────┐ │ + │ INTERACT │◄─┘ + │ (白天交互模式) │ + │ ├ 声纹识别 → 区分说话人 │ + │ ├ 对话理解 → 正则提取饮食记录 │ + │ │ → 写入饮食习惯.md │ + │ │ → 写入家庭菜谱.md │ + │ ├ 推荐 → 膳食指南+家庭菜谱约束 │ + │ └ 简报注入 → AI 知道今日关注点 │ + └──────────────┬───────────────────┘ + │ + 每晚 00:00(或关闭时) + │ + ▼ + ┌──────────────┐ + │ CONSOLIDATE │ + │ (晚间整理) │ + │ ├ 汇总今日 │ + │ ├ 检查是否到 │ + │ │ 三天总结 │ + │ └ 写明日预设 │ + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ DONE │ + │ 标记今日完成 │ + └──────────────┘ +``` + +> **批注(已采纳):** 每晚 00:00 自动总结,设置状态位。程序关闭后第二天启动自动检查状态位。界面设计:4 个子页面。 + +### 3.3 状态机定义(v5 改进版) + +```python +class LoopState(Enum): + LAUNCH = "launch" # 启动检查:检查今日状态位 + FAMILY_QUIZ = "family_quiz" # 家庭问卷:检查是否需要询问菜谱/体重 + SUMMARIZE = "summarize" # 昨日总结:分析昨日饮食/体重/家庭记忆 + ANALYZE = "analyze" # 营养分析:对比 DRIs + BRIEF = "brief" # 生成今日简报 + INTERACT = "interact" # 白天交互模式(等待用户操作) + THREE_DAY = "three_day" # 每三天自动总结(由 CONSOLIDATE 触发) + CONSOLIDATE = "consolidate" # 晚间整理 + DONE = "done" + +_TRANSITIONS = { + (LoopState.LAUNCH, "need_summary"): LoopState.FAMILY_QUIZ, + (LoopState.LAUNCH, "already_done"): LoopState.INTERACT, + (LoopState.FAMILY_QUIZ, "ok"): LoopState.SUMMARIZE, + (LoopState.SUMMARIZE, "ok"): LoopState.ANALYZE, + (LoopState.ANALYZE, "ok"): LoopState.BRIEF, + (LoopState.BRIEF, "ok"): LoopState.INTERACT, + (LoopState.INTERACT, "day_ended"): LoopState.CONSOLIDATE, + (LoopState.CONSOLIDATE, "need_3day"): LoopState.THREE_DAY, + (LoopState.CONSOLIDATE, "ok"): LoopState.DONE, + (LoopState.THREE_DAY, "ok"): LoopState.DONE, +} ``` -> **✅ 已根据你的批注修正:去掉 toolcall 假设,改用标记格式解析;去掉向量检索;详细解释了 Markdown 模板引擎。** +> **批注(已采纳):** 状态机设计根据目的迭代,已整合上下批注。 ---- +### 3.4 状态处理器 -### 模块 4:📊 营养报告(基于中国官方标准) +| 状态 | Handler | 功能 | +|------|---------|------| +| `LAUNCH` | `_state_launch()` | 读取 `data/presets/.daily_status.json` → 决定流程 | +| `FAMILY_QUIZ` | `_state_family_quiz()` | 检查是否需要询问新菜谱/体重 → 写入今日简报待办 | +| `SUMMARIZE` | `_state_summarize()` | 遍历执行 SUMMARIZE 阶段 Plugin | +| `ANALYZE` | `_state_analyze()` | 遍历执行 ANALYZE 阶段 Plugin | +| `BRIEF` | `_state_brief()` | 汇总为 `今日简报.json` | +| `INTERACT` | `_state_interact()` | 等待用户交互,消费 Gradio event queue | +| `THREE_DAY` | `_state_three_day()` | 每三天运行:分析营养缺失 → 改进家庭菜单建议 | +| `CONSOLIDATE` | `_state_consolidate()` | 晚间整理,检查是否触发 THREE_DAY | +| `DONE` | `_state_done()` | 写入状态位,清理资源 | + +> **批注(已采纳):** 状态处理器根据目的迭代,已整合上下批注。 + +### 3.5 主循环入口 + +```python +class PregoPalLoop: + def __init__(self): + self.plugins = PluginRegistry() + self.state = LoopState.LAUNCH + self._register_default_plugins() + + async def run(self) -> None: + while self.state is not LoopState.DONE: + handler_name = f"_state_{self.state.value}" + handler = getattr(self, handler_name) + event = await handler() + next_state = _TRANSITIONS.get((self.state, event)) + if next_state is None: + raise RuntimeError(f"No transition from {self.state} on {event}") + self.state = next_state +``` -**核心变化:从「凭记忆编数据库」变为「查中国官方营养标准」** +> **批注:** ________ -**业务需求:** -- 基于中国官方营养标准做分析 -- 输出可视化报告 + 改善建议 -- 模板预先设计好,美观专业 +--- -**需要你下载的文件清单:** +## 四、插件系统设计(可扩展性) + +### 4.1 插件基类 + +```python +class LoopStage(Enum): + FAMILY_QUIZ = "family_quiz" + SUMMARIZE = "summarize" + ANALYZE = "analyze" + BRIEF = "brief" + THREE_DAY = "three_day" + CONSOLIDATE = "consolidate" + +@dataclass +class LoopContext: + briefing: dict = field(default_factory=dict) + weight_data: dict = field(default_factory=dict) + diet_records: list = field(default_factory=list) + family_recipes: list = field(default_factory=list) # 新增:家庭菜谱 + family_memory: dict = field(default_factory=dict) # 新增:家庭记忆 + analysis_results: dict = field(default_factory=dict) + errors: list = field(default_factory=list) + +class LoopPlugin(ABC): + @abstractmethod + def stage(self) -> LoopStage: ... + @abstractmethod + def name(self) -> str: ... + @abstractmethod + async def run(self, ctx: LoopContext) -> PluginResult: ... +``` -请搜索并下载以下文件,放到 `data/nutrition/raw/` 目录下: +> **批注:** ________ -| # | 文件名/关键词 | 发布机构 | 建议搜索方式 | -|---|-------------|---------|------------| -| 1 | **中国食物成分表(标准版)** 第6版 | 中国疾控中心营养与健康所 | 搜索 "中国食物成分表 标准版 第6版 Excel" 或 PDF | -| 2 | **中国居民膳食营养素参考摄入量(DRIs 2023)** 摘要 | 中国营养学会 | 搜索 "DRIs 2023 中国居民膳食营养素参考摄入量" | -| 3 | **中国孕期妇女膳食指南(2022)** | 中国营养学会 | 搜索 "孕期妇女膳食指南 2022 中国营养学会" | -| 4 | **妊娠期妇女体重增长推荐值(WS/T 801-2022)** | 国家卫健委 | 搜索 "WS/T 801-2022 妊娠期妇女体重增长推荐值" | +### 4.2 内置插件清单(v5 更新版) -如果你有些文件找不到,给我说,我协助搜索。 +| 插件 | 类名 | 注册阶段 | 功能 | 数据源 | +|------|------|---------|------|--------| +| 家庭菜谱检查 | `FamilyRecipeQuizPlugin` | FAMILY_QUIZ | 检查是否需要询问新菜谱 | `data/family/recipes.md` | +| 体重询问检查 | `WeightQuizPlugin` | FAMILY_QUIZ | 检查是否需要询问体重 | `data/logs/` | +| 昨日饮食总结 | `DietSummaryPlugin` | SUMMARIZE | 读取昨日饮食日志,汇总三餐 | `data/logs/*.md` | +| 体重检查 | `WeightCheckPlugin` | SUMMARIZE | 检查体重记录,对比 WS/T 801 | `WeightStandardParser` | +| 家庭记忆加载 | `FamilyMemoryPlugin` | SUMMARIZE | 加载家庭关系/事件记忆 | `data/family/memory.md` | +| DRIs 分析 | `DRIAnalysisPlugin` | ANALYZE | 对比营养素参考摄入量 | `DRIsParser` + 昨日饮食 | +| 简报生成 | `BriefingGeneratorPlugin` | BRIEF | 汇总分析为结构化 JSON | `LoopContext` | +| 三天总结 | `ThreeDaySummaryPlugin` | THREE_DAY | 分析营养缺失趋势,改进家庭菜单 | 近三天饮食记录 | +| 预设写入 | `PresetWriterPlugin` | CONSOLIDATE | 生成明日预设文件 | `data/presets/` | -**如果没有下载到正式文件怎么办:** -我已编写了一份基于 DRIs 公开数据的营养数据库作为 baseline(当前 `DEFAULT_NUTRITION_DB`),可以先用它做原型开发,等你找到正式文件后替换即可。 +> **批注:** ________ -> **✅ 已根据你的批注修正:列出具体文件清单,你下载后我整理成 JSON。** +### 4.3 扩展方式 ---- +```python +# 新增功能只需两步: +class NewPlugin(LoopPlugin): + def stage(self) -> LoopStage: return LoopStage.SUMMARIZE + def name(self) -> str: return "new_feature" + async def run(self, ctx: LoopContext) -> PluginResult: ... -## 三、交互流程设计 +loop.plugins.register(NewPlugin()) +# 不需要修改 loop.py 的状态机代码 +``` -### 核心设计原则 +> **批注:** ________ -你批注得对:**不需要穷举所有情况,而是设计好 MiniCPM-o 的系统提示词 + 当前模式状态。** +--- -### 系统提示词架构 +## 五、整体文件结构(v5 更新版) ``` -你叫 PregoPal,是一个孕期陪护 AI 助手。 -你的任务: -1. 识别说话人身份(通过声音判断是谁在说话) -2. 根据对话理解用户需求(记录饮食、推荐菜品、闲聊陪伴) -3. 当用户有明确需求时,按指定格式输出记录 - -当前模式:{模式名称} ← 动态注入(任务模式/闲聊模式) -当前说话人:{身份} ← 声纹识别结果 - -输出格式规则: -- 当用户描述了饮食内容,请在回复末尾附加 [DIET_RECORD]...[/DIET_RECORD] -- 当用户要求推荐菜品,直接推荐并给出理由 -- 普通对话保持自然、温馨、关心的语气 +PregoPal/ +├── app.py # 主入口:启动 Gradio + Loop +├── config.py # 全局配置 +├── utils.py # 工具函数 +├── loop.py ← 🆕 新增 # 核心循环引擎(状态机驱动) +│ +├── plugins/ ← 🆕 新增 # Loop 插件 +│ ├── __init__.py +│ ├── base.py # LoopPlugin 基类 + PluginRegistry +│ ├── family_quiz.py # 家庭菜谱/体重询问检查 +│ ├── diet_summary.py # 昨日饮食总结 +│ ├── weight_check.py # 体重检查(WS/T 801) +│ ├── family_memory.py # 家庭记忆加载 +│ ├── dri_analysis.py # DRIs 营养素对比分析 +│ ├── briefing_generator.py # 今日简报生成器 +│ ├── three_day_summary.py # 每三天总结 +│ └── preset_writer.py # 预设文件写入 +│ +├── modules/ # 业务逻辑层 +│ ├── __init__.py +│ ├── nutrition_standards.py # ← 🆕 新增:营养标准解析器 +│ │ ├── WeightStandardParser # 体重标准解析 +│ │ ├── DietaryGuideParser # 膳食指南解析 +│ │ └── DRIsParser # DRIs 解析 +│ ├── family_manager.py # ← 🆕 新增:家庭信息管理 +│ │ ├── 读取/写入 recipes.md +│ │ ├── 读取/写入 preferences.md +│ │ └── 读取/写入 memory.md +│ ├── diet_extractor.py # ← 🆕 新增:system prompt + 正则提取 +│ │ ├── 从对话中提取饮食记录 +│ │ ├── 从对话中提取菜谱信息 +│ │ └── 从对话中提取家庭关系 +│ ├── voiceprint.py # 声纹识别 +│ ├── meal_recommender.py # 菜品推荐 +│ ├── diet_logger.py # 饮食记录 + Markdown 生成 +│ └── nutrition_analyzer.py # 营养分析 + 可视化 +│ +├── core/ # AI 核心层(等待 MiniCPM-o) +│ ├── __init__.py +│ ├── model_loader.py +│ ├── voice_processor.py +│ ├── vision_processor.py +│ └── conversation_manager.py +│ +├── ui/ # 表现层 +│ ├── __init__.py +│ └── app_builder.py # Gradio 界面 +│ +├── data/ +│ ├── nutrition/ +│ │ └── raw/ # 三个 MD 文件 +│ ├── family/ ← 🆕 新增 # 家庭信息 +│ │ ├── recipes.md # 家庭菜谱(家人会做的菜) +│ │ ├── preferences.md # 饮食偏好/忌口/过敏 +│ │ └── memory.md # 家庭记忆(关系/事件) +│ ├── presets/ # 每日简报 +│ │ └── .daily_status.json # 状态位文件 +│ ├── voices/ +│ ├── logs/ # Markdown 饮食日志 +│ └── reports/ # 导出报告 +│ +├── docs/ +│ ├── 项目理解_技术架构.md +│ └── 开发日志.md +├── requirements.txt +└── README.md ``` -### 交互流程 +> **批注:** ________ -``` -用户打开摄像头+麦克风 - │ - ▼ -AI 根据系统提示词主动问候 - │ - ▼ -用户说话 → MiniCPM-o 全双工处理 - ├→ 声纹识别 → 更新「当前说话人」 - ├→ 理解语义 → 更新「当前模式」 - └→ 生成回复 → 含 [DIET_RECORD] 标记(如需) - │ - ▼ -后端解析回复: - ├→ 检测到 [DIET_RECORD] → 提取数据 → 存 JSON + Markdown - ├→ 检测到推荐请求 → 显示推荐结果到 UI - └→ 其他 → 正常对话 -``` +--- -### 双模式设计 +## 六、UI 界面设计(根据批注新增) -| 模式 | 判定方式 | 行为 | -|------|---------|------| -| 🎯 **任务模式** | 用户提到「记录」「推荐」「分析」「报告」等关键词 | 执行对应业务逻辑 + 标记输出 | -| 💬 **闲聊模式** | 用户开启日常对话 | 自然陪伴,不做结构化记录 | +根据你的批注,Gradio 界面改为 **4 个子页面**: + +### 页面 1:语音交互(主界面) +- 语音输入/输出(未来支持视频) +- **显示 PregoPal 当前思考/操作关键词**(通过 system prompt + 正则提取,每次执行下一步时用特定字符包裹下一步主题) +- **自动展示相关文件操作的输出结果**(截断 + 省略号) +- 声纹识别状态显示 + +### 页面 2:家庭成员饮食习惯 +- 展示所有家庭成员的饮食偏好、忌口、过敏 +- 展示家庭菜谱(家人会做的菜) +- 支持手动添加/修改 + +### 页面 3:每三天自动总结 +- 分析孕妇饮食是否存在营养缺失 +- 对家庭菜单给出改进建议 +- 由 `THREE_DAY` 状态触发 -> **✅ 已根据你的批注修正:聚焦系统提示词设计 + 模式管理,不穷举情况。** +### 页面 4:营养报告 +- 可视化报告(保留原有) +- 体重增长趋势图 +- 营养素摄入对比图 + +> **批注:** ________ --- -## 四、技术栈一览 +## 七、system prompt + 正则提取设计(根据批注新增) + +这是 PregoPal 的核心机制,用于在不依赖 toolcall 的情况下实现结构化数据提取。 + +### 7.1 提取标记格式 + +在 system prompt 中告诉模型,当需要记录信息时,使用以下标记格式: -| 层级 | 技术 | 用途 | -|------|------|------| -| 核心模型 | **MiniCPM-o 4.5**(统一多模态模型) | 全双工推理:视觉+语音+文本+语音输出 | -| 前端框架 | **Gradio** | Web 界面,实时对话交互 | -| 声纹识别 | **Whisper-medium encoder embedding**(模型内置) | 说话人身份识别 | -| 频谱方案 | **NumPy + SoundFile**(备选 fallback) | 声纹识别兜底 | -| 数据存储 | **JSON + Markdown**(结构化可扩展) | 饮食记录 + 报告存档 | -| 营养标准 | **中国官方 DRIs / 食物成分表**(你下载后我整理) | 营养分析基准 | -| 部署平台 | **Modal**(你有黑客松额度,后续配置) | 云端 GPU 部署 | - -### 数据存储 Schema 设计(可扩展) - -**JSON 格式(diet_logs.json):** -```json -{ - "schema_version": "1.0", - "records": [ - { - "id": "a1b2c3d4", - "date": "2026-06-07", - "speaker": {"name": "小明", "relation": "丈夫"}, - "meals": {"早餐": "...", "午餐": "...", "晚餐": "", "加餐": "..."}, - "mood": "挺好", - "notes": "", - "extensions": {}, - "created_at": "2026-06-07T10:30:00" - } - ] -} ``` +[EXTRACT_DIET] +日期: 2026-06-08 +餐次: 午餐 +食物: 番茄牛腩, 米饭 +份量: 一碗 +记录人: 丈夫 +备注: 孕妇说想吃 +[/EXTRACT_DIET] + +[EXTRACT_RECIPE] +菜名: 清蒸鲈鱼 +制作人: 丈夫 +难度: 中等 +食材: 鲈鱼, 葱, 姜, 蒸鱼豉油 +备注: 孕妇爱吃 +[/EXTRACT_RECIPE] + +[EXTRACT_PREFERENCE] +人员: 丈夫 +类型: 忌口 +内容: 不吃香菜 +[/EXTRACT_PREFERENCE] + +[EXTRACT_WEIGHT] +日期: 2026-06-08 +体重: 62.5 +记录人: 孕妇 +[/EXTRACT_WEIGHT] + +[THINKING] +当前步骤: 分析昨日钙摄入 +下一步: 对比DRIs标准 +[/THINKING] +``` + +### 7.2 后端正则解析 + +```python +# modules/diet_extractor.py +import re + +class DietExtractor: + """从 AI 回复中提取结构化数据""" + + DIET_PATTERN = re.compile( + r'\[EXTRACT_DIET\](.*?)\[/EXTRACT_DIET\]', re.DOTALL + ) + RECIPE_PATTERN = re.compile( + r'\[EXTRACT_RECIPE\](.*?)\[/EXTRACT_RECIPE\]', re.DOTALL + ) + PREFERENCE_PATTERN = re.compile( + r'\[EXTRACT_PREFERENCE\](.*?)\[/EXTRACT_PREFERENCE\]', re.DOTALL + ) + WEIGHT_PATTERN = re.compile( + r'\[EXTRACT_WEIGHT\](.*?)\[/EXTRACT_WEIGHT\]', re.DOTALL + ) + THINKING_PATTERN = re.compile( + r'\[THINKING\](.*?)\[/THINKING\]', re.DOTALL + ) + + @staticmethod + def extract_all(text: str) -> dict: + """从文本中提取所有结构化数据""" + return { + "diets": DietExtractor._parse_diets(text), + "recipes": DietExtractor._parse_recipes(text), + "preferences": DietExtractor._parse_preferences(text), + "weights": DietExtractor._parse_weights(text), + "thinking": DietExtractor._parse_thinking(text), + } + + @staticmethod + def _parse_diets(text: str) -> list[dict]: + """解析 [EXTRACT_DIET] 块""" + results = [] + for match in DietExtractor.DIET_PATTERN.finditer(text): + block = match.group(1) + entry = {} + for line in block.strip().split('\n'): + if ':' in line: + key, val = line.split(':', 1) + entry[key.strip()] = val.strip() + if entry: + results.append(entry) + return results +``` + +### 7.3 稳定性设计 -- `extensions: {}` 保留给未来扩展(如血糖、体重、胎动等) -- `schema_version` 支持后续数据结构升级 +- **正则匹配失败时的 fallback**:如果模型输出格式不完整,尝试用关键词匹配(如 "吃了"、"想吃"、"记录" 等) +- **用户确认机制**:提取的数据先展示给用户确认,再写入文件 +- **多轮累积**:同一餐次的信息可能分散在多轮对话中,需要累积合并 -> **✅ 已根据你的批注修正:去掉「全家桶」表述,添加数据 schema 设计说明。** +> **批注:** ________ --- -## 五、模块拆分方案(最终版) +## 八、数据流全景(v5 更新版) ``` -PregoPal/ -├── app.py # 主入口:启动 Gradio + 加载模型 -├── config.py # 路径 / 枚举 / 模型参数 / 系统提示词模板 -├── core/ # AI 核心层 -│ ├── __init__.py -│ ├── model_loader.py # 加载 MiniCPM-o 4.5 模型 -│ ├── voice_processor.py # Whisper ASR + 声纹 embedding 提取 -│ ├── vision_processor.py # SigLip2 视觉处理 -│ └── conversation_manager.py # 对话管理 + 系统提示词构建 + [DIET_RECORD] 解析 -├── modules/ # 业务逻辑层 -│ ├── __init__.py -│ ├── voiceprint.py # 声纹注册/识别(调用 core 或 fallback 频谱方案) -│ ├── meal_recommender.py # 菜品推荐(调用 MiniCPM-o 或兜底规则) -│ ├── diet_logger.py # 饮食记录 + Markdown 生成(JSON 双写) -│ └── nutrition_analyzer.py # 营养分析(基于官方标准)+ matplotlib 可视化 -├── ui/ # 表现层 -│ ├── __init__.py -│ └── app_builder.py # Gradio 界面 + 事件绑定 -├── data/ # 数据存储 -│ ├── nutrition/ -│ │ └── raw/ # ← 你下载的营养标准原始文件放这里 -│ ├── voices/ # 声纹数据 -│ ├── logs/ # Markdown 饮食日志 -│ └── reports/ # 导出报告 -├── docs/ -│ ├── 项目理解_技术架构.md # 本文档 -│ └── 开发日志.md # 记录错误、调试经验、模型调用技巧 -├── requirements.txt # 每次运行成功后根据环境版本锁定 -└── README.md + data/nutrition/raw/ data/family/ + ├── 体重标准.md ├── recipes.md + ├── 膳食指南.md ├── preferences.md + └── DRIs.md └── memory.md + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────────┐ + │ nutrition_ │ │ family_manager.py │ + │ standards.py │ │ 读取/写入家庭信息 │ + │ 编译为结构化数据 │ └──────────┬───────────┘ + └────────┬─────────┘ │ + │ │ + ▼ ▼ + ┌──────────────────────────────────────────────────┐ + │ loop.py │ + │ LAUNCH → FAMILY_QUIZ → SUMMARIZE → ANALYZE │ + │ → BRIEF → INTERACT → CONSOLIDATE → (THREE_DAY) │ + │ │ + │ 每个阶段调用对应 Plugin │ + │ Plugin 调用 modules/ 层的函数 │ + └──────────────────────┬───────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────────┐ + │ data/presets/今日简报_YYYY-MM-DD.json │ + │ { │ + │ "date": "2026-06-08", │ + │ "weight_check": {"need_ask": true/false}, │ + │ "recipe_check": {"need_ask": true/false}, │ + │ "nutrient_focus": ["钙", "铁", "叶酸"], │ + │ "recommended_foods": ["菠菜", "瘦肉", "豆腐"],│ + │ "family_recipes_available": ["番茄牛腩",...], │ + │ "conversation_topics": ["今天想吃鱼吗?"], │ + │ "family_memory": "昨天丈夫说...", │ + │ "thinking_keywords": "分析钙摄入" │ + │ } │ + └──────────────────────┬───────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────────┐ + │ ui/app_builder.py (Gradio 4页面) │ + │ 页面1: 语音交互 (显示 thinking_keywords + 截断) │ + │ 页面2: 家庭饮食习惯 │ + │ 页面3: 三天总结 │ + │ 页面4: 营养报告 │ + └──────────────────────┬───────────────────────────┘ + │ + ▼ + modules/diet_extractor.py (从对话中正则提取) + modules/family_manager.py (写入家庭信息) + modules/diet_logger.py (记录饮食) + │ + ▼ + 次日 loop.py 再次启动 → 循环 ``` -> **✅ 已确认。** +> **批注:** ________ --- -## 六、决策记录(你已回答) +## 九、技术栈一览 + +| 层级 | 技术 | 用途 | +|------|------|------| +| 核心模型 | **MiniCPM-o 4.5**(统一多模态模型) | 全双工推理:视觉+语音+文本+语音输出 | +| 前端框架 | **Gradio 6.x** | Web 界面,4 个子页面 | +| 声纹识别 | Whisper-medium encoder embedding(优先)| 说话人身份识别 | +| 频谱方案 | NumPy + SoundFile(备选 fallback) | 声纹识别兜底 | +| 数据存储 | JSON + Markdown | 饮食记录 + 报告存档 + 每日简报 + 家庭信息 | +| 营养标准 | 三个 MD 文件(已下载)+ 自写解析器 | 体重标准/膳食指南/DRIs | +| 循环引擎 | 自研状态机 + 插件注册 | 每日生命周期管理 | +| 数据提取 | 正则表达式(system prompt 标记格式) | 从对话中提取结构化数据 | +| 部署平台 | HuggingFace Spaces(目前)+ Modal(后续) | 云端部署 | -| # | 问题 | 你的决策 | -|---|------|---------| -| 1 | 声纹识别方案? | **Whisper-medium embedding**(优先),频谱相似度(备选) | -| 2 | 部署方式? | **Modal**(你有黑客松额度),后续我帮你配置 | -| 3 | 营养标准谁来整理? | **你下载文件**到 `data/nutrition/raw/`,我整理成 JSON | -| 4 | 拆分时机? | **处理好上述问题后**立即拆分 | +> **批注:** ________ --- -## 七、后续行动计划(按顺序) +## 十、待讨论问题清单 -- [ ] **你做的事:** 下载营养标准文件到 `data/nutrition/raw/` -- [ ] **我做的事:** 更新开发日志 → 列出需要下载的营养文件清单 → 等文件就绪后执行文件拆分 -- [ ] **然后:** 研究 Whisper-medium embedding 提取方案 -- [ ] **然后:** 配置 Modal 部署 -- [ ] **然后:** 逐步替换规则引擎为 AI 驱动 +| # | 问题 | 你的选择 / 意见 | +|---|------|----------------| +| 1 | **Loop 触发时机**:每晚 00:00 自动总结 + 程序启动时检查状态位(已采纳) | ✅ 已确认 | +| 2 | **简报承载形式**:JSON 文件 `data/presets/` 落盘(已采纳) | ✅ 已确认 | +| 3 | **状态位机制**:`data/presets/.daily_status.json` 记录每日完成状态 | ________ | +| 4 | **家庭信息文件格式**:三个 md 文件(recipes.md / preferences.md / memory.md) | ________ | +| 5 | **system prompt 标记格式**:`[EXTRACT_DIET]` / `[EXTRACT_RECIPE]` / `[THINKING]` 等 | ________ | +| 6 | **UI 4 页面**:语音交互 / 家庭饮食习惯 / 三天总结 / 营养报告 | ________ | +| 7 | **三天总结触发条件**:每自然日 00:00 检查是否满 3 天?还是每 3 次 CONSOLIDATE? | ________ | +| 8 | **还有什么需要调整的?** | ________ | --- -*文档生成时间:2026-06-07 | v3 - 已整合所有批注* +*文档生成时间:2026-06-08 | v5 - 整合批注,细化实现方案* diff --git a/loop.py b/loop.py new file mode 100644 index 0000000000000000000000000000000000000000..3164eab6a0a88705817815696ca3e90de3029c4e --- /dev/null +++ b/loop.py @@ -0,0 +1,291 @@ +""" +PregoPal - 核心循环引擎 +======================= +状态机驱动的每日生命周期管理。 + +状态流转: + LAUNCH → FAMILY_QUIZ → SUMMARIZE → ANALYZE → BRIEF → INTERACT + → CONSOLIDATE → (THREE_DAY) → DONE + +状态位机制: + data/presets/.daily_status.json 记录每日完成状态 +""" + +import json +import datetime +import asyncio +from enum import Enum +from pathlib import Path +from dataclasses import dataclass, field + +from plugins.base import LoopPlugin, PluginResult, PluginRegistry, LoopStage, LoopContext + +PRESETS_DIR = Path("data/presets") +STATUS_FILE = PRESETS_DIR / ".daily_status.json" + + +# ============================================================ +# 状态枚举 +# ============================================================ +class LoopState(Enum): + LAUNCH = "launch" # 启动检查:检查今日状态位 + FAMILY_QUIZ = "family_quiz" # 家庭问卷:检查是否需要询问菜谱/体重 + SUMMARIZE = "summarize" # 昨日总结:分析昨日饮食/体重/家庭记忆 + ANALYZE = "analyze" # 营养分析:对比 DRIs + BRIEF = "brief" # 生成今日简报 + INTERACT = "interact" # 白天交互模式(等待用户操作) + THREE_DAY = "three_day" # 每三天自动总结 + CONSOLIDATE = "consolidate" # 晚间整理 + DONE = "done" # 标记今日完成 + + +# ============================================================ +# 状态转移表 +# ============================================================ +_TRANSITIONS = { + (LoopState.LAUNCH, "need_summary"): LoopState.FAMILY_QUIZ, + (LoopState.LAUNCH, "already_done"): LoopState.INTERACT, + (LoopState.FAMILY_QUIZ, "ok"): LoopState.SUMMARIZE, + (LoopState.SUMMARIZE, "ok"): LoopState.ANALYZE, + (LoopState.ANALYZE, "ok"): LoopState.BRIEF, + (LoopState.BRIEF, "ok"): LoopState.INTERACT, + (LoopState.INTERACT, "day_ended"): LoopState.CONSOLIDATE, + (LoopState.CONSOLIDATE, "need_3day"): LoopState.THREE_DAY, + (LoopState.CONSOLIDATE, "ok"): LoopState.DONE, + (LoopState.THREE_DAY, "ok"): LoopState.DONE, +} + + +# ============================================================ +# 状态位管理 +# ============================================================ +class DailyStatus: + """管理每日状态位""" + + STATUS_FILE = STATUS_FILE + + @staticmethod + def load() -> dict: + """加载状态位文件""" + if STATUS_FILE.exists(): + with open(STATUS_FILE, 'r', encoding='utf-8') as f: + return json.load(f) + return {} + + @staticmethod + def save(status: dict): + """保存状态位文件""" + PRESETS_DIR.mkdir(parents=True, exist_ok=True) + with open(STATUS_FILE, 'w', encoding='utf-8') as f: + json.dump(status, f, ensure_ascii=False, indent=2) + + @staticmethod + def is_today_done() -> bool: + """检查今日是否已完成总结""" + today = datetime.date.today().isoformat() + status = DailyStatus.load() + return status.get(today, {}).get("summary_done", False) + + @staticmethod + def mark_summary_done(): + """标记今日总结完成""" + today = datetime.date.today().isoformat() + status = DailyStatus.load() + status[today] = status.get(today, {}) + status[today]["summary_done"] = True + status[today]["completed_at"] = datetime.datetime.now().isoformat() + DailyStatus.save(status) + + @staticmethod + def mark_day_ended(): + """标记今日结束""" + today = datetime.date.today().isoformat() + status = DailyStatus.load() + status[today] = status.get(today, {}) + status[today]["day_ended"] = True + status[today]["ended_at"] = datetime.datetime.now().isoformat() + DailyStatus.save(status) + + @staticmethod + def get_last_summary_date() -> str | None: + """获取最近一次总结的日期""" + status = DailyStatus.load() + done_dates = [d for d, v in status.items() if v.get("summary_done")] + return max(done_dates) if done_dates else None + + @staticmethod + def days_since_last_summary() -> int: + """距离上次总结的天数""" + last = DailyStatus.get_last_summary_date() + if last is None: + return 999 + last_date = datetime.date.fromisoformat(last) + return (datetime.date.today() - last_date).days + + @staticmethod + def should_three_day_summary() -> bool: + """检查是否需要进行三天总结""" + return DailyStatus.days_since_last_summary() >= 3 + + +# ============================================================ +# 主循环引擎 +# ============================================================ +class PregoPalLoop: + """核心循环引擎""" + + def __init__(self): + self.plugins = PluginRegistry() + self.state = LoopState.LAUNCH + self.context = LoopContext() + self._register_default_plugins() + + def _register_default_plugins(self): + """注册默认插件""" + from plugins.family_quiz import FamilyRecipeQuizPlugin, WeightQuizPlugin + from plugins.diet_summary import DietSummaryPlugin + from plugins.weight_check import WeightCheckPlugin + from plugins.family_memory import FamilyMemoryPlugin + from plugins.dri_analysis import DRIAnalysisPlugin + from plugins.briefing_generator import BriefingGeneratorPlugin + from plugins.three_day_summary import ThreeDaySummaryPlugin + from plugins.preset_writer import PresetWriterPlugin + + self.plugins.register(FamilyRecipeQuizPlugin()) + self.plugins.register(WeightQuizPlugin()) + self.plugins.register(DietSummaryPlugin()) + self.plugins.register(WeightCheckPlugin()) + self.plugins.register(FamilyMemoryPlugin()) + self.plugins.register(DRIAnalysisPlugin()) + self.plugins.register(BriefingGeneratorPlugin()) + self.plugins.register(ThreeDaySummaryPlugin()) + self.plugins.register(PresetWriterPlugin()) + + async def run(self) -> None: + """主循环入口""" + while self.state is not LoopState.DONE: + handler_name = f"_state_{self.state.value}" + handler = getattr(self, handler_name) + event = await handler() + next_state = _TRANSITIONS.get((self.state, event)) + if next_state is None: + raise RuntimeError( + f"No transition from {self.state} on '{event}'" + ) + self.state = next_state + + # ============================================================ + # 状态处理器 + # ============================================================ + + async def _state_launch(self) -> str: + """启动检查:检查今日状态位""" + if DailyStatus.is_today_done(): + return "already_done" + return "need_summary" + + async def _state_family_quiz(self) -> str: + """家庭问卷:检查是否需要询问菜谱/体重""" + for plugin in self.plugins.get_plugins(LoopStage.FAMILY_QUIZ): + result = await plugin.run(self.context) + if not result.success: + self.context.errors.append(result.message) + return "ok" + + async def _state_summarize(self) -> str: + """昨日总结:执行所有 SUMMARIZE 阶段插件""" + for plugin in self.plugins.get_plugins(LoopStage.SUMMARIZE): + result = await plugin.run(self.context) + if not result.success: + self.context.errors.append(result.message) + return "ok" + + async def _state_analyze(self) -> str: + """营养分析:执行所有 ANALYZE 阶段插件""" + for plugin in self.plugins.get_plugins(LoopStage.ANALYZE): + result = await plugin.run(self.context) + if not result.success: + self.context.errors.append(result.message) + return "ok" + + async def _state_brief(self) -> str: + """生成今日简报:执行所有 BRIEF 阶段插件""" + for plugin in self.plugins.get_plugins(LoopStage.BRIEF): + result = await plugin.run(self.context) + if not result.success: + self.context.errors.append(result.message) + return "ok" + + async def _state_interact(self) -> str: + """ + 白天交互模式:等待用户操作 + 此状态由外部事件触发(Gradio 界面交互) + """ + return "day_ended" + + async def _state_three_day(self) -> str: + """每三天总结:执行所有 THREE_DAY 阶段插件""" + for plugin in self.plugins.get_plugins(LoopStage.THREE_DAY): + result = await plugin.run(self.context) + if not result.success: + self.context.errors.append(result.message) + return "ok" + + async def _state_consolidate(self) -> str: + """晚间整理:执行所有 CONSOLIDATE 阶段插件""" + for plugin in self.plugins.get_plugins(LoopStage.CONSOLIDATE): + result = await plugin.run(self.context) + if not result.success: + self.context.errors.append(result.message) + + if DailyStatus.should_three_day_summary(): + return "need_3day" + return "ok" + + async def _state_done(self) -> str: + """标记今日完成""" + DailyStatus.mark_day_ended() + return "done_complete" + + # ============================================================ + # 外部接口 + # ============================================================ + + def get_briefing(self) -> dict: + """获取今日简报""" + return self.context.briefing + + def get_thinking_keywords(self) -> str: + """获取当前思考关键词(用于 UI 显示)""" + return self.context.briefing.get("thinking_keywords", "") + + def get_errors(self) -> list[str]: + """获取错误列表""" + return self.context.errors + + def run_sync(self): + """同步运行(用于非异步环境)""" + asyncio.run(self.run()) + + +# ============================================================ +# 便捷函数 +# ============================================================ +def run_daily_loop(): + """运行每日循环(同步入口)""" + loop = PregoPalLoop() + loop.run_sync() + return loop + + +def check_and_run_loop() -> PregoPalLoop: + """ + 检查并运行循环(供 Gradio 启动时调用) + + 如果今日已总结,直接返回 loop 实例(不执行总结流程) + 如果今日未总结,执行完整总结流程 + """ + loop = PregoPalLoop() + if not DailyStatus.is_today_done(): + loop.run_sync() + return loop diff --git a/modules/__pycache__/__init__.cpython-313.pyc b/modules/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a93a3d8c49eb3e7a72fbd8a8d4973adb50b176e7 Binary files /dev/null and b/modules/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/__pycache__/diet_extractor.cpython-311.pyc b/modules/__pycache__/diet_extractor.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e178026b8e2a7b563d45d2a39e67d0e039ecc1d5 Binary files /dev/null and b/modules/__pycache__/diet_extractor.cpython-311.pyc differ diff --git a/modules/__pycache__/diet_extractor.cpython-313.pyc b/modules/__pycache__/diet_extractor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..839c3b11eb18cef933acace80bf7f4b08ef7d634 Binary files /dev/null and b/modules/__pycache__/diet_extractor.cpython-313.pyc differ diff --git a/modules/__pycache__/family_manager.cpython-311.pyc b/modules/__pycache__/family_manager.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8464d949456a78e02bd44ac09d5ac5dfa836525d Binary files /dev/null and b/modules/__pycache__/family_manager.cpython-311.pyc differ diff --git a/modules/__pycache__/family_manager.cpython-313.pyc b/modules/__pycache__/family_manager.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea09d2f77a1b336cfc4eef41ddb8cb1f9c6734ff Binary files /dev/null and b/modules/__pycache__/family_manager.cpython-313.pyc differ diff --git a/modules/__pycache__/nutrition_standards.cpython-311.pyc b/modules/__pycache__/nutrition_standards.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9572db4e4e6b13c708862d860989e53c7ab4beb7 Binary files /dev/null and b/modules/__pycache__/nutrition_standards.cpython-311.pyc differ diff --git a/modules/__pycache__/nutrition_standards.cpython-313.pyc b/modules/__pycache__/nutrition_standards.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2105b6677023afe0fe58d6573c085529dc5dbec1 Binary files /dev/null and b/modules/__pycache__/nutrition_standards.cpython-313.pyc differ diff --git a/modules/diet_extractor.py b/modules/diet_extractor.py new file mode 100644 index 0000000000000000000000000000000000000000..be1a74dea0c9207249df0fe5c457fd726d9cb421 --- /dev/null +++ b/modules/diet_extractor.py @@ -0,0 +1,274 @@ +""" +PregoPal - 对话内容提取器 +========================= +从 AI 回复中通过正则表达式提取结构化数据。 + +提取标记格式(在 system prompt 中告知模型使用): + [EXTRACT_DIET]...[/EXTRACT_DIET] # 饮食记录 + [EXTRACT_RECIPE]...[/EXTRACT_RECIPE] # 菜谱信息 + [EXTRACT_PREFERENCE]...[/EXTRACT_PREFERENCE] # 偏好/忌口/过敏 + [EXTRACT_WEIGHT]...[/EXTRACT_WEIGHT] # 体重记录 + [EXTRACT_MEMORY]...[/EXTRACT_MEMORY] # 家庭记忆 + [THINKING]...[/THINKING] # 当前思考步骤 +""" + +import re +import datetime + + +class DietExtractor: + """从 AI 回复中提取所有结构化数据""" + + # ============================================================ + # 正则模式 + # ============================================================ + DIET_PATTERN = re.compile( + r'\[EXTRACT_DIET\](.*?)\[/EXTRACT_DIET\]', re.DOTALL + ) + RECIPE_PATTERN = re.compile( + r'\[EXTRACT_RECIPE\](.*?)\[/EXTRACT_RECIPE\]', re.DOTALL + ) + PREFERENCE_PATTERN = re.compile( + r'\[EXTRACT_PREFERENCE\](.*?)\[/EXTRACT_PREFERENCE\]', re.DOTALL + ) + WEIGHT_PATTERN = re.compile( + r'\[EXTRACT_WEIGHT\](.*?)\[/EXTRACT_WEIGHT\]', re.DOTALL + ) + MEMORY_PATTERN = re.compile( + r'\[EXTRACT_MEMORY\](.*?)\[/EXTRACT_MEMORY\]', re.DOTALL + ) + THINKING_PATTERN = re.compile( + r'\[THINKING\](.*?)\[/THINKING\]', re.DOTALL + ) + + # ============================================================ + # 统一提取接口 + # ============================================================ + @staticmethod + def extract_all(text: str) -> dict: + """从文本中提取所有结构化数据""" + return { + "diets": DietExtractor._parse_diets(text), + "recipes": DietExtractor._parse_recipes(text), + "preferences": DietExtractor._parse_preferences(text), + "weights": DietExtractor._parse_weights(text), + "memories": DietExtractor._parse_memories(text), + "thinking": DietExtractor._parse_thinking(text), + } + + # ============================================================ + # 各类型解析 + # ============================================================ + @staticmethod + def _parse_block(text: str, pattern: re.Pattern) -> list[dict]: + """通用解析块""" + results = [] + for match in pattern.finditer(text): + block = match.group(1).strip() + entry = {} + for line in block.split('\n'): + line = line.strip() + if ':' in line: + key, val = line.split(':', 1) + entry[key.strip()] = val.strip() + if entry: + results.append(entry) + return results + + @staticmethod + def _parse_diets(text: str) -> list[dict]: + """解析 [EXTRACT_DIET] 块""" + results = [] + for match in DietExtractor.DIET_PATTERN.finditer(text): + block = match.group(1).strip() + entry = {"meals": {}} + for line in block.split('\n'): + line = line.strip() + if not line or ':' not in line: + continue + key, val = line.split(':', 1) + key = key.strip() + val = val.strip() + if key in ('日期', '记录人', '备注'): + entry[key] = val + elif key in ('早餐', '午餐', '晚餐', '加餐'): + if val: + entry["meals"][key] = val + if entry.get("meals"): + results.append(entry) + return results + + @staticmethod + def _parse_recipes(text: str) -> list[dict]: + """解析 [EXTRACT_RECIPE] 块""" + return DietExtractor._parse_block(text, DietExtractor.RECIPE_PATTERN) + + @staticmethod + def _parse_preferences(text: str) -> list[dict]: + """解析 [EXTRACT_PREFERENCE] 块""" + return DietExtractor._parse_block(text, DietExtractor.PREFERENCE_PATTERN) + + @staticmethod + def _parse_weights(text: str) -> list[dict]: + """解析 [EXTRACT_WEIGHT] 块""" + return DietExtractor._parse_block(text, DietExtractor.WEIGHT_PATTERN) + + @staticmethod + def _parse_memories(text: str) -> list[dict]: + """解析 [EXTRACT_MEMORY] 块""" + return DietExtractor._parse_block(text, DietExtractor.MEMORY_PATTERN) + + @staticmethod + def _parse_thinking(text: str) -> dict | None: + """解析 [THINKING] 块(只取第一个)""" + match = DietExtractor.THINKING_PATTERN.search(text) + if not match: + return None + block = match.group(1).strip() + result = {} + for line in block.split('\n'): + line = line.strip() + if ':' in line: + key, val = line.split(':', 1) + result[key.strip()] = val.strip() + return result if result else None + + # ============================================================ + # Fallback 关键词匹配(当正则失败时使用) + # ============================================================ + @staticmethod + def fallback_extract_diet(text: str) -> dict | None: + """ + 当 [EXTRACT_DIET] 格式不完整时,用关键词匹配尝试提取 + + 匹配模式: + - "吃了" → 记录食物 + - "想吃" → 记录愿望 + - "记录" → 记录内容 + """ + result = {"meals": {}, "notes": ""} + + # 尝试匹配 "吃了" 模式 + eat_patterns = [ + r'(早餐|午餐|晚餐|加餐).{0,5}(?:吃了|吃的|吃的是|想吃)(.{1,50})', + r'(?:吃了|吃的|吃的是|想吃).{0,5}(早餐|午餐|晚餐|加餐)(.{1,50})', + ] + for pattern in eat_patterns: + for match in re.finditer(pattern, text): + meal_time = match.group(1) + food = match.group(2).strip() + if meal_time in ('早餐', '午餐', '晚餐', '加餐') and food: + result["meals"][meal_time] = food + + # 尝试匹配 "记录" 模式 + record_match = re.search(r'记录[了::]?\s*(.{1,100})', text) + if record_match: + result["notes"] = record_match.group(1).strip() + + return result if result["meals"] else None + + @staticmethod + def fallback_extract_thinking(text: str) -> dict | None: + """ + 当 [THINKING] 格式不完整时,用关键词匹配尝试提取 + """ + result = {} + step_match = re.search(r'(?:当前步骤|正在|当前).{0,5}(.{5,30})', text) + if step_match: + result["当前步骤"] = step_match.group(1).strip() + next_match = re.search(r'(?:下一步|接下来|然后).{0,5}(.{5,30})', text) + if next_match: + result["下一步"] = next_match.group(1).strip() + return result if result else None + + # ============================================================ + # 综合提取(正则优先 + fallback) + # ============================================================ + @staticmethod + def robust_extract(text: str) -> dict: + """综合提取:先用正则,失败时用 fallback""" + result = DietExtractor.extract_all(text) + + # 如果正则没提取到饮食,尝试 fallback + if not result["diets"]: + fallback = DietExtractor.fallback_extract_diet(text) + if fallback: + result["diets"] = [fallback] + + # 如果正则没提取到 thinking,尝试 fallback + if not result["thinking"]: + fallback = DietExtractor.fallback_extract_thinking(text) + if fallback: + result["thinking"] = fallback + + return result + + +# ============================================================ +# System Prompt 模板(告知模型使用标记格式) +# ============================================================ +EXTRACT_SYSTEM_PROMPT = """ +## 数据记录格式 + +当对话中出现以下情况时,请使用特定标记包裹结构化数据: + +### 1. 记录饮食 +当孕妇或家人提到吃了什么、想吃什么时,输出: +[EXTRACT_DIET] +日期: {date} +餐次: 早餐/午餐/晚餐/加餐 +食物: 具体食物名称 +份量: 大概份量 +记录人: 说话人身份 +备注: 孕妇说想吃/孕妇吃了/家人说孕妇想吃 +[/EXTRACT_DIET] + +### 2. 记录菜谱 +当家人提到会做什么菜时,输出: +[EXTRACT_RECIPE] +菜名: 菜名 +制作人: 谁做的 +难度: 简单/中等/困难 +食材: 主要食材 +备注: 孕妇爱吃/家人爱吃 +[/EXTRACT_RECIPE] + +### 3. 记录偏好/忌口/过敏 +当提到饮食偏好、忌口或过敏时,输出: +[EXTRACT_PREFERENCE] +人员: 姓名 +类型: 偏好/忌口/过敏 +内容: 具体内容 +[/EXTRACT_PREFERENCE] + +### 4. 记录体重 +当孕妇提到体重时,输出: +[EXTRACT_WEIGHT] +日期: {date} +体重: 数值(kg) +记录人: 说话人身份 +[/EXTRACT_WEIGHT] + +### 5. 记录家庭记忆 +当提到家庭关系或重要事件时,输出: +[EXTRACT_MEMORY] +类型: 关系/事件/日常 +内容: 具体内容 +[/EXTRACT_MEMORY] + +### 6. 显示当前思考 +每次执行下一步操作时,输出: +[THINKING] +当前步骤: 正在做什么 +下一步: 接下来要做什么 +[/THINKING] + +注意:标记块可以出现在回复的任何位置,不影响正常对话内容。 +""" + + +def get_extract_prompt(date_str: str = None) -> str: + """获取带日期的提取提示词""" + if date_str is None: + date_str = datetime.date.today().isoformat() + return EXTRACT_SYSTEM_PROMPT.format(date=date_str) diff --git a/modules/family_manager.py b/modules/family_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..d0c48be3754d47c6bc8e680f96ecfef35b950034 --- /dev/null +++ b/modules/family_manager.py @@ -0,0 +1,320 @@ +""" +PregoPal - 家庭信息管理 +======================= +管理家庭菜谱、饮食偏好/忌口/过敏、家庭记忆(关系/事件)。 + +数据文件: + data/family/ + ├── recipes.md # 家庭菜谱(家人会做的菜) + ├── preferences.md # 饮食偏好/忌口/过敏 + └── memory.md # 家庭记忆(关系/事件) +""" + +import datetime +import re +from pathlib import Path + +FAMILY_DIR = Path("data/family") + + +# ============================================================ +# 确保目录存在 +# ============================================================ +def ensure_family_dir(): + FAMILY_DIR.mkdir(parents=True, exist_ok=True) + + +# ============================================================ +# 1. 家庭菜谱管理 +# ============================================================ +class RecipeManager: + """管理家庭菜谱(家人会做的菜)""" + + RECIPES_FILE = FAMILY_DIR / "recipes.md" + + @classmethod + def ensure_file(cls): + ensure_family_dir() + if not cls.RECIPES_FILE.exists(): + with open(cls.RECIPES_FILE, 'w', encoding='utf-8') as f: + f.write("# 家庭菜谱\n\n> 记录家人会做的菜。\n\n## 菜谱列表\n\n") + + @classmethod + def load_all(cls) -> list[dict]: + """读取所有菜谱""" + cls.ensure_file() + content = cls.RECIPES_FILE.read_text(encoding='utf-8') + recipes = [] + blocks = re.split(r'\n### ', content)[1:] + for block in blocks: + lines = block.strip().split('\n') + name = lines[0].strip() + if not name or name.startswith('