|
|
@@ -141,12 +141,13 @@ class InteractiveController:
|
|
|
print(" 2. 触发经验总结(reflect)")
|
|
|
print(" 3. 查看当前 GoalTree")
|
|
|
print(" 4. 手动压缩上下文(compact)")
|
|
|
- print(" 5. 继续执行")
|
|
|
- print(" 6. 停止执行")
|
|
|
+ print(" 5. 从指定消息续跑")
|
|
|
+ print(" 6. 继续执行")
|
|
|
+ print(" 7. 停止执行")
|
|
|
print("=" * 60)
|
|
|
|
|
|
while True:
|
|
|
- choice = input("请输入选项 (1-6): ").strip()
|
|
|
+ choice = input("请输入选项 (1-7): ").strip()
|
|
|
|
|
|
if choice == "1":
|
|
|
# 插入干预消息
|
|
|
@@ -189,11 +190,16 @@ class InteractiveController:
|
|
|
continue
|
|
|
|
|
|
elif choice == "5":
|
|
|
+ # 从指定消息续跑
|
|
|
+ await self.resume_from_message(trace_id)
|
|
|
+ return {"action": "stop"} # 返回 stop,让外层循环退出
|
|
|
+
|
|
|
+ elif choice == "6":
|
|
|
# 继续执行
|
|
|
print("\n继续执行...")
|
|
|
return {"action": "continue"}
|
|
|
|
|
|
- elif choice == "6":
|
|
|
+ elif choice == "7":
|
|
|
# 停止执行
|
|
|
print("\n停止执行...")
|
|
|
return {"action": "stop"}
|
|
|
@@ -272,3 +278,150 @@ class InteractiveController:
|
|
|
print(f"❌ 压缩任务启动失败: {e}")
|
|
|
except Exception as e:
|
|
|
print(f"❌ 发生错误: {e}")
|
|
|
+
|
|
|
+ async def resume_from_message(self, trace_id: str):
|
|
|
+ """
|
|
|
+ 从指定消息续跑
|
|
|
+
|
|
|
+ 让用户选择一条消息,然后从该消息之后重新执行。
|
|
|
+
|
|
|
+ Args:
|
|
|
+ trace_id: Trace ID
|
|
|
+ """
|
|
|
+ print("\n正在加载消息列表...")
|
|
|
+
|
|
|
+ # 1. 获取所有消息
|
|
|
+ messages = await self.store.get_messages(trace_id)
|
|
|
+ if not messages:
|
|
|
+ print("❌ 没有找到任何消息")
|
|
|
+ return
|
|
|
+
|
|
|
+ # 2. 显示消息列表(只显示 user 和 assistant 消息)
|
|
|
+ display_messages = [
|
|
|
+ msg for msg in messages
|
|
|
+ if msg.role in ("user", "assistant")
|
|
|
+ ]
|
|
|
+
|
|
|
+ if not display_messages:
|
|
|
+ print("❌ 没有可选择的消息")
|
|
|
+ return
|
|
|
+
|
|
|
+ print("\n" + "=" * 60)
|
|
|
+ print(" 消息列表")
|
|
|
+ print("=" * 60)
|
|
|
+
|
|
|
+ for i, msg in enumerate(display_messages, 1):
|
|
|
+ role_label = "👤 User" if msg.role == "user" else "🤖 Assistant"
|
|
|
+ content_preview = self._get_content_preview(msg.content)
|
|
|
+ print(f"{i}. [{msg.sequence:04d}] {role_label}: {content_preview}")
|
|
|
+
|
|
|
+ print("=" * 60)
|
|
|
+
|
|
|
+ # 3. 让用户选择
|
|
|
+ while True:
|
|
|
+ choice = input(f"\n请选择消息编号 (1-{len(display_messages)}),或输入 'c' 取消: ").strip()
|
|
|
+
|
|
|
+ if choice.lower() == 'c':
|
|
|
+ print("已取消")
|
|
|
+ return
|
|
|
+
|
|
|
+ try:
|
|
|
+ idx = int(choice) - 1
|
|
|
+ if 0 <= idx < len(display_messages):
|
|
|
+ selected_msg = display_messages[idx]
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ print(f"无效编号,请输入 1-{len(display_messages)}")
|
|
|
+ except ValueError:
|
|
|
+ print("无效输入,请输入数字或 'c'")
|
|
|
+
|
|
|
+ # 4. 确认是否重新生成最后一条消息
|
|
|
+ regenerate_last = False
|
|
|
+ if selected_msg.role == "assistant":
|
|
|
+ confirm = input("\n是否重新生成这条 Assistant 消息?(y/n): ").strip().lower()
|
|
|
+ regenerate_last = (confirm == 'y')
|
|
|
+
|
|
|
+ # 5. 调用 runner.run() 续跑
|
|
|
+ print(f"\n从消息 {selected_msg.sequence:04d} 之后续跑...")
|
|
|
+ if regenerate_last:
|
|
|
+ print("将重新生成最后一条 Assistant 消息")
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 加载 trace 和消息历史
|
|
|
+ trace = await self.store.get_trace(trace_id)
|
|
|
+ if not trace:
|
|
|
+ print("❌ Trace 不存在")
|
|
|
+ return
|
|
|
+
|
|
|
+ # 截断消息到指定位置
|
|
|
+ truncated_messages = []
|
|
|
+ for msg in messages:
|
|
|
+ if msg.sequence <= selected_msg.sequence:
|
|
|
+ truncated_messages.append({
|
|
|
+ "role": msg.role,
|
|
|
+ "content": msg.content,
|
|
|
+ "id": msg.message_id,
|
|
|
+ })
|
|
|
+
|
|
|
+ # 如果需要重新生成,删除最后一条 assistant 消息
|
|
|
+ if regenerate_last and truncated_messages and truncated_messages[-1]["role"] == "assistant":
|
|
|
+ truncated_messages.pop()
|
|
|
+
|
|
|
+ # 调用 runner.run() 续跑
|
|
|
+ print("\n开始执行...")
|
|
|
+ async for event in self.runner.run(
|
|
|
+ messages=truncated_messages,
|
|
|
+ trace_id=trace_id,
|
|
|
+ model=trace.model,
|
|
|
+ temperature=trace.llm_params.get("temperature", 0.3),
|
|
|
+ max_iterations=200,
|
|
|
+ tools=None, # 使用原有配置
|
|
|
+ ):
|
|
|
+ # 简单输出事件
|
|
|
+ if event.get("type") == "message":
|
|
|
+ msg = event.get("message")
|
|
|
+ if msg and msg.get("role") == "assistant":
|
|
|
+ content = msg.get("content", {})
|
|
|
+ if isinstance(content, dict):
|
|
|
+ text = content.get("text", "")
|
|
|
+ else:
|
|
|
+ text = str(content)
|
|
|
+ if text:
|
|
|
+ print(f"\n🤖 Assistant: {text[:200]}...")
|
|
|
+
|
|
|
+ print("\n✅ 执行完成")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ print(f"❌ 执行失败: {e}")
|
|
|
+ import traceback
|
|
|
+ traceback.print_exc()
|
|
|
+
|
|
|
+ def _get_content_preview(self, content: Any, max_length: int = 60) -> str:
|
|
|
+ """
|
|
|
+ 获取消息内容预览
|
|
|
+
|
|
|
+ Args:
|
|
|
+ content: 消息内容
|
|
|
+ max_length: 最大长度
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ 内容预览字符串
|
|
|
+ """
|
|
|
+ if isinstance(content, dict):
|
|
|
+ text = content.get("text", "")
|
|
|
+ tool_calls = content.get("tool_calls", [])
|
|
|
+ if text:
|
|
|
+ preview = text.strip()
|
|
|
+ elif tool_calls:
|
|
|
+ preview = f"[调用工具: {', '.join(tc.get('function', {}).get('name', '?') for tc in tool_calls)}]"
|
|
|
+ else:
|
|
|
+ preview = "[空消息]"
|
|
|
+ elif isinstance(content, str):
|
|
|
+ preview = content.strip()
|
|
|
+ else:
|
|
|
+ preview = str(content)
|
|
|
+
|
|
|
+ if len(preview) > max_length:
|
|
|
+ preview = preview[:max_length] + "..."
|
|
|
+
|
|
|
+ return preview
|