Interactive Questionnaire
当你要向用户提问时(哪怕只有一个问题),用本技能组织提问,而不是在聊天里零散追问。一经在对话中启用,就对整个对话持续生效(见下方「生效范围」)。
何时用
- 你需要向用户提问、或收集偏好/需求/结构化输入时——哪怕只有一个问题。
- 需要把用户回答变成可解析的结构化结果,而非一段散文。
生效范围:一经调用,全会话生效
本技能一旦在某次对话中被调用,就对该对话的所有后续回合持续生效,直到用户明确要求停止。不要问完一份问卷,就在下一回合把它忘掉、退回随口提问或散在聊天里问——那会导致「上一份问完、下一回合就不遵守、还退回没有约定的散问」,正是要杜绝的。
任何时候你要向用户提问——无论问题多少、无论多简单——只能走以下两条之一,不存在第三条「随口简单问」的路线:
- 复杂 → HTML 问卷;
- 简单 → 文字版(题号/选项号约定 + 每题描述),即使只有一个问题也照此办理。
即:普通自由散问被禁用;每一次提问,要么是问卷,要么是符合约定的文字版。
问题域与串并行(内部编排,强制)
开始提问前,先在内部圈定问题域:为完成当前任务,有哪些决策必须由用户拍板。问题域只作你自己的对账,不要写进回复、问卷标题或副标题,也不要请用户确认这份清单。
收到答案后更新问题域:已拍板的划掉;若答案引出了新的疑问,且该疑问无法从已有答案准确、唯一地推导出来,就把新疑问并入问题域。停止提问的唯一条件是:当前问题域内每一项都已得到用户确定的回复与决策,且没有未消化的新疑问。用户答完后若问题域已扩展,结束与否取决于新的问题域是否已无疑问——不是「这份问卷交回来了」就结束。
一题必须可单独决断
问卷里每一题,用户都要能在不知道其他题答案的情况下做完。禁止「若第 1 题选 x,则回答本题」这类把依赖写进题面的问法。
串行 / 并行
- 有前后依赖(本题的选项、是否该问、问什么,取决于另一题的答案)→ 串行多轮:本轮只问前面那题,以及所有与之互不依赖、现在就能答的题;等用户把这一份答完回传后,再根据答案设计下一轮。不要把后置题提前塞进同一份。
- 没有明显前后/逻辑依赖 → 并行:可以放在同一份问卷里一起问。不设题量上限,也不要只因为「主题不同」就拆成多轮。
同一份里若有多个主题:用题目顺序表达——问完一个主题再问下一个。需要时题干写成 「主题」问题正文;并非每份问卷、每一题都要加主题标识。各题本身已经很好区分时(例如十题十个主题),不要再加主题前缀。
一题的粒度
尺度只有一条:用户是否容易理解、容易单独作答。
- 同一个问题点,一题就能问完的(含题内短附属,如 toggle-reveal 的「是/否 + 开启后填一句」),不要拆成多轮。
- 不要为了少拆一轮,把多个决策点挤进同一题的题干、描述或选项里。
每轮结尾自检(强制)
本技能生效期间,每一轮回复的结尾都必须执行一次自检。修辞性反问、不期待用户回答的语句不在核对范围内。核对:
- 面向用户、期待其回答的提问,是否全部走了问卷或文字版(无散问)。
- 本轮问卷里的每一题是否可单独决断:无「若第 n 题选 x 则回答本题」;有依赖的后置题是否已留到下一轮,而不是堆进同一份。
- 是否把多个决策点挤进了同一题;同一问题点能一题问完的,是否被无故拆轮。
- 文字版是否按「文字版约定」写成会话 markdown 块:题干加粗、说明斜体且无抢眼前缀、选项为
- a.列表(裸a.换行在会话里会收成一行)。 - 若本轮是在收答案而非提问:内部问题域是否仍有缺口或不可推导的新疑问——有则必须在本轮输出末尾给出下一轮问卷/文字版,不得当作已经问完。
- 静默执行:自检通过时不输出任何标注,不打扰用户。
- 发现违规 → 当场补救:在本轮结尾简要标注自检结果,并立即按规则重出——散问改为问卷/文字版;同卷依赖改为只保留本轮可独立作答的题;挤成一题的拆开;文字版版式不对的按约定重排。是补救,不是只报告违规。
- 本自检是「生效范围」与「问题域/串并行」的强制执行机制:防止问完一份就退回散问,也防止一股脑把有依赖的题堆在同一份里。
用法:先正常输出,末尾再把本轮问题总结成问卷/文字版
本技能不改变你自身的输出行为。 先按你的惯例正常展开分析、给出思路与建议——该怎么写就怎么写。等这一轮正常输出结束后,再做一次「总结性提问」:只把本轮可独立作答的问题,用最小心智负担、精确而完整地概括成「标题 + 描述 + 选项(如有)」,据此生成问卷(复杂)或文字版(简单)。有依赖、现在还不能答的题不要写进这一份;等答案回传后再设计下一轮。
每个问题都带一段描述(见下):讲清这个问题为什么会出现、要解决什么、推荐哪种做法、为什么。用意是——用户若能仅凭问卷/文字版就做决定,就直接决定;否则可对照你上面的原始输出理解、或就原始输出向你追问,从而以最快、最省心的方式与你的思路对齐。
路由:按复杂度二选一
技能触发后不默认走问卷,先判复杂度:
- 简单(问题少、每题选项少、无排序/区间/多层嵌套)→ 文字版:编号列出问题,用下面的题号/选项号约定。
- 复杂(问题多,或含多选长列表、排序、数值区间等)→ HTML 问卷:装配交给用户填。
- 用户可显式指定走哪一种,以用户要求为准。
「本题要等另一题的答案才能问」不是改走 HTML 的理由,而是改走下一轮的理由。
文字版约定(题号 / 选项号)
只用于文字版;HTML 问卷不带题号/选项号(可视化已区分各题)。文字版写在智能体会话里,会话按 markdown 渲染。版式必须迁就这件事,否则「源码里已经分行」在屏幕上仍会糊成一块。
为什么选项会挤成一行:CommonMark 里只有 1. 2. 这种数字会开有序列表;a. b. c. 只是普通文本。相邻的普通文本行会收成同一段落,换行变成空格。行首裸写 1. 题干 还会开启有序列表,把后面的说明和选项吞进同一项。这是 markdown 规则,不是 Cursor 单独的渲染 bug。
题号(强制):题干整行加粗,形如 **1. 出行人数?**。每份从 1. 重新编号;第 10 题及以后用 **10. 题干**。题与题之间空一行。加粗有两件事:题号是视觉焦点;行首不是裸的 1. ,不会触发有序列表。
问题描述(强制版式):题干之下空一行,说明整段斜体,形如 *……*。讲清为什么问、要解决什么、推荐怎么选——与 HTML 问卷里的描述区同义;描述不带题号/选项号。不要加 ※、不要加 >,也不要加任何比题号更抢眼的前缀——说明是旁注,不能喧宾夺主。禁止把说明写成普通正文,更禁止和选项挤在同一段。
选项号:
- 选择类问题 → 每个选项写成一条 markdown 无序列表:
- a. 美食。前面的-是为了让会话把它当成独立块,才会一行一条;用户仍只靠字母ab作答。禁止只写裸的a.再换行(会被收成一段)。禁止把多个选项写在同一行。 - 自由回答类问题 → 不加选项号;斜体说明写完即可。
- 选项超过 26 个(超出
z)时,把最后一个选项固定为「其他」,请用户用自己的话补充。
用户回复与解析:选择题按字母作答(多选给多个字母,如 1: a,c)、自由题写文字,按题号定位。解析这些回复填回你的流程;不要另造固定回传语法,自然作答即可。
示例(智能体输出时按下面的 markdown 写,不要包进代码块):
**1. 出行人数?**
*同行人数决定房型与整体节奏,填你确定的人数即可;带老人小孩可在备注说明。*
- a. 1 人
- b. 2 人
- c. 3–4 人
- d. 5 人以上
**2. 兴趣(可多选)?**
*决定我优先推荐哪类目的地与活动,选你真正想要的即可。*
- a. 美食
- b. 自然风光
- c. 历史人文
**3. 怎么称呼你?(直接写)**
*只是方便称呼你,随意填,不影响推荐。*
HTML 问卷:装配流程
引擎已封装为 assets/template.html(外壳)+ references/snippets/(每种组件一个片段)。装配步骤(详见 references/components.md):
- 复制
assets/template.html。 - 替换顶部
<QUESTIONNAIRE_TITLE>(标题)与<QUESTIONNAIRE_SUBTITLE>(一句副标题,不写填写说明)。 - 每个问题选一种组件 → 从
snippets/取片段 → 替换其中<大写下划线>占位(片段末尾有注释指引)→ 删掉注释行。 - 所有填好的片段按顺序拼接,整体替换模板里那行
<!-- QUESTION_FIELDS -->。 - 把完成的 HTML 交给用户打开填写。用户填完点「复制结果」得到 JSON,回贴给你解析。
要点:data-field 全问卷唯一、snake_case(= 结果 JSON 的 key);每题预选最可能的默认项。每题标题下、预设区之上有一段描述区(填 <QUESTION_DESC>),承载该问题的「为什么 / 解决什么 / 推荐什么」——它只帮助用户理解,不出现在结果 JSON 里。完整 13 组件范例见 assets/demo.html。同卷内按主题顺序排列题目(问完一个主题再问下一个);编排规则与文字版相同,见「问题域与串并行」。
组件选择
| 需求 | 用 | data-type |
|---|---|---|
| 单选 · 选项少且短 | segmented | segmented |
| 单选 · 选项多 | select | select |
| 单选 · 每项需一句说明 | radio-cards | radiocards |
| 多选 · 选项少且短 | chips | chips |
| 多选 · 选项多或较长 | multi-select | multiselect |
| 是/否(可开启后追问) | toggle-reveal | toggle |
| 有序档位(悠闲/适中/紧凑) | slider(label 模式) | slider |
| 单个数值(预算等) | slider(number 模式) | slider |
| 数值区间(上下限) | range | range |
| 小整数计数(住几晚) | stepper | stepper |
| 单行短文本 | text | text |
| 多行长文本 | textarea | textarea |
| 优先级排序 | rank | rank |
toggle-reveal 只用于同一决策点的是/否及其附属短信息。若开启之后要问的是另一个必须单独拍板的决策,拆成下一轮,不要靠隐藏字段把两题焊在一份问卷里。
三条硬规则:
- 每个可选组件都带「+ 改用文字填写」入口(用户随时可改为自由文字,值可能是字符串)——
text/textarea本身即文字,无此入口。 - 必预选最可能项:单选选一项、多选选一或多个、数值/区间/计数给默认值、排序给合理初始次序。
- 能用直观控件就不用下拉:选项少优先 segmented 而非 select;是/否用 toggle 而非两个选项。
结果 JSON 契约
用户点「复制结果」得到(与 assets/demo.html 引擎一致,只含可见字段——隐藏的条件子字段不出现):
{
"title": "问卷标题",
"responses": {
"party_size": { "type": "answer", "answer": { "value": "2 人", "dirty": false } },
"budget": { "type": "custom", "custom": { "value": "看行程再定" } },
"need_hotel": { "type": "objection", "objection": { "text": "这题不适用" } }
},
"system": {
"theme": null,
"display_mode": "preview",
"fold_mode": "collapsed"
}
}
responses的 key = 组件data-field。每题一个条目,形如{ "type": ..., "<type>": {...} },只带当前type对应的那个对象:type: "answer"→answer: { value, dirty }:用户按控件作答。dirty=false表示仍是预选默认值(未改动),true表示用户改过。type: "custom"→custom: { value }:用户点了「改用文字填写」,value是其自由文字。type: "objection"→objection: { text }:用户对该题有异议/认为不适用,text是其说明(可空)。
answer.value的形态随组件:单选/档位为字符串,多选为字符串数组,区间为[下限, 上限],排序为有序数组,计数/数值为数字。以引擎实际输出为准。system:theme、display_mode(preview/raw)、fold_mode(collapsed/expanded)。theme取值null|"light"|"dark"且恒出现:首次问卷为null(页面据环境prefers-color-scheme探测——探到则回填light/dark,探不到保持null;null按 light 渲染但值不写成 light),用户手动切换后落为"light"/"dark";一旦非null便不再被探测覆盖。若要在同一会话的后续问卷里保持用户上次的选择,把新问卷template.html中的var themeState={value:null}改为上一份结果的system.theme。解析结果时通常只关心responses。
完备性自检
三份清单必须行数相等:references/components.md 登记表的行数 == references/snippets/ 的文件数 == assets/demo.html 里 COMPONENTS 的条目数(当前均为 12)。
加一种组件只需三处小改:assets/demo.html(及 template.html)里 COMPONENTS 加一条 {def, wire, collect, ...} 条目 + components.md 登记表加一行 + snippets/ 加一个片段文件。共享逻辑(折叠、异议、自定义、dirty、JSON 组装)已在引擎核心,无需触碰。