# LangGraph 智能出题系统设计 **日期**: 2026-09-30 **状态**: 设计稿 **版本**: v1.0 --- ## 概述 ### 背景 当前五维自检问卷存在两个核心问题: 1. **题目数量不足**:固定 15 题,无法提供深度画像 2. **缺乏个性化**:所有用户看到相同题目,无法针对用户画像(如 body=58, 其余维度未测)进行定向出题 浠艾福五维主动健康理论强调:**测评不仅是打分,更是引导用户关注需要提升的维度**。因此需要一套能够基于用户历史数据、当前状态、系统使用行为动态生成题目的 AI 出题系统。 ### 目标 - 通过 LangGraph 实现 **智能、个性化** 的五维自检题目生成 - 确保生成的题目满足系统约束(至少 10 题、每维至少 1 题、E 选项跳过机制) - 与现有 15 题固定题库并存,作为 **"首次深度自检" 和 "再测进阶"** 的扩展选项 - 基于用户画像(body 有分/其他维度为 0)优先覆盖短板维度 ### 非目标 - 不替代现有的 15 题固定题库(`QUESTION_BANK`) - 不修改现有评分逻辑(`A=3/B=2/C=1/D=0/E=跳过`) - 不修改 `submitSelfCheck` 接口签名 - 不涉及前端页面重构 --- ## 1. 架构设计 ### 1.1 系统边界 ``` ┌────────────────────────────────────────────────────────────────────────┐ │ XAF 系统整体架构 │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ 小程序端 (Vue 2) │ │ └── pages/five-dimension/self-check.vue │ │ └── 调用 /api/family/self-check/ai-generate-questions │ │ └── 展示 AI 生成的题目卡片 │ │ └── 提交答案到 /api/family/self-check/submit │ │ │ ├────────────────────────────────────────────────────────────────────────┤ │ cfc-backend (Java Spring Boot) │ │ ├── FiveDimensionSelfCheckController │ │ │ └── POST /api/family/self-check/ai-generate-questions │ │ │ └── 调用 LangGraphService.generateQuestions() │ │ │ │ │ └── LangGraphService.java │ │ └── generateQuestions(userId, count, difficulty) │ │ └── HTTP POST to LangGraph service │ │ │ ├────────────────────────────────────────────────────────────────────────┤ │ cfc-langgraph (Python FastAPI + LangGraph) │ │ └── app/graphs/question_generation_graph.py │ │ ├── analyze_user_profile() 节点1:读取用户画像 │ │ ├── select_dimensions() 节点2:策略性选择维度 │ │ ├── generate_questions() 节点3:LLM 生成题目 │ │ ├── validate_questions() 节点4:格式校验 │ │ └── avoid_duplicates() 节点5:查重与去重 │ │ │ │ └── app/routers/langgraph.py │ │ └── POST /api/langgraph/generate-questions │ │ │ └────────────────────────────────────────────────────────────────────────┘ ``` ### 1.2 模块职责 | 模块 | 职责 | 技术栈 | |------|------|--------| | `FiveDimensionSelfCheckController` | API 入口,参数校验,调用 LangGraph | Java/Spring | | `LangGraphService` | HTTP 客户端,调用 LangGraph,解析响应 | Java/OkHttp | | `question_generation_graph.py` | LangGraph 工作流定义 | Python/LangGraph | | `langgraph.py (router)` | REST API 端点 | Python/FastAPI | | `SelfCheckQuestionVO` | 题目数据传输对象 | Java | --- ## 2. LangGraph 工作流设计 ### 2.1 状态定义 ```python from dataclasses import dataclass, field from typing import List, Optional, Dict, Any from datetime import datetime @dataclass class QuestionGenState: """问题生成状态""" # 输入 user_id: str family_member_id: Optional[str] = None dimensions: Optional[List[str]] = None # 如 ["body", "wisdom", "wealth", "action", "mind"] exclude_ids: Optional[List[int]] = None count: int = 20 difficulty_hint: str = "balanced" # balanced/easy/hard/contextual # 中间状态 user_profile: Dict[str, Any] = field(default_factory=dict) selected_dimensions: List[str] = field(default_factory=list) dim_allocation: Dict[str, int] = field(default_factory=dict) generated_questions: List[Dict] = field(default_factory=list) # 输出 output_questions: List[Dict] = field(default_factory=list) rationale: str = "" dimension_distribution: Dict[str, int] = field(default_factory=dict) # 元数据 start_time: float = 0.0 generation_time: float = 0.0 validation_errors: List[str] = field(default_factory=list) retry_count: int = 0 ``` ### 2.2 工作流图 ```mermaid graph TD A[analyze_user_profile] --> B[select_dimensions] B --> C[generate_questions] C --> D{validate_questions} D -->|失败| E[retry_count < 3?] E -->|是| C E -->|否| F[fill_missing_dimensions] F --> G[avoid_duplicates] D -->|通过| G G --> H[output] subgraph "用户画像分析" A --> A1[查询 five_dimension_scores] A --> A2[查询 five_dimension_self_checks] A --> A3[查询 tasks 完成率] A --> A4[构建 user_profile] end subgraph "维度选择策略" B --> B1[识别低分维度 ≤3] B --> B2[识别中高分维度 ≥7] B --> B3[计算 dim_allocation] end subgraph "LLM 生成" C --> C1[构造 prompt] C --> C2[调用 LLM] C --> C3[解析 JSON 响应] end subgraph "验证与格式化" D --> D1[检查每维至少 1 题] D --> D2[验证选项格式 A/B/C/D/E] D --> D3[验证维度代码合法] F --> F1[补充缺失维度的题目] end ``` ### 2.3 节点详细设计 #### 节点 1: `analyze_user_profile` ```python async def analyze_user_profile(state: QuestionGenState) -> QuestionGenState: """ 分析用户画像,确定出题重点 输入: user_id 输出: user_profile 字典 """ # 1. 查询五维分数 dim_scores = await fetch_five_dimension_scores(state.user_id) # 2. 查询历史自检记录 last_check = await fetch_latest_self_check(state.user_id) previous_question_ids = last_check.question_ids_json if last_check else [] # 3. 查询行为数据(任务完成率等) task_stats = await fetch_task_completion_stats(state.user_id) # 4. 构建画像 state.user_profile = { "dim_scores": dim_scores or {}, # {"body": 58, "mind": 0, ...} "last_check_date": last_check.created_at if last_check else None, "has_previous_check": last_check is not None, "previous_question_ids": previous_question_ids or [], "task_completion_rate": task_stats.get("completion_rate", 0), "pending_tasks_count": task_stats.get("pending_tasks", 0), } return state ``` **关键决策逻辑**: - 如果 `body >= 58` 且 `mind/wisdom/wealth/action ≈ 0` → 标记为 **"首次深度自检"** 模式,优先覆盖全部维度 - 如果已有历史记录 → 标记为 **"再测轮换"** 模式,避免重复题目 #### 节点 2: `select_dimensions` ```python async def select_dimensions(state: QuestionGenState) -> QuestionGenState: """ 基于用户画像选择维度分布策略 策略:低分维度 60% 题目,中高分维度 40% 题目 """ dim_scores = state.user_profile.get("dim_scores", {}) # 识别维度状态 all_dims = ["body", "wisdom", "wealth", "action", "mind"] low_dims = [d for d in all_dims if dim_scores.get(d, 0) <= 3] high_dims = [d for d in all_dims if dim_scores.get(d, 0) >= 7] zero_dims = [d for d in all_dims if dim_scores.get(d, 0) == 0] # 情况 A:用户从未自检(所有维度均为 0) if len(zero_dims) == 5: # 均衡覆盖,每维 4 题 state.dim_allocation = {d: 4 for d in all_dims} state.rationale = "用户首次自检,均衡覆盖五维" # 情况 B:部分维度未测(如 body=58, others=0) elif len(zero_dims) > 0: # 未测维度优先,每维 5 题 # 已有分数维度,每维 3 题 for dim in all_dims: if dim_scores.get(dim, 0) == 0: state.dim_allocation[dim] = 5 else: state.dim_allocation[dim] = 3 state.rationale = f"重点关注未测维度: {zero_dims}" # 情况 C:有部分历史数据 else: # 低分维度 60%,中高分 40% if len(low_dims) > 0: low_ratio = 0.6 else: low_ratio = 0.4 state.dim_allocation = calculate_dim_allocation( state.count, low_dims, high_dims, low_ratio ) state.rationale = f"基于历史数据,侧重低分维度: {low_dims}" return state ``` #### 节点 3: `generate_questions` ```python SYSTEM_PROMPT = """ 你是一个专业的五维主动健康评估问卷设计师,服务于浠艾福平台。 ## 五维理论约束(必须严格遵守) ### 维度定义 - **身(body)**: 物理健康——睡眠、运动、身体状态(五行:土) - **智(wisdom)**: 学习认知——成长型思维、知识输入(五行:金) - **富(wealth)**: 利他创富——财务素养、资源分享(五行:水) - **行(action)**: 人际和谐——关系、领导、社交(五行:木) - **心(mind)**: 精神情绪——情绪稳定、自我接纳(五行:火) ### 生克关系 - 相生:智→富→行→心→身→智 - 相克:行(克)身(克)富(克)心(克)智(克)行 ### 计分规则 - A=3分, B=2分, C=1分, D=0分 - E=跳过选项(不计入 answered 计数) - 每维度满分 9 分(3 题 × 3 分) - 等级判定:≥7 健康 / 4-6 留意 / ≤3 紧绷 ## 题目设计要求 ### 1. 情境化叙事 每道题必须有一个具体的生活/工作场景,而非直接询问事实。 ❌ 错误示例:"你每周运动几次?" ✅ 正确示例:"周末清晨,你本可以去跑步,但今天选择了..." ### 2. 中性语言 避免引导性问题,不暗示正确答案。 ❌ 错误示例:"你应该每天运动,你做到了吗?" ✅ 正确示例:"过去一周,你的运动安排是:" ### 3. 四选一格式 选项必须包含 A/B/C/D/E,E 代表"跳过/不适用"。 ### 4. 维度映射 每道题必须明确映射到一个维度代码:body/wisdom/wealth/action/mind(小写) ### 5. 子维度关联(可选) 部分题目可关联子维度(如 actionParentChild, driveAutonomy),用于后续细分分析。 ### 6. 避免重复 绝对不能与已知的 15 题固定题库重复,也不能与用户过往自检记录重复。 ## 输出格式(严格 JSON) ```json { "questions": [ { "id": 1, "dimension": "body", "text": "情境描述...", "options": { "A": "选项A文本", "B": "选项B文本", "C": "选项C文本", "D": "选项D文本", "E": "跳过" }, "sub_dim": null } ], "rationale": "生成 rationale", "dimension_distribution": { "body": 4, "wisdom": 4, "wealth": 4, "action": 4, "mind": 4 } } ``` """ async def generate_questions(state: QuestionGenState) -> QuestionGenState: """ 使用 LLM 生成题目 输入: user_profile, dim_allocation 输出: generated_questions 列表 """ # 构造 prompt prompt = build_generation_prompt(state) # 调用 LLM(通过 LangGraph 的 tool 或直接 HTTP 请求) llm_response = await call_llm(prompt) # 解析 JSON try: parsed = json.loads(llm_response) state.generated_questions = parsed.get("questions", []) state.rationale = parsed.get("rationale", "") state.dimension_distribution = parsed.get("dimension_distribution", {}) except json.JSONDecodeError: # 降级处理:使用模板生成保底题目 state.generated_questions = generate_fallback_questions(state) state.rationale = "LLM 解析失败,使用模板降级生成" return state ``` #### 2.3.4 节点 4: `validate_questions` ```python async def validate_questions(state: QuestionGenState) -> QuestionGenState: """ 验证生成的题目格式和内容 验证项: 1. 每维度至少 1 题 2. 选项格式 A/B/C/D/E 存在 3. 维度代码合法(body/wisdom/wealth/action/mind) 4. 题目文本非空 5. 不重复(与历史题目 ID 对比) """ errors = [] dim_counts = defaultdict(int) for q in state.generated_questions: # 验证维度 if q.get("dimension") not in VALID_DIMENSIONS: errors.append(f"题目 {q['id']} 维度代码无效: {q.get('dimension')}") continue dim_counts[q["dimension"]] += 1 # 验证选项 if not all(opt in q.get("options", {}) for opt in ["A", "B", "C", "D", "E"]): errors.append(f"题目 {q['id']} 选项格式不完整") # 验证文本 if not q.get("text", "").strip(): errors.append(f"题目 {q['id']} 文本为空") # 检查每维至少 1 题 for dim in VALID_DIMENSIONS: if dim_counts.get(dim, 0) < 1: errors.append(f"维度 [{dim}] 至少需要 1 题,当前 {dim_counts.get(dim, 0)} 题") state.validation_errors = errors state.generated_questions = state.generated_questions # 暂保留,后续去重时过滤 return state ``` #### 2.3.5 节点 5: `avoid_duplicates` ```python async def avoid_duplicates(state: QuestionGenState) -> QuestionGenState: """ 去重处理:确保不与历史题目重复 策略: 1. 如果历史题目 ID 在排除列表中 → 替换为该维度的新题目 2. 如果题目内容高度相似(语义相似度 > 0.8)→ 重新生成 3. 最多尝试 3 次 """ exclude_ids = set(state.user_profile.get("previous_question_ids", [])) duplicates_found = [] valid_questions = [] for q in state.generated_questions: if q["id"] in exclude_ids: duplicates_found.append(q["id"]) else: valid_questions.append(q) # 补充缺失的题目 if len(valid_questions) < state.count: missing_count = state.count - len(valid_questions) supplementary = await generate_supplementary_questions( state, missing_count, exclude_ids ) valid_questions.extend(supplementary) state.output_questions = valid_questions[:state.count] return state ``` ### 2.4 边/转移逻辑 ```python def question_gen_workflow(): """ 定义 LangGraph 工作流 """ workflow = StateGraph(QuestionGenState) # 添加节点 workflow.add_node("analyze_user_profile", analyze_user_profile) workflow.add_node("select_dimensions", select_dimensions) workflow.add_node("generate_questions", generate_questions) workflow.add_node("validate_questions", validate_questions) workflow.add_node("avoid_duplicates", avoid_duplicates) # 设置入口和出口 workflow.set_entry_point("analyze_user_profile") workflow.add_edge("analyze_user_profile", "select_dimensions") workflow.add_edge("select_dimensions", "generate_questions") workflow.add_edge("generate_questions", "validate_questions") # 条件边:验证通过后进入去重,失败则重试 workflow.add_conditional_edges( "validate_questions", lambda state: "retry" if state.validation_errors else "deduplicate", { "retry": "generate_questions", "deduplicate": "avoid_duplicates", } ) workflow.add_edge("avoid_duplicates", END) return workflow.compile() ``` --- ## 3. API 接口设计 ### 3.1 Java 后端接口 **新增端点**: `POST /api/family/self-check/ai-generate-questions` ```java @RestController @RequestMapping("/api/family/self-check") public class FiveDimensionSelfCheckController { @Resource private LangGraphService langGraphService; @Operation(summary = "AI生成五维自检题目(个性化)") @PostMapping("/ai-generate-questions") public Result> aiGenerateQuestions( @RequestAttribute("userId") Long userId, @RequestBody(required = false) Map body) { if (userId == null) { return Result.error("请先登录"); } try { // 1. 解析参数 Integer count = body != null && body.get("count") != null ? Integer.valueOf(body.get("count").toString()) : 20; String difficulty = body != null && body.get("difficulty") != null ? body.get("difficulty").toString() : "balanced"; List dimensions = body != null && body.get("dimensions") != null ? (List) body.get("dimensions") : null; // 2. 调用 LangGraph Map result = langGraphService.generateQuestions( userId, count, difficulty, dimensions ); // 3. 转换为 VO @SuppressWarnings("unchecked") List questions = (List) result.get("questions"); return Result.success(questions); } catch (IllegalArgumentException e) { return Result.error(e.getMessage()); } catch (Exception e) { log.error("AI 生成题目失败: {}", e.getMessage(), e); return Result.error("生成失败,请稍后重试"); } } } ``` **LangGraphService.java**(新增服务类): ```java @Service public class LangGraphService { private static final Logger log = LoggerFactory.getLogger(LangGraphService.class); @Value("${langgraph.base.url:http://127.0.0.1:9000}") private String langGraphBaseUrl; /** * 调用 LangGraph 生成五维自检题目 */ public Map generateQuestions( Long userId, Integer count, String difficulty, List dimensions) { // 构造请求体 Map requestBody = new HashMap<>(); requestBody.put("user_id", String.valueOf(userId)); requestBody.put("count", count); requestBody.put("difficulty", difficulty); if (dimensions != null && !dimensions.isEmpty()) { requestBody.put("dimensions", dimensions); } // 调用 LangGraph String url = langGraphBaseUrl + "/api/langgraph/generate-questions"; String response = HttpUtils.post(url, requestBody); // 解析响应 try { Map respMap = objectMapper.readValue(response, Map.class); return respMap; } catch (Exception e) { log.error("解析 LangGraph 响应失败: {}", e.getMessage()); throw new RuntimeException("AI 服务异常", e); } } } ``` ### 3.2 Python LangGraph 接口 **新增端点**: `POST /api/langgraph/generate-questions` ```python # app/routers/langgraph.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List, Optional, Dict, Any router = APIRouter() class GenerateQuestionsRequest(BaseModel): user_id: str count: int = 20 difficulty: str = "balanced" # balanced/easy/hard/contextual dimensions: Optional[List[str]] = None # 可选,如 ["body", "wisdom"] exclude_ids: Optional[List[int]] = None class GenerateQuestionsResponse(BaseModel): questions: List[Dict[str, Any]] total_generated: int generation_time: float rationale: str dimension_distribution: Dict[str, int] @router.post("/generate-questions") async def generate_questions(request: GenerateQuestionsRequest) -> GenerateQuestionsResponse: """ AI 生成五维自检题目 请求体: - user_id: 用户 ID - count: 生成题目数量,默认 20 - difficulty: 题目难度/复杂度,默认 "balanced" - dimensions: 指定维度,可选 - exclude_ids: 需要排除的题目 ID 响应体: - questions: 题目列表 - total_generated: 实际生成数量 - generation_time: 生成耗时(秒) - rationale: 生成 rationale - dimension_distribution: 各维度题目数量分布 """ try: # 构建状态 state = QuestionGenState( user_id=request.user_id, count=request.count, difficulty_hint=request.difficulty, dimensions=request.dimensions, exclude_ids=request.exclude_ids, start_time=time.time() ) # 执行工作流 result = await question_gen_workflow().ainvoke(state) # 构造响应 response = GenerateQuestionsResponse( questions=result["output_questions"], total_generated=len(result["output_questions"]), generation_time=result["generation_time"], rationale=result["rationale"], dimension_distribution=result["dimension_distribution"] ) return response except Exception as e: log.error(f"AI 生成题目失败: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}") ``` --- ## 4. 题目示例(20 道设计稿) 以下是为 **body=58, 其余维度为 0** 的用户设计的示例题目,体现"情境化"和"针对性": ### 4.1 身 (body) — 4 题 **Q1**(已有:Q1,不重复,场景变化) - 文本:深夜,明明很困倦却怎么也入睡不着,这种情况在最近两周每周发生几次? - A: 几乎每晚都这样 · B: 3-4 次/周 · C: 1-2 次/周 · D: 几乎不会 · E: 跳过(无睡眠困扰) **Q2**(已有:Q2 的变体) - 文本:下班回家后,你通常如何放松身心? - A: 立刻换上睡衣,泡个热水澡或拉伸 · B: 散步 15 分钟,听首歌 · C: 躺沙发刷手机到凌晨 · D: 直接倒头就睡 · E: 跳过(每天运动后立刻睡) **Q3**(新增) - 文本:周末爬完楼梯后,你感觉腿部有酸痛感。这种酸痛: - A: 很正常,休息两天就好了 · B: 有点沉重,多喝水就好 · C: 持续两天以上,不太舒服 · D: 很严重,影响正常生活 · E: 跳过(平时不爬楼) **Q4**(新增) - 文本:这周你最自豪的身体状态是: - A: 精力充沛,随时都能去跑步 · B: 一般,有点累但能扛 · C: 经常觉得乏力 · D: 长期处于亚健康状态 · E: 跳过 ### 4.2 智 (wisdom) — 4 题 **Q5**(新增) - 文本:最近你主动学了什么新东西(不计工作必修)? - A: 看完一本专业书/学会一项新技能 · B: 刷了几篇行业文章 · C: 关注了几个新话题 · D: 一点都没有 · E: 跳过(无学习机会) **Q6**(新增) - 文本:遇到不会的技术问题,你的第一反应是: - A: 立刻谷歌/百度查 · B: 先试试自己的想法 · C: 问同事/朋友 · D: 放弃不做 · E: 跳过(从不遇到此类问题) **Q7**(新增) - 文本:上次有个朋友问你某个问题,你是怎么回答的? - A: 深度复盘,给了详细方案 · B: 大概说说要领 · C: 摇摇头,我也不太清楚 · D: 没有主动去帮 · E: 跳过(没人问过我) **Q8**(新增) - 文本:最近的挫折让你觉得: - A: "这次失败,下次一定能更好" · B: "也没什么,随它去" · C: "反正我不行,别指望我" · D: "这就是我的底线了" · E: 跳过(近期无挫折) ### 4.3 富 (wealth) — 4 题 **Q9**(新增) - 文本:上个月家里的固定开销(房租水电粮食等),你大概知道多少? - A: 很清楚,月月有明细账 · B: 大概知道,记得大头 · C: 知道大概范围 · D: 完全不记得 · E: 跳过(生活费由父母/配偶管理) **Q10**(新增) - 文本:除了主要工作的工资,家里还有其他收入来源吗? - A: 有,稳定且有增长(如租金/分红) · B: 有,但不稳定/波动大 · C: 正在尝试寻找 · D: 没有,也没想过 · E: 跳过(全靠单一工资) **Q11**(新增) - 文本:面对意外开销(比如几千块的医药费),你的应对是: - A: 从储蓄里轻松拿出 · B: 会有压力,但能拆东墙 · C: 会很被动,透支下月 · D: 根本没法应对 · E: 跳过(有全额医保覆盖) **Q12**(新增) - 文本:如果你手上有多余的资源(书/工具/联系人),遇到需要的人,你会: - A: 主动分享,甚至不求回报 · B: 想帮但怕麻烦,拖延 · C: 看在面子上勉强答应 · D: 直接拒绝,这东西对我来说很重要 · E: 跳过(手上真的没多余资源) ### 4.4 行 (action) — 4 题 **Q13**(新增) - 文本:周末的时候,你跟家里的长辈(父母/公婆)聊了些什么? - A: 深聊 2 小时以上,从年轻时事聊到未来规划 · B: 聊了 1 小时,多是家常琐事 · C: 说了几句问候就结束 · D: 避免话题尴尬,尽量不聊 · E: 跳过(不跟长辈住同一屋檐) **Q14**(新增) - 文本:最近遇到一件棘手的事(理发/车坏了/合同纠纷),你第一时间想找谁? - A: 直系亲属/挚友,毫不犹豫 · B: 同事/普通朋友,勉强求助 · C: 先在网上找攻略 · D: 独自硬扛,不想麻烦别人 · E: 跳过(最近无此类事件) **Q15**(新增) - 文本:跟伴侣/配偶上次出现分歧时,你的做法是: - A: 主动找时间坐下来好好谈 · B: 过两天气消了再聊 · C: 冷战,不再说话 · D: 直接大吵大闹 · E: 跳过(最近无此类冲突) **Q16**(子维度:actionParentChild,新增) - 文本:(如果有孩子)你每天有固定时间陪伴家人/孩子聊天吗? - A: 几乎每天 · B: 每周多次 · C: 偶尔 · D: 几乎没有 · E: 跳过(没有孩子) ### 4.5 心 (mind) — 4 题 **Q17**(新增) - 文本:在这个家里,你能做真实的自己吗? - A: 完全可以,随时随地都能 · B: 大部分时候可以,偶尔需要掩饰 · C: 有时候需要戴面具 · D: 常常觉得不能说真话 · E: 跳过(家庭关系特殊情况) **Q18**(新增) - 文本:你家有没有一些固定的"家庭仪式"(哪怕很小,比如睡前说晚安)? - A: 有,且一直在坚持 · B: 有,但不稳定,有时忘 · C: 以前有,现在没了 · D: 从来没有 · E: 跳过(个人选择) **Q19**(新增) - 文本:你对自己家庭的未来感觉是: - A: 乐观,方向明确,有计划 · B: 比较乐观,但有些不确定 · C: 有点焦虑,不知道怎么办 · D: 很焦虑,睡不着觉 · E: 跳过(无法预测/特殊处境) **Q20**(子维度:selfConceptOverall,新增) - 文本:你总体上接纳并喜欢现在的自己吗? - A: 非常接纳,就是现在的我最好 · B: 比较接纳,偶尔会有自卑感 · C: 一般,既有长处也有短处 · D: 不太接纳,总想改自己 · E: 跳过(心理原因导致接纳困难) --- ## 5. 系统集成要点 ### 5.1 与现有 15 题题库的关系 | 场景 | 使用的题目 | 说明 | |------|-----------|------| | **用户首次自检** | 15 题固定题库 + AI 生成 5 题 → 总计 20 题 | 满足系统"≥10 题"门槛 | | **用户再测** | AI 完全新的 20 题(通过 `exclude_ids` 避免重复) | 确保新鲜感 | | **系统默认流程** | 继续 15 题固定题库 | 向后兼容 | | **高级用户选项** | 可选"AI 深度自检"20 题 | 提供差异化体验 | ### 5.2 评分兼容性保证 1. **选项格式**:严格保持 `A/B/C/D/E` 格式,对应 `scoreOf()` 方法: - `"A"` → 3 分 - `"B"` → 2 分 - `"C"` → 1 分 - `D/E` → 0 分(E 跳过不计入 answered) 2. **维度代码**:必须为 `body/wisdom/wealth/action/mind`(小写),对应 `DIMENSION_META` 3. **计分公式**: ``` dimScore = round(sum * 3.0 / answered) capped at 9 totalScore = sum of all dimScore (max 45) ``` ### 5.3 降级策略 ``` LangGraph 服务不可用 ↓ 降级到 Java 本地题库生成(随机从 FULL_QUESTION_POOL 抽取 20 题) ↓ 仍需满足"每维至少 1 题"的约束 ``` **实现位置**:`LangGraphService.generateQuestions()` 中捕获异常后自动降级。 --- ## 6. 实施计划 ### 阶段 1: LangGraph 服务开发(2 周) - [ ] 在 `cfc-langgraph/app/graphs/` 新建 `question_generation_graph.py` - [ ] 实现 5 个节点的 Python 代码 - [ ] 添加 `/api/langgraph/generate-questions` 端点 - [ ] 编写单元测试(题目格式验证、维度分布验证) ### 阶段 2: Java 后端集成(1 周) - [ ] 新建 `LangGraphService.java` - [ ] 在 `FiveDimensionSelfCheckController` 新增 `POST /api/family/self-check/ai-generate-questions` - [ ] 实现降级逻辑(LangGraph 不可用时的本地题库生成) - [ ] 集成测试(端到端调用链) ### 阶段 3: 前端展示(1 周) - [ ] 在自检页面新增"AI 深度自检"入口按钮 - [ ] 实现题目卡片组件(20 题滚动展示) - [ ] 实现提交答案流程(与现有 `/submit` 兼容) - [ ] UI 文案设计(中文、鼓舞人心) ### 阶段 4: 文档与发布(1 周) - [ ] 编写用户操作指南 - [ ] 编写技术文档(API 规范、集成要点) - [ ] 内部 Beta 测试 - [ ] 灰度发布 --- ## 7. 风险评估 | 风险 | 等级 | 应对策略 | |------|------|----------| | AI 生成题目质量不达标 | 中 | 严格的 prompt 工程 + 自动化验证 + 人工复核首批次 | | 与现有 15 题冲突 | 低 | `exclude_ids` 机制 + 内容相似度检测 | | LangGraph 服务不可用 | 高 | 降级到 Java 本地题库生成 | | 用户界面体验差 | 中 | 与前端协同设计 + A/B 测试 | --- ## 8. 参考资料 - [五维能量系统设计](./2026-06-05-five-dimension-energy-design.md) - [五维五行哲学体系](./2026-06-08-five-dimension-wuxing-philosophy.md) - [API 参考文档](../api/API_REFERENCE.md) - [LangGraph 迁移设计](./2026-07-20-langgraph-migration-design.md) --- ## 附录 A: 与现有代码的关联 | 文件 | 关联点 | 说明 | |------|--------|------| | `FiveDimensionSelfCheckService.java` | `buildQuestionBank()` | 新增的 AI 题目不替换此方法,而是作为扩展 | | `FiveDimensionSelfCheckController.java` | 新增 `/ai-generate-questions` | 保留现有所有端点不变 | | `WuxingSourcingService.java` | `DIMENSION_META` | AI 生成题目需引用此映射 | | `langgraph-service` | 新增 `question_generation_graph.py` | 核心工作流实现 | | `SelfCheckQuestionVO.java` | 题目数据传输 | 无需修改,直接复用 | --- ## 附录 B: 关键设计决策记录 | 决策 | 结论 | 理由 | |------|------|------| | AI 题目是否替换 15 题固定题库? | **否** | 保持向后兼容,作为"可选深度模式" | | 每道题必须有 E 选项吗? | **是** | 符合系统 `E=skip` 逻辑 | | 子维度题目如何计分? | **A=100/B=66/C=33/D=0** | 沿用现有 `subScoreOf()` 逻辑 | | 降级策略是什么? | **本地题库随机抽取** | 保证服务可用性 | | 题目 ID 如何处理? | **AI 生成非连续 ID** | 避免与固定题库 (1-15) 冲突 | --- **文档状态**: 设计稿 **下一步**: 评审通过后开始阶段 1 实施 **负责人**: [待分配]