日期:2026-08-14 分支:cfclub 状态:待评审 技术选型:LangGraph(cfc-langgraph 服务,用户指定)
现有问卷功能均为静态形态:
| 现状 | 路径 | 局限 |
|---|---|---|
| 亲子关系问卷 | 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 出题 |
用户需求(已确认):
设计目标:
cfc_knowledge collection 已含 microbiome_ / danknowledge / article 三源)、AiGateway 熔断模式。| # | 问题 | 决策 |
|---|---|---|
| 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) |
方案 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 三源)
选型理由:
(场景配置, 历史) → 下一题/画像,输入输出干净。LangGraph 有状态方案(弃):MemorySaver 内存态重启丢失;持久化需引入 checkpoint store;Java 审计历史需同步。 复用 analysis/run 方案(弃):无状态机、RAG 集成残缺、与 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" |
generate_profile |
LLM 基于完整 history + 知识库 → 画像 JSON {user_profile:[...], need_profile:[...]} |
问题结构(Question):
{
"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 不带两者。画像结构(灵活 JSON 维度):
{
"user_profile": [
{"dimension": "肠道菌群状态", "score": 72, "description": "……", "evidence": ["答1: ……"]}
],
"need_profile": [
{"dimension": "营养需求", "description": "……", "evidence": ["……"], "suggestion": "……"}
]
}
dimensions_json 定义,引擎只校验"数组 + 必填字段",不约束维度名。端点契约:
POST /api/v1/qna/advance
// 请求
{
"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,响应画像)
{
"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/。
新 Entity/Mapper/Service/Controller:
entity/AiQScene.java、entity/AiQSession.java、entity/AiQProfile.javamapper/AiQSceneMapper.java、AiQSessionMapper.java、AiQProfileMapper.javaservice/AiQuestionnaireService.java + impl/AiQuestionnaireServiceImpl.javacontroller/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 降级src/views/admin/AiQuestionnaireScenes.vue:场景 CRUD
/admin/ai-questionnaire/scenes(admin 权限)src/api/aiQuestionnaire.js 接口封装pages/health/ai-questionnaire.vue(逐题对话式):
/api/ai-questionnaire/scene/list)→ 选择成员 → startpages/health/ai-questionnaire-result.vue(画像展示):
pages/health-main/index.vue 或 pages/body-detail/ 健康相关页加「AI 健康问卷」入口utils/api.js 增加:aiQStart、aiQAnswer、aiQFinish、aiQSceneList、aiQHistory、aiQProfileDetail?.、禁止 :key 表达式、日期用 parseDate()、Options APIai_q_scene(场景配置)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 动态问卷-场景配置';
ai_q_session(问卷会话)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 动态问卷-会话';
ai_q_profile(画像)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 动态问卷-画像结果';
DatabaseInitializer.runMigrations() 新增迁移(CREATE TABLE IF NOT EXISTS ×3,幂等 try-catch),迁移编号按现有最大编号递增。schema.sql(完整快照)。microbiome 场景(scene_key=microbiome,菌群健康评估)便于演示。统一 @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 内部流程:
status=running;校验 memberId 归属(家长可代填家庭成员)。status='running',后到者返回"问卷已完成"。current_question_json 为待答题,与本次 answer 组装 {question, answer} 追加到 history_json;question_count+1;answer 统一为字符串(单选=选中 label,多选=逗号分隔 labels,量表=数字字符串,文本=文本,前端提交前规整)。question_count >= max_questions → 直接走画像生成(调 /api/v1/qna/profile)→ 存 ai_q_profile + session 置 finished → 返回 {action:"finish", profile}。/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。
start内部流程:查 scene → 建 session(status=running)→ 调/api/v1/qna/advance(history 为空)→ 返回题存入current_question_json→ 返回{sessionId, question}。
| 场景 | 处理 |
|---|---|
| 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 结构 |
mvn clean compile 通过(唯一编译验证)node --check 语法校验(不打包,HBuilderX 打包)AiGateway.recommend / 商城搜索),画像标签对接推荐留待后续迭代。将以下约定写入 AGENTS.md(根 + cfc-backend/AGENTS.md):
AI 服务统一走 LangGraph:所有 AI 能力(对话/问卷/画像/推荐/解析)基于
cfc-langgraphPython 服务(FastAPI + LangGraph + ChromaDB RAG,端口 9000,生产ai.etotem.com.cn),Java 侧通过AiGateway调用(熔断 + 超时),禁止直接对接 Dify 之外的第三方 LLM 服务;新增 AI 功能优先以 LangGraph graph 形式实现。