|
|
@@ -220,6 +220,82 @@ def _print_message_details(message: Message):
|
|
|
|
|
|
# ===== 交互菜单 =====
|
|
|
|
|
|
+async def perform_reflection(
|
|
|
+ runner: AgentRunner,
|
|
|
+ trace_id: str,
|
|
|
+ store: FileSystemTraceStore,
|
|
|
+ focus: str = "",
|
|
|
+ intervention_message: str = "",
|
|
|
+) -> str:
|
|
|
+ """
|
|
|
+ 执行经验总结(反思)
|
|
|
+
|
|
|
+ Args:
|
|
|
+ runner: AgentRunner 实例
|
|
|
+ trace_id: Trace ID
|
|
|
+ store: TraceStore 实例
|
|
|
+ focus: 可选的反思重点
|
|
|
+ intervention_message: 可选的干预消息,如果提供则一并记录到经验文件中
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ 反思文本内容,如果失败则返回空字符串
|
|
|
+ """
|
|
|
+ from agent.trace.compaction import build_reflect_prompt
|
|
|
+ from datetime import datetime
|
|
|
+
|
|
|
+ # 保存当前 head_sequence
|
|
|
+ trace = await store.get_trace(trace_id)
|
|
|
+ if not trace:
|
|
|
+ print("未找到 Trace,无法执行反思")
|
|
|
+ return ""
|
|
|
+
|
|
|
+ saved_head = trace.head_sequence
|
|
|
+
|
|
|
+ prompt = build_reflect_prompt()
|
|
|
+ if focus:
|
|
|
+ prompt += f"\n\n请特别关注:{focus}"
|
|
|
+
|
|
|
+ print("正在生成反思...")
|
|
|
+ reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
|
|
|
+
|
|
|
+ reflection_text = ""
|
|
|
+ try:
|
|
|
+ result = await runner.run_result(
|
|
|
+ messages=[{"role": "user", "content": prompt}],
|
|
|
+ config=reflect_cfg,
|
|
|
+ )
|
|
|
+ reflection_text = result.get("summary", "")
|
|
|
+ finally:
|
|
|
+ # 恢复 head_sequence(反思消息成为侧枝)
|
|
|
+ await store.update_trace(trace_id, head_sequence=saved_head)
|
|
|
+
|
|
|
+ # 追加到 experiences 文件
|
|
|
+ if reflection_text:
|
|
|
+ experiences_path = runner.experiences_path or "./.cache/experiences.md"
|
|
|
+ os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
|
|
|
+ header = f"\n\n---\n\n## {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
|
|
|
+
|
|
|
+ # 如果提供了干预消息,也一并记录
|
|
|
+ content_parts = []
|
|
|
+ if intervention_message:
|
|
|
+ content_parts.append(f"**干预消息:**\n{intervention_message}\n")
|
|
|
+ content_parts.append(f"**反思总结:**\n{reflection_text}")
|
|
|
+
|
|
|
+ with open(experiences_path, "a", encoding="utf-8") as f:
|
|
|
+ f.write(header + "\n".join(content_parts) + "\n")
|
|
|
+ print(f"\n反思已保存到: {experiences_path}")
|
|
|
+ if intervention_message:
|
|
|
+ print("\n--- 干预消息 ---")
|
|
|
+ print(intervention_message)
|
|
|
+ print("\n--- 反思内容 ---")
|
|
|
+ print(reflection_text)
|
|
|
+ print("--- 结束 ---\n")
|
|
|
+ else:
|
|
|
+ print("未生成反思内容")
|
|
|
+
|
|
|
+ return reflection_text
|
|
|
+
|
|
|
+
|
|
|
def _read_multiline() -> str:
|
|
|
"""
|
|
|
读取多行输入,以连续两次回车(空行)结束。
|
|
|
@@ -282,6 +358,13 @@ async def show_interactive_menu(
|
|
|
# 从 store 读取实际的 last_sequence,避免本地 current_sequence 过时
|
|
|
live_trace = await store.get_trace(trace_id)
|
|
|
actual_sequence = live_trace.last_sequence if live_trace and live_trace.last_sequence else current_sequence
|
|
|
+
|
|
|
+ # 触发干预后,自动执行一次经验总结(并将干预消息一并记录)
|
|
|
+ print("\n" + "=" * 60)
|
|
|
+ print("自动触发经验总结...")
|
|
|
+ print("=" * 60)
|
|
|
+ await perform_reflection(runner, trace_id, store, focus="", intervention_message=text)
|
|
|
+
|
|
|
return {
|
|
|
"action": "continue",
|
|
|
"messages": [{"role": "user", "content": text}],
|
|
|
@@ -292,46 +375,8 @@ async def show_interactive_menu(
|
|
|
# 触发经验总结
|
|
|
print("\n触发经验总结...")
|
|
|
focus = input("请输入反思重点(可选,直接回车跳过): ").strip()
|
|
|
-
|
|
|
- from agent.trace.compaction import build_reflect_prompt
|
|
|
-
|
|
|
- # 保存当前 head_sequence
|
|
|
- trace = await store.get_trace(trace_id)
|
|
|
- saved_head = trace.head_sequence
|
|
|
-
|
|
|
- prompt = build_reflect_prompt()
|
|
|
- if focus:
|
|
|
- prompt += f"\n\n请特别关注:{focus}"
|
|
|
-
|
|
|
- print("正在生成反思...")
|
|
|
- reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
|
|
|
-
|
|
|
- reflection_text = ""
|
|
|
- try:
|
|
|
- result = await runner.run_result(
|
|
|
- messages=[{"role": "user", "content": prompt}],
|
|
|
- config=reflect_cfg,
|
|
|
- )
|
|
|
- reflection_text = result.get("summary", "")
|
|
|
- finally:
|
|
|
- # 恢复 head_sequence(反思消息成为侧枝)
|
|
|
- await store.update_trace(trace_id, head_sequence=saved_head)
|
|
|
-
|
|
|
- # 追加到 experiences 文件
|
|
|
- if reflection_text:
|
|
|
- from datetime import datetime
|
|
|
- experiences_path = runner.experiences_path or "./.cache/experiences.md"
|
|
|
- os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
|
|
|
- header = f"\n\n---\n\n## {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
|
|
|
- with open(experiences_path, "a", encoding="utf-8") as f:
|
|
|
- f.write(header + reflection_text + "\n")
|
|
|
- print(f"\n反思已保存到: {experiences_path}")
|
|
|
- print("\n--- 反思内容 ---")
|
|
|
- print(reflection_text)
|
|
|
- print("--- 结束 ---\n")
|
|
|
- else:
|
|
|
- print("未生成反思内容")
|
|
|
-
|
|
|
+
|
|
|
+ await perform_reflection(runner, trace_id, store, focus=focus)
|
|
|
continue
|
|
|
|
|
|
elif choice == "3":
|
|
|
@@ -724,6 +769,19 @@ async def main():
|
|
|
print(f"\n✓ Messages 可视化已保存: {html_path}")
|
|
|
except Exception as e:
|
|
|
print(f"\n⚠ 生成 HTML 失败: {e}")
|
|
|
+
|
|
|
+ # 流程执行完退出时,自动总结一次经验
|
|
|
+ try:
|
|
|
+ final_trace = await store.get_trace(current_trace_id)
|
|
|
+ if final_trace and final_trace.status in ("completed", "failed"):
|
|
|
+ print("\n" + "=" * 60)
|
|
|
+ print("流程执行完成,自动触发经验总结...")
|
|
|
+ print("=" * 60)
|
|
|
+ await perform_reflection(runner, current_trace_id, store, focus="")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"\n⚠ 自动经验总结失败: {e}")
|
|
|
+ import traceback
|
|
|
+ traceback.print_exc()
|
|
|
|
|
|
# 6. 输出结果
|
|
|
if final_response:
|