Files
life-echo/docs/TODO.md
Kevin 3121d1384d 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
2026-04-22 16:56:28 +08:00

211 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
结论先说
如果你要走你选的这条路:
先让模型做“本轮重点判断”,再生成回复
那这不是只修一个点,而是在修仓库里一个更普遍的反模式:
系统过早承诺 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. 每次最多 **12 个**资料相关问点...
这个也有点像“替模型编舞”,但这里我会更宽容。
因为这个任务本身就更窄:
目标明确
信息字段有限
风险主要是漏字段和重复问
所以这里的 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
然后正式生成模型再基于这个来写回复。
也就是说,你们不是没地方放这个方案,而是现在已经有一个很接近的位置,只是它做的事太弱、太晚。