Spaces:
Runtime error
Runtime error
J.B-Lin commited on
Commit ·
56b0dc2
1
Parent(s): 8193cd7
docs: 部署成功报告 + 清理神秘文件 c
Browse files- c +0 -502
- docs/deploy_success_report.md +205 -0
c
DELETED
|
@@ -1,502 +0,0 @@
|
|
| 1 |
-
# 🌸 PregoPal 项目理解与技术架构(v4 - Loop 驱动 + 三文档操作)
|
| 2 |
-
|
| 3 |
-
> 本文档已完成重构,每个小节末尾附有批注区,便于你边看边标注意见。
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 一、一句话定位
|
| 8 |
-
|
| 9 |
-
**一个基于每日生命周期 Loop 驱动的孕期陪护 AI 助手**:每天首次启动时自动总结昨日饮食、对比国家营养标准(DRIs/膳食指南/体重标准),生成今日简报注入对话上下文;白天与家庭成员(通过声纹区分)自然交互,实时记录饮食、推荐食谱;次日循环往复。
|
| 10 |
-
|
| 11 |
-
> **批注:** ________
|
| 12 |
-
|
| 13 |
-
---
|
| 14 |
-
|
| 15 |
-
## 二、三个营养文档的操作方法
|
| 16 |
-
|
| 17 |
-
你已经下载到 `data/nutrition/raw/` 的三个文件,各自承担不同角色:
|
| 18 |
-
|
| 19 |
-
| 文档 | 文件 | 用途 | PregoPal 的操作方式 |
|
| 20 |
-
|------|------|------|-------------------|
|
| 21 |
-
| **① 体重增长标准** | `妊娠期妇女体重增长推荐值标准.md` | 体重监测基准 | **主动询问体重** — 每天 Loop 中检查是否需要询问,对照 BMI 分类给出评价 |
|
| 22 |
-
| **② 膳食指南** | `中国孕期妇女膳食指南2022图片转md版.md` | 食谱推荐基准 | **作为推荐食谱的约束基础** — 根据孕期阶段用宝塔推荐量生成模板 |
|
| 23 |
-
| **③ DRIs** | `备孕&孕早中晚三期和哺乳期的所有膳食营养素参考摄入量.md` | 营养分析基准 | **Agent 定期思考** — 每天自动对比 RNI,分析不足,写入次日预设 |
|
| 24 |
-
|
| 25 |
-
> **批注:** ________
|
| 26 |
-
|
| 27 |
-
### 2.1 体重增长标准 → 主动询问体重
|
| 28 |
-
|
| 29 |
-
**数据来源**:WS/T 801-2022 表1
|
| 30 |
-
|
| 31 |
-
```
|
| 32 |
-
每天早上 Loop 的 weight_check 插件:
|
| 33 |
-
├→ 检查 data/logs/ 中是否有近期的体重记录
|
| 34 |
-
├→ 若无记录或记录超过 7 天
|
| 35 |
-
│ └→ 在今日简报中标记「需询问体重」
|
| 36 |
-
│ (UI 弹出体重卡片,或语音询问:「今天称体重了吗?」)
|
| 37 |
-
└→ 若有记录
|
| 38 |
-
├→ 计算当前孕周、孕前 BMI、当前总增长值
|
| 39 |
-
├→ 对照标准表:
|
| 40 |
-
|
| 41 |
-
| 孕前 BMI 分类 | 总增长范围 | 每周增长(中+晚) |
|
| 42 |
-
|----------------------|------------|----------------|
|
| 43 |
-
| 低体重 BMI<18.5 | 11.0~16.0 | 0.46(0.37~0.56) |
|
| 44 |
-
| 正常 18.5≤BMI<24.0 | 8.0~14.0 | 0.37(0.26~0.48) |
|
| 45 |
-
| 超重 24.0≤BMI<28.0 | 7.0~11.0 | 0.30(0.22~0.37) |
|
| 46 |
-
| 肥胖 BMI≥28.0 | 5.0~9.0 | 0.22(0.15~0.30) |
|
| 47 |
-
|
| 48 |
-
├→ 在范围内 → ✅ 鼓励「体重增长很好,继续保持!」
|
| 49 |
-
└→ 超出范围 → ⚠️ 提醒「增长偏快/偏慢,建议调整饮食/咨询医生」
|
| 50 |
-
```
|
| 51 |
-
|
| 52 |
-
> **批注:** ________
|
| 53 |
-
|
| 54 |
-
### 2.2 膳食指南 → 食谱推荐基础
|
| 55 |
-
|
| 56 |
-
**数据来源**:中国孕期妇女平衡膳食宝塔(备孕/孕中期/孕晚期/哺乳期)
|
| 57 |
-
|
| 58 |
-
```
|
| 59 |
-
推荐食谱时,AI 的约束条件:
|
| 60 |
-
├→ 孕中期宝塔:
|
| 61 |
-
│ 奶类 300-500g,鱼禽蛋肉类 150-200g,谷类 200-250g
|
| 62 |
-
│ 蔬菜 400-500g,水果 200-300g,薯类 75g
|
| 63 |
-
│ 大豆 20g,坚果 10g,油 25g,盐 5g
|
| 64 |
-
│
|
| 65 |
-
├→ 孕晚期宝塔:
|
| 66 |
-
│ 奶类 300-500g,鱼禽蛋肉类 175-225g,谷类 225-275g
|
| 67 |
-
│ 蔬菜 400-500g,水果 200-350g,薯类 75g
|
| 68 |
-
│ 大豆 20g,坚果 10g,油 25g,盐 5g
|
| 69 |
-
│
|
| 70 |
-
└→ 膳食指南六条核心建议(嵌入系统提示词):
|
| 71 |
-
1. 调整孕前体重至正常范围
|
| 72 |
-
2. 常吃含铁食物,选用碘盐,补充叶酸和维生素D
|
| 73 |
-
3. 孕吐严重者少量多餐
|
| 74 |
-
4. 孕中晚期增加奶、鱼、禽、蛋、瘦肉
|
| 75 |
-
5. 经常户外活动,禁烟酒
|
| 76 |
-
6. 愉快孕育新生命,积极准备母乳喂养
|
| 77 |
-
```
|
| 78 |
-
|
| 79 |
-
**原有 MEAL_TEMPLATES 的改造**:不再硬编码模板字符串,而是将上述宝塔约束条作为结构化 JSON 传递给 AI(或作为本地 fallback 兜底),由 AI 根据宝塔约束 + 用户偏好生成个性化食谱。
|
| 80 |
-
|
| 81 |
-
> **批注:** ________
|
| 82 |
-
|
| 83 |
-
### 2.3 DRIs → Agent 定期分析(自动运行 → 写入次日预设)
|
| 84 |
-
|
| 85 |
-
**数据来源**:表15(孕早期)/ 表16(孕中期)/ 表17(孕晚期)/ 表18(哺乳期)
|
| 86 |
-
|
| 87 |
-
```
|
| 88 |
-
每天 Loop 的 dri_analysis 插件:
|
| 89 |
-
├→ 读取昨日饮食记录(data/logs/饮食日志_YYYY-MM-DD.md)
|
| 90 |
-
├→ 根据当前孕期阶段选择对应 DRIs 表
|
| 91 |
-
├→ 关键营养素逐项对比(重点标记 RNI 与摄入量差异):
|
| 92 |
-
|
| 93 |
-
┌─────────────────────────────────────────────┐
|
| 94 |
-
│ 营养素 RNI(孕中期) 昨日摄入 差异 │
|
| 95 |
-
├─────────────────────────────────────────────┤
|
| 96 |
-
│ 能量(MJ) 9.84(PAL II) 8.2 -17% │
|
| 97 |
-
│ 蛋白质(g) 70 55 -21% │
|
| 98 |
-
│ 钙(mg) 800 420 -48% │
|
| 99 |
-
│ 铁(mg) 25 12 -52% ← 重点关注 │
|
| 100 |
-
│ 叶酸(μg DFE) 600 350 -42% ← 重点关�� │
|
| 101 |
-
│ 维生素D(μg) 10 2.1 -79% ← 重点关注 │
|
| 102 |
-
└─────────────────────────────────────────────┘
|
| 103 |
-
|
| 104 |
-
├→ 生成分析结论(自然语言):
|
| 105 |
-
│ 「昨天钙、铁、叶酸摄入偏低,建议今天多吃:
|
| 106 |
-
│ ✓ 深绿色蔬菜(菠菜、西兰花)
|
| 107 |
-
│ ✓ 瘦肉/动物肝脏
|
| 108 |
-
│ ✓ 豆制品/奶制品」
|
| 109 |
-
│
|
| 110 |
-
└→ 写入 data/presets/今日简报_YYYY-MM-DD.json
|
| 111 |
-
├ 今日重点关注营养素列表
|
| 112 |
-
├ 今日推荐食物类别与份量
|
| 113 |
-
├ 体重提醒(如需)
|
| 114 |
-
└ 与孕妇及家人交谈的建议开场话题
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
> **批注:** ________
|
| 118 |
-
|
| 119 |
-
---
|
| 120 |
-
|
| 121 |
-
## 三、核心架构:Loop 驱动(状态机 + 插件注册)
|
| 122 |
-
|
| 123 |
-
### 3.1 设计理念
|
| 124 |
-
|
| 125 |
-
PregoPal 不是一个「用户点按钮 → 出结果」的被动应用,而是**有每日生命周期的主动伴侣**。
|
| 126 |
-
|
| 127 |
-
参考你提供的 `nanobot_core/agent/loop.py` 中的**状态机事件驱动模式**,PregoPal 的 Loop 提取两个核心设计:
|
| 128 |
-
|
| 129 |
-
1. **TurnState 状态机**(类比 `_process_message` 的 `_state_*` 状态链)— 管理每日生命周期的阶段流转
|
| 130 |
-
2. **可注册的扩展能力**(类比 ToolRegistry)— 每个阶段的具体逻辑由 Plugin 实现
|
| 131 |
-
|
| 132 |
-
> **批注:** ________
|
| 133 |
-
|
| 134 |
-
### 3.2 每日生命周期
|
| 135 |
-
|
| 136 |
-
```
|
| 137 |
-
┌─────────────┐
|
| 138 |
-
│ LAUNCH │ ← 首次启动检查
|
| 139 |
-
│ (启动) │
|
| 140 |
-
└──────┬──────┘
|
| 141 |
-
│
|
| 142 |
-
┌───────────┴───────────┐
|
| 143 |
-
│ │
|
| 144 |
-
首次运行今日 已运行过今日
|
| 145 |
-
│ │
|
| 146 |
-
▼ ▼
|
| 147 |
-
┌──────────────┐ ┌──────────────┐
|
| 148 |
-
│ SUMMARIZE │ │ │
|
| 149 |
-
│ (昨日总结) │ │ │
|
| 150 |
-
│ ├ 读取饮食日志 │ │ │
|
| 151 |
-
│ └ 检查体重 │ │ │
|
| 152 |
-
└──────┬───────┘ │ │
|
| 153 |
-
│ │ │
|
| 154 |
-
▼ │ │
|
| 155 |
-
┌──────────────┐ │ │
|
| 156 |
-
│ ANALYZE │ │ │
|
| 157 |
-
│ (营养分析) │ │ │
|
| 158 |
-
│ └ 对比DRIs │ │ │
|
| 159 |
-
└──────┬───────┘ │ │
|
| 160 |
-
│ │ │
|
| 161 |
-
▼ │ │
|
| 162 |
-
┌──────────────┐ │ │
|
| 163 |
-
│ BRIEF │ │ │
|
| 164 |
-
│ (生成简报) │ │ │
|
| 165 |
-
│ └ 写JSON │ │ │
|
| 166 |
-
└──────┬───────┘ │ │
|
| 167 |
-
│ │ │
|
| 168 |
-
▼ ▼ │
|
| 169 |
-
┌──────────────────────────────────┐ │
|
| 170 |
-
│ INTERACT │◄─┘
|
| 171 |
-
│ (白天交互模式) │
|
| 172 |
-
│ ├ 声纹识别 → 区分说话人 │
|
| 173 |
-
│ ├ 对话理解 → 记录饮食/推荐/闲聊 │
|
| 174 |
-
│ └ 简报注入 → AI 知道今日关注点 │
|
| 175 |
-
└──────────────┬───────────────────┘
|
| 176 |
-
│
|
| 177 |
-
用户结束今日
|
| 178 |
-
│
|
| 179 |
-
▼
|
| 180 |
-
┌──────────────┐
|
| 181 |
-
│ CONSOLIDATE │
|
| 182 |
-
│ (晚间整理) │ ← 可选(关闭页面时触发)
|
| 183 |
-
│ ├ 汇总今日 │
|
| 184 |
-
│ └ 写明日预设 │
|
| 185 |
-
└──────┬───────┘
|
| 186 |
-
│
|
| 187 |
-
▼
|
| 188 |
-
┌──────────────┐
|
| 189 |
-
│ DONE │
|
| 190 |
-
└──────────────┘
|
| 191 |
-
```
|
| 192 |
-
|
| 193 |
-
> **批注:** ________
|
| 194 |
-
|
| 195 |
-
### 3.3 状态机定义
|
| 196 |
-
|
| 197 |
-
```python
|
| 198 |
-
# 状态枚举
|
| 199 |
-
class LoopState(Enum):
|
| 200 |
-
LAUNCH = "launch" # 启动检查:是否是今日首次运行
|
| 201 |
-
SUMMARIZE = "summarize" # 昨日总结:��析昨日饮食/体重
|
| 202 |
-
ANALYZE = "analyze" # 营养分析:对比 DRIs
|
| 203 |
-
BRIEF = "brief" # 生成今日简报
|
| 204 |
-
INTERACT = "interact" # 白天交互模式(等待用户操作)
|
| 205 |
-
CONSOLIDATE = "consolidate" # 晚间整理
|
| 206 |
-
DONE = "done"
|
| 207 |
-
|
| 208 |
-
# 状态转移表(类比 nanobot_core 的 _TRANSITIONS)
|
| 209 |
-
_TRANSITIONS = {
|
| 210 |
-
(LoopState.LAUNCH, "first_run_today"): LoopState.SUMMARIZE,
|
| 211 |
-
(LoopState.LAUNCH, "already_run"): LoopState.INTERACT,
|
| 212 |
-
(LoopState.SUMMARIZE, "ok"): LoopState.ANALYZE,
|
| 213 |
-
(LoopState.ANALYZE, "ok"): LoopState.BRIEF,
|
| 214 |
-
(LoopState.BRIEF, "ok"): LoopState.INTERACT,
|
| 215 |
-
(LoopState.INTERACT, "day_ended"): LoopState.CONSOLIDATE,
|
| 216 |
-
(LoopState.CONSOLIDATE, "ok"): LoopState.DONE,
|
| 217 |
-
}
|
| 218 |
-
```
|
| 219 |
-
|
| 220 |
-
> **批注:** ________
|
| 221 |
-
|
| 222 |
-
### 3.4 状态处理器
|
| 223 |
-
|
| 224 |
-
每个状态对应一个 handler(类比 `_state_restore`, `_state_compact`, `_state_run` 等):
|
| 225 |
-
|
| 226 |
-
| 状态 | Handler | 功能 |
|
| 227 |
-
|------|---------|------|
|
| 228 |
-
| `LAUNCH` | `_state_launch()` | 检查 `data/presets/` 中是否有今日简报 → 决定首次/已运行 |
|
| 229 |
-
| `SUMMARIZE` | `_state_summarize()` | 遍历执行所有注册在 SUMMARIZE 阶段的 Plugin |
|
| 230 |
-
| `ANALYZE` | `_state_analyze()` | 遍历执行所有注册在 ANALYZE 阶段的 Plugin |
|
| 231 |
-
| `BRIEF` | `_state_brief()` | 遍历执行所有注册在 BRIEF 阶段的 Plugin |
|
| 232 |
-
| `INTERACT` | `_state_interact()` | 等待用户交互(从 Gradio event queue 消费消息) |
|
| 233 |
-
| `CONSOLIDATE` | `_state_consolidate()` | 遍历执行所有注册在 CONSOLIDATE 阶段的 Plugin |
|
| 234 |
-
| `DONE` | `_state_done()` | 清理资源,标记今日完成 |
|
| 235 |
-
|
| 236 |
-
> **批注:** ________
|
| 237 |
-
|
| 238 |
-
### 3.5 主循环入口
|
| 239 |
-
|
| 240 |
-
```python
|
| 241 |
-
# loop.py 核心逻辑
|
| 242 |
-
class PregoPalLoop:
|
| 243 |
-
def __init__(self):
|
| 244 |
-
self.plugins = PluginRegistry()
|
| 245 |
-
self.state = LoopState.LAUNCH
|
| 246 |
-
self._register_default_plugins()
|
| 247 |
-
|
| 248 |
-
async def run(self) -> None:
|
| 249 |
-
"""主循环入口"""
|
| 250 |
-
while self.state is not LoopState.DONE:
|
| 251 |
-
handler_name = f"_state_{self.state.value}"
|
| 252 |
-
handler = getattr(self, handler_name)
|
| 253 |
-
event = await handler()
|
| 254 |
-
next_state = _TRANSITIONS.get((self.state, event))
|
| 255 |
-
if next_state is None:
|
| 256 |
-
raise RuntimeError(f"No transition from {self.state} on {event}")
|
| 257 |
-
self.state = next_state
|
| 258 |
-
|
| 259 |
-
async def _state_summarize(self) -> str:
|
| 260 |
-
"""SUMMARIZE 阶段:执行所有相关插件"""
|
| 261 |
-
for plugin in self.plugins.get_plugins(LoopStage.SUMMARIZE):
|
| 262 |
-
await plugin.run(self.context)
|
| 263 |
-
return "ok"
|
| 264 |
-
```
|
| 265 |
-
|
| 266 |
-
> **批注:** ________
|
| 267 |
-
|
| 268 |
-
---
|
| 269 |
-
|
| 270 |
-
## 四、插件系统设计(可扩展性)
|
| 271 |
-
|
| 272 |
-
### 4.1 插件基类
|
| 273 |
-
|
| 274 |
-
```python
|
| 275 |
-
# plugins/base.py
|
| 276 |
-
from abc import ABC, abstractmethod
|
| 277 |
-
from dataclasses import dataclass, field
|
| 278 |
-
from enum import Enum
|
| 279 |
-
|
| 280 |
-
class LoopStage(Enum):
|
| 281 |
-
"""插件可注册的阶段"""
|
| 282 |
-
SUMMARIZE = "summarize"
|
| 283 |
-
ANALYZE = "analyze"
|
| 284 |
-
BRIEF = "brief"
|
| 285 |
-
CONSOLIDATE = "consolidate"
|
| 286 |
-
|
| 287 |
-
@dataclass
|
| 288 |
-
class LoopContext:
|
| 289 |
-
"""插件上下文:共享数据容器"""
|
| 290 |
-
briefing: dict = field(default_factory=dict) # 今日简报数据
|
| 291 |
-
weight_data: dict = field(default_factory=dict) # 体重记录
|
| 292 |
-
diet_records: list = field(default_factory=list) # 饮食记录
|
| 293 |
-
analysis_results: dict = field(default_factory=dict) # 分析结果
|
| 294 |
-
errors: list = field(default_factory=list) # 错误收集
|
| 295 |
-
|
| 296 |
-
@dataclass
|
| 297 |
-
class PluginResult:
|
| 298 |
-
"""插件执行结果"""
|
| 299 |
-
success: bool
|
| 300 |
-
data: dict = field(default_factory=dict)
|
| 301 |
-
message: str = ""
|
| 302 |
-
|
| 303 |
-
class LoopPlugin(ABC):
|
| 304 |
-
"""Loop 扩展插件基类 — 所有插件继承此类"""
|
| 305 |
-
|
| 306 |
-
@abstractmethod
|
| 307 |
-
def stage(self) -> LoopStage:
|
| 308 |
-
"""此插件注册到哪个阶段"""
|
| 309 |
-
|
| 310 |
-
@abstractmethod
|
| 311 |
-
def name(self) -> str:
|
| 312 |
-
"""插件唯一名称"""
|
| 313 |
-
|
| 314 |
-
@abstractmethod
|
| 315 |
-
async def run(self, ctx: LoopContext) -> PluginResult:
|
| 316 |
-
"""执行插件逻辑"""
|
| 317 |
-
```
|
| 318 |
-
|
| 319 |
-
> **批注:** ________
|
| 320 |
-
|
| 321 |
-
### 4.2 内置插件清单
|
| 322 |
-
|
| 323 |
-
| 插件 | 类名 | 注册阶段 | 功能 | 数据源 |
|
| 324 |
-
|------|------|---------|------|--------|
|
| 325 |
-
| 昨日饮食总结 | `DietSummaryPlugin` | SUMMARIZE | 读取昨日饮食日志,汇总三餐 | `data/logs/*.md` |
|
| 326 |
-
| 体重检查 | `WeightCheckPlugin` | SUMMARIZE | 检查体重记录,对比 WS/T 801 | `data/logs/*.md` + 用户输入 |
|
| 327 |
-
| DRIs 分析 | `DRIAnalysisPlugin` | ANALYZE | 对比营养素参考摄入量 | DRIs 表 + 昨日饮食 |
|
| 328 |
-
| 简报生成 | `BriefingGeneratorPlugin` | BRIEF | 汇总分析为结构化 JSON | `LoopContext` |
|
| 329 |
-
| 预设写入 | `PresetWriterPlugin` | CONSOLIDATE | 生成明日预设文件 | `data/presets/` |
|
| 330 |
-
|
| 331 |
-
> **批注:** ________
|
| 332 |
-
|
| 333 |
-
### 4.3 扩展方式
|
| 334 |
-
|
| 335 |
-
```python
|
| 336 |
-
# 后续新增功能(如血糖监测)只需两步:
|
| 337 |
-
|
| 338 |
-
# 1. 写一个新插件
|
| 339 |
-
class BloodSugarPlugin(LoopPlugin):
|
| 340 |
-
def stage(self) -> LoopStage: return LoopStage.SUMMARIZE
|
| 341 |
-
def name(self) -> str: return "blood_sugar"
|
| 342 |
-
async def run(self, ctx: LoopContext) -> PluginResult:
|
| 343 |
-
# 读取血糖记录 → 分析趋势 → 写入 ctx
|
| 344 |
-
...
|
| 345 |
-
|
| 346 |
-
# 2. 注册到 loop
|
| 347 |
-
loop.plugins.register(BloodSugarPlugin())
|
| 348 |
-
|
| 349 |
-
# 不需要修改 loop.py 的任何状态机代码
|
| 350 |
-
```
|
| 351 |
-
|
| 352 |
-
> **批注:** ________
|
| 353 |
-
|
| 354 |
-
---
|
| 355 |
-
|
| 356 |
-
## 五、整体文件结构
|
| 357 |
-
|
| 358 |
-
```
|
| 359 |
-
PregoPal/
|
| 360 |
-
├── app.py # 主入口:启动 Gradio + Loop
|
| 361 |
-
├── config.py # 全局配置(路径/枚举/系统提示词模板)
|
| 362 |
-
├── utils.py # 工具函数
|
| 363 |
-
├── loop.py ← 🆕 新增 # 核心循环引擎(状态机驱动)
|
| 364 |
-
│
|
| 365 |
-
├── plugins/ ← 🆕 新增 # Loop 插件
|
| 366 |
-
│ ├── __init__.py
|
| 367 |
-
│ ├── base.py # LoopPlugin 基类 + PluginRegistry
|
| 368 |
-
│ ├── diet_summary.py # 昨日饮食总结
|
| 369 |
-
│ ├── weight_check.py # 体重检查(WS/T 801)
|
| 370 |
-
│ ├── dri_analysis.py # DRIs 营养素对比分析
|
| 371 |
-
│ ├── briefing_generator.py # 今日简报生成器
|
| 372 |
-
│ └── preset_writer.py # 预设文件写入
|
| 373 |
-
│
|
| 374 |
-
├── core/ # AI 核心层(等待 MiniCPM-o 部署)
|
| 375 |
-
│ ├── __init__.py
|
| 376 |
-
│ ├── model_loader.py # 模型加载器(空接口)
|
| 377 |
-
│ ├── voice_processor.py # 语音处理器(空接口)
|
| 378 |
-
│ ├── vision_processor.py # 视觉处理器(空接口)
|
| 379 |
-
│ └── conversation_manager.py # 对话管理器(空接口)
|
| 380 |
-
│
|
| 381 |
-
├── modules/ # 业务逻辑层
|
| 382 |
-
│ ├── __init__.py
|
| 383 |
-
│ ├── voiceprint.py # 声纹识别
|
| 384 |
-
│ ├── meal_recommender.py # 菜品推荐
|
| 385 |
-
│ ├── diet_logger.py # 饮食记录 + Markdown 生成
|
| 386 |
-
│ └── nutrition_analyzer.py # 营养分析 + 可视化
|
| 387 |
-
│
|
| 388 |
-
├── ui/ # 表现层
|
| 389 |
-
│ ├── __init__.py
|
| 390 |
-
│ └── app_builder.py # Gradio 界面 + 事件绑定
|
| 391 |
-
│
|
| 392 |
-
├── data/ # 数据存储
|
| 393 |
-
│ ├── nutrition/
|
| 394 |
-
│ │ └── raw/ # ← 你已下载的三个 MD 文件
|
| 395 |
-
│ │ ├── 妊娠期妇女体重增长推荐值标准.md
|
| 396 |
-
│ │ ├── 中国孕期妇女膳食指南2022图片转md版.md
|
| 397 |
-
│ │ └── 备孕&孕早中晚三期和哺乳期的所有膳食营养素参考摄入量.md
|
| 398 |
-
│ ├── presets/ ← 🆕 新增 # 每日简报 / 预设文件
|
| 399 |
-
│ ├── voices/ # 声纹数据
|
| 400 |
-
│ ├── logs/ # Markdown 饮食日志
|
| 401 |
-
│ └── reports/ # 导出报告
|
| 402 |
-
│
|
| 403 |
-
├── docs/
|
| 404 |
-
│ ├── 项目理解_技术架构.md # ← 本文档
|
| 405 |
-
│ └── 开发日志.md
|
| 406 |
-
│
|
| 407 |
-
├── requirements.txt # 依赖清单
|
| 408 |
-
└── README.md
|
| 409 |
-
```
|
| 410 |
-
|
| 411 |
-
> **批注:** ________
|
| 412 |
-
|
| 413 |
-
---
|
| 414 |
-
|
| 415 |
-
## 六、数据流全景
|
| 416 |
-
|
| 417 |
-
```
|
| 418 |
-
data/nutrition/raw/
|
| 419 |
-
├── 体重标准.md ─────────────┐
|
| 420 |
-
├── 膳食指南.md ─────────────┤
|
| 421 |
-
└── DRIs.md ─────────────────┤
|
| 422 |
-
│
|
| 423 |
-
┌───────────────────────▼──────────────────────┐
|
| 424 |
-
│ data/presets/ │
|
| 425 |
-
│ (缓存编译后的结构化数据) │
|
| 426 |
-
│ 体重标准.json 膳食指南.json DRIs.json │
|
| 427 |
-
└──────────────┬────────────────────────────────┘
|
| 428 |
-
│
|
| 429 |
-
▼
|
| 430 |
-
┌──────────────────────────────────────────────────────┐
|
| 431 |
-
│ loop.py │
|
| 432 |
-
│ ┌────────────────────────────────────────────────┐ │
|
| 433 |
-
│ │ 状态机驱动 (LAUNCH→SUMMARIZE→ANALYZE→BRIEF) │ │
|
| 434 |
-
│ │ ↓ 依次调用 Plugin │ │
|
| 435 |
-
│ │ diet_summary → weight_check → dri_analysis │ │
|
| 436 |
-
│ │ ↓ │ │
|
| 437 |
-
│ │ briefing_generator │ │
|
| 438 |
-
│ │ ↓ │ │
|
| 439 |
-
│ │ data/presets/今日简报.json │ │
|
| 440 |
-
│ └────────────────────────────────────────────────┘ │
|
| 441 |
-
└──────────────────────┬───────────────────────────────┘
|
| 442 |
-
│
|
| 443 |
-
▼
|
| 444 |
-
┌──────────────────────────────────────────────────────┐
|
| 445 |
-
│ ui/app_builder.py (Gradio 界面) │
|
| 446 |
-
│ ┌────────────────────────────────────────────────┐ │
|
| 447 |
-
│ │ Tab1 声纹识别 │ Tab2 菜品推荐 │ Tab3 饮食记录 │ │
|
| 448 |
-
│ │ Tab4 营养报告 │ Tab5 关于 │ │
|
| 449 |
-
│ │ │ │
|
| 450 |
-
│ │ 今日简报注入对话上下文 │ │
|
| 451 |
-
│ │ AI 知道:今天该关注什么营养素 │ │
|
| 452 |
-
│ │ 该问谁体重?该聊什么话题? │ │
|
| 453 |
-
│ └────────────────────────────────────────────────┘ │
|
| 454 |
-
└──────────────────────┬───────────────────────────────┘
|
| 455 |
-
│
|
| 456 |
-
▼
|
| 457 |
-
modules/diet_logger.py (记录新数据)
|
| 458 |
-
modules/meal_recommender.py (膳食指南约束)
|
| 459 |
-
modules/nutrition_analyzer.py (DRIs 对比)
|
| 460 |
-
│
|
| 461 |
-
▼
|
| 462 |
-
次日 loop.py 再次启动 → 循环
|
| 463 |
-
```
|
| 464 |
-
|
| 465 |
-
> **批注:** ________
|
| 466 |
-
|
| 467 |
-
---
|
| 468 |
-
|
| 469 |
-
## 七、技术栈一览
|
| 470 |
-
|
| 471 |
-
| 层级 | 技术 | 用途 |
|
| 472 |
-
|------|------|------|
|
| 473 |
-
| 核心模型 | **MiniCPM-o 4.5**(统一多模态模型) | 全双工推理:视觉+语音+文本+语音输出 |
|
| 474 |
-
| 前端框架 | **Gradio 6.x** | Web 界面,实时对话交互 |
|
| 475 |
-
| 声纹识别 | Whisper-medium encoder embedding(优先)| 说话人身份识别 |
|
| 476 |
-
| 频谱方案 | NumPy + SoundFile(备选 fallback) | 声纹识别兜底 |
|
| 477 |
-
| 数据存储 | JSON + Markdown | 饮食记录 + 报告存档 + 每日简报 |
|
| 478 |
-
| 营养标准 | 三个 MD 文件(已下载) | 体重标准/膳食指南/DRIs |
|
| 479 |
-
| 循环引擎 | 自研状态机 + 插件注册 | 每日生命周期管理 |
|
| 480 |
-
| 部署平台 | HuggingFace Spaces(目前)+ Modal(后续) | 云端部署 |
|
| 481 |
-
|
| 482 |
-
> **批注:** ________
|
| 483 |
-
|
| 484 |
-
---
|
| 485 |
-
|
| 486 |
-
## 八、待讨论问题清单
|
| 487 |
-
|
| 488 |
-
> 📝 下表汇总了本文档中需要你决策的问题。请在 `____` 处标注你的意见(如需更多讨论,在对应小节批注区补充)。
|
| 489 |
-
|
| 490 |
-
| # | 问题 | 你的选择 / 意见 |
|
| 491 |
-
|---|------|----------------|
|
| 492 |
-
| 1 | **Loop 启动时机**:Gradio 启动时先跑完 Loop 总结阶段再开服务(A)/ 用户打开页面时才触发(B) | ________ |
|
| 493 |
-
| 2 | **简报承载形式**:JSON 文件落盘作为中介(A)/ 仅内存变量不落盘(B) | ________ |
|
| 494 |
-
| 3 | **Loop 复杂度边界**:只需管好每日生命周期 3~5 个阶段,约 200~300 行(同意)/ 需要更复杂(请说明) | ________ |
|
| 495 |
-
| 4 | **插件优先级**:DietSummary P0 / WeightCheck P0 / DRIAnalysis P0 / BriefingGenerator P0 / PresetWriter P1(需要调整?) | ________ |
|
| 496 |
-
| 5 | **体重数据来源**:用户手动输入(A)/ 语音对话 AI 询问记录(B)/ 蓝牙同步未来扩展(C) | ________ |
|
| 497 |
-
| 6 | **core/ 层处理**:现阶段 Loop 直接调用 modules/ 规则引擎,core/ 保持空接口等 MiniCPM-o(同意?) | ________ |
|
| 498 |
-
| 7 | **还有什么我没考虑到的问题?** | ________ |
|
| 499 |
-
|
| 500 |
-
---
|
| 501 |
-
|
| 502 |
-
*文档生成时间:2026-06-08 | v4 - 引入 Loop 驱动架构 + 三文档操作方案*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/deploy_success_report.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 PregoPal × MiniCPM-o-4_5 Modal 部署成功报告
|
| 2 |
+
|
| 3 |
+
> **状态**: ✅ 已成功部署到 Modal (T4 GPU)
|
| 4 |
+
> **日期**: 2026-06-09 ~ 2026-06-10
|
| 5 |
+
> **API URL**: `https://andrew-jiabin--prego-pal-minicpm-serve.modal.run`
|
| 6 |
+
> **GPU**: T4 (16GB VRAM, $0.50/hr) ← 从 A100 ($1.50/hr) 降级
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 1. 最终架构
|
| 11 |
+
|
| 12 |
+
```
|
| 13 |
+
┌─────────────┐ HTTP ┌──────────────────────────────────┐
|
| 14 |
+
│ prego-pal │ ──────────→ │ Modal T4 (asgi_app) │
|
| 15 |
+
│ (Gradio UI) │ │ ├── FastAPI (端点包装) │
|
| 16 |
+
└─────────────┘ │ └── llama-cpp-python (CUDA) │
|
| 17 |
+
│ ↕ │
|
| 18 |
+
│ Modal Volume (GGUF 模型持久化) │
|
| 19 |
+
└──────────────────────────────────┘
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
## 2. 关键决策与经验
|
| 23 |
+
|
| 24 |
+
### 2.1 预编译 wheel vs 源码编译
|
| 25 |
+
|
| 26 |
+
| 方案 | 构建时间 | 稳定性 | 最终选择 |
|
| 27 |
+
|------|---------|--------|---------|
|
| 28 |
+
| **预编译 llama-cpp-python wheel** | ~1min ✅ | 稳定 ✅ | **✅ 采用** |
|
| 29 |
+
| 源码编译 llama.cpp + llama-server | ~8min | 易踩坑 | ❌ 放弃 |
|
| 30 |
+
|
| 31 |
+
**核心经验**: 只要最终目标是 Python 调用,`llama-cpp-python` 的预编译 CUDA wheel (`extra-index-url=https://ggml-org.github.io/llama-cpp-python/whl/cu121`) 就足够了,完全不需要走源码编译路线。
|
| 32 |
+
|
| 33 |
+
### 2.2 为什么预编译 wheel 能跑
|
| 34 |
+
|
| 35 |
+
```
|
| 36 |
+
deploy.py (FastAPI) → llama-cpp-python (Llama class)
|
| 37 |
+
↕
|
| 38 |
+
MiniCPM-o-4_5 GGUF (Q4_K_M)
|
| 39 |
+
↕
|
| 40 |
+
CUDA (T4 GPU, n_gpu_layers=-1)
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
- llama-cpp-python 内部封装了完整的 llama.cpp C++ 后端
|
| 44 |
+
- 通过 `mmproj=` 参数挂载视觉投影层,支持多模态
|
| 45 |
+
- GGUF 格式量化模型 (Q4_K_M) 在 T4 上推理速度 ~15-25 tok/s
|
| 46 |
+
|
| 47 |
+
### 2.3 T4 降级可行
|
| 48 |
+
|
| 49 |
+
| 资源 | 用量 | T4 容量 |
|
| 50 |
+
|------|------|---------|
|
| 51 |
+
| 主模型 (Q4_K_M) | ~4.7 GB | 16 GB |
|
| 52 |
+
| Vision mmproj | ~0.5 GB | |
|
| 53 |
+
| KV Cache (8192 ctx) | ~1 GB | |
|
| 54 |
+
| **总计** | **~6.2 GB** | **余量 >60%** |
|
| 55 |
+
|
| 56 |
+
### 2.4 镜像构建关键
|
| 57 |
+
|
| 58 |
+
```python
|
| 59 |
+
_image = (
|
| 60 |
+
Image.debian_slim(python_version="3.11")
|
| 61 |
+
.pip_install("fastapi", "uvicorn[standard]", "httpx", "numpy", "Pillow")
|
| 62 |
+
.pip_install(
|
| 63 |
+
"llama-cpp-python", # ← 预编译 wheel
|
| 64 |
+
extra_index_url="https://ggml-org.github.io/llama-cpp-python/whl/cu121",
|
| 65 |
+
)
|
| 66 |
+
.run_commands(
|
| 67 |
+
"python -c 'import llama_cpp; print(\"llama-cpp-python OK\")'",
|
| 68 |
+
)
|
| 69 |
+
)
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**构建时间**: ~45秒(纯 pip install,无编译)
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
## 3. 最终 deploy.py 核心逻辑
|
| 77 |
+
|
| 78 |
+
```python
|
| 79 |
+
# deploy.py 中的关键参数
|
| 80 |
+
@app.function(
|
| 81 |
+
image=_image,
|
| 82 |
+
volumes={MODEL_DIR: model_volume}, # 挂载 Volume
|
| 83 |
+
gpu="T4", # ← T4 降级
|
| 84 |
+
timeout=1200,
|
| 85 |
+
scaledown_window=300, # 5min 空闲缩容
|
| 86 |
+
)
|
| 87 |
+
@modal.concurrent(max_inputs=10) # 10 并发
|
| 88 |
+
@asgi_app()
|
| 89 |
+
def serve():
|
| 90 |
+
# 模型加载
|
| 91 |
+
llm = Llama(
|
| 92 |
+
model_path="/models/MiniCPM-o-4_5-gguf/MiniCPM-o-4_5-Q4_K_M.gguf",
|
| 93 |
+
mmproj="/models/MiniCPM-o-4_5-gguf/vision/MiniCPM-o-4_5-vision-F16.gguf",
|
| 94 |
+
n_gpu_layers=-1, # 全部层到 GPU
|
| 95 |
+
n_ctx=8192, # 8K 上下文
|
| 96 |
+
verbose=False,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# 暴露端点: POST /v1/chat/completions (OpenAI 兼容)
|
| 100 |
+
# POST /v1/vision (多模态)
|
| 101 |
+
# GET /health (健康检查)
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
完整代码见 `modal_deploy/deploy.py`。
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## 4. 踩坑与修复
|
| 109 |
+
|
| 110 |
+
### 坑①:`find_mmproj_files()` 搜索到 9 个 GGUF
|
| 111 |
+
|
| 112 |
+
**症状**: 旧代码递归搜索所有 `.gguf` 文件并全部作为 `--mmproj` 传入,llama-server 启动失败
|
| 113 |
+
|
| 114 |
+
**修复**: 显式指定 vision mmproj 路径,不自动搜索
|
| 115 |
+
|
| 116 |
+
```python
|
| 117 |
+
# ❌ 错误做法
|
| 118 |
+
mmproj_files = find_mmproj_files(MODEL_SUBDIR) # 找到 9 个 GGUF!
|
| 119 |
+
llama_server_cmd += [f"--mmproj", mmproj_file for mmproj_file in mmproj_files]
|
| 120 |
+
|
| 121 |
+
# ✅ 正确做法
|
| 122 |
+
VISION_MMPROJ = "vision/MiniCPM-o-4_5-vision-F16.gguf"
|
| 123 |
+
if os.path.isfile(os.path.join(MODEL_SUBDIR, VISION_MMPROJ)):
|
| 124 |
+
kwargs["mmproj"] = os.path.join(MODEL_SUBDIR, VISION_MMPROJ)
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
### 坑②:源码编译 llama.cpp 构建超 8 分钟
|
| 128 |
+
|
| 129 |
+
**症状**: `modal deploy` 时构建镜像耗时 458 秒,且 cmake 步骤容易超时
|
| 130 |
+
|
| 131 |
+
**修复**: 改用预编译 llama-cpp-python wheel,构建时间降至 ~45 秒
|
| 132 |
+
|
| 133 |
+
### 坑③:Volume 路径不匹配
|
| 134 |
+
|
| 135 |
+
**症状**: 模型路径 `/MiniCPM-o-4_5-gguf/...` 需要子目录前缀
|
| 136 |
+
|
| 137 |
+
**修复**:
|
| 138 |
+
```python
|
| 139 |
+
MODEL_SUBDIR = f"{MODEL_DIR}/MiniCPM-o-4_5-gguf"
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
### 坑④:500 Internal Server Error
|
| 143 |
+
|
| 144 |
+
**症状**: 模型加载成功但 API 返回 500
|
| 145 |
+
|
| 146 |
+
**修复**: 增加 try/except + traceback 日志��定位到参数拼写错误
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
## 5. 测试结果
|
| 151 |
+
|
| 152 |
+
| 测试项 | 结果 |
|
| 153 |
+
|--------|------|
|
| 154 |
+
| 中文对话 | ✅ "你好,我是 MiniCPM-o" |
|
| 155 |
+
| 英文对话 | ✅ "Paris" |
|
| 156 |
+
| 健康检查 GET /health | ✅ 200 |
|
| 157 |
+
| 模型列表 GET /v1/models | ✅ 200 |
|
| 158 |
+
| Streaming SSE | ✅ 逐 token 输出 |
|
| 159 |
+
| 冷启动时间 | ~40-60s |
|
| 160 |
+
| 推理速度 (T4) | ~15-25 tok/s |
|
| 161 |
+
|
| 162 |
+
---
|
| 163 |
+
|
| 164 |
+
## 6. 命令行速查
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
# 部署
|
| 168 |
+
modal deploy modal_deploy.deploy
|
| 169 |
+
|
| 170 |
+
# 开发模式
|
| 171 |
+
modal serve modal_deploy.deploy
|
| 172 |
+
|
| 173 |
+
# 测试推理
|
| 174 |
+
modal run modal_deploy.deploy::test_inference
|
| 175 |
+
|
| 176 |
+
# 查看日志
|
| 177 |
+
modal app logs prego-pal-minicpm
|
| 178 |
+
|
| 179 |
+
# 上传模型
|
| 180 |
+
modal volume put minicpm-o-4_5-models ./models/MiniCPM-o-4_5-gguf /
|
| 181 |
+
|
| 182 |
+
# 查看 Volume
|
| 183 |
+
modal volume ls minicpm-o-4_5-models /MiniCPM-o-4_5-gguf
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
## 7. 成本对比
|
| 187 |
+
|
| 188 |
+
| GPU | 单价/小时 | 每次请求(2s) | 每月 1000 次 |
|
| 189 |
+
|-----|----------|-------------|-------------|
|
| 190 |
+
| **T4** (当前) | **$0.50** | **$0.00028** | **$0.28** |
|
| 191 |
+
| A100 (旧) | $1.50 | $0.00083 | $0.83 |
|
| 192 |
+
| **节省** | **67%** | **67%** | **67%** |
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## 8. 文件清单
|
| 197 |
+
|
| 198 |
+
| 文件 | 说明 |
|
| 199 |
+
|------|------|
|
| 200 |
+
| `modal_deploy/deploy.py` | **主部署文件** — FastAPI + llama-cpp-python |
|
| 201 |
+
| `modal_deploy/client.py` | Python API 客户端 (OpenAI 兼容) |
|
| 202 |
+
| `modal_deploy/README.md` | 部署文档 |
|
| 203 |
+
| `core/model_loader.py` | 项目集成层 (默认走远端 API) |
|
| 204 |
+
| `_test_inference.py` | 测试脚本 |
|
| 205 |
+
| `docs/部署经验_Modal_MiniCPM-o.md` | 旧版部署经验(含源码编译方案) |
|