ChatDev/runtime/node/agent/memory/blackboard_memory.py
2026-01-07 16:24:01 +08:00

100 lines
3.2 KiB
Python
Executable File

"""Lightweight append-only Blackboard memory implementation."""
import json
import os
import time
import uuid
from typing import List
from entity.configs import MemoryStoreConfig
from entity.configs.node.memory import BlackboardMemoryConfig
from runtime.node.agent.memory.memory_base import (
MemoryBase,
MemoryContentSnapshot,
MemoryItem,
MemoryWritePayload,
)
class BlackboardMemory(MemoryBase):
"""Simple append-only memory: save raw outputs, retrieve by recency."""
def __init__(self, store: MemoryStoreConfig):
config = store.as_config(BlackboardMemoryConfig)
if not config:
raise ValueError("BlackboardMemory requires a blackboard memory store configuration")
super().__init__(store)
self.config = config
self.memory_path = config.memory_path
self.max_items = config.max_items
# -------- Persistence --------
def load(self) -> None:
if not self.memory_path or not os.path.exists(self.memory_path):
self.contents = []
return
try:
with open(self.memory_path, "r", encoding="utf-8") as file:
data = json.load(file)
contents: List[MemoryItem] = []
for raw in data:
try:
contents.append(MemoryItem.from_dict(raw))
except Exception:
continue
self.contents = contents
except Exception:
# Corrupted file -> reset to empty to avoid blocking execution
self.contents = []
def save(self) -> None:
if not self.memory_path:
return
os.makedirs(os.path.dirname(self.memory_path), exist_ok=True)
payload = [item.to_dict() for item in self.contents[-self.max_items :]]
with open(self.memory_path, "w", encoding="utf-8") as file:
json.dump(payload, file, ensure_ascii=False, indent=2)
# -------- Memory operations --------
def retrieve(
self,
agent_role: str,
query: MemoryContentSnapshot,
top_k: int,
similarity_threshold: float,
) -> List[MemoryItem]:
if not self.contents:
return []
if top_k <= 0 or top_k >= len(self.contents):
return list(self.contents)
return list(self.contents[-top_k:])
def update(self, payload: MemoryWritePayload) -> None:
snapshot = payload.output_snapshot or payload.input_snapshot
content = (snapshot.text if snapshot else payload.inputs_text or "").strip()
if not content:
return
metadata = {
"agent_role": payload.agent_role,
"input_preview": (payload.inputs_text or "")[:200],
"attachments": snapshot.attachment_overview() if snapshot else [],
}
memory_item = MemoryItem(
id=f"bb_{uuid.uuid4().hex}",
content_summary=content,
metadata=metadata,
timestamp=time.time(),
input_snapshot=payload.input_snapshot,
output_snapshot=payload.output_snapshot,
)
self.contents.append(memory_item)
if len(self.contents) > self.max_items:
self.contents = self.contents[-self.max_items :]