@harrylabsj/chichulaide
v0.1.1
Published
吃出来的 AI 后端代理服务
Readme
吃出来的 AI 后端代理
这是 App 的本地/服务器端 AI 代理。App 只调用这个代理,模型 API Key 放在后端环境变量里,不放在手机里。
结论
- Claude 视觉模型可以通过 Anthropic API 调用,但当前 App 不能直接调用。App 现在只会调用你配置的后端代理。
- DeepSeek 官方
deepseek-v4-pro是文本/推理模型,官方模型表没有标注图像输入能力,不适合作为拍照识别模型。 - 拍照识别推荐国内视觉模型:阿里云百炼
qwen-vl-plus或智谱glm-4.5v。本代理默认使用qwen-vl-plus做图片识别,deepseek-v4-pro做文本解析。
启动
开发模式先用 mock,不需要 API Key:
cd ai_backend
node src/server.mjs本地真机测试建议使用根目录脚本,方便后台运行和查看状态:
# 在项目根目录
scripts/ai-backend-start.sh
scripts/ai-backend-status.sh
scripts/ai-backend-stop.sh等价 npm 命令:
cd ai_backend
npm run dev:start
npm run dev:status
npm run dev:stop脚本会把 pid 和日志写到根目录 .runtime/。该目录已被 git 忽略。
健康检查:
curl http://127.0.0.1:8787/health文本解析:
curl -X POST http://127.0.0.1:8787/v1/meal/parse-text \
-H 'Content-Type: application/json' \
-d '{"text":"午餐一个鸡蛋两个馒头三个肉饼","foods":[{"id":"egg","name":"鸡蛋","aliases":["水煮蛋"],"defaultAmount":1},{"id":"steamed_bun","name":"馒头","aliases":[],"defaultAmount":1},{"id":"meat_patty","name":"肉饼","aliases":[],"defaultAmount":1}]}'连接真实模型
阿里云百炼千问视觉:
cd ai_backend
AI_PROVIDER=dashscope \
AI_PROVIDER_API_KEY=你的百炼APIKey \
AI_TEXT_MODEL=deepseek-v4-pro \
AI_VISION_MODEL=qwen-vl-plus \
APP_AI_TOKEN=可选的App访问令牌 \
node src/server.mjs智谱 GLM-4.5V:
cd ai_backend
AI_TEXT_PROVIDER=deepseek \
AI_VISION_PROVIDER=bigmodel \
AI_PROVIDER_API_KEY=你的APIKey \
AI_TEXT_MODEL=deepseek-v4-pro \
AI_VISION_MODEL=glm-4.5v \
node src/server.mjs如果文本和视觉来自不同厂商,建议部署两个代理或后续拆成 AI_TEXT_API_KEY / AI_VISION_API_KEY。当前 MVP 先保持一个 API Key,便于本地测试。
App 配置
模拟器访问本机:
http://127.0.0.1:8787
iPhone 真机访问 Mac:
- 确保手机和 Mac 在同一 Wi-Fi。
- 查看 Mac 局域网 IP,例如
ipconfig getifaddr en0 || ipconfig getifaddr en1。 ai_backend/.env里把HOST设为0.0.0.0。- 在 App「我的」→「AI 服务」填写
http://<Mac局域网IP>:8787。 - 如果设置了
APP_AI_TOKEN,访问令牌填写同一个值。 - iOS MVP 真机测试保留局域网 HTTP;正式发布前后端应使用 HTTPS。
- Android release 默认只允许 HTTPS 和 localhost HTTP;局域网 HTTP 仅保留在 debug/profile 配置。
也可以在构建时注入配置:
flutter build ios --release \
--dart-define=AI_BACKEND_URL=http://<Mac局域网IP>:8787 \
--dart-define=AI_BACKEND_TOKEN=<APP_AI_TOKEN>API
与根目录 docs-ai-backend-proxy.md 保持一致:
POST /v1/meal/parse-textPOST /v1/meal/recognize-image
WorkBuddy 饮食教练 API(兼容/调试入口)
注意:v0.1 起 WorkBuddy Skill
chichulaide-meal-review为包内本地确定性计算(模块与 373 条食物数据随包上传,离线自包含),运行时不依赖本后端;部署本服务不是 Skill 上线或 Gate 0 的前置条件。下面两条 API 保留作兼容与本地调试入口(例如对比 Skill 本地输出与后端是否一致),与 Skill 共用同一份领域模块源码,输出语义完全一致。
面向 WorkBuddy Skill 的无状态接口,确定性计算,不调用外部模型,不记录 profile、mealText 或 token:
POST /v1/workbuddy/daily-targets:用最少个人信息(性别、年龄、身高、体重、活动水平、目标)估算每日营养所需。严格校验 profile:缺字段/非法枚举返回 400;出现 name、phone、city、idCard、medicalRecord 等无关敏感字段也返回 400。安全转介(未成年、孕哺)返回dailyTargets: null、energy: null、confidence: "low",不用成人 Mifflin 公式给数值;肾功能关注为 caution,仍返回保守目标。POST /v1/workbuddy/meal-review:接收profile、mealText(中文文字描述)、可选mealType、todayBefore(当天此前已累计营养)和dayComplete(当天记录是否已经完成),返回dailyTargets、recognizedItems、unrecognizedSegments、mealNutrition、daySoFar、assessment、action、assumptions、confidence、safety。每次只返回一个主要问题和一个行动原则(2–3 个中式饮食例子)。
评估范围(basis-aware):
- 未提供
todayBefore:assessment.scope = "meal",只评价当前这一餐,对照餐次参考份额(assessment.comparisonTargets = dailyTargets × 份额;早餐 25%、午餐 35%、晚餐 35%、加餐/夜宵 10%、未指定 33%)。单餐只标记明显过量(高脂/高钠按超出倍数取优先、高热量)与内容风险,不输出“今天蛋白质不足”这类全天判断;status同样只表达过量:proteinG/carbsG/fiberG固定为null(单餐不评估不足),calories/fatG/sodiumMg用上限型分类。 - 提供
todayBefore:assessment.scope = "day",按截至目前累计评估。默认只判断明显过量,proteinG/carbsG/fiberG状态为null;只有同时提供dayComplete: true,明确当天记录已经完成后,才判断 protein_low/fiber_low 等全天不足。 safety.level = "refer"时真实停止建议:assessment.type = "safety_referral"、status: null、action.examples = [],只给转介原则;保留已识别餐食与mealNutrition。confidence按最低可信度聚合:识别条目默认为 medium,整体通常为 medium;无识别或含低可信条目(标记“估算误差大”)时为 low,当前不产生 high。
未识别的食物只列入 unrecognizedSegments,不编造营养数字。安全分流规则(未成年、孕哺、进食障碍、急性症状、诊断/停药请求、肾异常)见 workbuddy/skills/chichulaide-meal-review/references/safety-boundaries.md。
领域层在 src/workbuddy/,与 Flutter App 的 lib/core/(UserProfile、EnergyEngine、NutritionTargetEngine、FoodTextParser、营养汇总、RecommendationEngine)核心规则对齐;改动 Flutter 行为不属于本层职责。有意分歧:多食物分段时 Node 端会剥掉下一个食物的引导份量词(Dart 端保留原行为),并支持“鸡蛋2个”这类食物后数量。
示例:
curl -X POST http://127.0.0.1:8787/v1/workbuddy/meal-review \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $APP_AI_TOKEN" \
-d '{"profile":{"gender":"male","age":35,"heightCm":175,"weightKg":80,"activityLevel":"light","goal":"fatLoss"},"mealText":"一碗米饭,一份红烧肉","mealType":"lunch"}'食物库导出与 Skill 同步
WorkBuddy 接口内置加载 ai_backend/data/food-database.json,由 App 的食物库确定性导出(373 个食物):
# 在项目根目录
dart run tool/export_food_database.dartlib/data/seed_foods.dart 或 lib/data/expanded_chinese_foods.dart 变更后需重新导出。领域模块(src/workbuddy/)或食物库变更后,还需同步到 Skill 包内:
node tool/sync_workbuddy_skill.mjsnpm test 会断言 Skill 包内副本与源逐字节一致。
WorkBuddy Skill 与专家
Skill(含调用脚本、包内计算模块与食物数据、schema、安全边界、示例)和专家系统提示词在根目录 workbuddy/,详见 workbuddy/README.md。
Pay Skill 支付入口(代码完成,待凭证/部署/实机联调)
POST /v1/workbuddy/pay/invoke:付费版 Skill(workbuddy/skills/chichulaide-meal-review-pay)的统一履约入口,body 固定为 {"operation":"daily-targets"|"meal-review","payload":{...}},首次与重试的 body 必须完全一致;首次请求还必须带 Idempotency-Key,生产请求需使用独立的 CHICHULAIDE_PAY_SOURCE_TOKEN 完成来源认证。
流程(SkillHub 官方商户改造最佳实践 + 微信支付 V3):
- 无
X-Out-Trade-No:先做领域预检(输入非法 400;安全转介 422NOT_ELIGIBLE_FOR_PAID_REVIEW;无识别食物 422INSUFFICIENT_INPUT,均不创建订单、不调支付通道)→ 微信 Native 下单(含必填appid、notify_url)得code_url→ SkillHub X402 preorder 得payment_code→ 返回 HTTP 402:HeaderWeixinPay-Required的值就是 payment_code,另有X-Out-Trade-No;Body 含code=PAYMENT_REQUIRED、out_trade_no、amount(元,两位小数字符串)+amount_total(分)+currency,以及精确大小写的WeixinPay提示块(内含WeixinPay-Required: payment_code与官方 prompt)。 - Agent 调用
weixinpay_pay(payment_code 作为paymentCode)完成支付,用语义完全相同的业务 body(字段值不可改)携带两个 Header 重试,两者缺一不可。 - 服务端先对 payment_code 做 SHA256 摘要 constant-time 比对(缺失 401、不匹配 409),再按
out_trade_no查单,trade_state=SUCCESS且订单号、应用、金额、商户和币种全部一致才计算并交付;重复履约返回首次固化结果(already_fulfilled=true)。 out_trade_no≤ 32 位;payment_code最长 15 分钟(L2expires_at为 Unix 秒级时间戳字符串)。过期语义:已 fulfilled 的订单重试直接重算返回(不查单、不受过期影响);pending 订单查单为 SUCCESS 即使超过 15 分钟也允许首次履约;只有 pending 且查单非 SUCCESS 且已过期才返回 410。串单 409、金额/商户不符 409。
实现要点(src/pay/):
- X402 L2(skill_info/pay_type=SKILL_PAY/pay_mode=AUTH_AND_PAY/pay_items/expires_at,标准 Base64);L1 签名串严格为
POST\n/palmpayminiapp/clawagentpay/preorder\n{timestamp}\n{nonce}\n{payment_required}\n,RSA SHA256 PKCS#1 v1.5,SkillHub 开发者私钥。 - 微信支付 V3 请求签名(商户私钥)与响应验签(平台公钥 +
Wechatpay-Serial与配置平台证书序列号比对)均为 Node crypto 真实实现;三类密钥严格区分;错误统一脱敏,不输出密钥细节。 - 文件订单存储(单实例):目录 0700、文件 0600、原子写、每订单排他锁;只持久化订单元数据、payload SHA256、支付凭证(用于幂等恢复)及固化结果,不保存原始 profile、mealText、私钥。历史订单按保留期清理并有文件数上限。多实例/多机生产部署必须替换为数据库事务实现。
notify_url目前仅作为微信下单必填配置,仓库尚未实现支付回调解密/处理,履约依赖重试时主动查单;生产上线建议(微信侧也要求)补回调作为到账确认通道。- 来源认证:
APP_AI_TOKEN不用作公开 Pay Skill 凭证;后端默认要求独立CHICHULAIDE_PAY_SOURCE_TOKEN,仅在明确接受公开调用风险时才允许CHICHULAIDE_PAY_ALLOW_UNAUTHENTICATED_SOURCE=true。SkillHub 官方签名方案明确后应替换当前 token 门禁。当前边界为 64KB body 限制 + 每来源限流(30 次/分钟)+ 订单 15 分钟过期 + 查单后履约。应用默认不信任客户端可伪造的X-Forwarded-For;反向代理部署需用CHICHULAIDE_PAY_TRUSTED_PROXY_IPS明确列出 TCP 对端代理 IP,并保证代理覆盖或追加真实来源地址,同时在代理层独立限流。
配置见 .env.example(CHICHULAIDE_PAY_*,全部使用占位符,仓库不含真实密钥);未启用时该入口返回 503,其余接口不受影响。付费 Skill 侧部署说明见 workbuddy/skills/chichulaide-meal-review-pay/references/setup.md——未配置真实 HTTPS URL 不可提交审核。
Hermes MCP 适配
src/mcp/server.mjs 是零新增依赖的 stdio MCP 服务,Hermes 可直接启动并发现以下工具:
chichulaide_capabilities:版本、能力、支付配置和安全边界。chichulaide_daily_targets:本地确定性每日营养目标。chichulaide_meal_review:本地确定性中文餐食复盘。chichulaide_paid_invoke:转发到CHICHULAIDE_PAY_API_URL,首次调用需提供并复用idempotencyKey,保留首次 402 的paymentCode/outTradeNo,并支持支付后原 body 重试。该工具不会在本地绕过支付。
本地启动与协议测试:
cd ai_backend
npm run mcp:start注册到 Hermes(--args 必须放在最后):
hermes mcp add chichulaide \
--command "$(command -v node)" \
--args "$(pwd)/src/mcp/server.mjs"
hermes mcp test chichulaide若需要付费工具,在 --args 之前增加:
--env CHICHULAIDE_PAY_API_URL=https://你的域名/v1/workbuddy/pay/invoke \
--env CHICHULAIDE_PAY_SOURCE_TOKEN=与后端相同的独立来源令牌MCP stdout 只输出 JSON-RPC,诊断信息不得写入 stdout。Hermes 中首次可调用 chichulaide_capabilities 验证;本地免费工具不记录 profile/mealText,付费工具会把业务请求发送到所配置的 HTTPS 后端。
WorkBuddy 开放平台 Connector(未来版本,首发不启用)
远程 chichulaide-history Connector 位于 src/mcp/remote-server.mjs,使用 HTTPS streamableHttp 暴露 save_confirmed_meal、get_recent_meals、build_7day_report 和 delete_my_data 四个工具。它只接受当前 Token 对应的匿名主体,不接受 user_id;图片、原始聊天和未经确认的候选不会进入 Connector。首发专家不声明此依赖,也不启动该服务。
本地部署:
cd ai_backend
cp .env.example .env
# 设置 DATABASE_URL、CHICHULAIDE_SAFETY_RECEIPT_SECRET、HISTORY_PUBLIC_BASE_URL
npm run history:migrate
npm run history:provision-token -- tester
npm run mcp:remotesave_confirmed_meal 要求平台可信 safetyReceipt、domainVersion 和确认动作幂等键;没有可信安全凭证时默认返回 SAFETY_CHECK_UNAVAILABLE,不计算也不保存。公开发布前仍需 WorkBuddy 书面确认 OAuth/PKCE 或等效保护、原始消息安全事件来源、HTTPS、健康类目和回滚流程。
