2026-08-28-plan-standard-content-design.md 8.0 KB

生成方案标准内容格式设计

日期: 2026-08-28 状态: 待审查 优先级: P1

一、背景与问题

现有健康方案生成链路存在三处脱节:

  1. AI 返回侧cfc-langgraph/app/api/adapter.pyPLAN_SYSTEM_PROMPT 已要求 LLM 输出结构化 JSON(overview + sections[].content/items + abnormal_indicators),但 JSON 解析靠 find("{")/rfind("}") 手动截取,无 Pydantic 校验,失败时静默降级为 raw 文本——前端需同时兼容两种形态。

  2. 呈现/编辑侧cfc-frontend/pages/health/health-plan-summary.vue 已支持分段确认 + 反馈式重生成,但 submitPlan 组装 planJson漏写 sections[].tasks(当前 schema 根本没有该字段),导致保存时丢失任务意图。规划师端 HealthPlanReview.vue 仅提供 planContent 纯文本编辑,无结构化任务条目 UI。

  3. 任务转化侧HealthPlanServiceImpl.generateDailyTasksFromPlan 用正则(TASK_PATTERN + 动作词分类)从 plan_content 纯文本猜测任务行,可靠性建立在"LLM 输出恰好符合正则预期"上。结构化的 plan_json 在此链路完全未被消费。前后端各维护一份重复正则(TASK_PATTERN 与前端 parseTasksFromPlan)。

目标:plan_json.sections[].tasks 成为任务生成的唯一事实源,AI 有标准格式返回、用户看到标准格式可编辑、结果确定性地转化为任务。

二、方案选型(用户已确认)

采用方案 A

  • LangGraph prompt 输出 tasks 数组 + Pydantic 校验
  • 校验失败时用现有正则从 content 兜底提取,仍写入 tasks
  • 任务生成优先读 planJson.sections[].tasks,缺失才回落正则
  • 存量方案用懒加载回填统一(每次生成任务时若发现无 tasks 则即时用正则回填并落库)

不做方案 B(完全移除正则,容错低)或方案 C(仅前端展示,未解决可靠性)。

三、数据契约

在现有 sections[].items 基础上,为每个 section 增加 tasks 数组(机器可读任务意图),与 content(人类可读 Markdown)分离:

{
  "overview": "总体概述",
  "sections": [
    {
      "key": "nutrition",
      "title": "营养补充建议",
      "content": "## 建议 1...(Markdown 展示文本)",
      "items": [{"name","dosage","timing","reason"}],
      "tasks": [
        {
          "action_type": "buy",
          "title": "购买维生素D3补充剂",
          "dimension": "wealth",
          "frequency": "once",
          "notes": "每日一粒,随餐服用"
        }
      ]
    }
  ],
  "abnormal_indicators": []
}

字段约定

字段 类型 说明
action_type string 枚举 buy/read/exercise/checkin/diet/activity,决定任务类别与后处理
title string 任务标题(最终写入 Task.title)
dimension string 枚举 五维维度 body/mind/wisdom/action/wealth
frequency string once=一次性 / daily=每日重复
notes string? 补充说明(可选)

action_type 与现有分类映射

action_type 现有 TaskDraft 分类 维度 特殊后处理
buy 购买任务 wealth 派生"每日使用/服用"子任务(parentTaskId)
read 阅读任务 wisdom
exercise 运动任务 body
checkin 打卡任务 mind
diet 饮食任务 body
activity 活动任务 action

四、LangGraph 层改造

