memory.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. """
  2. Memory 相关工具 —— 目前只包含 dream 操作(见 cyber_agent/docs/memory.md 第四节)。
  3. dream 整理 Agent 身份的长期记忆:回顾最近 trace 的执行历史,
  4. 逐个 trace 做反思,再跨 trace 整合写回记忆文件。
  5. 设计要点:
  6. - 需要 config.memory(MemoryConfig)才可用;否则报错。
  7. - 不是 knowledge_save_pending 那样每 trace 都要用的日常工具 ——
  8. 所以放在独立 group "memory",通过 tool_groups 显式开启。
  9. """
  10. from __future__ import annotations
  11. import logging
  12. from typing import Optional
  13. from cyber_agent.core.dream import DreamScope, run_dream
  14. from cyber_agent.core.memory import MemoryConfig
  15. from cyber_agent.tools import tool, ToolResult, ToolContext
  16. logger = logging.getLogger(__name__)
  17. @tool(groups=["memory"], hidden_params=["context"])
  18. async def dream(
  19. reflect_model: str = "",
  20. dream_model: str = "",
  21. context: Optional[ToolContext] = None,
  22. ) -> ToolResult:
  23. """整理长期记忆。回顾最近的执行历史,更新记忆文件。
  24. 本工具做两件事:
  25. 1. per-trace 反思:扫描未反思的 trace,为每个生成反思摘要
  26. 2. 跨 trace 整合:汇总未消化的反思 + 当前记忆,让 LLM 更新记忆文件
  27. 需要 RunConfig.memory(MemoryConfig)才可调用。
  28. Args:
  29. reflect_model: per-trace 反思用的模型(空则默认 gpt-4o-mini)
  30. dream_model: 跨 trace 整合用的模型(空则默认 gpt-4o)
  31. """
  32. runner = context.get("runner") if context else None
  33. if runner is None:
  34. return ToolResult(
  35. title="❌ dream 不可用",
  36. output="缺少 runner(需要从 AgentRunner 上下文调用)",
  37. error="runner not in context",
  38. )
  39. memory_config = context.get("memory_config") if context else None
  40. if not isinstance(memory_config, MemoryConfig):
  41. return ToolResult(
  42. title="❌ dream 不可用",
  43. output="当前工具调用未注入 MemoryConfig,不是 memory-bearing Agent",
  44. error="memory_config not in tool context",
  45. )
  46. dream_scope = context.get("dream_scope") if context else None
  47. if not isinstance(dream_scope, DreamScope):
  48. return ToolResult(
  49. title="❌ dream 不可用",
  50. output="当前工具调用未注入 DreamScope,为避免跨身份扫描已拒绝执行",
  51. error="dream_scope not in tool context",
  52. )
  53. if not runner.trace_store or not runner.llm_call:
  54. return ToolResult(
  55. title="❌ dream 不可用",
  56. output="runner 缺少 trace_store 或 llm_call",
  57. error="runner dependencies missing",
  58. )
  59. report = await run_dream(
  60. store=runner.trace_store,
  61. llm_call=runner.llm_call,
  62. memory_config=memory_config,
  63. dream_scope=dream_scope,
  64. reflect_model=reflect_model or "gpt-4o-mini",
  65. dream_model=dream_model or "gpt-4o",
  66. )
  67. lines = []
  68. lines.append(f"per-trace 反思: {len(report.per_trace_summaries)} 条")
  69. if report.skipped_traces:
  70. lines.append(f"跳过: {len(report.skipped_traces)} 条 trace(日志详见 logger)")
  71. lines.append(f"消化 reflection: {report.consumed_reflection_count} 条")
  72. lines.append(f"更新记忆文件: {len(report.updated_files)} 个")
  73. for p in report.updated_files:
  74. lines.append(f" - {p}")
  75. if report.reasoning:
  76. lines.append(f"\n整合理由: {report.reasoning}")
  77. output = "\n".join(lines)
  78. return ToolResult(
  79. title="🧠 dream 完成",
  80. output=output,
  81. long_term_memory=f"dream: reflected={len(report.per_trace_summaries)}, "
  82. f"consumed={report.consumed_reflection_count}, "
  83. f"files_updated={len(report.updated_files)}",
  84. metadata={
  85. "per_trace_count": len(report.per_trace_summaries),
  86. "consumed": report.consumed_reflection_count,
  87. "updated_files": report.updated_files,
  88. },
  89. )