结论先说 如果你要走你选的这条路: 先让模型做“本轮重点判断”,再生成回复 那这不是只修一个点,而是在修仓库里一个更普遍的反模式: 系统过早承诺 micro-decisions,模型只能在被预分配好的轨道里写答案。 这个反模式在你们的 chat 链路里确实存在多处。 一类:和这次问题本质相同 1. interview_turn_plan.py 这是最典型的同类问题。 它先把复杂语义压成 3 个 mode: emotion_first memoir_push follow_user_only 然后再进一步规定: 要不要问 最多几个问句 是否必须问 问句必须挂哪类锚点 这里不是在“给模型方向”,而是在替模型做回合级编排决定。 interview_turn_plan.py Lines 157-170 def plan_interview_turn( *, current_stage: str, empty_slots: list[str], normalized_user_message: str, memory_evidence_text: str, stage_switched_this_turn: bool, ) -> InterviewTurnPlan: """ 粗规则(可迭代): - 情绪浓:先共情,不强推叙述槽搜集问。 - 刚切换人生阶段:跟着用户节奏,不做「新阶段问卷首开」。 - 当前阶段无空槽:深度跟进,不重启盘点。 - 默认:memoir_push,锁一个主槽 + 挂钩摘录。 """ interview_turn_plan.py Lines 288-290 - **回忆推进(memoir_push)**:对用户可见回复中须有**恰好一个**开放式回忆追问, 且意图明显在补足下面「主追问方向」;问句必须挂住**挂钩摘录**或**用户本轮原词**(二者至少其一)。 这和你指出的问题是同一种: 模型还没理解“这轮到底最重要的是什么”,系统已经先替它决定“这轮应该长成什么样”。 2. prompt_layers.py 这里是第二个高危点,而且和上面叠加。 它不只是给高层原则,而是在定义很多具体动作脚本,比如: 连续两轮没问,这轮必须问 问句最多一个 情绪重也不能连续三轮不问 更偏好从体感和画面往下长 prompt_layers.py Lines 242-243 - **追问义务回正(防多轮零问)**:通读**近期你方(助手)的连续回复**:若已**连续两轮**都没有任何问句...则本轮**必须在**短承接之后给出**恰好一条**带锚的开放式问... - **纯跑题 ≠ 情绪红灯**:若用户本轮**几乎只有**寒暄、天气、泛泛近况、社交客气,而**没有**人生经历实质——**不适用**「整轮只陪不问」... 这是同类问题,因为它把“什么时候该追、什么时候该停、什么时候该纯承接”写成了比较硬的流程规则。 这种规则不是安全边界,而是对中间思考过程的强干预。 3. orchestrator.py 的 scene_cues 这也是同类问题,只是形式不同。 系统在正式生成前,先偷偷把“场景氛围提示”塞进 context: orchestrator.py Lines 296-300 scene_cues = extract_scene_cues(normalized_user_message) if scene_cues: cue_block = "\n".join(f"- {c}" for c in scene_cues) scene_hint = f"\n\n[场景氛围提示——可借用这些感官细节自然接话,不要原样抄]\n{cue_block}" memory_evidence_text = (memory_evidence_text or "") + scene_hint 这本质上也是“替模型预判重点”,而且还是单边偏置。 系统没有同等强度的 relationship_cues / identity_cues,却有 scene_cues 特权通道。 所以如果你上“先让模型判断重点”的方案,这里几乎一定也要一起收口。 4. reply_planner.py 这个文件很有意思。 它其实已经是一个“两阶段生成”的雏形了,但目前它做得太窄,没有判断本轮重点,只是在微调几个受限字段: memory_usage reply_shape memory_reference_style reply_planner.py Lines 57-94 def _build_reply_planner_prompt( *, plan: InterviewTurnPlan, user_message_preview: str, memory_evidence_preview: str, ) -> str: ... ## 当前规则基线(已由服务端算出,通常应尊重) - mode: {plan.mode} - memory_usage: {plan.memory_usage} - reply_shape: {plan.reply_shape} ... 这说明你们其实已经有一个很自然的插槽去做你想要的方案 A。 只是现在这个 planner 不是“判断重点”,而是“在既定重点下微调姿势”。 所以它解决不了你说的问题。 二类:有点像,但不是核心病灶 5. interview_agent.py 的重复问句守卫 这里有两层后处理: apply_duplicate_question_guard dedupe 失败后再做一次 LLM retry interview_agent.py Lines 82-86 out, deduped = apply_duplicate_question_guard( out, state=memoir_state, recent_questions=recent_questions, ) interview_agent.py Lines 51-57 上一版模型输出因包含与「最近已问过的问题」或「已确认事实」重复的问句,已被系统弃用。请**重新写一整条回复**: - 仍须遵守上文全部主规则; - 先贴着用户本轮原话承接半句到一两句(可有画面感); - **禁止**再用与刚才同义、仅换说法的确认型问句; ... 这类逻辑有副作用,但我不会把它视作和你这次 case 完全同级的问题。 原因是它更像“去重护栏”,不是“决定本轮重点”。它可能让模型更难问出正确问题,但它不是主要在替模型定义重点。 我的判断是:它是次级 handcuff,不是主 handcuff。 6. prompts_profile.py 的资料收集 choreography 这个模块里也有比较明确的流程约束: prompts_profile.py Lines 132-137 1. **先接住**... 2. **话题优先**... 3. **资料穿插**... 4. **轮换**... 5. 每次最多 **1~2 个**资料相关问点... 这个也有点像“替模型编舞”,但这里我会更宽容。 因为这个任务本身就更窄: 目标明确 信息字段有限 风险主要是漏字段和重复问 所以这里的 choreography 不优雅,但不一定是严重问题。 它不像正式访谈主链那样,直接压扁复杂的人类语义。 三类:不是这类问题,应该保留 7. 自传污染边界 / 禁编造 / memoir fidelity 这些不是 handcuff,而是必要边界。 比如: 不准把用户经历写成助手自己的经历 不准编造未提及的人名、地点、对话 memoir 生成不能越过事实边界 这些属于 safety / fidelity contract,不是“替模型做重点判断”。 所以如果你要上方案 A,我会明确区分: 该放开的,是重点判断与表达路径。 不该放开的,是事实边界与身份边界。 所以,是否“有类似问题”? 我的判断是: 有,而且主要集中在 chat 主回复链路 最明显的同类问题是这 4 个: api/app/agents/chat/interview_turn_plan.py api/app/agents/chat/prompt_layers.py api/app/agents/chat/orchestrator.py api/app/agents/chat/reply_planner.py(不是坏,但能力位置放错了) 它们共同的问题不是“规则太多”这么简单,而是: 系统在多个地方分别替模型决定“这轮重要什么、怎么组织、该不该问、问什么形状”,导致模型没有真正做一次独立的 salience judgment。 这和你想引入的方案 A,正好是正面冲突的。 如果你选方案 A,哪里最适合承接它 最现实的一点是: 你们已经有一个两阶段接口,就是 reply_planner.py。 但如果要真做“先判断本轮重点,再生成”,我不会把它继续定位成现在这种“reply_shape 微调器”,而会把它升级成: focus planner / salience planner 它先输出类似这样的隐藏判定: primary_focus secondary_focus why_now should_ask candidate_anchor_span 然后正式生成模型再基于这个来写回复。 也就是说,你们不是没地方放这个方案,而是现在已经有一个很接近的位置,只是它做的事太弱、太晚。