文件: cfc-langgraph/app/api/adapter.pycfc-langgraph/app/schemas.py

  1. PLAN_SYSTEM_PROMPT 追加 tasks 输出契约:在每个 section 内强制输出 tasks 数组,明确枚举取值与 frequency 语义。

  2. 新增 Pydantic 模型app/schemas.py):

    from typing import List, Optional, Literal
    from pydantic import BaseModel
    
    class PlanTask(BaseModel):
    action_type: Literal["buy","read","exercise","checkin","diet","activity"]
    title: str
    dimension: Literal["body","mind","wisdom","action","wealth"]
    frequency: Literal["once","daily"] = "daily"
    notes: Optional[str] = None
    
    class PlanSection(BaseModel):
    key: str
    title: str = ""
    content: str = ""
    items: List[dict] = []
    tasks: List[PlanTask] = []
    
    class PlanResponse(BaseModel):
    overview: str = ""
    sections: List[PlanSection] = []
    abnormal_indicators: List[dict] = []
    
  3. 生成逻辑:LLM 调用用结构化输出(response_format={"type":"json_object"}),随后 PlanResponse.model_validate_json() 校验;校验失败 → 触发一次正则兜底重新提取 + 二次校验,仍失败则降级(raw 文本 + 前端兼容)。

  4. regenerate-section:反馈重生成改为返回 {content, tasks},前端同步更新,不再只回传纯字符串。

五、Java 层改造

文件: HealthPlanServiceImpl.javaDatabaseInitializer.javaschema.sql

5.1 任务生成优先读 tasks

generateDailyTasksFromPlan 新逻辑:

  1. 优先:遍历 plan_json.sections[].tasks[],按 action_type 直接映射现有 TaskDraft 分类。
  2. 兜底:该 section 无 tasks 数组(老数据/LLM 漏输出)→ 回落现有 TASK_PATTERN 正则从 content 提取。
  3. TaskDraft 的 dimension/frequency 直接取自 tasks[].dimension/tasks[].frequency,不再依赖正则猜维度。

5.2 存量方案懒加载回填(用户确认)

generateDailyTasksFromPlan 开头:

  • plan_json 中该 section 无 tasks 数组 → 用现有正则(TASK_PATTERN + 动作分类)从 content 提取任务,写入 plan_json.sections[].tasks落库updateById)。
  • 幂等:已回填的 section 跳过;重复调用不产生重复任务。

无需预跑全量脚本,零运维成本;存量方案在首次发布/确认时自动统一。

六、前端改造

文件: cfc-frontend/pages/health/health-plan-summary.vuecfc-web/src/views/teacher/HealthPlanReview.vue

6.1 家长端(只读展示 + 反馈式重生成)

  • 修复 bugsubmitPlan 组装 planJson 时补上 sections[].tasks(当前只写 overview+sections,漏 tasks)。
  • 展示无 tasks 时降级 review 模式(兼容)。
  • 反馈式重生成保留;regenerate 返回 tasks 时前端同步更新。

6.2 规划师端(结构化任务条目编辑)

  • HealthPlanReview.vue 当前只有 planContent 纯文本编辑,无 tasks UI → 新增任务条目级增删改(action_type/title/dimension/frequency/notes)。
  • 任务编辑直接改 plan_json.sections[].tasks,保存走现有 pending-review/update 接口(已是透传 planJson)。

七、测试与验证

单元测试

  • LangGraphPlanResponse 校验(合法/非法 action_type、frequency 越界)、正则兜底回填。
  • JavagenerateDailyTasksFromPlan 覆盖"有 tasks(优先走 tasks)"与"无 tasks(回落正则)"两分支;TaskDraft 映射正确性(buy→wealth+派生使用子任务等);懒加载回填幂等性。

验证命令

模块 命令
后端 cd cfc-backend && mvn clean compile + 针对性单测
LangGraph cd cfc-langgraph && pytest tests/ -v
cfc-web cd cfc-web && npm run build

手工验收

  1. 新方案 AI 生成含 tasks → 发布 → 任务正确生成且维度匹配。
  2. 存量无 tasks 老 planJson → 首次发布/确认自动回填 → 任务正常。
  3. 规划师 cfc-web 编辑任务条目 → 保存 → 重新发布反映改动。
  4. LLM 漏输出 tasks → 正则兜底仍能生成任务。

八、边界与注意事项

  • 正则 TASK_PATTERN 短期保留作兜底,不退役;等所有活跃方案走新格式后再评估退役。
  • 发现 getPlanList 中残留 SortUtil.applySort 调用(之前 revert 掉的分页+排序残留)——不在本设计范围内,仅提示后续清理。
  • 前端小程序需在 HBuilderX 重新打包验证(禁止 Agent 自行 npm run build:mp-weixin)。