WIP: memory system improvements (in progress)
Interview/chat prompt layers, reply planner, style profiles, memory injection, interview meta store, and related tests. Work not finished. Made-with: Cursor
This commit is contained in:
211
docs/TODO.md
Normal file
211
docs/TODO.md
Normal file
@@ -0,0 +1,211 @@
|
||||
结论先说
|
||||
如果你要走你选的这条路:
|
||||
|
||||
先让模型做“本轮重点判断”,再生成回复
|
||||
|
||||
那这不是只修一个点,而是在修仓库里一个更普遍的反模式:
|
||||
|
||||
系统过早承诺 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
|
||||
然后正式生成模型再基于这个来写回复。
|
||||
|
||||
也就是说,你们不是没地方放这个方案,而是现在已经有一个很接近的位置,只是它做的事太弱、太晚。
|
||||
Reference in New Issue
Block a user