Per-chat persistent memory: conversation history + condensed summary + facts.
| 87 | # ═══════════════════════════════════════════════════════════════ |
| 88 | |
| 89 | class ChatMemory: |
| 90 | """Per-chat persistent memory: conversation history + condensed summary + facts.""" |
| 91 | |
| 92 | MAX_MESSAGES = 40 |
| 93 | KEEP_RECENT = 10 |
| 94 | |
| 95 | def __init__(self, chat_id: str) -> None: |
| 96 | self.chat_id = chat_id |
| 97 | safe_id = re.sub(r'[<>:"/\\|?*\x00-\x1f]', '_', chat_id)[:120] |
| 98 | self._path = _CHAT_MEMORY_DIR / f"{safe_id}.json" |
| 99 | self._summary: str = "" |
| 100 | self._messages: list[dict[str, str]] = [] |
| 101 | self._facts: list[str] = [] |
| 102 | self._invalidated = False |
| 103 | self._load() |
| 104 | |
| 105 | def _load(self) -> None: |
| 106 | if self._path.exists(): |
| 107 | try: |
| 108 | data = json.loads(self._path.read_text(encoding="utf-8")) |
| 109 | self._summary = data.get("summary", "") |
| 110 | self._messages = data.get("messages", []) |
| 111 | self._facts = data.get("facts", []) |
| 112 | except (json.JSONDecodeError, OSError): |
| 113 | pass |
| 114 | |
| 115 | def save(self) -> None: |
| 116 | if self._invalidated: |
| 117 | return |
| 118 | _CHAT_MEMORY_DIR.mkdir(parents=True, exist_ok=True) |
| 119 | data = { |
| 120 | "chat_id": self.chat_id, |
| 121 | "summary": self._summary, |
| 122 | "messages": self._messages[-self.MAX_MESSAGES:], |
| 123 | "facts": self._facts[-50:], |
| 124 | } |
| 125 | try: |
| 126 | self._path.write_text( |
| 127 | json.dumps(data, ensure_ascii=False, indent=2), |
| 128 | encoding="utf-8", |
| 129 | ) |
| 130 | except OSError as e: |
| 131 | logger.warning("保存记忆失败: %s", e) |
| 132 | |
| 133 | @property |
| 134 | def summary(self) -> str: |
| 135 | return self._summary |
| 136 | |
| 137 | @summary.setter |
| 138 | def summary(self, value: str) -> None: |
| 139 | self._summary = value |
| 140 | |
| 141 | @property |
| 142 | def messages(self) -> list[dict[str, str]]: |
| 143 | return self._messages |
| 144 | |
| 145 | @property |
| 146 | def facts(self) -> list[str]: |