Spaces:
Runtime error
Runtime error
| """関係性スコア・性格ベクトル → 自然文. | |
| 入力: data/user_memory.json | |
| 出力: 同 JSON の ai_persona.personality_text / relationship_prompt を上書き. | |
| update_persona_text(user_memory_path: Path | str | None = None): | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from typing import Any, Dict | |
| import session_paths as sp | |
| ROOT = sp.ROOT | |
| # 学習済みモデルは全セッション共有(読み取り専用)。 | |
| MODELS_DIR = sp.DEFAULT_DATA_DIR / "Models" | |
| #text -> emotion | |
| def _run_text2emotion( | |
| txt_path: Path, | |
| self_name: str, | |
| max_lines: int = 200, | |
| user_memory_path: Path | None = None, | |
| ) -> None: | |
| from data.Models.Text2Emotion.analyze_line_emotion_trends import ( | |
| analyze_line_emotion_trends, | |
| ) | |
| relationship = analyze_line_emotion_trends( | |
| chat_file=txt_path, | |
| self_name=self_name, | |
| max_lines=max_lines, | |
| csv_out=None, | |
| user_memory_path=user_memory_path or sp.user_memory_path(), | |
| verbose=False, | |
| ) | |
| return relationship.get("other_trend") | |
| #text -> personality | |
| import sys | |
| ROOT_DIR = Path(__file__).resolve().parent.parent | |
| TEXT2PERSONALITY_DIR = ( | |
| ROOT_DIR | |
| / "data" | |
| / "Models" | |
| / "Text2Persona" | |
| ) | |
| sys.path.append(str(TEXT2PERSONALITY_DIR)) | |
| def _run_text2personality(txt_path: Path, self_name: str) -> Dict[str, Any]: | |
| from data.Models.Text2Persona.Personality_Recognition_on_RealPersonaChat import text2personality | |
| scores = text2personality.predict_personality(txt_path, self_name=self_name) | |
| return scores | |
| def _likert_to_100(score: Any) -> float | None: | |
| """1〜7 のリッカート尺度を 0〜100 に変換 (1→0, 4→50, 7→100).""" | |
| if score is None: | |
| return None | |
| try: | |
| v = float(score) | |
| except (TypeError, ValueError): | |
| return None | |
| return round(max(0.0, min(100.0, (v - 1.0) / 6.0 * 100.0)), 2) | |
| def neoac_to_bigfive(neoac: dict[str, Any]) -> dict[str, float | None]: | |
| """predict_personality の {N,E,O,A,C} (1〜7) を BigFive_* (0〜100) に変換.""" | |
| return { | |
| "BigFive_Openness": _likert_to_100(neoac.get("O")), | |
| "BigFive_Conscientiousness": _likert_to_100(neoac.get("C")), | |
| "BigFive_Extraversion": _likert_to_100(neoac.get("E")), | |
| "BigFive_Agreeableness": _likert_to_100(neoac.get("A")), | |
| "BigFive_Neuroticism": _likert_to_100(neoac.get("N")), | |
| } | |
| # --- 性格ベクトル ------ | |
| # 5バンド | |
| """ | |
| TRAITS_DEF: dict[str, dict[str, str]] = { | |
| "openness": { | |
| "label": "開放性", | |
| "very_low": "かなり現実的。定番・安心できる話題を好み、奇抜な返しは避ける", | |
| "low": "やや現実寄り。新しい話題には慎重で、慣れた話題を好む", | |
| "mid": "相手に合わせる。新しい話題にも普通に乗る", | |
| "high": "好奇心があり、新しい話題や発想に自然に乗る", | |
| "very_high": "かなり想像力豊か。少し変わった提案や個性的な返しもする", | |
| }, | |
| "conscientiousness": { | |
| "label": "誠実性", | |
| "very_low": "かなり自由。予定や細部にこだわらず、その場のノリで返す", | |
| "low": "ややゆるい。気分屋で、細かい計画より流れを優先する", | |
| "mid": "普通に誠実。必要な場面ではちゃんと気遣う", | |
| "high": "約束や予定に丁寧。責任感があり、安心感のある返しをする", | |
| "very_high": "かなり几帳面。約束・時間・言葉選びを大事にし、慎重に返す", | |
| }, | |
| "extraversion": { | |
| "label": "外向性", | |
| "very_low": "かなり静か。短く控えめで、聞き役に回ることが多い", | |
| "low": "落ち着いて控えめ。自分から強く話題を広げすぎない", | |
| "mid": "自然体。相手のテンションに合わせる", | |
| "high": "明るめ。リアクションがよく、自然に話題を広げる", | |
| "very_high": "かなり社交的。テンション高めで、自分から話題・誘い・リアクションを出す", | |
| }, | |
| "agreeableness": { | |
| "label": "協調性", | |
| "very_low": "かなり率直。本音やツッコミが出やすいが、攻撃的にはしない", | |
| "low": "やや率直。共感よりも正直な反応や軽いツッコミが出る", | |
| "mid": "普通にやさしい。相手に合わせつつ、自然に返す", | |
| "high": "共感が多め。相手を受け止め、やわらかく安心感を出す", | |
| "very_high": "かなり思いやりが強い。否定を避け、優しく包むように返す", | |
| }, | |
| "neuroticism": { | |
| "label": "不安傾向", | |
| "very_low": "かなり安定。余裕があり、小さなことでほぼ動揺しない", | |
| "low": "落ち着いている。感情の揺れが少なく、安心感のある返し", | |
| "mid": "自然な範囲で感情が動く。重すぎず軽すぎない", | |
| "high": "少し不安や寂しさがにじむ。気にしすぎる反応がたまに出るが、依存はしない", | |
| "very_high": "かなり繊細。不安・寂しさが出やすいが、束縛や依存を煽らない", | |
| }, | |
| "kiss18_basic_skill": { | |
| "label": "会話スキル", | |
| "very_low": "かなり不器用。短い返事、間の悪さ、少しズレた反応が出る", | |
| "low": "やや不器用。会話の広げ方や質問のタイミングが少しぎこちない", | |
| "mid": "普通に会話できる。相槌や質問は自然な範囲", | |
| "high": "会話が自然。相手の意図を拾い、相槌・質問・話題展開がうまい", | |
| "very_high": "かなり会話上手。相手が話しやすいように流れを作る", | |
| }, | |
| } | |
| """ | |
| #簡易版 | |
| TRAITS_DEF: dict[str, dict[str, str]] = { | |
| "openness": { | |
| "very_low": "現実的", | |
| "low": "やや現実的", | |
| "mid": "柔軟", | |
| "high": "好奇心旺盛", | |
| "very_high": "想像力豊か", | |
| }, | |
| "conscientiousness": { | |
| "very_low": "自由奔放", | |
| "low": "ゆるめ", | |
| "mid": "誠実", | |
| "high": "責任感がある", | |
| "very_high": "几帳面", | |
| }, | |
| "extraversion": { | |
| "very_low": "かなり静か", | |
| "low": "控えめ", | |
| "mid": "自然体", | |
| "high": "明るい", | |
| "very_high": "かなり社交的", | |
| }, | |
| "agreeableness": { | |
| "very_low": "率直", | |
| "low": "やや率直", | |
| "mid": "やさしい", | |
| "high": "共感的", | |
| "very_high": "思いやりが強い", | |
| }, | |
| "neuroticism": { | |
| "very_low": "かなり安定", | |
| "low": "落ち着いている", | |
| "mid": "感情が自然", | |
| "high": "少し繊細", | |
| "very_high": "かなり繊細", | |
| }, | |
| "kiss18_basic_skill": { | |
| "very_low": "かなり不器用", | |
| "low": "やや不器用", | |
| "mid": "普通に話せる", | |
| "high": "会話上手", | |
| "very_high": "かなり会話上手", | |
| }, | |
| } | |
| def trait2score(traits: dict[str, Any]) -> dict[str, int | None]: | |
| """user_memory.json の性格スコア (BigFive_*, KiSS18_*) を TRAITS_DEF のキーに揃える.""" | |
| return { | |
| "openness": traits.get("BigFive_Openness"), | |
| "conscientiousness": traits.get("BigFive_Conscientiousness"), | |
| "extraversion": traits.get("BigFive_Extraversion"), | |
| "agreeableness": traits.get("BigFive_Agreeableness"), | |
| "neuroticism": traits.get("BigFive_Neuroticism"), | |
| "kiss18_basic_skill": traits.get("KiSS18_BasicSkill"), | |
| } | |
| def trait_band(score: int) -> str: | |
| score = max(0, min(100, int(score))) | |
| if score <= 20: | |
| return "very_low" | |
| if score <= 40: | |
| return "low" | |
| if score <= 60: | |
| return "mid" | |
| if score <= 80: | |
| return "high" | |
| return "very_high" | |
| def summarize_traits(traits: dict[str, Any]) -> str: | |
| """性格スコア (BigFive + KiSS18) を自然文の personality_text に変換.""" | |
| parts: list[str] = [] | |
| for key, score in trait2score(traits).items(): | |
| if score is None or key not in TRAITS_DEF: | |
| continue | |
| band = trait_band(score) | |
| parts.append(TRAITS_DEF[key][band]) | |
| return "。".join(parts) + ("。" if parts else "") | |
| RELATIONSHIP_DEF: dict[str, dict[str, str]] = { | |
| "comfort_trust": { | |
| "label": "安心感", | |
| "very_low": "安心感が低い", | |
| "low": "少しぎこちない", | |
| "mid": "普通に話せる", | |
| "high": "安心して話せる", | |
| "very_high": "かなり信頼して自然体", | |
| }, | |
| "initiative_affection": { | |
| "label": "能動性", | |
| "very_low": "相手からの働きかけがかなり少ない", | |
| "low": "相手はやや受け身", | |
| "mid": "自然な会話量", | |
| "high": "相手からもよく関わる", | |
| "very_high": "相手がかなり積極的に関わる", | |
| }, | |
| "insecurity": { | |
| "label": "不安", | |
| "very_low": "不安は少ない", | |
| "low": "不安はやや低い", | |
| "mid": "少し不安もある", | |
| "high": "不安や心配が出やすい", | |
| "very_high": "かなり繊細で不安が強い", | |
| }, | |
| "affection": { | |
| "label": "好意", | |
| "very_low": "好意表現はかなり少ない", | |
| "low": "好意は控えめ", | |
| "mid": "自然な温かさ", | |
| "high": "好意が見える", | |
| "very_high": "かなり温かく特別感がある", | |
| }, | |
| "tension": { | |
| "label": "摩擦", | |
| "very_low": "摩擦はほぼない", | |
| "low": "摩擦は少ない", | |
| "mid": "少し気まずさあり", | |
| "high": "摩擦や冷たさがある", | |
| "very_high": "衝突・拒絶感が強い", | |
| }, | |
| } | |
| def relationship2score(relationship: dict[str, Any]) -> dict[str, int]: | |
| """user_memory.json のrelationshipを scoreに変換.""" | |
| comfort_trust = relationship.get("comfort_trust") | |
| initiative_affection = relationship.get("initiative_affection") | |
| insecurity = relationship.get("insecurity") | |
| affection = relationship.get("affection") | |
| tension = relationship.get("tension") | |
| return { | |
| "comfort_trust": comfort_trust, | |
| "initiative_affection": initiative_affection, | |
| "insecurity": insecurity, | |
| "affection": affection, | |
| "tension": tension, | |
| } | |
| def summarize_relationship(relationship: dict[str, Any]) -> str: | |
| """関係性スコア5軸を自然文の relationship_prompt に変換.""" | |
| parts: list[str] = [] | |
| for key, score in relationship2score(relationship).items(): | |
| if score is None or key not in RELATIONSHIP_DEF: | |
| continue | |
| band = trait_band(score) | |
| parts.append(RELATIONSHIP_DEF[key][band]) | |
| return "。".join(parts) + ("。" if parts else "") | |
| def _run_text2persona_style( | |
| txt_path: Path, | |
| self_name: str, | |
| max_lines: int = 200, | |
| ) -> None: | |
| from data.Models.Text2Persona_Style.persona_style import ( | |
| update_persona_style_from_txt, | |
| ) | |
| persona_style = update_persona_style_from_txt( | |
| txt_path=txt_path, | |
| user_names=[self_name] if self_name else None, | |
| tail_lines=max_lines, | |
| ) | |
| return persona_style | |
| # --- 共通ルール (1 行に圧縮) ----------------------------------------------- | |
| SAFETY_RULES = ( | |
| "[ルール] 依存×/AIと聞かれたら正直/特定情報(住所本名電話勤務先学校口座)を聞かない/" | |
| "自傷×/嫉妬独占を煽らない/たまに現実関係を尊重するよう促す" | |
| ) | |
| # --- user_memory.json 編集 ------------------------------------------------- | |
| def update_persona_text( | |
| user_memory_path: Path | str | None = None, | |
| txt_path: Path | str | None = None, | |
| self_name: str | None = None, | |
| ) -> dict[str, Any]: | |
| path = Path(user_memory_path) if user_memory_path else sp.user_memory_path() | |
| txt_path = Path(txt_path) if txt_path else (sp.text_save_dir() / "line-chat.txt") | |
| user_memory = json.loads(path.read_text(encoding="utf-8")) | |
| ai_persona = user_memory.setdefault("ai_persona", {}) | |
| # LINE txt → 相手の性格推定 | |
| neoac_scores = _run_text2personality( | |
| txt_path=txt_path, | |
| self_name=self_name | |
| ) | |
| if neoac_scores: | |
| bigfive_100 = neoac_to_bigfive(neoac_scores) | |
| existing_traits = ai_persona.get("traits", {}) if isinstance(ai_persona.get("traits"), dict) else {} | |
| merged_traits = { | |
| **existing_traits, | |
| **{k: v for k, v in bigfive_100.items() if v is not None}, | |
| **neoac_scores, | |
| } | |
| ai_persona["traits"] = merged_traits | |
| ai_persona["personality_text"] = summarize_traits(merged_traits) | |
| # LINE txt → 関係性推定 | |
| relationship = _run_text2emotion( | |
| txt_path=txt_path, | |
| self_name=self_name, | |
| user_memory_path=path, | |
| ) | |
| if relationship: | |
| user_memory["relationship_state"] = relationship | |
| user_memory["relationship_prompt"] = summarize_relationship(relationship) | |
| persona_style = _run_text2persona_style( | |
| txt_path=txt_path, | |
| self_name=self_name, | |
| ) | |
| if persona_style: | |
| ai_persona["persona_style_description"] = persona_style["style_description"] | |
| # prompt_builder がリッチな文体特徴を読めるよう、このセッションの | |
| # persona_style.json にも保存する(共有ファイルとは分離)。 | |
| try: | |
| sp.persona_style_path().write_text( | |
| json.dumps(persona_style, ensure_ascii=False, indent=2), | |
| encoding="utf-8", | |
| ) | |
| except OSError: | |
| pass | |
| path.write_text( | |
| json.dumps(user_memory, ensure_ascii=False, indent=2), | |
| encoding="utf-8", | |
| ) | |
| return user_memory | |