refactor(wm): unify turn-summary extraction (<summary> first, else full text, skip empty) across ga/worldline/continue_cmd; fix hinfo delta slicing base for old-tree mixed lineage

This commit is contained in:
Liang Jiaqing committed 2026-08-11 11:50:11 +08:00
1 parent a6ea3fa53c
commit e55e198dc2
3 files changed
+28 -25

No files matched your search

+6 -6
View File
@@ -145,8 +145,9 @@ def parse_native_log(path, allow_empty=False):
def _derive_hist_info(history):
"""从 native history 重建 history_info(轮级纪要):真实用户提问 → `[USER]: …`;每条
assistant(=一轮) → `[Agent] <summary>`(无 summary 取首行)。与 ga.turn_end_callback /
worldline 树同口径。纯函数、不依赖 worldline,供续接 opt-in 恢复工作记忆(见 restore_wm)。"""
assistant(=一轮) → 有字才写 `[Agent] …`(`<summary>` 优先,否则全文;无字跳过)。
与 ga.turn_end_callback / worldline 同口径;截断延用 [:80]。纯函数、不依赖 worldline,
供续接 opt-in 恢复工作记忆(见 restore_wm)。"""
def _all_text(m):
c = m.get('content') if isinstance(m, dict) else None
if isinstance(c, str): return c
@@ -170,10 +171,9 @@ def _derive_hist_info(history):
elif role == 'assistant':
txt = re.sub(r'```.*?```|<thinking>.*?</thinking>', '', _all_text(m), flags=re.DOTALL)
mt = re.search(r'<summary>(.*?)</summary>', txt, re.DOTALL)
s = mt.group(1).strip()[:80] if (mt and mt.group(1).strip()) else ''
if not s:
s = next((ln.strip()[:80] for ln in txt.splitlines() if ln.strip()), '(无摘要)')
out.append(f'[Agent] {s}')
raw = (mt.group(1) if mt else txt).strip()
if raw:
out.append('[Agent] ' + raw.replace('\n', '')[:80])
return out
+17 -13
View File
@@ -198,7 +198,11 @@ class RewindStore:
hinfo = None
hinfo_len = None
if hist_info is not None:
parent_hlen = len(self.rebuild_hist_info(parent)) if parent is not None else 0
# 老树节点按旧口径存过「(无摘要)」条目,stored 长度会偏大切丢新纪要;
# history 在手时用同口径派生 parent 前缀定切片基点(前缀派生恒为全量前缀)。
parent_hlen = 0 if parent is None else (
len(self._derive_hist_info(history[:parent_len])) if history is not None
else len(self.rebuild_hist_info(parent)))
hdelta = list(hist_info[parent_hlen:])
hinfo_len = len(hist_info)
hinfo = self._put_blob(
@@ -713,24 +717,22 @@ class RewindStore:
return ""
@classmethod
def _summary_of(cls, msg) -> str:
"""从 assistant 消息提取 `<summary>`(GA 轮级纪要的来源);无则退化取首行。
与 ga.turn_end_callback 的提取口径一致,使从日志重建的 history_info 贴近原值。"""
def _summary_of(cls, msg) -> Optional[str]:
"""与 ga.turn_end_callback 同口径: `<summary>` 优先,否则全文;无字 → None(不进 history_info)。
截断延用 [:80](重建路径不依赖 smart_format)。"""
text = re.sub(r'```.*?```|<thinking>.*?</thinking>', '',
cls._all_text(msg), flags=re.DOTALL)
m = re.search(r'<summary>(.*?)</summary>', text, re.DOTALL)
if m and m.group(1).strip():
return m.group(1).strip()[:80]
for line in text.splitlines():
if line.strip():
return line.strip()[:80]
return "(无摘要)"
raw = (m.group(1) if m else text).strip()
if not raw:
return None
return raw.replace('\n', '')[:80]
@classmethod
def _derive_hist_info(cls, history) -> list:
"""从对话历史重建 `handler.history_info`(轮级纪要):真实用户提问 → `[USER]: …`;
每条 assistant(=一轮) → `[Agent] <summary>`。这正是 GA 原本构建 history_info 的
方式,故 reconcile 从日志吸收外部/老会话轮次时可据此把工作记忆补回(否则续接后
每条 assistant(=一轮) → 有字才写 `[Agent] …`(与 ga.turn_end_callback 同口径)。
reconcile 从日志吸收外部/老会话轮次时可据此把工作记忆补回(否则续接后
rewind 到这些轮,纪要会缺失 → 串味)。"""
out: list = []
for m in history:
@@ -742,7 +744,9 @@ class RewindStore:
if q:
out.append(f"[USER]: {q}")
elif role == "assistant":
out.append(f"[Agent] {cls._summary_of(m)}")
s = cls._summary_of(m)
if s is not None:
out.append('[Agent] ' + s)
return out
@classmethod
+5 -6
View File
@@ -571,13 +571,12 @@ class GenericAgentHandler(BaseHandler):
def turn_end_callback(self, response, tool_calls, tool_results, turn, next_prompt, exit_reason):
_c = re.sub(r'```.*?```|<thinking>.*?</thinking>', '', response.content, flags=re.DOTALL)
rsumm = re.search(r"<summary>(.*?)</summary>", _c, re.DOTALL)
if rsumm: summary = rsumm.group(1).strip()
else:
tc = tool_calls[0]; clean_args = {k: v for k, v in tc['args'].items() if not k.startswith('_')} # at least one because no_tool
summary = _c.strip() or smart_format("直接回答了用户问题" if tc['tool_name'] == 'no_tool' else f"{tc['tool_name']}, args: {clean_args}", max_str_len=40)
raw = (rsumm.group(1) if rsumm else _c).strip()
if raw:
summary = smart_format(raw.replace('\n', ''), max_str_len=80)
self.history_info.append('[Agent] ' + summary)
if not rsumm and tool_calls and tool_calls[0]['tool_name'] != 'no_tool':
next_prompt += "\n\n\n[TIPS] 必须在回复文本中包含<summary>!\n\n"
summary = smart_format(summary.replace('\n', ''), max_str_len=80)
self.history_info.append(f'[Agent] {summary}')
_plan = self._in_plan_mode()
if turn % 175 == 0 and (not _plan):