|
|
@@ -0,0 +1,350 @@
|
|
|
+# AI 动态问卷引擎(通用引擎 + 菌群健康第一场景)设计
|
|
|
+
|
|
|
+> 日期:2026-08-14
|
|
|
+> 分支:cfclub
|
|
|
+> 状态:待评审
|
|
|
+> 技术选型:LangGraph(cfc-langgraph 服务,用户指定)
|
|
|
+
|
|
|
+## 1. 背景与目标
|
|
|
+
|
|
|
+现有问卷功能均为**静态形态**:
|
|
|
+
|
|
|
+| 现状 | 路径 | 局限 |
|
|
|
+|------|------|------|
|
|
|
+| 亲子关系问卷 | `relationship-questionnaire.vue` → `/api/family/questionnaire/*` → LangGraph `/api/v1/questionnaire/generate` | 一次性生成 8-12 题静态问卷,无动态交互、无画像、无知识库 |
|
|
|
+| 定期调研 | `survey-questionnaire.vue` → `SurveyService.aiGenerateQuestions` → `/api/v1/analysis/run` | 按模板/一次性生成 5 题,非对话式 |
|
|
|
+| 健康现状档案 | `health-status-form.vue` → `health_status` 表 | 固定字段表单(身高/血压/血糖…),非 AI 出题 |
|
|
|
+
|
|
|
+**用户需求**(已确认):
|
|
|
+
|
|
|
+1. 问卷不事先编好:从第一题开始,AI 根据用户回答**动态生成下一题**(逐题对话式)。
|
|
|
+2. AI 对问卷结果处理,生成**用户画像 + 需求画像**。
|
|
|
+3. 问卷内容**结合菌群知识库**(出题时 RAG 检索 + 画像分析时引用 + 降级兼容)。
|
|
|
+4. 做成**通用动态问卷引擎**:话题可配(菌群是第一个场景,未来可加睡眠/运动等)。
|
|
|
+5. AI 能力统一走 **LangGraph**(cfc-langgraph Python 服务),并写入开发规范。
|
|
|
+
|
|
|
+**设计目标**:
|
|
|
+
|
|
|
+- 引擎与场景解耦:引擎负责"动态出题 + 画像生成"通用机制,场景通过配置驱动(管理端可视化配置)。
|
|
|
+- 画像灵活 JSON 维度:场景定义维度,引擎不硬编码。
|
|
|
+- 画像三用途:小程序展示 + 入库(member 维度)+ 驱动推荐。
|
|
|
+- 复用现有 LangGraph 服务、ChromaDB 知识库(`cfc_knowledge` collection 已含 microbiome_ / dan_knowledge_ / article 三源)、AiGateway 熔断模式。
|
|
|
+
|
|
|
+## 2. 需求决策(头脑风暴确认)
|
|
|
+
|
|
|
+| # | 问题 | 决策 |
|
|
|
+|---|------|------|
|
|
|
+| 1 | 问卷定位 | **通用动态问卷引擎**,话题可配,菌群健康为第一场景 |
|
|
|
+| 2 | 交互形态 | **逐题对话式**:答一题 → AI 根据回答生成下一题 → … |
|
|
|
+| 3 | 画像结构 | **灵活 JSON 维度**:`user_profile[]` + `need_profile[]`,维度由场景配置定义,引擎不硬编码 |
|
|
|
+| 4 | 画像用途 | **三者都要**:展示给用户 + 存入数据库(member 维度)+ 驱动推荐 |
|
|
|
+| 5 | 结束条件 | **题数上限 + AI 判定 + 用户主动结束** 三者并存 |
|
|
|
+| 6 | 知识库结合 | **出题时 RAG 检索 + 画像分析时引用 + 检索失败降级纯 LLM** |
|
|
|
+| 7 | 场景配置方式 | **cfc-web 管理端可视化配置**(CRUD:场景名/开场引导/维度定义/知识库范围/题数上限) |
|
|
|
+| 8 | 填写者与关联 | **家庭成员(memberId)**,家长可代孩子填,画像存 member 维度 |
|
|
|
+| 9 | AI 技术栈 | **LangGraph**(cfc-langgraph),写入开发规范(AGENTS.md) |
|
|
|
+
|
|
|
+## 3. 目标架构
|
|
|
+
|
|
|
+**方案 1(已选):Java 会话管理 + LangGraph 无状态推理引擎**
|
|
|
+
|
|
|
+```
|
|
|
+小程序 (ai-questionnaire.vue 逐题对话)
|
|
|
+ │ POST /api/ai-questionnaire/*(JWT)
|
|
|
+ ▼
|
|
|
+Java 后端 (cfc-backend)
|
|
|
+ ├── AiQuestionnaireController ──→ AiQuestionnaireService
|
|
|
+ ├── ai_q_scene / ai_q_session / ai_q_profile(MySQL)
|
|
|
+ └── AiGateway.advanceQuestionnaire / generateProfile(熔断+超时)
|
|
|
+ │ HTTP(ai.etotem.com.cn / localhost:9000)
|
|
|
+ ▼
|
|
|
+LangGraph (cfc-langgraph, Python)
|
|
|
+ └── POST /api/v1/qna/advance(出题,快)
|
|
|
+ └── POST /api/v1/qna/profile(画像,慢 30-60s)
|
|
|
+ └── qna_graph: retrieve_knowledge → decide_next → generate_profile
|
|
|
+ └── ChromaDB cfc_knowledge(microbiome_/dan_knowledge_/article 三源)
|
|
|
+```
|
|
|
+
|
|
|
+**选型理由**:
|
|
|
+
|
|
|
+- 历史持久化可靠(DB 天然支持中断恢复、审计、回显历史)。
|
|
|
+- 引擎纯函数可测:`(场景配置, 历史) → 下一题/画像`,输入输出干净。
|
|
|
+- LangGraph 发挥价值:知识检索→推理→画像的图结构在服务端;Java 只做编排。
|
|
|
+- 会话/画像入库,复用 AiGateway 熔断模式;后端治理一致。
|
|
|
+- 避免"LangGraph 有状态会话"方案的内存态(MemorySaver 重启丢失)与额外持久化运维。
|
|
|
+- 避免"复用 analysis/run"方案的无法扩展与 RAG 集成残缺。
|
|
|
+
|
|
|
+**LangGraph 有状态方案(弃)**:MemorySaver 内存态重启丢失;持久化需引入 checkpoint store;Java 审计历史需同步。
|
|
|
+**复用 analysis/run 方案(弃)**:无状态机、RAG 集成残缺、与 LangGraph 技术路线冲突。
|
|
|
+
|
|
|
+## 4. 组件设计
|
|
|
+
|
|
|
+### 4.1 LangGraph 侧(cfc-langgraph,核心引擎)
|
|
|
+
|
|
|
+新增 `src/qna/` 模块(在既有 `src/` 问卷模块基础上演进),无状态纯函数:
|
|
|
+
|
|
|
+```
|
|
|
+qna_graph.py
|
|
|
+ START → load_scene → retrieve_knowledge → decide_next ──ask──→ END
|
|
|
+ │
|
|
|
+ └──finish──→ generate_profile → END
|
|
|
+
|
|
|
+qna_profile_graph.py(独立,供 /api/v1/qna/profile)
|
|
|
+ START → load_scene → retrieve_knowledge → generate_profile → END
|
|
|
+```
|
|
|
+
|
|
|
+| 节点 | 职责 |
|
|
|
+|------|------|
|
|
|
+| `load_scene` | 透传 Java 传入的 scene_config,组装 system prompt(含 opening_prompt、dimensions_json、kb_scope) |
|
|
|
+| `retrieve_knowledge` | 从 history 提取关键词(复用 `_extract_health_keywords` 思路)→ `RagRetriever.retrieve`(按 kb_scope 过滤 source 前缀)→ 注入知识上下文;检索失败/空 → 置空降级 |
|
|
|
+| `decide_next` | LLM 基于 (场景配置 + 知识 + 历史) 输出决策 JSON:`{"action":"ask"|"finish", "question":{...}, "reason":"..."}`;已达 max_questions → 强制 finish |
|
|
|
+| `generate_profile` | LLM 基于完整 history + 知识库 → 画像 JSON `{user_profile:[...], need_profile:[...]}` |
|
|
|
+
|
|
|
+**问题结构(Question)**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "id": "q3",
|
|
|
+ "type": "single | multi | scale | text",
|
|
|
+ "text": "您通常一周吃几次富含膳食纤维的食物(如全谷物、蔬菜、豆类)?",
|
|
|
+ "options": [{"id": "a", "label": "几乎不吃"}],
|
|
|
+ "scale": {"min": 0, "max": 10, "minLabel": "从不", "maxLabel": "每天"}
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- `single`/`multi` 必须带 `options`;`scale` 必须带 `scale`;`text` 不带两者。
|
|
|
+- 引擎校验 question 结构,非法 → 自动重试 1 次 → 兜底题。
|
|
|
+
|
|
|
+**画像结构(灵活 JSON 维度)**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "user_profile": [
|
|
|
+ {"dimension": "肠道菌群状态", "score": 72, "description": "……", "evidence": ["答1: ……"]}
|
|
|
+ ],
|
|
|
+ "need_profile": [
|
|
|
+ {"dimension": "营养需求", "description": "……", "evidence": ["……"], "suggestion": "……"}
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- 维度由场景 `dimensions_json` 定义,引擎只校验"数组 + 必填字段",不约束维度名。
|
|
|
+
|
|
|
+**端点契约**:
|
|
|
+
|
|
|
+`POST /api/v1/qna/advance`
|
|
|
+
|
|
|
+```json
|
|
|
+// 请求
|
|
|
+{
|
|
|
+ "scene": {
|
|
|
+ "scene_key": "microbiome",
|
|
|
+ "opening_prompt": "我将了解您的肠道健康状况……",
|
|
|
+ "dimensions_json": {"user": [...], "need": [...]},
|
|
|
+ "kb_scope": ["microbiome", "dan_knowledge"],
|
|
|
+ "max_questions": 12,
|
|
|
+ "system_prompt": "可选:场景自定义 system prompt"
|
|
|
+ },
|
|
|
+ "history": [{"question": {"type": "single", "text": "…"}, "answer": "…"}]
|
|
|
+}
|
|
|
+// 响应
|
|
|
+{
|
|
|
+ "action": "ask",
|
|
|
+ "question": {"id": "q4", "type": "single", "text": "…", "options": [...]},
|
|
|
+ "finished": false
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+`POST /api/v1/qna/profile`(请求同 advance,响应画像)
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "profile": {"user_profile": [...], "need_profile": [...]},
|
|
|
+ "kb_used": true
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**前提(风险项)**:cfc-langgraph 大部分源码文件当前为 0 字节(commit `f18dd86e` 清空),但 git 历史有完整版本(如 `2f685d22` 的 `app/api/adapter.py` 10356B、`app/main.py`、`app/rag/*`、`src/*` 问卷模块)。实现第一步:从 git 历史恢复最小可运行集(`app/config.py`、`app/rag/retriever.py`+`loader.py`+`splitter.py`+`embeddings.py`、`app/tools/java_client.py`、`app/tasks/knowledge_sync.py`、`app/middleware.py`、`app/main.py`、`src/llm/client.py`),验证 `uvicorn src.app:app` 可启动后,再新增 `src/qna/`。
|
|
|
+
|
|
|
+### 4.2 Java 侧(cfc-backend,编排与存储)
|
|
|
+
|
|
|
+**新 Entity/Mapper/Service/Controller**:
|
|
|
+
|
|
|
+- `entity/AiQScene.java`、`entity/AiQSession.java`、`entity/AiQProfile.java`
|
|
|
+- `mapper/AiQSceneMapper.java`、`AiQSessionMapper.java`、`AiQProfileMapper.java`
|
|
|
+- `service/AiQuestionnaireService.java` + `impl/AiQuestionnaireServiceImpl.java`
|
|
|
+- `controller/AiQuestionnaireController.java`(路由前缀 `/api/ai-questionnaire`)
|
|
|
+
|
|
|
+**AiGateway 扩展**(复用熔断/连接池):
|
|
|
+
|
|
|
+- `Map<String,Object> advanceQuestionnaire(Map scene, List history)` → `POST /api/v1/qna/advance`,超时 `python.timeout-ms`(15s)
|
|
|
+- `Map<String,Object> generateProfile(Map scene, List history)` → `POST /api/v1/qna/profile`,独立超时 90s(画像推理 30-60s),失败返回 null 由 Service 降级
|
|
|
+
|
|
|
+### 4.3 cfc-web 管理端
|
|
|
+
|
|
|
+- 新页面 `src/views/admin/AiQuestionnaireScenes.vue`:场景 CRUD
|
|
|
+ - 字段:scene_key、scene_name、description、opening_prompt、dimensions_json(JSON 编辑器)、kb_scope(多选:microbiome/dan_knowledge/article)、max_questions、system_prompt、enabled
|
|
|
+- 路由 + 菜单:`/admin/ai-questionnaire/scenes`(admin 权限)
|
|
|
+- `src/api/aiQuestionnaire.js` 接口封装
|
|
|
+
|
|
|
+### 4.4 小程序前端
|
|
|
+
|
|
|
+- 新页面 `pages/health/ai-questionnaire.vue`(逐题对话式):
|
|
|
+ - 场景列表选择(调用 `/api/ai-questionnaire/scene/list`)→ 选择成员 → start
|
|
|
+ - 展示当前题(单选/多选/量表/文本)→ 作答 → answer → 下一题(loading 态"AI 思考中…")
|
|
|
+ - 进度提示(已答 n/max)、主动结束按钮(确认弹窗)
|
|
|
+ - 若该成员有 running 会话 → 提示续答或重新开始
|
|
|
+- 新页面 `pages/health/ai-questionnaire-result.vue`(画像展示):
|
|
|
+ - 用户画像:维度条/雷达(score 0-100)+ description + evidence
|
|
|
+ - 需求画像:维度列表 + suggestion + 推荐入口按钮
|
|
|
+- 入口:`pages/health-main/index.vue` 或 `pages/body-detail/` 健康相关页加「AI 健康问卷」入口
|
|
|
+- `utils/api.js` 增加:`aiQStart`、`aiQAnswer`、`aiQFinish`、`aiQSceneList`、`aiQHistory`、`aiQProfileDetail`
|
|
|
+- 小程序限制:禁止可选链 `?.`、禁止 `:key` 表达式、日期用 `parseDate()`、Options API
|
|
|
+
|
|
|
+## 5. 数据库设计
|
|
|
+
|
|
|
+### 5.1 新表 `ai_q_scene`(场景配置)
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS ai_q_scene (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ scene_key VARCHAR(50) NOT NULL COMMENT '场景唯一标识(如 microbiome)',
|
|
|
+ scene_name VARCHAR(100) NOT NULL COMMENT '场景名称(如 菌群健康评估)',
|
|
|
+ description VARCHAR(500) DEFAULT NULL COMMENT '场景描述',
|
|
|
+ opening_prompt TEXT COMMENT '开场引导(AI 出第一题时的引导语)',
|
|
|
+ dimensions_json JSON COMMENT '画像维度定义 {user:[...], need:[...]}',
|
|
|
+ kb_scope VARCHAR(200) DEFAULT 'microbiome' COMMENT '知识库范围(逗号分隔 source 前缀)',
|
|
|
+ max_questions INT DEFAULT 12 COMMENT '题数上限',
|
|
|
+ system_prompt TEXT COMMENT '可选:场景自定义 system prompt',
|
|
|
+ enabled TINYINT DEFAULT 1 COMMENT '1=启用 0=禁用',
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ UNIQUE KEY uk_scene_key (scene_key)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 动态问卷-场景配置';
|
|
|
+```
|
|
|
+
|
|
|
+### 5.2 新表 `ai_q_session`(问卷会话)
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS ai_q_session (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ scene_id BIGINT NOT NULL COMMENT '场景ID',
|
|
|
+ user_id BIGINT NOT NULL COMMENT '填写者用户ID',
|
|
|
+ member_id BIGINT NOT NULL COMMENT '画像关联成员ID',
|
|
|
+ family_id BIGINT DEFAULT NULL COMMENT '家庭ID',
|
|
|
+ status VARCHAR(16) DEFAULT 'running' COMMENT 'running/finished/aborted',
|
|
|
+ history_json JSON COMMENT '已回答历史 [{question:{...}, answer:"..."}]',
|
|
|
+ current_question_json JSON COMMENT '当前待答题(LangGraph advance 返回、尚未回答)',
|
|
|
+ question_count INT DEFAULT 0 COMMENT '已答题数',
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ finished_at DATETIME DEFAULT NULL COMMENT '完成时间',
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ INDEX idx_member_status (member_id, status),
|
|
|
+ INDEX idx_scene (scene_id)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 动态问卷-会话';
|
|
|
+```
|
|
|
+
|
|
|
+### 5.3 新表 `ai_q_profile`(画像)
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS ai_q_profile (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ session_id BIGINT NOT NULL COMMENT '会话ID',
|
|
|
+ scene_id BIGINT NOT NULL COMMENT '场景ID',
|
|
|
+ member_id BIGINT NOT NULL COMMENT '成员ID',
|
|
|
+ user_profile_json JSON COMMENT '用户画像 [{dimension,score,description,evidence}]',
|
|
|
+ need_profile_json JSON COMMENT '需求画像 [{dimension,description,evidence,suggestion}]',
|
|
|
+ raw_result TEXT COMMENT 'LLM 原始输出(审计)',
|
|
|
+ kb_used TINYINT DEFAULT 0 COMMENT '是否使用了知识库',
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ UNIQUE KEY uk_session (session_id),
|
|
|
+ INDEX idx_member (member_id)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 动态问卷-画像结果';
|
|
|
+```
|
|
|
+
|
|
|
+### 5.4 迁移
|
|
|
+
|
|
|
+- `DatabaseInitializer.runMigrations()` 新增迁移(`CREATE TABLE IF NOT EXISTS` ×3,幂等 try-catch),迁移编号按现有最大编号递增。
|
|
|
+- 同步 `schema.sql`(完整快照)。
|
|
|
+- 种子数据:插入一个 `microbiome` 场景(scene_key=microbiome,菌群健康评估)便于演示。
|
|
|
+
|
|
|
+## 6. API 契约(Java 侧)
|
|
|
+
|
|
|
+统一 `@PostMapping`,`Result<T>` 响应,JWT 认证:
|
|
|
+
|
|
|
+| 接口 | 说明 | 请求 | 返回 |
|
|
|
+|------|------|------|------|
|
|
|
+| `POST /api/ai-questionnaire/scene/list` | 启用场景列表(登录可见) | `{}` | `List<AiQScene>` |
|
|
|
+| `POST /api/ai-questionnaire/scene/save` | 场景新增/更新(admin) | AiQScene | 保存后场景 |
|
|
|
+| `POST /api/ai-questionnaire/scene/delete` | 场景删除(admin) | `{id}` | `void` |
|
|
|
+
|
|
|
+> `scene/delete`:若该场景已有 `ai_q_session` 引用则拒绝物理删除(返回 `Result.error("该场景已有问卷记录,请改用禁用")`),无引用时可物理删;日常禁用推荐 `enabled=0`(scene/save 置 enabled 字段)。
|
|
|
+| `POST /api/ai-questionnaire/start` | 开始问卷 | `{sceneId, memberId}` | `{sessionId, question, answeredCount}` |
|
|
|
+| `POST /api/ai-questionnaire/answer` | 提交答案 → 下一题或结束 | `{sessionId, answer}` | `{action:"ask"\|"finish", question?, profile?, finished}` |
|
|
|
+| `POST /api/ai-questionnaire/finish` | 用户主动结束 → 生成画像 | `{sessionId}` | `{profile}` |
|
|
|
+| `POST /api/ai-questionnaire/history` | 成员问卷会话列表 | `{memberId, sceneId?}` | `List<AiQSession 摘要>` |
|
|
|
+| `POST /api/ai-questionnaire/profile/detail` | 画像详情 | `{sessionId}` | `AiQProfile` |
|
|
|
+| `POST /api/ai-questionnaire/abort` | 放弃会话(可选) | `{sessionId}` | `void` |
|
|
|
+
|
|
|
+**answer 内部流程**:
|
|
|
+
|
|
|
+1. 校验 session 存在且 `status=running`;校验 memberId 归属(家长可代填家庭成员)。
|
|
|
+2. 并发防护:更新时校验 `status='running'`,后到者返回"问卷已完成"。
|
|
|
+3. 取 `current_question_json` 为待答题,与本次 `answer` 组装 `{question, answer}` 追加到 `history_json`;`question_count+1`;`answer` 统一为**字符串**(单选=选中 label,多选=逗号分隔 labels,量表=数字字符串,文本=文本,前端提交前规整)。
|
|
|
+4. 若 `question_count >= max_questions` → 直接走画像生成(调 `/api/v1/qna/profile`)→ 存 `ai_q_profile` + session 置 `finished` → 返回 `{action:"finish", profile}`。
|
|
|
+5. 否则调 `/api/v1/qna/advance`:
|
|
|
+ - `action=ask` → 返回的新题存入 `current_question_json` → 返回 `{action:"ask", question}`。
|
|
|
+ - `action=finish` → 调 `/api/v1/qna/profile` → 存画像 + 置 finished → 返回 `{action:"finish", profile}`。
|
|
|
+ - 调用失败(熔断/超时/解析失败)→ 返回兜底题(保存为 `current_question_json`)+ 前端 toast 提示;画像失败 → `Result.error("画像生成失败,请稍后重试")`,session 保持 running,用户可重新 finish。
|
|
|
+6. 知识库检索失败由 LangGraph 内部降级(不阻塞)。
|
|
|
+
|
|
|
+> `start` 内部流程:查 scene → 建 session(status=running)→ 调 `/api/v1/qna/advance`(history 为空)→ 返回题存入 `current_question_json` → 返回 `{sessionId, question}`。
|
|
|
+
|
|
|
+## 7. 错误处理与边界
|
|
|
+
|
|
|
+| 场景 | 处理 |
|
|
|
+|------|------|
|
|
|
+| LangGraph 出题超时/熔断 | 返回兜底题(模板池轮换),toast 提示"AI 暂时繁忙,已使用备选题目" |
|
|
|
+| LangGraph 画像超时/失败 | `Result.error` 提示重试;session 保持 running 可重试 finish |
|
|
|
+| LLM 输出 JSON 解析失败 | 引擎内自动重试 1 次(换 temperature=0 确定性重试)→ 兜底题 |
|
|
|
+| question 结构非法 | 同上 |
|
|
|
+| 知识库检索失败/空 | 降级纯 LLM(`kb_used=false`) |
|
|
|
+| 并发 answer 同 session | 更新时校验 status,后到者返回"问卷已完成" |
|
|
|
+| 会话中断恢复 | 进入页面查询 member running 会话 → 从 DB history 续答 |
|
|
|
+| 成员权限 | 只能对本人或本人家庭内成员填写(复用 FamilyMemberService 校验) |
|
|
|
+| memberId 缺失 | `Result.error("请先完善家庭成员信息")`(复用 SurveyServiceImpl 的 resolveMemberId 思路) |
|
|
|
+| 画像维度/score 越界 | 引擎 validate 钳制(score 0-100);Java 存库前二次校验 JSON 结构 |
|
|
|
+
|
|
|
+## 8. 测试策略
|
|
|
+
|
|
|
+- **LangGraph(pytest)**:fake LLM 返回固定 JSON,验证:
|
|
|
+ - advance:正常 ask / 达上限强制 finish / JSON 非法重试 / 知识库注入(mock RagRetriever)
|
|
|
+ - profile:画像结构校验(user_profile/need_profile 必填字段)、kb_used 标记
|
|
|
+- **Java**:
|
|
|
+ - `mvn clean compile` 通过(唯一编译验证)
|
|
|
+ - Service 单测(可选):answer 流程(mock AiGateway)、并发防护、成员校验
|
|
|
+ - Controller 集成测试(mock LangGraph)
|
|
|
+- **前端**:`node --check` 语法校验(不打包,HBuilderX 打包)
|
|
|
+- **手工验证清单**:
|
|
|
+ 1. 管理端建场景 → 小程序可见
|
|
|
+ 2. start → 首题出现 → 逐题回答 → 下一题与历史回答相关
|
|
|
+ 3. 达题数上限 → 自动出画像
|
|
|
+ 4. 主动结束 → 画像生成 → 展示
|
|
|
+ 5. 中断会话 → 续答
|
|
|
+ 6. 停掉 LangGraph → 兜底题可继续答题
|
|
|
+ 7. 画像入库 → 历史查询可见 → 推荐入口可跳转
|
|
|
+
|
|
|
+## 9. 非目标(本次不做)
|
|
|
+
|
|
|
+- 不做流式输出(SSE)——逐题交互已是异步形态,画像生成可等待。
|
|
|
+- 不做画像驱动的自动推荐引擎——本期画像入库 + 展示,推荐入口跳转复用现有推荐接口(`AiGateway.recommend` / 商城搜索),画像标签对接推荐留待后续迭代。
|
|
|
+- 不做会话过期定时清理(DB 保留即可,后续可加)。
|
|
|
+- 不做 LangGraph 服务的全量源码恢复——只恢复 qna 所需最小集 + 验证可运行;其余模块(chat/report_parse/tongue 等)按生产需要单独处理。
|
|
|
+- 不做场景配置的版本历史/草稿。
|
|
|
+- 不做多语言。
|
|
|
+
|
|
|
+## 10. 开发规范更新(用户指定)
|
|
|
+
|
|
|
+将以下约定写入 AGENTS.md(根 + cfc-backend/AGENTS.md):
|
|
|
+
|
|
|
+> **AI 服务统一走 LangGraph**:所有 AI 能力(对话/问卷/画像/推荐/解析)基于 `cfc-langgraph` Python 服务(FastAPI + LangGraph + ChromaDB RAG,端口 9000,生产 `ai.etotem.com.cn`),Java 侧通过 `AiGateway` 调用(熔断 + 超时),禁止直接对接 Dify 之外的第三方 LLM 服务;新增 AI 功能优先以 LangGraph graph 形式实现。
|