manual_reflect.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """
  2. 手动反思工具
  3. 针对已完成或正在进行的 trace,手动触发反思并提供建议。
  4. 用法:
  5. python manual_reflect.py --trace <trace_id> --sequence <seq>
  6. python manual_reflect.py --trace <trace_id> --sequence <seq> --feedback "建议内容"
  7. """
  8. import asyncio
  9. import argparse
  10. import sys
  11. from pathlib import Path
  12. # 添加项目根目录到 Python 路径
  13. sys.path.insert(0, str(Path(__file__).parent))
  14. from dotenv import load_dotenv
  15. load_dotenv()
  16. from agent.core.runner import AgentRunner, RunConfig
  17. from agent.trace import FileSystemTraceStore
  18. from agent.llm import create_openrouter_llm_call
  19. from agent.tools.builtin.knowledge import KnowledgeConfig
  20. def read_multiline() -> str:
  21. """读取多行输入,以连续两次回车(空行)结束"""
  22. print("\n请输入你的建议(连续输入两次回车结束):")
  23. lines = []
  24. blank_count = 0
  25. while True:
  26. line = input()
  27. if line == "":
  28. blank_count += 1
  29. if blank_count >= 2:
  30. break
  31. lines.append("")
  32. else:
  33. blank_count = 0
  34. lines.append(line)
  35. # 移除末尾的空行
  36. while lines and lines[-1] == "":
  37. lines.pop()
  38. return "\n".join(lines)
  39. async def main():
  40. parser = argparse.ArgumentParser(
  41. description="手动反思工具:基于用户建议对 trace 历史进行反思"
  42. )
  43. parser.add_argument("--trace", required=True, help="Trace ID")
  44. parser.add_argument("--sequence", type=int, required=True, help="反思截止点(包含此 sequence)")
  45. parser.add_argument("--feedback", help="用户建议(可选,不指定则交互式输入)")
  46. parser.add_argument("--store-path", default=".trace", help="TraceStore 路径")
  47. parser.add_argument("--model", default="claude-sonnet-4.5", help="使用的模型")
  48. parser.add_argument("--config", help="配置文件路径(如 examples/research/config.py),用于加载 KnowledgeConfig")
  49. args = parser.parse_args()
  50. trace_id = args.trace
  51. sequence = args.sequence
  52. # 初始化 TraceStore
  53. store = FileSystemTraceStore(base_path=args.store_path)
  54. # 验证 trace 存在
  55. trace = await store.get_trace(trace_id)
  56. if not trace:
  57. print(f"❌ 错误:Trace 不存在: {trace_id}")
  58. sys.exit(1)
  59. print(f"\n=== 手动反思 ===")
  60. print(f"Trace ID: {trace_id[:8]}...")
  61. print(f"状态: {trace.status}")
  62. print(f"总消息数: {trace.total_messages}")
  63. print(f"反思截止点: sequence {sequence}")
  64. # 获取用户建议
  65. if args.feedback:
  66. feedback = args.feedback
  67. else:
  68. feedback = read_multiline()
  69. if not feedback.strip():
  70. print("❌ 错误:未提供建议")
  71. sys.exit(1)
  72. print(f"\n建议长度: {len(feedback)} 字符")
  73. # 获取历史消息(从 sequence 回溯到 root)
  74. print(f"\n正在加载历史消息...")
  75. messages = await store.get_main_path_messages(trace_id, sequence)
  76. print(f" 加载了 {len(messages)} 条消息")
  77. # 转换为 OpenAI 格式(使用 to_llm_dict() 正确处理 assistant 消息的 dict 格式)
  78. history = []
  79. for msg in messages:
  80. if msg.role in ("user", "assistant"):
  81. history.append(msg.to_llm_dict())
  82. print(f" 转换后历史消息数: {len(history)}")
  83. # 构造反思 prompt(插入用户建议)
  84. from agent.core.prompts.knowledge import MANUAL_REFLECT_PROMPT
  85. reflect_prompt = MANUAL_REFLECT_PROMPT.format(user_feedback=feedback)
  86. # 初始化 Runner
  87. runner = AgentRunner(
  88. trace_store=store,
  89. llm_call=create_openrouter_llm_call(model=f"anthropic/{args.model}"),
  90. debug=False
  91. )
  92. # 加载 KnowledgeConfig
  93. knowledge_config = KnowledgeConfig()
  94. if args.config:
  95. # 从配置文件加载
  96. config_path = Path(args.config)
  97. if not config_path.exists():
  98. print(f"❌ 错误:配置文件不存在: {args.config}")
  99. sys.exit(1)
  100. print(f"\n正在加载配置: {args.config}")
  101. # 动态导入配置模块
  102. import importlib.util
  103. spec = importlib.util.spec_from_file_location("config", config_path)
  104. config_module = importlib.util.module_from_spec(spec)
  105. spec.loader.exec_module(config_module)
  106. if hasattr(config_module, 'RUN_CONFIG') and hasattr(config_module.RUN_CONFIG, 'knowledge'):
  107. knowledge_config = config_module.RUN_CONFIG.knowledge
  108. print(f" owner: {knowledge_config.owner or '(默认)'}")
  109. print(f" default_tags: {knowledge_config.default_tags}")
  110. print(f" default_scopes: {knowledge_config.default_scopes}")
  111. else:
  112. print(f" ⚠️ 配置文件中未找到 RUN_CONFIG.knowledge,使用默认配置")
  113. # 使用加载的 KnowledgeConfig
  114. config = RunConfig(
  115. model=args.model,
  116. knowledge=knowledge_config
  117. )
  118. # 执行反思
  119. print(f"\n正在执行反思...")
  120. print(f" 模型: {args.model}")
  121. print(f" Prompt 长度: {len(reflect_prompt)} 字符")
  122. await runner._run_reflect(
  123. trace_id=trace_id,
  124. history=history,
  125. config=config,
  126. reflect_prompt=reflect_prompt,
  127. source_name="manual_reflection"
  128. )
  129. print(f"\n✅ 反思完成")
  130. print(f"\n提示:经验已保存到知识库,可通过 knowledge_search 工具查询")
  131. if __name__ == "__main__":
  132. asyncio.run(main())