Spaces:
Sleeping
Sleeping
| """長期記憶 (JSON ファイル) の読み書き.""" | |
| from __future__ import annotations | |
| import copy | |
| import json | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Any | |
| import session_paths as sp | |
| # パスはセッションごとに切り替わるため、定数ではなく関数で取得する。 | |
| ROOT = sp.ROOT | |
| BATCH_SIZE = 20 | |
| DEFAULT_USER_MEMORY: dict[str, Any] = { | |
| "user_profile": { | |
| "user_name": "", | |
| "nickname": "", | |
| "goal": "" | |
| }, | |
| "ai_persona": { | |
| "name": "", | |
| "persona": "", | |
| "traits": { | |
| "BigFive_Openness": 50, | |
| "BigFive_Conscientiousness": 50, | |
| "BigFive_Extraversion": 50, | |
| "BigFive_Agreeableness": 50, | |
| "BigFive_Neuroticism": 50, | |
| "KiSS18_BasicSkill": 50, | |
| "N": 0, | |
| "E": 0, | |
| "O": 0, | |
| "A": 0, | |
| "C": 0 | |
| }, | |
| "persona_style_description": "", | |
| "personality_text": "" | |
| }, | |
| "relationship_prompt": "", | |
| "relationship_state": { | |
| "affection": 0, | |
| "tension": 0.0, | |
| "comfort_trust": 0, | |
| "insecurity": 0, | |
| "initiative_affection": 0 | |
| }, | |
| "history_summary": "" | |
| } | |
| def _new_default_user_memory() -> dict[str, Any]: | |
| return copy.deepcopy(DEFAULT_USER_MEMORY) | |
| def _deep_merge(default: dict[str, Any], loaded: dict[str, Any]) -> dict[str, Any]: | |
| """default に loaded を重ねる。古い JSON に足りないキーがあっても落ちないようにする。""" | |
| merged = copy.deepcopy(default) | |
| for key, value in loaded.items(): | |
| if isinstance(value, dict) and isinstance(merged.get(key), dict): | |
| merged[key] = _deep_merge(merged[key], value) | |
| else: | |
| merged[key] = value | |
| return merged | |
| # user_memory.json を初期化する(無ければデフォルトで作成、あれば読み込み) | |
| def init_user_memory(overwrite: bool = False) -> dict[str, Any]: | |
| user_memory_path = sp.user_memory_path() | |
| if overwrite or not user_memory_path.exists(): | |
| data = _new_default_user_memory() | |
| save_json(user_memory_path, data) | |
| return data | |
| loaded = load_json(user_memory_path, {}) | |
| return _deep_merge(_new_default_user_memory(), loaded) | |
| def load_json(path: Path, default: Any) -> Any: | |
| if not path.exists(): | |
| return default | |
| try: | |
| return json.loads(path.read_text(encoding="utf-8")) | |
| except json.JSONDecodeError: | |
| return default | |
| def save_json(path: Path, data: Any) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_text( | |
| json.dumps(data, ensure_ascii=False, indent=2), | |
| encoding="utf-8", | |
| ) | |
| def _summary_to_text(summary: Any) -> str: | |
| if isinstance(summary, dict): | |
| return str(summary.get("recent_summary") or "") | |
| return str(summary or "") | |
| #load memory from json files, if not exist, return default values | |
| def load_memory() -> dict[str, Any]: | |
| memory = init_user_memory() | |
| memory["conversation_history"] = load_json( | |
| sp.conversation_path(), | |
| memory.get("conversation_history", []), | |
| ) | |
| summary_data = load_json(sp.summary_path(), {}) | |
| memory["history_summary"] = _summary_to_text(summary_data) | |
| return memory | |
| def update_history_summary(memory: dict[str, Any], summary: str) -> None: | |
| memory["history_summary"] = summary | |
| save_json( | |
| sp.summary_path(), | |
| { | |
| "recent_summary": summary, | |
| "updated_at": datetime.now().isoformat(timespec="seconds"), | |
| }, | |
| ) | |
| def create_summary_from_txt( | |
| memory: dict[str, Any], | |
| txt_path: Path, | |
| self_name: str, | |
| batch_size: int = BATCH_SIZE, | |
| max_lines: int = BATCH_SIZE, | |
| *, | |
| tokenizer: Any | None = None, | |
| model: Any | None = None, | |
| device: str | None = None, | |
| ) -> None: | |
| from data.Models.Text2History.TinySwallow import summarize_history_from_txt | |
| summary = summarize_history_from_txt( | |
| txt_path, | |
| self_name=self_name, | |
| batch_size=batch_size, | |
| max_lines=max_lines, | |
| tokenizer=tokenizer, | |
| model=model, | |
| device=device, | |
| ) | |
| memory["history_summary"] = _summary_to_text(summary) | |
| update_history_summary(memory, memory["history_summary"]) | |
| def save_memory(memory: dict[str, Any]) -> None: | |
| save_json(sp.conversation_path(), memory.get("conversation_history", [])) | |
| update_history_summary( | |
| memory, | |
| _summary_to_text(memory.get("history_summary", "")), | |
| ) | |
| user_memory = { | |
| key: value | |
| for key, value in memory.items() | |
| if key not in {"conversation_history", "episodic_memory"} | |
| } | |
| user_memory["history_summary"] = _summary_to_text(memory.get("history_summary", "")) | |
| save_json(sp.user_memory_path(), user_memory) | |
| def reset_memory() -> dict[str, Any]: | |
| """全メモリを初期値に戻して保存する.""" | |
| memory = _new_default_user_memory() | |
| save_memory(memory) | |
| return memory | |
| def get_conversation(memory: dict[str, Any], n: int = 6) -> list[dict[str, str]]: | |
| return memory.get("conversation_history", [])[-n:] | |
| def get_history(memory: dict[str, Any]): | |
| return _summary_to_text(memory.get("history_summary", "")) | |
| def add_message( | |
| memory: dict[str, Any], | |
| role: str, | |
| text: str, | |
| *, | |
| keep_last: int | None = None, | |
| ) -> None: | |
| if role not in {"user", "assistant"}: | |
| raise ValueError("role must be 'user' or 'assistant'") | |
| history = memory.setdefault("conversation_history", []) | |
| history.append({ | |
| "role": role, | |
| "text": text, | |
| "timestamp": datetime.now().isoformat(timespec="seconds"), | |
| }) | |
| if keep_last is not None: | |
| memory["conversation_history"] = history[-keep_last:] | |
| #初期設定のhistory_summary.json | |
| def create_summary(memory: dict[str, Any], batch_size: int = BATCH_SIZE): | |
| from data.Models.Text2History.TinySwallow import summarize_history_from_txt | |
| summary = summarize_history_from_txt( | |
| sp.data_dir(), | |
| self_name="Rayta", | |
| batch_size=batch_size, | |
| max_lines=200, | |
| ) | |
| memory["history_summary"] = summary | |
| #history_summary.jsonをUpdate | |
| def update_summary( | |
| memory: dict[str, Any], | |
| *, | |
| tokenizer, | |
| model, | |
| device, | |
| batch_size: int = BATCH_SIZE, | |
| ) -> None: | |
| history = memory.get("conversation_history", []) | |
| if len(history) < batch_size: | |
| return | |
| from data.Models.Text2History.TinySwallow import summarize | |
| previous_summary = _summary_to_text(memory.get("history_summary", "")) | |
| new_summary = summarize(previous_summary, history, tokenizer, model, device) | |
| update_history_summary(memory, new_summary) | |
| memory["conversation_history"] = [] | |