Interview/chat prompt layers, reply planner, style profiles, memory injection, interview meta store, and related tests. Work not finished. Made-with: Cursor
7.6 KiB
结论先说 如果你要走你选的这条路:
先让模型做“本轮重点判断”,再生成回复
那这不是只修一个点,而是在修仓库里一个更普遍的反模式:
系统过早承诺 micro-decisions,模型只能在被预分配好的轨道里写答案。
这个反模式在你们的 chat 链路里确实存在多处。
一类:和这次问题本质相同
- 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):对用户可见回复中须有恰好一个开放式回忆追问, 且意图明显在补足下面「主追问方向」;问句必须挂住挂钩摘录或用户本轮原词(二者至少其一)。 这和你指出的问题是同一种: 模型还没理解“这轮到底最重要的是什么”,系统已经先替它决定“这轮应该长成什么样”。
- prompt_layers.py 这里是第二个高危点,而且和上面叠加。
它不只是给高层原则,而是在定义很多具体动作脚本,比如:
连续两轮没问,这轮必须问 问句最多一个 情绪重也不能连续三轮不问 更偏好从体感和画面往下长
prompt_layers.py Lines 242-243
- 追问义务回正(防多轮零问):通读近期你方(助手)的连续回复:若已连续两轮都没有任何问句...则本轮必须在短承接之后给出恰好一条带锚的开放式问...
- 纯跑题 ≠ 情绪红灯:若用户本轮几乎只有寒暄、天气、泛泛近况、社交客气,而没有人生经历实质——不适用「整轮只陪不问」... 这是同类问题,因为它把“什么时候该追、什么时候该停、什么时候该纯承接”写成了比较硬的流程规则。 这种规则不是安全边界,而是对中间思考过程的强干预。
- 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 特权通道。
所以如果你上“先让模型判断重点”的方案,这里几乎一定也要一起收口。
- 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。
- prompts_profile.py 的资料收集 choreography 这个模块里也有比较明确的流程约束:
prompts_profile.py Lines 132-137
- 先接住...
- 话题优先...
- 资料穿插...
- 轮换...
- 每次最多 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 然后正式生成模型再基于这个来写回复。
也就是说,你们不是没地方放这个方案,而是现在已经有一个很接近的位置,只是它做的事太弱、太晚。