
每年高考出分后的那两周是做志愿咨询类产品的团队最紧张的时候。我在这行折腾过好几个版本也踩过不少坑今天拿“高考志愿AI”这个场景把FDE落地时最关键的一层窗户纸捅破AI到底应该怎么“给建议”而不是“替人做决定”先把这个系列里我理解的FDE定义一下。FDE不是某个神秘框架而是 Feature-Driven Engineering功能驱动工程。核心思路是先把用户真实场景里的功能拆出来再决定哪些环节用大模型、哪些环节用规则、哪些环节必须让人来确认。这套思路用在高考志愿AI上特别合适因为这个场景容错率极低一条建议错了影响的是一个年轻人接下来四年的轨迹。这篇文章不是教你怎么搭一个看起来能聊天的空壳而是讲清楚“参谋式AI”的功能拆解、实现细节和避坑经验。不管你是准备用大模型做类似教育咨询产品还是单纯对AI辅助决策的边界感兴趣下面这些内容都值得耐心看完。1. 项目整体设计与思路拆解1.1 先把“建议”和“决定”的边界画出来做高考志愿AI之前我团队内部先吵了一轮产品到底该做到哪一步有人觉得AI应该直接输出“你就报A大学B专业”体验直接、转化率高也有人坚持做“参谋”型给信息、给对比、给风险提示最后让学生和家长自己拍板。后来我们达成一致AI可以无限逼近一个资深咨询师但绝不能扮演家长的角色。资深咨询师怎么干活他会问你的分数、位次、选科、想去哪个城市、家里经济条件如何、有没有特别感兴趣的专业然后给你三五个方案告诉你每个方案的收益和风险最后加一句“这个选择最终还是看你”。这背后有个很朴素的理由志愿填报是一个高度个人化的决策。同一个位次有人愿意去偏远985图个名校光环有人宁可留在本省上个普通一本方便就业这两种选择没有绝对的对错。AI如果默认了一套价值排序就是在替用户做价值判断——这比给错一个数据更危险。所以项目启动的第一件事不是调模型而是写一份“功能边界文档”。这份文档明确了几条硬约束所有输出必须包含“前提假设”和“不确定度”最终给的是“方案集合”不是“唯一答案”硬性限制选科、体检、招生要求用规则引擎拦截不让大模型自由发挥这个过程其实就是FDE的“需求澄清”环节。很多AI项目翻车不是模型不行而是一开始就没想清楚功能边界最后做出来的东西既不像工具也不像专家。1.2 替人做决定的AI为什么必然会翻车把“替人决定”这条路堵死不是道德洁癖而是纯技术层面的理性选择。我总结下来有三个很现实的原因。第一信息永远是不完备的。考生的真实偏好有时连他自己都说不清。你说“我想学计算机”但他可能只是觉得程序员收入高实际上完全不能接受每天面对代码的生活。AI再强也读不到这些隐藏信息。模型基于一个残缺的画像强行输出唯一答案本质上就是闭着眼睛做判断。第二价值排序因人而异。有的家庭认为“城市 学校 专业”有的认为“专业 学校 城市”还有的认为只要离家近什么都好。这些排序没有对错但会彻底改变最优解。AI一旦在Prompt里预设了“学校越好越值得报”就已经失去客观性了。第三责任链会反噬产品。志愿填报出问题之后用户的情绪是非常强烈的。如果AI当初信誓旦旦说“按这个顺序填肯定没问题”最后滑档了用户不会怪大模型只会怪这个产品。但如果你给的是“三个方案风险分析”用户自己做了选择他会把这次经历理解为“我综合了各方信息后的决定”对产品的质疑就会小很多。所以设计系统时我反复强调一句话AI负责把决策所需的信息成本降到最低把决策权完整留在用户手里。这句话是后面所有Prompt、所有规则、所有交互设计的总纲。1.3 FDE视角下的用户旅程拆解用FDE方法做这个项目第一步不是写代码而是画用户旅程。我把一次完整的志愿咨询拆成了六段信息采集分数、位次、省份、选科、体检情况偏好澄清城市倾向、专业方向、学校层次接受度候选生成根据硬条件和偏好召回一批可报院校梯度分析把候选分为“冲、稳、保”三档方案展示让用户看到每个方案的优劣和权衡风险确认提示退档风险、专业调剂风险、就业不确定每一段都要回答三个问题这段要什么数据这段该不该用大模型这段的结果怎么防错有意思的是拆完之后我们发现真正适合大模型的只有第2段和第5段。第1段和第6段是典型的结构化表单和规则判断第3段和第4段用结构化查询加打分脚本更可靠。这个结论可能出乎很多人的意料但偏偏就是这个“少用大模型”的决策让整个系统的稳定性和可信度上了一个台阶。后面我会详细说每段怎么实现。2. 核心功能实现与实操细节2.1 一个Agent底座让对话有状态很多新手做对话型AI直接调大模型API一问一答发现用户多聊几句就晕了。原因是大模型没有会话状态——你上句话说完它记不住有位次、选科、地域这些关键信息。我在项目里搭的是一个很轻的Agent底座核心就三件东西状态槽位、意图路由、动作调度。状态槽位是一张表记录了当前对话里已经收集到的字段比如位次、省份、选科组合、城市偏好意图路由负责判断用户这句话是在回答问题、提出新要求还是修正信息动作调度则决定下一步调用搜索工具、评分脚本还是直接生成文案。这套东西不复杂但能解决90%的“对话失忆”问题。比如用户先说“我是四川考生理科位次12000”聊了一会儿又问“那成都的学校有哪些能报”系统能从槽位里拿出来“四川、理科、12000”去查而不是让用户重新说一遍。做主控的时候我的经验是用状态机管流程用大模型管表达。状态机保证每个环节不遗漏大模型把用户表达转换成结构化意图再用规则把意图映射到动作。完全依赖大模型做状态跳转我试过复杂对话下经常出现不可控的跳变。2.2 关键Prompt把自己定位成“参谋”而不是“家长”Prompt是整个“给建议”体验的灵魂。我最初的版本写的角色设定是“你是高考志愿专家”结果模型输出非常教科书喜欢用“应该报考”“建议首选”这类指令式口吻。后来我把角色描述改成了一段很长的“参谋行为守则”效果立刻不一样。下面这段Prompt模板经过了几轮线上验证你可以直接参考你是一名高考志愿辅助参谋任务不是替用户做决定而是帮用户把选择空间看全、看懂。 你在回答时必须遵守以下原则 1. 不要使用“你应该”“我推荐你直接报”这类表达改用“如果看重...可以优先考虑...”“相比而言...更适合...”。 2. 每条实质建议都要附带依据依据可以是往年位次、招生计划、院校层次、专业方向也可以是明确的常识性假设。 3. 如果你使用的信息存在不确定性必须说明“这个数据存在波动”或“这是我基于你刚才提供的信息做的推断”。 4. 在给出方案时同时输出方案的收益、代价和潜在风险不得只讲优点。 5. 如果用户的提问信息不足禁止直接开药方先列明“我还需要了解的信息”。 6. 不允许用绝对化表述禁止出现“一定录取”“肯定稳”“百分百不会退档”等说法。这段Prompt的价值不是让模型更聪明而是给模型的表达套上了一条护栏。很多用户分不清AI是在建议还是在指挥往往就是被几个词影响的。把“你应该”换成“如果你更看重...那么...”整个对话的主动权就回到了用户那边。2.3 让输出可追溯、可反驳就算Prompt写得再好大模型也难免编数据。为了减少这种“一本正经胡说八道”我给系统加了一个硬性要求模型输出必须走JSON格式且所有数据类结论必须带上来源ID。具体做法是让输出符合这样一个结构{ assumptions: [用户希望留本省就业], options: [ { school: 某大学, major: 软件工程, evidence: [ {type: 位次, value: 去年最低录取位次约9000, source_id: data_2024_sichuan_0032}, {type: 招生人数, value: 2025年计划招生120人, source_id: plan_2025_sichuan_0032} ], risk: 该校在四川投档线近年波动较大存在大小年现象, uncertainty: 中 } ] }为什么强制JSON而不是直接生成自然语言因为JSON格式让下游校验变得非常容易。程序可以先检查每个source_id是否能检索到再检查位次数字是否在合理区间。如果模型幻觉出了一个根本不存在的专业校验模块直接丢弃这条结果而不是让它混进最终文案。用户界面上还会加一个“为什么推荐这所”的按钮点击后展开的正是这些证据ID和原始数据截图。这个功能极大提升了用户信任感——很多人一开始怀疑AI但看到AI能把依据摊开反而愿意聊得更多。2.4 多AI协作别让一个大模型干所有事项目做到中期我发现让一个模型既做意图理解、又做信息检索、又做方案推荐、又做风险审查效果非常不稳定。同时并发量上来后一次调用能省几十毫秒都是好的可功能混在一起导致 Prompt 越来越长反而拖慢响应。后来我按“多AI协作”的思路把系统拆成了四个角色子看门领航员Agent负责对话主流程维护槽位判断什么时候该问、什么时候该答检索Agent接收结构化查询条件从本地知识库和数据库中检索院校专业信息分析Agent接收“冲稳保”梯度结果生成自然语言解释质检Agent对任意Agent的输出做合规检查拦截绝对化表达、非法院校名称、过期分数线这些子Agent可以共用一个大模型底座但Prompt各自独立也可以把某些Agent降到规则脚本。比如“检索Agent”本质上是我写的一个Python函数加上少量Embedding召回根本不需要大模型下场“质检Agent”则是一组敏感词和格式校验规则。多AI协作在实际运行中最大的收益是各个模块可以独立升级、单独测试。我调分析Agent的口吻时完全不用担心检索数据被带偏发现质检规则漏了某个限制条件直接加正则就行不用重新调Prompt。3. 实操过程与核心环节实现3.1 数据准备志愿填报的“食材”先从源头把关高考志愿AI的地基不是模型是数据。没有一份干净的招生计划和往年录取数据模型说什么都是空中楼阁。这个项目的数据准备我踩过的坑比模型调优踩过的坑多得多。我需要准备的核心数据有四类数据域关键字段更新频率常见问题招生计划院校代码、专业代码、选科要求、招生人数每年一次专业代码歧义、大小年合并历年录取院校、专业、最低分、最低位次每年一次位次口径不统一、数据缺失院校属性所在城市、985/211/双一流、学科评估低频院校更名、合并限制规则体检限制、单科成绩要求、性别限制每年一次规则分散、描述口语化其中最容易翻车的是招生计划里的选科要求。新高考改革后每个专业的选科要求五花八门有的要求“物理化学”有的要求“物理或化学均可”。我一开始用规则脚本解析后来发现人工整理时经常出现“物理和化学”和“物理或化学”混在一起导致推荐结果出错。最终我干脆把选科要求做成结构化字段每个专业存一个允许选的组合列表查询时直接集合运算绝不依赖文本匹配。数据清洗是RAG项目的灵魂。院校名称这一步尤其要小心有些学校叫“某学院”实际是一本院校有些叫“某大学”实际录取位次很低。建议做一个别名表和层级表把所有官方名称、简称、历史名称都映射到一个统一ID上否则向量检索一召回模型很容易被别名绕晕。3.2 用混合检索而不是纯向量检索一开始我天真地认为搞个Embedding模型把所有院校专业信息向量化用户提问后做相似度检索就行了。后来发现高考志愿场景里用户的问法既有语义模糊的部分“有没有计算机比较强的学校”又有结构化硬条件“位次12000四川理科”纯向量检索对硬条件的处理非常糟糕。实践中我更依赖混合检索粗召回用向量或关键词先捞出一批候选院校比如按语义匹配“计算机强校”“软件工程老牌”这类说法精过滤用结构化的硬条件做SQL或内存过滤比如位次范围、选科组合、所在省份、招生名额大于0再排序用规则评分按“冲稳保”打标签再让大模型对Top候选做解释这一步的核心收获是大模型只做它擅长的事情——理解语义和生成解释硬条件筛选一定要交给确定性的代码。你把“位次12000”丢给大模型让它自己过滤它就给你漏数据但你把经过过滤后的候选列表丢给大模型“用自然语言总结”效果非常稳定。我记得第一次混合检索联调时系统从1200多所院校里筛出来37所符合条件的排序后用户问“为什么没有某大学”一查才发现那所大学当年没在四川招生。这就是硬条件过滤的价值——模型可能不知道某校今年不在某省投放计划但数据表知道。3.3 冲稳保的评分透明规则比黑盒概率更好用很多AI产品喜欢给用户呈现一个“录取概率85%”的数字看上去很专业实际上经不起推敲。概率来自历史数据的统计推断不同年份的位次波动、招生计划变化都会让这个数字严重失真。我最终采用的做法是给梯度标签不报精确概率。计算方式用一套公开、可验证的规则假设考生位次为 rank_student 某院校专业近三年最低位次分别为 p1, p2, p3 取中位数 median_rank。 条件判断 - 如果 rank_student median_rank * 0.75标签“冲”提示录取存在不确定性 - 如果 rank_student median_rank * 1.15标签“稳”提示录取把握较大 - 如果 rank_student median_rank * 1.5标签“保”提示可作为保底选择 - 其余落在中间区间的标签“较稳”提示属于正常波动范围为什么用位次而不是分数因为高考每年的批次线会变但位次具有跨年可比性。为什么不做精确概率因为样本量只有三年任何超过两位小数的概率都是假精确。用户看到“冲、稳、保”标签后对不同档位随便输入系统还配了一段解释“沿用近三年最低录取位次中位数对比你的位次计算得出。该判断受招生计划变动影响仅供参考。”这样用户既理解结论怎么来的也知道结论有边界。后台另外做了一层“覆盖率检查”如果候选方案里保底学校少于两所就强制提示“当前方案保底不足存在滑档风险”这是规则层面的最后一条防线。3.4 一次完整的咨询对话还原纸上谈兵没意思我把系统上线后的一次真实对话简化出来给你看看“给建议”到底长什么样。用户文本输入我是四川考生理科位次12000左右不想出省对计算机相关专业有兴趣。系统先做意图路由和槽位更新确认省份四川、科类理科、位次12000、地域偏好本省、专业意向计算机。然后系统认为还缺两个关键信息选科组合和有没有特别不能接受的专业类型于是多问了一句。系统已记录你的基本信息。还想跟你确认两点你的选科组合是“物理化学生物”吗另外你能接受软件工程、人工智能这类偏应用型的专业吗还是只考虑传统的计算机科学与技术用户选科是物理化学生物专业方面应用型也可以。系统这时才进入候选生成。检索Agent从数据表里筛出四川本地院校、计算机相关专业、选科要求匹配、招生计划向下等条件再用评分脚本打梯度标签。系统输出的最终回答做了分块展示核心内容大概是这样冲某大学“计算机类”去年最低位次约8000属于冲刺档。优点是985平台就业认可度高风险是位次差距明显录取概率较低且可能被调剂到非热门专业。稳某211大学“软件工程”去年最低位次约11000比较匹配。该校软件工程学科评估不错成都本地互联网就业机会多风险是专业热度上升可能拉高位次。保某省属重点“计算机科学与技术”去年最低位次约18000位次充裕。可作为兜底选项同时该校在大三有校企联合培养项目就业出口稳但学校整体层次低于前两项。每个条目后都挂了一个“查看依据”按钮点击后能看到近三年的位次曲线和招生计划数。最后系统固定加了一句话“以上方案基于你提供的信息和公开录取数据推算最终请结合你家庭的实际偏好综合判断。”没有替用户按下那个确认键。用户看完后追问“那第一所值不值得冲”系统没直接回答“值”或“不值”而是把两种决策逻辑摆出来如果更看重名校平台且能接受专业被调剂冲一冲是值得的如果对专业有明确偏好不接受调剂那么同档次的偏冷门院校可能更稳。用户自己做了权衡说“我再想想”结束了对话。3.5 用户不说清楚时用默认假设推动对话真实用户远比测试集复杂很多人上来就问“帮我看看能报什么学校”既不提供位次也不说省份。如果系统机械地回复“请提供位次”对话就冷掉了。我后来在领航员Agent里加了一套“默认假设”机制用户信息不全时系统可以先按常见情况假设但必须明确告诉用户“我先做一个假设”。比如用户只说“理科生450分”没说省份系统会回复“我先假设你在四川参加高考如需修改省份请告诉我按去年理科450分对应位次约15万左右你需要把保底学校数量提高一些填报策略会偏保守。”这种回复既推进了对话又把主动权留给了用户。用户如果纠正槽位更新就行用户不纠正系统也不会假装自己无所不知。这个机制上线后对话完成率提升非常明显。4. 常见问题与排查技巧实录4.1 模型顺嘴编数据怎么拦截这是大模型落地所有高严谨场景时遇到的头号问题。我在项目里用了三层拦截第一层输出约束。Prompt里强制要求所有数据必须来自检索结果如果检索结果里没有就输出“未查询到相关数据”禁止自行估算。这属于提示词层面的约束能挡住一部分幻觉但挡不住全部。第二层JSON校验。模型输出JSON后程序对所有source_id做存在性检查对数字做区间检查比如位次不能小于0录取分数不能超过总分上限。任何一个字段不合法直接丢弃整条建议并触发“重新生成”。实测下来这一层能拦住绝大多数胡编的数值。第三层规则兜底。Query和结果之间做一个一致性比对。例如用户选科是“历史政治地理”但推荐专业要求“物理化学”这属于硬冲突不管模型怎么解释一律过滤。这一层不依赖大模型是纯粹的业务规则。过程很痛苦但这一套组合拳打下来线上幻觉率从我初版的30%以上降到了可接受的1%到2%。顺便说一句不要迷信换一个更大的模型就能彻底解决问题更大模型一样会幻觉只是编得更像真的而已。4.2 忽略选科和体检限制怎么堵漏有一次测试模型给一位色弱考生推荐了某临床医学专业理由还写得很充分“该校医学实力雄厚建议冲刺。”我当时冷汗就下来了——这个错误如果上线后果非常严重。后来我把所有限制条件做成了独立的规则表不入Prompt。数据表里每一对“院校专业”都带有一份限制标签比如是否要求色觉正常、是否要求单科成绩、是否限制性别、是否有额外面试。候选生成阶段直接对这些标签做硬过滤大模型根本看不到不满足条件的专业自然也就没有幻觉的机会。这类“硬规则不进Prompt”的原则我建议所有做教育、医疗、法律等高严谨场景的团队都记下来。大模型可以帮你总结规则但不能让它直接执行规则。4.3 把位次预估说成铁板钉钉怎么扭转早期的回复文案里系统会说“该专业录取概率较高”但用户往往把这句话理解成“基本稳了”。后来我们统一把话术改成三层结构先说结论标签冲/稳/保再说依据近三年位次对比最后说不确定性来源招生计划变化、专业热度波动。所有地方禁止出现“必定”“肯定”“百分百”这类词。质检Agent里专门维护了一个“绝对化表述黑名单”包括“稳了”“一定能上”“闭眼报”“没问题”等。只要检测到自动让回复重新生成一次。用户可能觉得这个系统有点保守但保守在志愿填报这种场景里恰恰是专业感的来源。4.4 用户问“哪所更好”时AI该怎么接话“某大学和某大学哪个更好”是高频问题也是最容易把AI带偏的问题。我的经验是不要回答“A好”而是把“好”拆解成可比较的维度。系统会生成一个对比矩阵列出两所学校与目标专业相关的学科评估、所在城市就业环境、往年录取位次、深造比例等然后反问用户“你觉得哪个维度对你更重要”如果用户说“我主要想考研”系统就把深造出口数据单独拿出来讲。如果用户说“我更在乎就业”就侧重分析两校在当地或全国的就业结构。这个过程实际上是用结构化对比替代价值判断。AI充当磨刀石帮用户把模糊的问题磨成清晰的选项但刀往哪砍还是用户自己的决定。4.5 多轮对话中状态丢失怎么排查我踩过一个很实际的坑用户聊到第三轮说“那个学校怎么样”对话里指代的是前面提到过的某211但系统已经把上下文丢了反而去理解成另一个学校。排查下来的根因是我把上下文窗口设置得太短只保留最近两轮对话而指代词跨越了更多轮次。解决办法很简单把对话历史合并进槽位摘要系统不再依赖原始对话文本而是维护一个“目标画像最近一轮问题”的结构。比如用户问“那个学校怎么样”系统先查看槽位里的目标学校引用再去检索。跑了一段时间后指代问题基本绝迹。如果你的对话系统也出现“记不住前面说了什么”的情况不要急着加长上下文窗口。先检查你是不是把原始文本直接拼进了Prompt而没有形成结构化的状态摘要。原始文本越长模型越容易抓错重点结构化摘要反而更稳。最后再聊两句这个项目做到后面我最大的体会是做“给建议”的AI最难的不是让模型变聪明而是让产品团队克制住“替用户做决定”的冲动。早期版本我也追求过“一步到位给出最优志愿表”觉得那才是AI能力的体现。但上线后用户反馈里出现频率最高的一句话是“感觉被AI安排了”这让我重新审视了整个交互逻辑。后来我把产品调性改成了“参谋”风格结果也很微妙用户主动查“依据”的次数多了聊得更深了填完志愿之后回来的复购和转介绍反而上升了。用户不是不需要AI而是不需要一个替他承担人生决定的AI。他需要的是把纷繁复杂的信息摊开在桌面上、把权衡逻辑讲清楚的助手。如果你也在做类似的教育咨询或者高严谨决策类AI建议你在项目第一天就把“给建议而不做决定”写进功能需求而不是等模型上线后再修正。这条原则会贯穿你的数据设计、Prompt设计、结果校验和UI文案越早定下来后面返工越少。