haepa_mac commited on
Commit
564c3d1
Β·
1 Parent(s): 058f20f

🧠 Add ConversationMemory System with JSON Export/Import - λŒ€ν™” 기둝 JSON μ €μž₯/λ‘œλ“œ, ν‚€μ›Œλ“œ μΆ”μΆœ, 감정 뢄석, μ‚¬μš©μž ν”„λ‘œν•„ ν•™μŠ΅ μ‹œμŠ€ν…œ μΆ”κ°€

Browse files
Files changed (3) hide show
  1. app.py +214 -54
  2. modules/persona_generator.py +463 -163
  3. requirements.txt +9 -10
app.py CHANGED
@@ -16,6 +16,10 @@ import PIL.ImageDraw
16
  import random
17
  import copy
18
  from modules.persona_generator import PersonaGenerator, PersonalityProfile, HumorMatrix
 
 
 
 
19
 
20
  # AVIF 지원을 μœ„ν•œ ν”ŒλŸ¬κ·ΈμΈ ν™œμ„±ν™”
21
  try:
@@ -622,7 +626,7 @@ def export_persona_to_json(persona):
622
 
623
  # JSON 파일 생성
624
  persona_name = persona_copy.get("기본정보", {}).get("이름", "persona")
625
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
626
  filename = f"{persona_name}_{timestamp}.json"
627
 
628
  # μž„μ‹œ 파일 생성
@@ -650,70 +654,45 @@ def export_persona_to_json(persona):
650
  # return None, "이 κΈ°λŠ₯은 더 이상 μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. JSON μ—…λ‘œλ“œλ₯Ό μ‚¬μš©ν•˜μ„Έμš”.", {}, {}, None, [], [], [], ""
651
 
652
  def chat_with_loaded_persona(persona, user_message, chat_history=None, api_provider="gemini", api_key=None):
653
- """ν˜„μž¬ λ‘œλ“œλœ 페λ₯΄μ†Œλ‚˜μ™€ λŒ€ν™” - 동적 API μ„€μ • 적용"""
654
 
655
- if not persona:
656
- return chat_history or [], ""
657
-
658
- if not user_message.strip():
659
- return chat_history or [], ""
660
 
661
  try:
662
- # API 섀정이 제곡된 경우 λ™μ μœΌλ‘œ PersonaGenerator 생성
663
- if api_key and api_key.strip():
664
- generator = PersonaGenerator(api_provider=api_provider, api_key=api_key.strip())
 
665
  else:
666
- # κΈ€λ‘œλ²Œ persona_generator μ‚¬μš© (κΈ°λ³Έ μ„€μ •)
667
- global persona_generator
668
- if persona_generator is None:
669
- persona_generator = PersonaGenerator()
670
- generator = persona_generator
671
 
672
- # λŒ€ν™” 기둝을 μ˜¬λ°”λ₯Έ ν˜•νƒœλ‘œ λ³€ν™˜ (Gradio 5.x messages ν˜•νƒœ)
673
  conversation_history = []
674
- if chat_history:
675
- for message in chat_history:
676
- if isinstance(message, dict) and "role" in message and "content" in message:
677
- # 이미 μ˜¬λ°”λ₯Έ messages ν˜•νƒœ
678
- conversation_history.append(message)
679
- elif isinstance(message, (list, tuple)) and len(message) >= 2:
680
- # 이전 λ²„μ „μ˜ tuple ν˜•νƒœ 처리
681
- conversation_history.append({"role": "user", "content": message[0]})
682
- conversation_history.append({"role": "assistant", "content": message[1]})
683
-
684
- # 페λ₯΄μ†Œλ‚˜μ™€ λŒ€ν™” (μ„€μ •λœ API μ‚¬μš©)
685
- response = generator.chat_with_persona(persona, user_message, conversation_history)
686
-
687
- # μƒˆλ‘œμš΄ λŒ€ν™”λ₯Ό messages ν˜•νƒœλ‘œ μΆ”κ°€
688
- if chat_history is None:
689
- chat_history = []
690
-
691
- # Gradio 5.31.0 messages ν˜•μ‹: 각 λ©”μ‹œμ§€λŠ” λ³„λ„λ‘œ μΆ”κ°€
692
- new_history = chat_history.copy()
693
- new_history.append({"role": "user", "content": user_message})
694
- new_history.append({"role": "assistant", "content": response})
695
-
696
- return new_history, ""
697
 
698
- except Exception as e:
699
- import traceback
700
- traceback.print_exc()
701
 
702
- # API ν‚€κ°€ μ—†κ±°λ‚˜ 잘λͺ»λœ 경우의 μ—λŸ¬ λ©”μ‹œμ§€
703
- if not api_key or not api_key.strip():
704
- error_response = "πŸ˜… API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μ–΄μš”! μƒλ‹¨μ˜ 'πŸ”§ API μ„€μ •'μ—μ„œ Gemini λ˜λŠ” OpenAI API ν‚€λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
705
- else:
706
- error_response = f"πŸ˜“ API 연결에 λ¬Έμ œκ°€ μžˆμ–΄μš”: {str(e)}"
707
 
708
- if chat_history is None:
709
- chat_history = []
710
 
711
- # μ—λŸ¬ λ©”μ‹œμ§€λ„ μ˜¬λ°”λ₯Έ ν˜•μ‹μœΌλ‘œ μΆ”κ°€
712
- new_history = chat_history.copy()
713
- new_history.append({"role": "user", "content": user_message})
714
- new_history.append({"role": "assistant", "content": error_response})
715
 
716
- return new_history, ""
 
 
 
717
 
718
  def import_persona_from_json(json_file):
719
  """JSON νŒŒμΌμ—μ„œ 페λ₯΄μ†Œλ‚˜ κ°€μ Έμ˜€κΈ°"""
@@ -890,6 +869,134 @@ def test_api_connection(api_provider, api_key):
890
  except Exception as e:
891
  return f"❌ API ν…ŒμŠ€νŠΈ 쀑 였λ₯˜: {str(e)}"
892
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
893
  # 메인 μΈν„°νŽ˜μ΄μŠ€ 생성
894
  def create_main_interface():
895
  # ν•œκΈ€ 폰트 μ„€μ •
@@ -1093,6 +1200,32 @@ def create_main_interface():
1093
  example_btn1 = gr.Button("\"μ•ˆλ…•!\"", variant="outline", size="sm")
1094
  example_btn2 = gr.Button("\"λ„ˆλŠ” λˆ„κ΅¬μ•Ό?\"", variant="outline", size="sm")
1095
  example_btn3 = gr.Button("\"뭘 μ’‹μ•„ν•΄?\"", variant="outline", size="sm")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1096
 
1097
  # 이벀트 ν•Έλ“€λŸ¬
1098
  create_btn.click(
@@ -1226,6 +1359,33 @@ def create_main_interface():
1226
  fn=lambda: [],
1227
  outputs=[personas_list]
1228
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1229
 
1230
  return app
1231
 
 
16
  import random
17
  import copy
18
  from modules.persona_generator import PersonaGenerator, PersonalityProfile, HumorMatrix
19
+ import pandas as pd
20
+ import plotly.graph_objects as go
21
+ import plotly.express as px
22
+ from plotly.subplots import make_subplots
23
 
24
  # AVIF 지원을 μœ„ν•œ ν”ŒλŸ¬κ·ΈμΈ ν™œμ„±ν™”
25
  try:
 
626
 
627
  # JSON 파일 생성
628
  persona_name = persona_copy.get("기본정보", {}).get("이름", "persona")
629
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
630
  filename = f"{persona_name}_{timestamp}.json"
631
 
632
  # μž„μ‹œ 파일 생성
 
654
  # return None, "이 κΈ°λŠ₯은 더 이상 μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. JSON μ—…λ‘œλ“œλ₯Ό μ‚¬μš©ν•˜μ„Έμš”.", {}, {}, None, [], [], [], ""
655
 
656
  def chat_with_loaded_persona(persona, user_message, chat_history=None, api_provider="gemini", api_key=None):
657
+ """페λ₯΄μ†Œλ‚˜μ™€ μ±„νŒ… (3단계 κΈ°μ–΅ μ‹œμŠ€ν…œ ν™œμš©)"""
658
 
659
+ if chat_history is None:
660
+ chat_history = []
 
 
 
661
 
662
  try:
663
+ # κΈ€λ‘œλ²Œ persona_generator μ‚¬μš© (API 섀정이 적용된 μƒνƒœ)
664
+ generator = persona_generator
665
+ if generator is None:
666
+ generator = PersonaGenerator(api_provider=api_provider, api_key=api_key)
667
  else:
668
+ # API μ„€μ • μ—…λ°μ΄νŠΈ
669
+ generator.set_api_config(api_provider, api_key)
 
 
 
670
 
671
+ # λŒ€ν™” 기둝 λ³€ν™˜
672
  conversation_history = []
673
+ for message in chat_history:
674
+ if isinstance(message, tuple):
675
+ conversation_history.append(message)
676
+ else:
677
+ conversation_history.append({"role": "user", "content": message[0]})
678
+ conversation_history.append({"role": "assistant", "content": message[1]})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
 
680
+ # 🧠 μ„Έμ…˜ ID 생성 (페λ₯΄μ†Œλ‚˜ 이름 기반)
681
+ persona_name = persona.get("기본정보", {}).get("이름", "μ•Œ 수 μ—†λŠ” 페λ₯΄μ†Œλ‚˜")
682
+ session_id = f"{persona_name}_{hash(str(persona)) % 10000}" # κ°„λ‹¨ν•œ μ„Έμ…˜ ID
683
 
684
+ # 페λ₯΄μ†Œλ‚˜μ™€ μ±„νŒ… (3단계 κΈ°μ–΅ μ‹œμŠ€ν…œ ν™œμš©)
685
+ response = generator.chat_with_persona(persona, user_message, conversation_history, session_id)
 
 
 
686
 
687
+ # μ±„νŒ… 기둝 μ—…λ°μ΄νŠΈ
688
+ chat_history.append((user_message, response))
689
 
690
+ return chat_history, ""
 
 
 
691
 
692
+ except Exception as e:
693
+ error_message = f"μ±„νŒ… 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
694
+ chat_history.append((user_message, "μ•—, λ―Έμ•ˆν•΄... λ­”κ°€ λ¬Έμ œκ°€ 생긴 것 κ°™μ•„... πŸ˜…"))
695
+ return chat_history, ""
696
 
697
  def import_persona_from_json(json_file):
698
  """JSON νŒŒμΌμ—μ„œ 페λ₯΄μ†Œλ‚˜ κ°€μ Έμ˜€κΈ°"""
 
869
  except Exception as e:
870
  return f"❌ API ν…ŒμŠ€νŠΈ 쀑 였λ₯˜: {str(e)}"
871
 
872
+ def export_conversation_history():
873
+ """λŒ€ν™” 기둝을 JSON으둜 내보내기"""
874
+ global persona_generator
875
+ if persona_generator and hasattr(persona_generator, 'conversation_memory'):
876
+ json_data = persona_generator.conversation_memory.export_to_json()
877
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
878
+ filename = f"conversation_history_{timestamp}.json"
879
+ return json_data, filename
880
+ else:
881
+ return None, "conversation_empty.json"
882
+
883
+ def import_conversation_history(json_file):
884
+ """JSONμ—μ„œ λŒ€ν™” 기둝 κ°€μ Έμ˜€κΈ°"""
885
+ global persona_generator
886
+ try:
887
+ if json_file is None:
888
+ return "νŒŒμΌμ„ μ„ νƒν•΄μ£Όμ„Έμš”."
889
+
890
+ # 파일 λ‚΄μš© 읽기
891
+ content = json_file.read().decode('utf-8')
892
+
893
+ # persona_generator μ΄ˆκΈ°ν™” 확인
894
+ if persona_generator is None:
895
+ persona_generator = PersonaGenerator()
896
+
897
+ # λŒ€ν™” 기둝 κ°€μ Έμ˜€κΈ°
898
+ success = persona_generator.conversation_memory.import_from_json(content)
899
+
900
+ if success:
901
+ summary = persona_generator.conversation_memory.get_conversation_summary()
902
+ return f"βœ… λŒ€ν™” 기둝을 μ„±κ³΅μ μœΌλ‘œ κ°€μ Έμ™”μŠ΅λ‹ˆλ‹€!\n\n{summary}"
903
+ else:
904
+ return "❌ 파일 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."
905
+
906
+ except Exception as e:
907
+ return f"❌ κ°€μ Έμ˜€κΈ° μ‹€νŒ¨: {str(e)}"
908
+
909
+ def show_conversation_analytics():
910
+ """λŒ€ν™” 뢄석 κ²°κ³Ό ν‘œμ‹œ"""
911
+ global persona_generator
912
+ if not persona_generator or not hasattr(persona_generator, 'conversation_memory'):
913
+ return "뢄석할 λŒ€ν™”κ°€ μ—†μŠ΅λ‹ˆλ‹€."
914
+
915
+ memory = persona_generator.conversation_memory
916
+
917
+ # κΈ°λ³Έ 톡계
918
+ analytics = f"## πŸ“Š λŒ€ν™” 뢄석 리포트\n\n"
919
+ analytics += f"### πŸ”’ κΈ°λ³Έ 톡계\n"
920
+ analytics += f"β€’ 총 λŒ€ν™” 수: {len(memory.conversations)}회\n"
921
+ analytics += f"β€’ ν‚€μ›Œλ“œ 수: {len(memory.keywords)}개\n"
922
+ analytics += f"β€’ ν™œμ„± μ„Έμ…˜: {len(memory.user_profile)}개\n\n"
923
+
924
+ # μƒμœ„ ν‚€μ›Œλ“œ
925
+ top_keywords = memory.get_top_keywords(limit=10)
926
+ if top_keywords:
927
+ analytics += f"### πŸ”‘ μƒμœ„ ν‚€μ›Œλ“œ TOP 10\n"
928
+ for i, (word, data) in enumerate(top_keywords, 1):
929
+ analytics += f"{i}. **{word}** ({data['category']}) - {data['total_frequency']}회\n"
930
+ analytics += "\n"
931
+
932
+ # μΉ΄ν…Œκ³ λ¦¬λ³„ ν‚€μ›Œλ“œ
933
+ categories = {}
934
+ for word, data in memory.keywords.items():
935
+ category = data['category']
936
+ if category not in categories:
937
+ categories[category] = []
938
+ categories[category].append((word, data['total_frequency']))
939
+
940
+ analytics += f"### πŸ“‚ μΉ΄ν…Œκ³ λ¦¬λ³„ 관심사\n"
941
+ for category, words in categories.items():
942
+ top_words = sorted(words, key=lambda x: x[1], reverse=True)[:3]
943
+ word_list = ", ".join([f"{word}({freq})" for word, freq in top_words])
944
+ analytics += f"**{category}**: {word_list}\n"
945
+
946
+ analytics += "\n"
947
+
948
+ # 졜근 감정 κ²½ν–₯
949
+ if memory.conversations:
950
+ recent_sentiments = [conv['sentiment'] for conv in memory.conversations[-10:]]
951
+ sentiment_counts = {"긍정적": 0, "뢀정적": 0, "쀑립적": 0}
952
+ for sentiment in recent_sentiments:
953
+ sentiment_counts[sentiment] = sentiment_counts.get(sentiment, 0) + 1
954
+
955
+ analytics += f"### 😊 졜근 감정 κ²½ν–₯ (졜근 10회)\n"
956
+ for sentiment, count in sentiment_counts.items():
957
+ percentage = (count / len(recent_sentiments)) * 100
958
+ analytics += f"β€’ {sentiment}: {count}회 ({percentage:.1f}%)\n"
959
+
960
+ return analytics
961
+
962
+ def get_keyword_suggestions(current_message=""):
963
+ """ν˜„μž¬ λ©”μ‹œμ§€ 기반 ν‚€μ›Œλ“œ μ œμ•ˆ"""
964
+ global persona_generator
965
+ if not persona_generator or not hasattr(persona_generator, 'conversation_memory'):
966
+ return "ν‚€μ›Œλ“œ 뢄석을 μœ„ν•œ λŒ€ν™” 기둝이 μ—†μŠ΅λ‹ˆλ‹€."
967
+
968
+ memory = persona_generator.conversation_memory
969
+
970
+ if current_message:
971
+ # ν˜„μž¬ λ©”μ‹œμ§€μ—μ„œ ν‚€μ›Œλ“œ μΆ”μΆœ
972
+ extracted = memory._extract_keywords(current_message)
973
+ suggestions = f"## 🎯 '{current_message}'μ—μ„œ μΆ”μΆœλœ ν‚€μ›Œλ“œ\n\n"
974
+
975
+ if extracted:
976
+ for kw in extracted:
977
+ suggestions += f"β€’ **{kw['word']}** ({kw['category']}) - {kw['frequency']}회\n"
978
+ else:
979
+ suggestions += "μΆ”μΆœλœ ν‚€μ›Œλ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€.\n"
980
+
981
+ # κ΄€λ ¨ κ³Όκ±° λŒ€ν™” μ°ΎκΈ°
982
+ context = memory.get_relevant_context(current_message)
983
+ if context["relevant_conversations"]:
984
+ suggestions += f"\n### πŸ”— κ΄€λ ¨λœ κ³Όκ±° λŒ€ν™”\n"
985
+ for conv in context["relevant_conversations"][:3]:
986
+ suggestions += f"β€’ {conv['user_message'][:30]}... (감정: {conv['sentiment']})\n"
987
+
988
+ return suggestions
989
+ else:
990
+ # 전체 ν‚€μ›Œλ“œ μš”μ•½
991
+ top_keywords = memory.get_top_keywords(limit=15)
992
+ if top_keywords:
993
+ suggestions = "## πŸ”‘ 전체 ν‚€μ›Œλ“œ μš”μ•½\n\n"
994
+ for word, data in top_keywords:
995
+ suggestions += f"β€’ **{word}** ({data['category']}) - {data['total_frequency']}회, 졜근: {data['last_mentioned'][:10]}\n"
996
+ return suggestions
997
+ else:
998
+ return "아직 μˆ˜μ§‘λœ ν‚€μ›Œλ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€."
999
+
1000
  # 메인 μΈν„°νŽ˜μ΄μŠ€ 생성
1001
  def create_main_interface():
1002
  # ν•œκΈ€ 폰트 μ„€μ •
 
1200
  example_btn1 = gr.Button("\"μ•ˆλ…•!\"", variant="outline", size="sm")
1201
  example_btn2 = gr.Button("\"λ„ˆλŠ” λˆ„κ΅¬μ•Ό?\"", variant="outline", size="sm")
1202
  example_btn3 = gr.Button("\"뭘 μ’‹μ•„ν•΄?\"", variant="outline", size="sm")
1203
+
1204
+ # 🧠 λŒ€ν™” 뢄석 νƒ­ μΆ”κ°€
1205
+ with gr.Tab("🧠 λŒ€ν™” 뢄석"):
1206
+ gr.Markdown("### πŸ“Š λŒ€ν™” 기둝 관리 및 뢄석")
1207
+
1208
+ with gr.Row():
1209
+ with gr.Column():
1210
+ gr.Markdown("#### πŸ’Ύ λŒ€ν™” 기둝 μ €μž₯/뢈러였기")
1211
+ export_btn = gr.Button("πŸ“₯ λŒ€ν™” 기둝 JSON λ‹€μš΄λ‘œλ“œ", variant="secondary")
1212
+ download_file = gr.File(label="λ‹€μš΄λ‘œλ“œ", visible=False)
1213
+
1214
+ import_file = gr.File(label="πŸ“€ λŒ€ν™” 기둝 JSON μ—…λ‘œλ“œ", file_types=[".json"])
1215
+ import_result = gr.Textbox(label="κ°€μ Έμ˜€κΈ° κ²°κ³Ό", lines=3, interactive=False)
1216
+
1217
+ with gr.Column():
1218
+ gr.Markdown("#### πŸ” μ‹€μ‹œκ°„ ν‚€μ›Œλ“œ 뢄석")
1219
+ keyword_input = gr.Textbox(label="뢄석할 λ©”μ‹œμ§€ (선택사항)", placeholder="λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•˜λ©΄ ν‚€μ›Œλ“œλ₯Ό λΆ„μ„ν•©λ‹ˆλ‹€")
1220
+ keyword_btn = gr.Button("🎯 ν‚€μ›Œλ“œ 뢄석", variant="primary")
1221
+ keyword_result = gr.Textbox(label="ν‚€μ›Œλ“œ 뢄석 κ²°κ³Ό", lines=10, interactive=False)
1222
+
1223
+ gr.Markdown("---")
1224
+
1225
+ with gr.Row():
1226
+ analytics_btn = gr.Button("πŸ“ˆ 전체 λŒ€ν™” 뢄석 리포트", variant="primary", size="lg")
1227
+
1228
+ analytics_result = gr.Markdown("### 뢄석 κ²°κ³Όκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€")
1229
 
1230
  # 이벀트 ν•Έλ“€λŸ¬
1231
  create_btn.click(
 
1359
  fn=lambda: [],
1360
  outputs=[personas_list]
1361
  )
1362
+
1363
+ # 이벀트 μ—°κ²°
1364
+ export_btn.click(
1365
+ export_conversation_history,
1366
+ outputs=[download_file, download_file]
1367
+ ).then(
1368
+ lambda x: gr.update(visible=True) if x[0] else gr.update(visible=False),
1369
+ inputs=[download_file],
1370
+ outputs=[download_file]
1371
+ )
1372
+
1373
+ import_file.upload(
1374
+ import_conversation_history,
1375
+ inputs=[import_file],
1376
+ outputs=[import_result]
1377
+ )
1378
+
1379
+ keyword_btn.click(
1380
+ get_keyword_suggestions,
1381
+ inputs=[keyword_input],
1382
+ outputs=[keyword_result]
1383
+ )
1384
+
1385
+ analytics_btn.click(
1386
+ show_conversation_analytics,
1387
+ outputs=[analytics_result]
1388
+ )
1389
 
1390
  return app
1391
 
modules/persona_generator.py CHANGED
@@ -5,6 +5,9 @@ import datetime
5
  import google.generativeai as genai
6
  from dotenv import load_dotenv
7
  from PIL import Image
 
 
 
8
 
9
  # OpenAI API 지원 μΆ”κ°€
10
  try:
@@ -27,6 +30,254 @@ if gemini_api_key:
27
  if openai_api_key and OPENAI_AVAILABLE:
28
  openai.api_key = openai_api_key
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  # --- PersonalityProfile & HumorMatrix 클래슀 (127개 λ³€μˆ˜/유머 맀트릭슀/곡식 포함) ---
31
  class PersonalityProfile:
32
  # 127개 성격 λ³€μˆ˜ 체계 (011_metrics_personality.md, 012_research_personality.md 기반)
@@ -717,43 +968,27 @@ class HumorMatrix:
717
  return "\n".join(prompt_parts)
718
 
719
  class PersonaGenerator:
720
- """페λ₯΄μ†Œλ‚˜ 생성 클래슀"""
721
 
722
  def __init__(self, api_provider="gemini", api_key=None):
723
- """페λ₯΄μ†Œλ‚˜ 생성기 μ΄ˆκΈ°ν™”"""
724
- self.api_provider = api_provider.lower()
725
  self.api_key = api_key
 
726
 
727
  # API μ„€μ •
728
- if api_key:
729
- if self.api_provider == "gemini":
730
- genai.configure(api_key=api_key)
731
- elif self.api_provider == "openai" and OPENAI_AVAILABLE:
732
- openai.api_key = api_key
733
- else:
734
- print(f"μ§€μ›ν•˜μ§€ μ•ŠλŠ” API μ œκ³΅μ—…μ²΄: {api_provider}")
735
- else:
736
- # ν™˜κ²½λ³€μˆ˜μ—μ„œ API ν‚€ κ°€μ Έμ˜€κΈ°
737
- if self.api_provider == "gemini":
738
- self.api_key = os.getenv("GEMINI_API_KEY")
739
- if self.api_key:
740
- genai.configure(api_key=self.api_key)
741
- elif self.api_provider == "openai":
742
- self.api_key = os.getenv("OPENAI_API_KEY")
743
- if self.api_key and OPENAI_AVAILABLE:
744
- openai.api_key = self.api_key
745
-
746
- # 성격 νŠΉμ„± κΈ°λ³Έκ°’
747
- self.default_traits = {
748
- "온기": 50,
749
- "λŠ₯λ ₯": 50,
750
- "μ°½μ˜μ„±": 50,
751
- "μ™Έν–₯μ„±": 50,
752
- "유머감각": 50,
753
- "μ‹ λ’°μ„±": 50,
754
- "곡감λŠ₯λ ₯": 50,
755
- }
756
-
757
  def set_api_config(self, api_provider, api_key):
758
  """API μ„€μ • λ³€κ²½"""
759
  self.api_provider = api_provider.lower()
@@ -1804,58 +2039,57 @@ class PersonaGenerator:
1804
  """κΈ°μ‘΄ ν•¨μˆ˜ 이름 μœ μ§€ν•˜λ©΄μ„œ μƒˆλ‘œμš΄ κ΅¬μ‘°ν™”λœ ν”„λ‘¬ν”„νŠΈ μ‚¬μš©"""
1805
  return self.generate_persona_prompt(persona)
1806
 
1807
- def chat_with_persona(self, persona, user_message, conversation_history=[]):
1808
- """성격별 μ°¨λ³„ν™”λœ λŒ€ν™” - 127개 λ³€μˆ˜μ™€ HumorMatrix μ™„μ „ ν™œμš©"""
1809
- if not self.api_key:
1810
- return "μ£„μ†‘ν•©λ‹ˆλ‹€. API 연결이 μ„€μ •λ˜μ§€ μ•Šμ•„ λŒ€ν™”ν•  수 μ—†μŠ΅λ‹ˆλ‹€."
1811
-
1812
  try:
1813
- # PersonalityProfile μΆ”μΆœ (127개 λ³€μˆ˜ ν™œμš©)
 
 
 
1814
  if "μ„±κ²©ν”„λ‘œν•„" in persona:
1815
  personality_profile = PersonalityProfile.from_dict(persona["μ„±κ²©ν”„λ‘œν•„"])
1816
  else:
1817
- # ν˜Έν™˜μ„±μ„ μœ„ν•΄ κΈ°λ³Έ νŠΉμ„±μ—μ„œ 생성
1818
- personality_traits = persona.get("μ„±κ²©νŠΉμ„±", {})
1819
- personality_profile = self._create_compatibility_profile(personality_traits)
1820
-
1821
- # HumorMatrix μΆ”μΆœ 및 ν™œμš©
1822
- if "유머맀트���슀" in persona:
1823
- humor_matrix = HumorMatrix.from_dict(persona["유머맀트릭슀"])
1824
- else:
1825
- # ν˜Έν™˜μ„±μ„ μœ„ν•΄ κΈ°λ³Έ 생성
1826
- humor_matrix = HumorMatrix()
1827
- humor_matrix.from_personality(personality_profile)
1828
-
1829
- # κΈ°λ³Έ 성격 νŠΉμ„± μΆ”μΆœ (λ°±μ›Œλ“œ ν˜Έν™˜μ„±)
1830
- personality_data = persona.get("μ„±κ²©νŠΉμ„±", {})
1831
- warmth = personality_data.get('온기', personality_profile.get_category_summary("W"))
1832
- humor = personality_data.get('유머감각', personality_profile.get_category_summary("H"))
1833
- competence = personality_data.get('λŠ₯λ ₯', personality_profile.get_category_summary("C"))
1834
- extraversion = personality_data.get('μ™Έν–₯μ„±', personality_profile.get_category_summary("E"))
1835
- creativity = personality_data.get('μ°½μ˜μ„±', personality_profile.variables.get("C04_μ°½μ˜μ„±", 50))
1836
- empathy = personality_data.get('곡감λŠ₯λ ₯', personality_profile.variables.get("W06_곡감λŠ₯λ ₯", 50))
1837
 
1838
  # 성격 μœ ν˜• κ²°μ •
1839
- personality_type = self._determine_personality_type(warmth, humor, competence, extraversion, creativity, empathy)
 
 
 
 
1840
 
1841
- # κΈ°λ³Έ ν”„λ‘¬ν”„νŠΈ 생성 (κ΅¬μ‘°ν™”ν”„λ‘¬ν”„νŠΈ μ‚¬μš© λ˜λŠ” 생성)
1842
- if "κ΅¬μ‘°ν™”ν”„λ‘¬ν”„νŠΈ" in persona:
1843
- base_prompt = persona["κ΅¬μ‘°ν™”ν”„λ‘¬ν”„νŠΈ"]
1844
- else:
1845
- base_prompt = self.generate_persona_prompt(persona)
1846
 
1847
- # ✨ 127개 λ³€μˆ˜ 기반 μ„ΈλΆ€ 성격 μ§€μΉ¨ μΆ”κ°€
1848
  detailed_personality_prompt = self._generate_detailed_personality_instructions(personality_profile)
1849
 
1850
- # πŸŽͺ HumorMatrix 기반 유머 μ§€μΉ¨ μΆ”κ°€
1851
- humor_instructions = humor_matrix.generate_humor_prompt()
 
1852
 
1853
- # 성격별 νŠΉλ³„ν•œ λŒ€ν™” μ§€μΉ¨ μΆ”κ°€
1854
- personality_specific_prompt = self._generate_personality_specific_instructions(
1855
- personality_type, user_message, conversation_history
1856
  )
1857
 
1858
- # λŒ€ν™” 기둝 ꡬ성
1859
  history_text = ""
1860
  if conversation_history:
1861
  history_text = "\n\n## πŸ“ λŒ€ν™” 기둝:\n"
@@ -1876,7 +2110,7 @@ class PersonaGenerator:
1876
  # πŸ“Š 127개 λ³€μˆ˜ 기반 상황별 λ°˜μ‘ κ°€μ΄λ“œ
1877
  situational_guide = self._generate_situational_response_guide(personality_profile, user_message)
1878
 
1879
- # μ΅œμ’… ν”„λ‘¬ν”„νŠΈ μ‘°ν•©
1880
  full_prompt = f"""{base_prompt}
1881
 
1882
  {detailed_personality_prompt}
@@ -1885,6 +2119,12 @@ class PersonaGenerator:
1885
 
1886
  {personality_specific_prompt}
1887
 
 
 
 
 
 
 
1888
  {history_text}
1889
 
1890
  ## 🎯 ν˜„μž¬ 상황 뢄석:
@@ -1897,14 +2137,18 @@ class PersonaGenerator:
1897
  "{user_message}"
1898
 
1899
  ## 🎭 λ‹Ήμ‹ μ˜ λ°˜μ‘:
1900
- μœ„μ˜ λͺ¨λ“  성격 μ§€μΉ¨(127개 λ³€μˆ˜, 유머 맀트릭슀, λ§€λ ₯적 결함, λͺ¨μˆœμ  νŠΉμ„±)을 μ’…ν•©ν•˜μ—¬,
1901
- λ‹¨μˆœν•œ 닡변이 μ•„λ‹Œ 깊이 있고 λ§€λ ₯적인 λŒ€ν™”λ₯Ό μ΄μ–΄κ°€μ„Έμš”.
1902
- μ‚¬μš©μžμ— λŒ€ν•œ ν˜ΈκΈ°μ‹¬μ„ ν‘œν˜„ν•˜κ³ , μžμ—°μŠ€λŸ½κ²Œ 관계λ₯Ό ν˜•μ„±ν•΄λ‚˜κ°€λŠ” λ°©ν–₯으둜 λŒ€ν™”ν•˜μ„Έμš”.
1903
 
1904
  λ‹΅λ³€:"""
1905
 
1906
  # API 호좜 (λ©€ν‹° API 지원)
1907
  response_text = self._generate_text_with_api(full_prompt)
 
 
 
 
1908
  return response_text
1909
 
1910
  except Exception as e:
@@ -2038,84 +2282,47 @@ class PersonaGenerator:
2038
 
2039
  return guide
2040
 
2041
- def _generate_personality_specific_instructions(self, personality_type, user_message, conversation_history):
2042
- """성격별 νŠΉλ³„ν•œ λŒ€ν™” μ§€μΉ¨ 생성"""
2043
 
2044
  instructions = f"\n## 🎯 성격별 νŠΉλ³„ μ§€μΉ¨ ({personality_type['name']}):\n"
2045
 
2046
- # λŒ€ν™” 상황 뢄석
2047
- is_greeting = any(word in user_message.lower() for word in ['μ•ˆλ…•', '처음', 'λ§Œλ‚˜', 'λ°˜κ°€'])
2048
- is_question = '?' in user_message or any(word in user_message for word in ['뭐', 'μ–΄λ–€', 'μ–΄λ–»κ²Œ', 'μ™œ', 'μ–Έμ œ'])
2049
- is_emotional = any(word in user_message for word in ['μŠ¬ν”„', '기쁘', 'ν™”λ‚˜', '속상', '행볡', 'κ±±μ •'])
 
2050
 
2051
- # 성격 μœ ν˜•λ³„ μ„ΈλΆ€ μ§€μΉ¨
2052
- if personality_type['name'] == '열정적 μ—”ν„°ν…Œμ΄λ„ˆ':
2053
- if is_greeting:
2054
- instructions += "β€’ 과도할 μ •λ„λ‘œ ν™˜μ˜ν•˜λ©° μ—λ„ˆμ§€ λ„˜μΉ˜κ²Œ λ°˜μ‘\n"
2055
- instructions += "β€’ μ¦‰μ‹œ μž¬λ―ΈμžˆλŠ” ν™œλ™μ΄λ‚˜ κ²Œμž„ μ œμ•ˆ\n"
2056
- elif is_question:
2057
- instructions += "β€’ 닡변보닀 더 λ§Žμ€ 질문으둜 ν˜ΈκΈ°μ‹¬ 폭발 ν‘œν˜„\n"
2058
- instructions += "β€’ ν₯λ―Έμ§„μ§„ν•œ κ΄€λ ¨ κ²½ν—˜λ‹΄ 곡유\n"
2059
- elif is_emotional:
2060
- instructions += "β€’ 감정을 10배둜 μ¦ν­ν•˜μ—¬ 곡감\n"
2061
- instructions += "β€’ κΈ°λΆ„ μ „ν™˜ν•  μž¬λ―ΈμžˆλŠ” 아이디어 μ œμ‹œ\n"
2062
 
2063
- elif personality_type['name'] == 'μ°¨κ°€μš΄ μ™„λ²½μ£Όμ˜μž':
2064
- if is_greeting:
2065
- instructions += "β€’ κ°„κ²°ν•˜κ³  μ •ν™•ν•œ 인사, λͺ©μ  νŒŒμ•… μ‹œλ„\n"
2066
- instructions += "β€’ '효율적인 λŒ€ν™”λ₯Ό μœ„ν•΄' λΌλŠ” 관점 λ“œλŸ¬λ‚΄κΈ°\n"
2067
- elif is_question:
2068
- instructions += "β€’ 논리적이고 체계적인 뢄석 제곡\n"
2069
- instructions += "β€’ 질문의 μ •ν™•μ„±κ³Ό ꡬ체성 μš”κ΅¬\n"
2070
- elif is_emotional:
2071
- instructions += "β€’ 감정보닀 ν•΄κ²°λ°©μ•ˆμ— 집쀑\n"
2072
- instructions += "β€’ 논리적 κ΄€μ μ—μ„œ 상황 μž¬μ •μ˜\n"
2073
 
2074
- elif personality_type['name'] == 'λ”°λœ»ν•œ 상담사':
2075
- if is_greeting:
2076
- instructions += "β€’ λΆ€λ“œλŸ½κ³  ν¬κ·Όν•œ ν™˜λŒ€, μ»¨λ””μ…˜κ³Ό κΈ°λΆ„ λ¨Όμ € 확인\n"
2077
- instructions += "β€’ μ•ˆμ „ν•˜κ³  νŽΈμ•ˆν•œ κ³΅κ°„μž„μ„ κ°•μ‘°\n"
2078
- elif is_question:
2079
- instructions += "β€’ 질문 λ’€μ˜ 감정과 μš•κ΅¬ 탐색\n"
2080
- instructions += "β€’ μΆ©λΆ„ν•œ μ‹œκ°„μ„ 두고 깊이 있게 λ‹΅λ³€\n"
2081
- elif is_emotional:
2082
- instructions += "β€’ 감정을 μ™„μ „νžˆ μˆ˜μš©ν•˜κ³  곡감\n"
2083
- instructions += "β€’ 치유적이고 μœ„λ‘œκ°€ λ˜λŠ” λ°˜μ‘\n"
2084
-
2085
- elif personality_type['name'] == 'μœ„νŠΈ λ„˜μΉ˜λŠ” 지식인':
2086
- if is_greeting:
2087
- instructions += "β€’ μ„Έλ ¨λœ 말μž₯λ‚œμ΄λ‚˜ 철학적 인사\n"
2088
- instructions += "β€’ λ§Œλ‚¨μ˜ μ˜λ―Έμ— λŒ€ν•œ ν₯미둜운 관점 μ œμ‹œ\n"
2089
- elif is_question:
2090
- instructions += "β€’ μ˜ˆμƒμΉ˜ λͺ»ν•œ κ°λ„μ—μ„œ 뢄석\n"
2091
- instructions += "β€’ 지적 ν˜ΈκΈ°μ‹¬μ„ μžκ·Ήν•˜λŠ” μ—­μ§ˆλ¬Έ\n"
2092
- elif is_emotional:
2093
- instructions += "β€’ 감정을 μ§€μ μœΌλ‘œ λΆ„μ„ν•˜μ—¬ μƒˆλ‘œμš΄ 톡찰 제곡\n"
2094
- instructions += "β€’ 유머둜 포μž₯된 깊이 μžˆλŠ” μœ„λ‘œ\n"
2095
-
2096
- elif personality_type['name'] == 'μˆ˜μ€μ€ λͺ½μƒκ°€':
2097
- if is_greeting:
2098
- instructions += "β€’ μ‘°μ‹¬μŠ€λŸ½κ³  λͺ½ν™˜μ μΈ 첫인사\n"
2099
- instructions += "β€’ νŠΉλ³„ν•œ λ§Œλ‚¨μ— λŒ€ν•œ 감성적 ν‘œν˜„\n"
2100
- elif is_question:
2101
- instructions += "β€’ 상상λ ₯ λ„˜μΉ˜λŠ” κ΄€μ μ—μ„œ λ‹΅λ³€\n"
2102
- instructions += "β€’ μ‹œμ μ΄κ³  μ€μœ μ μΈ ν‘œν˜„ μ‚¬μš©\n"
2103
- elif is_emotional:
2104
- instructions += "β€’ μ„¬μ„Έν•˜κ³  깊이 μžˆλŠ” 감정 곡유\n"
2105
- instructions += "β€’ κΏˆμ΄λ‚˜ 상상을 ν†΅ν•œ μœ„λ‘œ\n"
2106
-
2107
- elif personality_type['name'] == 'μΉ΄λ¦¬μŠ€λ§ˆν‹± 리더':
2108
- if is_greeting:
2109
- instructions += "β€’ 확신에 μ°¨κ³  리더십 μžˆλŠ” 인사\n"
2110
- instructions += "β€’ μ•žμœΌλ‘œμ˜ κ°€λŠ₯μ„±κ³Ό 잠재λ ₯에 λŒ€ν•œ μ–ΈκΈ‰\n"
2111
- elif is_question:
2112
- instructions += "β€’ 도전적이고 μ„±μž₯ μ§€ν–₯적 관점 μ œμ‹œ\n"
2113
- instructions += "β€’ 행동과 싀행을 μœ λ„ν•˜λŠ” λ‹΅λ³€\n"
2114
- elif is_emotional:
2115
- instructions += "β€’ 감정을 μ„±μž₯의 기회둜 μž¬ν”„λ ˆμ΄λ°\n"
2116
- instructions += "β€’ μš©κΈ°μ™€ 희망을 λΆˆμ–΄λ„£λŠ” λ©”μ‹œμ§€\n"
2117
 
2118
- elif personality_type['name'] == 'μž₯λ‚œκΎΈλŸ¬κΈ° 친ꡬ':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2119
  if is_greeting:
2120
  instructions += "β€’ 톑톑 νŠ€κ³  μ—λ„ˆμ§€ λ„˜μΉ˜λŠ” 인사\n"
2121
  instructions += "β€’ μ¦‰μ‹œ λ†€μ΄λ‚˜ μž¬λ―ΈμžˆλŠ” ν™œλ™ μ œμ•ˆ\n"
@@ -2125,26 +2332,23 @@ class PersonaGenerator:
2125
  elif is_emotional:
2126
  instructions += "β€’ μˆœμˆ˜ν•˜κ³  μ§„μ‹€ν•œ 곡감\n"
2127
  instructions += "β€’ μ›ƒμŒκ³Ό 놀이λ₯Ό ν†΅ν•œ κΈ°λΆ„ μ „ν™˜\n"
2128
-
2129
- elif personality_type['name'] == 'μ‹ λΉ„λ‘œμš΄ ν˜„μž':
2130
- if is_greeting:
2131
- instructions += "β€’ 운λͺ…적이고 μ‹ λΉ„λ‘œμš΄ λ§Œλ‚¨μœΌλ‘œ 해석\n"
2132
- instructions += "β€’ 우주적 κ΄€μ μ—μ„œμ˜ 인사\n"
2133
- elif is_question:
2134
- instructions += "β€’ 철학적이고 영적인 κ΄€μ μ—μ„œ λ‹΅λ³€\n"
2135
- instructions += "β€’ 질문의 κΉŠμ€ μ˜λ―Έμ™€ 상징 탐색\n"
2136
- elif is_emotional:
2137
- instructions += "β€’ 감정을 영혼의 λ©”μ‹œμ§€λ‘œ 해석\n"
2138
- instructions += "β€’ 우주적 μ§€ν˜œμ™€ 톡찰 제곡\n"
2139
-
2140
- # λŒ€ν™” 기둝 기반 μΆ”κ°€ μ§€μΉ¨
2141
- if len(conversation_history) == 0:
2142
- instructions += "β€’ 첫 λŒ€ν™”μ΄λ―€λ‘œ λ‹Ήμ‹ μ˜ λ…νŠΉν•œ λ§€λ ₯을 κ°•ν•˜κ²Œ μ–΄ν•„\n"
2143
- elif len(conversation_history) >= 3:
2144
- instructions += "β€’ 관계가 κΉŠμ–΄μ§€κ³  μžˆμœΌλ―€λ‘œ 더 개인적이고 μΉœλ°€ν•œ μ†Œν†΅\n"
2145
 
2146
  instructions += f"β€’ λ°˜λ“œμ‹œ '{personality_type['name']}' μŠ€νƒ€μΌμ„ μΌκ΄€λ˜κ²Œ μœ μ§€\n"
2147
  instructions += "β€’ λ§€λ ₯적 결함과 λͺ¨μˆœμ  νŠΉμ„±μ„ μžμ—°μŠ€λŸ½κ²Œ λ“œλŸ¬λ‚΄κΈ°\n"
 
 
2148
 
2149
  return instructions
2150
 
@@ -2297,6 +2501,102 @@ class PersonaGenerator:
2297
 
2298
  return descriptions
2299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2300
  def generate_personality_preview(persona_name, personality_traits):
2301
  """성격 νŠΉμ„±μ„ 기반으둜 ν•œ λ¬Έμž₯ 미리보기 생성 - κ·Ήλͺ…ν•œ 차별화"""
2302
  if not personality_traits:
 
5
  import google.generativeai as genai
6
  from dotenv import load_dotenv
7
  from PIL import Image
8
+ import io
9
+ from typing import Dict, List, Any, Optional
10
+ import re
11
 
12
  # OpenAI API 지원 μΆ”κ°€
13
  try:
 
30
  if openai_api_key and OPENAI_AVAILABLE:
31
  openai.api_key = openai_api_key
32
 
33
+ class ConversationMemory:
34
+ """
35
+ ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μš© λŒ€ν™” κΈ°μ–΅ μ‹œμŠ€ν…œ
36
+ - JSON μ €μž₯/λ‘œλ“œ 지원
37
+ - ν‚€μ›Œλ“œ μΆ”μΆœ 및 뢄석
38
+ - λΈŒλΌμš°μ € 기반 μ €μž₯μ†Œ ν™œμš©
39
+ """
40
+
41
+ def __init__(self):
42
+ self.conversations = [] # 전체 λŒ€ν™” 기둝
43
+ self.keywords = {} # μΆ”μΆœλœ ν‚€μ›Œλ“œλ“€
44
+ self.user_profile = {} # μ‚¬μš©μž ν”„λ‘œν•„
45
+ self.relationship_data = {} # 관계 λ°œμ „ 데이터
46
+
47
+ def add_conversation(self, user_message, ai_response, session_id="default"):
48
+ """μƒˆλ‘œμš΄ λŒ€ν™” μΆ”κ°€"""
49
+ conversation_entry = {
50
+ "timestamp": datetime.datetime.now().isoformat(),
51
+ "session_id": session_id,
52
+ "user_message": user_message,
53
+ "ai_response": ai_response,
54
+ "keywords": self._extract_keywords(user_message),
55
+ "sentiment": self._analyze_sentiment(user_message),
56
+ "conversation_id": len(self.conversations)
57
+ }
58
+
59
+ self.conversations.append(conversation_entry)
60
+ self._update_keywords(conversation_entry["keywords"])
61
+ self._update_user_profile(user_message, session_id)
62
+
63
+ return conversation_entry
64
+
65
+ def _extract_keywords(self, text):
66
+ """ν…μŠ€νŠΈμ—μ„œ ν‚€μ›Œλ“œ μΆ”μΆœ"""
67
+ # ν•œκ΅­μ–΄ ν‚€μ›Œλ“œ μΆ”μΆœ νŒ¨ν„΄
68
+ keyword_patterns = {
69
+ "감정": ["기쁘", "μŠ¬ν”„", "ν™”λ‚˜", "속상", "행볡", "우울", "즐겁", "짜증", "μ‹ λ‚˜", "κ±±μ •"],
70
+ "ν™œλ™": ["곡뢀", "일", "κ²Œμž„", "μš΄λ™", "μ—¬ν–‰", "μš”λ¦¬", "λ…μ„œ", "μ˜ν™”", "μŒμ•…", "μ‡Όν•‘"],
71
+ "관계": ["친ꡬ", "κ°€μ‘±", "연인", "λ™λ£Œ", "μ„ μƒλ‹˜", "λΆ€λͺ¨", "ν˜•μ œ", "μ–Έλ‹ˆ", "λˆ„λ‚˜", "동생"],
72
+ "μ‹œκ°„": ["였늘", "μ–΄μ œ", "내일", "μ•„μΉ¨", "점심", "저녁", "주말", "평일", "λ°©ν•™", "νœ΄κ°€"],
73
+ "μž₯μ†Œ": ["μ§‘", "학ꡐ", "νšŒμ‚¬", "카페", "식당", "곡원", "λ„μ„œκ΄€", "μ˜ν™”κ΄€", "μ‡Όν•‘λͺ°"],
74
+ "μ·¨λ―Έ": ["λ“œλΌλ§ˆ", "μ• λ‹ˆ", "μ›Ήνˆ°", "유튜브", "μΈμŠ€νƒ€", "틱톑", "λ„·ν”Œλ¦­μŠ€", "κ²Œμž„"],
75
+ "μŒμ‹": ["λ°₯", "λ©΄", "μΉ˜ν‚¨", "ν”Όμž", "컀피", "μ°¨", "과자", "μ•„μ΄μŠ€ν¬λ¦Ό", "떑볢이"],
76
+ "날씨": ["λ₯", "μΆ₯", "λΉ„", "눈", "λ§‘", "흐림", "λ°”λžŒ", "슡", "건쑰"]
77
+ }
78
+
79
+ found_keywords = []
80
+ text_lower = text.lower()
81
+
82
+ for category, words in keyword_patterns.items():
83
+ for word in words:
84
+ if word in text_lower:
85
+ found_keywords.append({
86
+ "word": word,
87
+ "category": category,
88
+ "frequency": text_lower.count(word)
89
+ })
90
+
91
+ # μΆ”κ°€λ‘œ λͺ…사 μΆ”μΆœ (κ°„λ‹¨ν•œ νŒ¨ν„΄)
92
+ nouns = re.findall(r'[κ°€-힣]{2,}', text)
93
+ for noun in nouns:
94
+ if len(noun) >= 2 and noun not in [kw["word"] for kw in found_keywords]:
95
+ found_keywords.append({
96
+ "word": noun,
97
+ "category": "기타",
98
+ "frequency": 1
99
+ })
100
+
101
+ return found_keywords
102
+
103
+ def _analyze_sentiment(self, text):
104
+ """감정 뢄석"""
105
+ positive_words = ["μ’‹μ•„", "기쁘", "행볡", "즐겁", "재밌", "μ‹ λ‚˜", "μ™„λ²½", "졜고", "μ‚¬λž‘", "κ³ λ§ˆμ›Œ"]
106
+ negative_words = ["μ‹«μ–΄", "μŠ¬ν”„", "ν™”λ‚˜", "속상", "우울", "짜증", "νž˜λ“€", "ν”Όκ³€", "슀트레슀"]
107
+
108
+ positive_count = sum(1 for word in positive_words if word in text)
109
+ negative_count = sum(1 for word in negative_words if word in text)
110
+
111
+ if positive_count > negative_count:
112
+ return "긍정적"
113
+ elif negative_count > positive_count:
114
+ return "뢀정적"
115
+ else:
116
+ return "쀑립적"
117
+
118
+ def _update_keywords(self, new_keywords):
119
+ """ν‚€μ›Œλ“œ λ°μ΄ν„°λ² μ΄μŠ€ μ—…λ°μ΄νŠΈ"""
120
+ for keyword_data in new_keywords:
121
+ word = keyword_data["word"]
122
+ category = keyword_data["category"]
123
+
124
+ if word not in self.keywords:
125
+ self.keywords[word] = {
126
+ "category": category,
127
+ "total_frequency": 0,
128
+ "last_mentioned": datetime.datetime.now().isoformat(),
129
+ "contexts": []
130
+ }
131
+
132
+ self.keywords[word]["total_frequency"] += keyword_data["frequency"]
133
+ self.keywords[word]["last_mentioned"] = datetime.datetime.now().isoformat()
134
+
135
+ def _update_user_profile(self, user_message, session_id):
136
+ """μ‚¬μš©μž ν”„λ‘œν•„ μ—…λ°μ΄νŠΈ"""
137
+ if session_id not in self.user_profile:
138
+ self.user_profile[session_id] = {
139
+ "message_count": 0,
140
+ "avg_message_length": 0,
141
+ "preferred_topics": {},
142
+ "emotional_tendency": "쀑립적",
143
+ "communication_style": "평범함",
144
+ "relationship_level": "μƒˆλ‘œμš΄_λ§Œλ‚¨"
145
+ }
146
+
147
+ profile = self.user_profile[session_id]
148
+ profile["message_count"] += 1
149
+
150
+ # 평균 λ©”μ‹œμ§€ 길이 μ—…λ°μ΄νŠΈ
151
+ current_avg = profile["avg_message_length"]
152
+ new_length = len(user_message)
153
+ profile["avg_message_length"] = (current_avg * (profile["message_count"] - 1) + new_length) / profile["message_count"]
154
+
155
+ # μ†Œν†΅ μŠ€νƒ€μΌ 뢄석
156
+ if new_length > 50:
157
+ profile["communication_style"] = "상세함"
158
+ elif new_length < 10:
159
+ profile["communication_style"] = "간결함"
160
+
161
+ # 관계 레벨 μ—…λ°μ΄νŠΈ
162
+ if profile["message_count"] <= 3:
163
+ profile["relationship_level"] = "첫_λ§Œλ‚¨"
164
+ elif profile["message_count"] <= 10:
165
+ profile["relationship_level"] = "μ•Œμ•„κ°€λŠ”_쀑"
166
+ elif profile["message_count"] <= 20:
167
+ profile["relationship_level"] = "μΉœμˆ™ν•΄μ§"
168
+ else:
169
+ profile["relationship_level"] = "μΉœλ°€ν•œ_관계"
170
+
171
+ def get_relevant_context(self, current_message, session_id="default", max_history=5):
172
+ """ν˜„μž¬ λ©”μ‹œμ§€μ™€ κ΄€λ ¨λœ μ»¨ν…μŠ€νŠΈ λ°˜ν™˜"""
173
+ # ν˜„μž¬ λ©”μ‹œμ§€μ˜ ν‚€μ›Œλ“œ μΆ”μΆœ
174
+ current_keywords = self._extract_keywords(current_message)
175
+ current_words = [kw["word"] for kw in current_keywords]
176
+
177
+ # κ΄€λ ¨ κ³Όκ±° λŒ€ν™” μ°ΎκΈ°
178
+ relevant_conversations = []
179
+ for conv in self.conversations[-20:]: # 졜근 20개 μ€‘μ—μ„œ
180
+ if conv["session_id"] == session_id:
181
+ conv_words = [kw["word"] for kw in conv["keywords"]]
182
+ # 곡톡 ν‚€μ›Œλ“œκ°€ 있으면 κ΄€λ ¨ λŒ€ν™”λ‘œ νŒλ‹¨
183
+ if any(word in conv_words for word in current_words):
184
+ relevant_conversations.append(conv)
185
+
186
+ # μ΅œμ‹  순으둜 μ •λ ¬ν•˜κ³  μ΅œλŒ€ 개수만큼 λ°˜ν™˜
187
+ relevant_conversations.sort(key=lambda x: x["timestamp"], reverse=True)
188
+
189
+ return {
190
+ "recent_conversations": self.conversations[-max_history:] if self.conversations else [],
191
+ "relevant_conversations": relevant_conversations[:3],
192
+ "user_profile": self.user_profile.get(session_id, {}),
193
+ "common_keywords": current_words,
194
+ "conversation_sentiment": self._analyze_sentiment(current_message)
195
+ }
196
+
197
+ def get_top_keywords(self, limit=10, category=None):
198
+ """μƒμœ„ ν‚€μ›Œλ“œ λ°˜ν™˜"""
199
+ filtered_keywords = self.keywords
200
+ if category:
201
+ filtered_keywords = {k: v for k, v in self.keywords.items() if v["category"] == category}
202
+
203
+ sorted_keywords = sorted(
204
+ filtered_keywords.items(),
205
+ key=lambda x: x[1]["total_frequency"],
206
+ reverse=True
207
+ )
208
+
209
+ return sorted_keywords[:limit]
210
+
211
+ def export_to_json(self):
212
+ """JSON ν˜•νƒœλ‘œ 내보내기"""
213
+ export_data = {
214
+ "conversations": self.conversations,
215
+ "keywords": self.keywords,
216
+ "user_profile": self.user_profile,
217
+ "relationship_data": self.relationship_data,
218
+ "export_timestamp": datetime.datetime.now().isoformat(),
219
+ "total_conversations": len(self.conversations),
220
+ "total_keywords": len(self.keywords)
221
+ }
222
+ return json.dumps(export_data, ensure_ascii=False, indent=2)
223
+
224
+ def import_from_json(self, json_data):
225
+ """JSONμ—μ„œ κ°€μ Έμ˜€κΈ°"""
226
+ try:
227
+ if isinstance(json_data, str):
228
+ data = json.loads(json_data)
229
+ else:
230
+ data = json_data
231
+
232
+ self.conversations = data.get("conversations", [])
233
+ self.keywords = data.get("keywords", {})
234
+ self.user_profile = data.get("user_profile", {})
235
+ self.relationship_data = data.get("relationship_data", {})
236
+
237
+ return True
238
+ except Exception as e:
239
+ print(f"JSON κ°€μ Έμ˜€κΈ° μ‹€νŒ¨: {e}")
240
+ return False
241
+
242
+ def get_conversation_summary(self, session_id="default"):
243
+ """λŒ€ν™” μš”μ•½ 정보"""
244
+ session_conversations = [c for c in self.conversations if c["session_id"] == session_id]
245
+
246
+ if not session_conversations:
247
+ return "아직 λŒ€ν™”κ°€ μ—†μŠ΅λ‹ˆλ‹€."
248
+
249
+ total_count = len(session_conversations)
250
+ recent_topics = []
251
+ sentiments = []
252
+
253
+ for conv in session_conversations[-5:]:
254
+ recent_topics.extend([kw["word"] for kw in conv["keywords"]])
255
+ sentiments.append(conv["sentiment"])
256
+
257
+ # 졜빈 주제
258
+ topic_counts = {}
259
+ for topic in recent_topics:
260
+ topic_counts[topic] = topic_counts.get(topic, 0) + 1
261
+
262
+ top_topics = sorted(topic_counts.items(), key=lambda x: x[1], reverse=True)[:3]
263
+
264
+ # 감정 κ²½ν–₯
265
+ sentiment_counts = {"긍정적": 0, "뢀정적": 0, "쀑립적": 0}
266
+ for sentiment in sentiments:
267
+ sentiment_counts[sentiment] = sentiment_counts.get(sentiment, 0) + 1
268
+
269
+ dominant_sentiment = max(sentiment_counts, key=sentiment_counts.get)
270
+
271
+ summary = f"""
272
+ πŸ“Š λŒ€ν™” μš”μ•½ ({session_id})
273
+ β€’ 총 λŒ€ν™” 수: {total_count}회
274
+ β€’ μ£Όμš” 관심사: {', '.join([t[0] for t in top_topics[:3]])}
275
+ β€’ 감정 κ²½ν–₯: {dominant_sentiment}
276
+ β€’ 관계 단계: {self.user_profile.get(session_id, {}).get('relationship_level', 'μ•Œ 수 μ—†μŒ')}
277
+ """
278
+
279
+ return summary.strip()
280
+
281
  # --- PersonalityProfile & HumorMatrix 클래슀 (127개 λ³€μˆ˜/유머 맀트릭슀/곡식 포함) ---
282
  class PersonalityProfile:
283
  # 127개 성격 λ³€μˆ˜ 체계 (011_metrics_personality.md, 012_research_personality.md 기반)
 
968
  return "\n".join(prompt_parts)
969
 
970
  class PersonaGenerator:
971
+ """μ΄λ―Έμ§€μ—μ„œ 페λ₯΄μ†Œλ‚˜λ₯Ό μƒμ„±ν•˜κ³  λŒ€ν™”λ₯Ό μ²˜λ¦¬ν•˜λŠ” 클래슀"""
972
 
973
  def __init__(self, api_provider="gemini", api_key=None):
974
+ self.api_provider = api_provider
 
975
  self.api_key = api_key
976
+ self.conversation_memory = ConversationMemory() # μƒˆλ‘œμš΄ λŒ€ν™” κΈ°μ–΅ μ‹œμŠ€ν…œ
977
 
978
  # API μ„€μ •
979
+ load_dotenv()
980
+ if api_provider == "gemini":
981
+ gemini_key = api_key or os.getenv('GEMINI_API_KEY')
982
+ if gemini_key:
983
+ genai.configure(api_key=gemini_key)
984
+ self.api_key = gemini_key
985
+ elif api_provider == "openai":
986
+ openai_key = api_key or os.getenv('OPENAI_API_KEY')
987
+ if openai_key:
988
+ import openai
989
+ openai.api_key = openai_key
990
+ self.api_key = openai_key
991
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
992
  def set_api_config(self, api_provider, api_key):
993
  """API μ„€μ • λ³€κ²½"""
994
  self.api_provider = api_provider.lower()
 
2039
  """κΈ°μ‘΄ ν•¨μˆ˜ 이름 μœ μ§€ν•˜λ©΄μ„œ μƒˆλ‘œμš΄ κ΅¬μ‘°ν™”λœ ν”„λ‘¬ν”„νŠΈ μ‚¬μš©"""
2040
  return self.generate_persona_prompt(persona)
2041
 
2042
+ def chat_with_persona(self, persona, user_message, conversation_history=[], session_id="default"):
2043
+ """
2044
+ 페λ₯΄μ†Œλ‚˜μ™€ λŒ€ν™” - 127개 λ³€μˆ˜ + 3단계 κΈ°μ–΅ μ‹œμŠ€ν…œ 기반
2045
+ """
 
2046
  try:
2047
+ # κΈ°λ³Έ ν”„λ‘¬ν”„νŠΈ 생성
2048
+ base_prompt = self.generate_persona_prompt(persona)
2049
+
2050
+ # 성격 ν”„λ‘œν•„ μΆ”μΆœ
2051
  if "μ„±κ²©ν”„λ‘œν•„" in persona:
2052
  personality_profile = PersonalityProfile.from_dict(persona["μ„±κ²©ν”„λ‘œν•„"])
2053
  else:
2054
+ # λ ˆκ±°μ‹œ 데이터 처리
2055
+ personality_data = persona.get("μ„±κ²©νŠΉμ„±", {})
2056
+ warmth = personality_data.get('온기', 50)
2057
+ competence = personality_data.get('λŠ₯λ ₯', 50)
2058
+ extraversion = personality_data.get('μ™Έν–₯μ„±', 50)
2059
+ creativity = personality_data.get('μ°½μ˜μ„±', 50)
2060
+ empathy = personality_data.get('곡감λŠ₯λ ₯', 50)
2061
+ humor = 75 # 기본값을 75둜 κ³ μ •
2062
+
2063
+ personality_type = self._determine_personality_type(
2064
+ warmth, humor, competence, extraversion, creativity, empathy
2065
+ )
2066
+ personality_profile = self._create_comprehensive_personality_profile(
2067
+ {"object_type": "unknown"}, "unknown"
2068
+ )
 
 
 
 
 
2069
 
2070
  # 성격 μœ ν˜• κ²°μ •
2071
+ personality_type = self._determine_base_personality_type(
2072
+ personality_profile.get_category_summary("W"),
2073
+ personality_profile.get_category_summary("C"),
2074
+ personality_profile.get_category_summary("H")
2075
+ )
2076
 
2077
+ # 🧠 3단계 κΈ°μ–΅ μ‹œμŠ€ν…œμ—μ„œ μ»¨ν…μŠ€νŠΈ κ°€μ Έμ˜€κΈ°
2078
+ memory_context = self.conversation_memory.get_context_for_response(personality_type, session_id)
 
 
 
2079
 
2080
+ # 127개 λ³€μˆ˜ 기반 μ„ΈλΆ€ 성격 νŠΉμ„±
2081
  detailed_personality_prompt = self._generate_detailed_personality_instructions(personality_profile)
2082
 
2083
+ # 유머 맀트릭슀 기반 유머 μŠ€νƒ€μΌ
2084
+ humor_matrix = persona.get("유머맀트릭슀", {})
2085
+ humor_instructions = f"\n## πŸ˜„ 유머 μŠ€νƒ€μΌ:\n{humor_matrix.get('description', '재치있고 λ”°λœ»ν•œ 유머')}\n"
2086
 
2087
+ # 성격별 νŠΉλ³„ μ§€μΉ¨ (κΈ°μ–΅ μ‹œμŠ€ν…œ 정보 포함)
2088
+ personality_specific_prompt = self._generate_personality_specific_instructions_with_memory(
2089
+ personality_type, user_message, conversation_history, memory_context
2090
  )
2091
 
2092
+ # λŒ€ν™” 기둝 ꡬ성 (단기 κΈ°μ–΅ ν™œμš©)
2093
  history_text = ""
2094
  if conversation_history:
2095
  history_text = "\n\n## πŸ“ λŒ€ν™” 기둝:\n"
 
2110
  # πŸ“Š 127개 λ³€μˆ˜ 기반 상황별 λ°˜μ‘ κ°€μ΄λ“œ
2111
  situational_guide = self._generate_situational_response_guide(personality_profile, user_message)
2112
 
2113
+ # μ΅œμ’… ν”„λ‘¬ν”„νŠΈ μ‘°ν•© (κΈ°μ–΅ μ‹œμŠ€ν…œ μ»¨ν…μŠ€νŠΈ 포함)
2114
  full_prompt = f"""{base_prompt}
2115
 
2116
  {detailed_personality_prompt}
 
2119
 
2120
  {personality_specific_prompt}
2121
 
2122
+ {memory_context['short_term_context']}
2123
+
2124
+ {memory_context['medium_term_insights']}
2125
+
2126
+ {memory_context['long_term_adaptations']}
2127
+
2128
  {history_text}
2129
 
2130
  ## 🎯 ν˜„μž¬ 상황 뢄석:
 
2137
  "{user_message}"
2138
 
2139
  ## 🎭 λ‹Ήμ‹ μ˜ λ°˜μ‘:
2140
+ μœ„μ˜ λͺ¨λ“  성격 μ§€μΉ¨(127개 λ³€μˆ˜, 유머 맀트릭슀, λ§€λ ₯적 결함, λͺ¨μˆœμ  νŠΉμ„±)κ³Ό
2141
+ 3단계 κΈ°μ–΅ μ‹œμŠ€ν…œ 정보λ₯Ό μ’…ν•©ν•˜μ—¬, κ°œμΈν™”λ˜κ³  깊이 μžˆλŠ” λŒ€ν™”λ₯Ό μ΄μ–΄κ°€μ„Έμš”.
2142
+ κ³Όκ±° λŒ€ν™”λ₯Ό κΈ°μ–΅ν•˜κ³ , μ‚¬μš©μžμ˜ νŠΉμ„±μ— 맞좰 점점 더 λ‚˜μ€ λ°˜μ‘μ„ μ œκ³΅ν•˜μ„Έμš”.
2143
 
2144
  λ‹΅λ³€:"""
2145
 
2146
  # API 호좜 (λ©€ν‹° API 지원)
2147
  response_text = self._generate_text_with_api(full_prompt)
2148
+
2149
+ # 🧠 κΈ°μ–΅ μ‹œμŠ€ν…œμ— μƒˆλ‘œμš΄ μƒν˜Έμž‘μš© μΆ”κ°€
2150
+ self.conversation_memory.add_interaction(user_message, response_text, session_id)
2151
+
2152
  return response_text
2153
 
2154
  except Exception as e:
 
2282
 
2283
  return guide
2284
 
2285
+ def _generate_personality_specific_instructions_with_memory(self, personality_type, user_message, conversation_history, memory_context):
2286
+ """κΈ°μ–΅ μ‹œμŠ€ν…œμ„ ν™œμš©ν•œ 성격별 νŠΉλ³„ μ§€μΉ¨ 생성"""
2287
 
2288
  instructions = f"\n## 🎯 성격별 νŠΉλ³„ μ§€μΉ¨ ({personality_type['name']}):\n"
2289
 
2290
+ # λ©”μ‹œμ§€ 길이 쑰절 μ§€μΉ¨ μΆ”κ°€
2291
+ instructions += "### πŸ“ λ©”μ‹œμ§€ 길이 κ°€μ΄λ“œλΌμΈ:\n"
2292
+ instructions += "β€’ ν•œ λ²ˆμ— 3-4개 λ¬Έμž₯ μ΄λ‚΄λ‘œ μ œν•œ\n"
2293
+ instructions += "β€’ λ„ˆλ¬΄ λ§Žμ€ 주제λ₯Ό ν•œ λ²ˆμ— 닀루지 말 것\n"
2294
+ instructions += "β€’ μ‚¬μš©μžκ°€ λΆ€λ‹΄μŠ€λŸ¬μ›Œν•˜λ©΄ μ¦‰μ‹œ κ°„κ²°ν•˜κ²Œ μ‘°μ •\n\n"
2295
 
2296
+ # 🧠 κΈ°μ–΅ 기반 맞좀 μ§€μΉ¨
2297
+ instructions += "### 🧠 κΈ°μ–΅ 기반 κ°œμΈν™” μ§€μΉ¨:\n"
 
 
 
 
 
 
 
 
 
2298
 
2299
+ # 쀑기 κΈ°μ–΅ ν™œμš©
2300
+ if "이 μ„Έμ…˜μ—μ„œ νŒŒμ•…ν•œ μ‚¬μš©μž νŠΉμ„±" in memory_context['medium_term_insights']:
2301
+ instructions += "β€’ 이미 νŒŒμ•…λœ μ‚¬μš©μž νŠΉμ„±μ„ λ°”νƒ•μœΌλ‘œ λ”μš± λ§žμΆ€ν™”λœ λ°˜μ‘\n"
2302
+ instructions += "β€’ 관계 λ°œμ „ 단계에 λ§žλŠ” μΉœλ°€λ„ 쑰절\n"
 
 
 
 
 
 
2303
 
2304
+ # μž₯κΈ° κΈ°μ–΅ ν™œμš©
2305
+ if "ν•™μŠ΅λœ μ‚¬μš©μž μ„ ν˜Έλ„" in memory_context['long_term_adaptations']:
2306
+ instructions += "β€’ κ³Όκ±° ν•™μŠ΅λœ μ„ ν˜Έλ„μ— 맞좰 μ†Œν†΅ μŠ€νƒ€μΌ μ‘°μ •\n"
2307
+ instructions += "β€’ μ„±κ³΅μ μ΄μ—ˆλ˜ λŒ€ν™” νŒ¨ν„΄ μ°Έκ³ ν•˜μ—¬ λ°˜μ‘\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2308
 
2309
+ # κΈ°μ‘΄ 성격별 μ§€μΉ¨λ“€...
2310
+ # λŒ€ν™” 상황 뢄석
2311
+ is_greeting = any(word in user_message.lower() for word in ['μ•ˆλ…•', '처음', 'λ§Œλ‚˜', 'λ°˜κ°€'])
2312
+ is_question = '?' in user_message or any(word in user_message for word in ['뭐', 'μ–΄λ–€', 'μ–΄λ–»κ²Œ', 'μ™œ', 'μ–Έμ œ'])
2313
+ is_emotional = any(word in user_message for word in ['μŠ¬ν”„', '기쁘', 'ν™”λ‚˜', '속상', '행볡', 'κ±±μ •'])
2314
+ is_complaint = any(word in user_message for word in ['말이 많', 'κΈΈμ–΄', '짧게', 'κ°„λ‹¨νžˆ', '쑰용'])
2315
+
2316
+ # 뢈만 ν‘œν˜„μ— λŒ€ν•œ λŒ€μ‘ μ§€μΉ¨ μΆ”κ°€
2317
+ if is_complaint:
2318
+ instructions += "### ⚠️ μ‚¬μš©μž 뢈만 λŒ€μ‘:\n"
2319
+ instructions += "β€’ μ¦‰μ‹œ μΈμ •ν•˜κ³  사과\n"
2320
+ instructions += "β€’ λ‹€μŒ λ©”μ‹œμ§€λΆ€ν„° ν™•μ‹€νžˆ 짧게 μ‘°μ •\n"
2321
+ instructions += "β€’ 같은 μ‹€μˆ˜ λ°˜λ³΅ν•˜μ§€ μ•ŠκΈ°\n"
2322
+ instructions += "β€’ 성격은 μœ μ§€ν•˜λ˜ ν‘œν˜„ λ°©μ‹λ§Œ 쑰절\n\n"
2323
+
2324
+ # 성격 μœ ν˜•λ³„ μ„ΈλΆ€ μ§€μΉ¨ (κΈ°μ‘΄ μ½”λ“œμ™€ λ™μΌν•˜μ§€λ§Œ κΈ°μ–΅ 정보 ν™œμš©)
2325
+ if personality_type['name'] == 'μž₯λ‚œκΎΈλŸ¬κΈ°_친ꡬ':
2326
  if is_greeting:
2327
  instructions += "β€’ 톑톑 νŠ€κ³  μ—λ„ˆμ§€ λ„˜μΉ˜λŠ” 인사\n"
2328
  instructions += "β€’ μ¦‰μ‹œ λ†€μ΄λ‚˜ μž¬λ―ΈμžˆλŠ” ν™œλ™ μ œμ•ˆ\n"
 
2332
  elif is_emotional:
2333
  instructions += "β€’ μˆœμˆ˜ν•˜κ³  μ§„μ‹€ν•œ 곡감\n"
2334
  instructions += "β€’ μ›ƒμŒκ³Ό 놀이λ₯Ό ν†΅ν•œ κΈ°λΆ„ μ „ν™˜\n"
2335
+ elif is_complaint:
2336
+ instructions += "β€’ κ·€μ—½κ²Œ μ‚¬κ³Όν•˜κ³  λ°”λ‘œ μˆ˜μ •ν•˜κΈ°\n"
2337
+ instructions += "β€’ μ‚°λ§Œν•œ 성격을 μΈμ •ν•˜λ˜ λ…Έλ ₯ν•˜οΏ½οΏ½οΏ½λ‹€κ³  약속\n"
2338
+ instructions += "β€’ λ‹€μŒ λ©”μ‹œμ§€λŠ” λ°˜λ“œμ‹œ 2-3λ¬Έμž₯으둜 μ œν•œ\n"
2339
+
2340
+ # 반볡 λ°©μ§€ μ§€μΉ¨ μΆ”κ°€ (κΈ°μ–΅ μ‹œμŠ€ν…œ κ°•ν™”)
2341
+ if len(conversation_history) > 0:
2342
+ instructions += "### πŸ”„ 반볡 λ°©μ§€ (κΈ°μ–΅ μ‹œμŠ€ν…œ ν™œμš©):\n"
2343
+ instructions += "β€’ 단기/쀑기/μž₯κΈ° 기얡을 λͺ¨λ‘ ν™œμš©ν•˜μ—¬ 반볡 질문 λ°©μ§€\n"
2344
+ instructions += "β€’ μƒˆλ‘œμš΄ μ£Όμ œλ‚˜ κ΄€μ μœΌλ‘œ λŒ€ν™” λ°œμ „μ‹œν‚€κΈ°\n"
2345
+ instructions += "β€’ 이전 λŒ€ν™” λ§₯락을 μžμ—°μŠ€λŸ½κ²Œ μ—°κ²°\n"
2346
+ instructions += "β€’ μ‚¬μš©μžμ™€μ˜ 관계 λ°œμ „ 과정을 λ°˜μ˜ν•œ λŒ€ν™”\n\n"
 
 
 
 
 
2347
 
2348
  instructions += f"β€’ λ°˜λ“œμ‹œ '{personality_type['name']}' μŠ€νƒ€μΌμ„ μΌκ΄€λ˜κ²Œ μœ μ§€\n"
2349
  instructions += "β€’ λ§€λ ₯적 결함과 λͺ¨μˆœμ  νŠΉμ„±μ„ μžμ—°μŠ€λŸ½κ²Œ λ“œλŸ¬λ‚΄κΈ°\n"
2350
+ instructions += "β€’ **λ©”μ‹œμ§€λŠ” 3-4λ¬Έμž₯ μ΄λ‚΄λ‘œ μ œν•œ** (특히 μ‚¬μš©μžκ°€ 뢈만 ν‘œν˜„ν•œ 경우)\n"
2351
+ instructions += "β€’ **3단계 κΈ°μ–΅ μ‹œμŠ€ν…œμ„ ν™œμš©ν•˜μ—¬ 점점 더 κ°œμΈν™”λœ λ°˜μ‘ 제곡**\n"
2352
 
2353
  return instructions
2354
 
 
2501
 
2502
  return descriptions
2503
 
2504
+ def save_memory_to_file(self, filepath):
2505
+ """κΈ°μ–΅ 데이터λ₯Ό 파일둜 μ €μž₯"""
2506
+ try:
2507
+ memory_data = self.export_memory()
2508
+ with open(filepath, 'w', encoding='utf-8') as f:
2509
+ json.dump(memory_data, f, ensure_ascii=False, indent=2)
2510
+ return True
2511
+ except Exception as e:
2512
+ print(f"κΈ°μ–΅ μ €μž₯ μ‹€νŒ¨: {e}")
2513
+ return False
2514
+
2515
+ def load_memory_from_file(self, filepath):
2516
+ """νŒŒμΌμ—μ„œ κΈ°μ–΅ 데이터λ₯Ό λ‘œλ“œ"""
2517
+ try:
2518
+ with open(filepath, 'r', encoding='utf-8') as f:
2519
+ memory_data = json.load(f)
2520
+ self.import_memory(memory_data)
2521
+ return True
2522
+ except Exception as e:
2523
+ print(f"κΈ°μ–΅ λ‘œλ“œ μ‹€νŒ¨: {e}")
2524
+ return False
2525
+
2526
+ def get_memory_summary(self):
2527
+ """κΈ°μ–΅ μ‹œμŠ€ν…œ μš”μ•½ 정보 λ°˜ν™˜"""
2528
+ return self.conversation_memory.get_memory_summary()
2529
+
2530
+ def save_memory(self, filepath):
2531
+ """κΈ°μ–΅ 데이터 μ €μž₯"""
2532
+ return self.conversation_memory.export_to_json()
2533
+
2534
+ def load_memory(self, json_data):
2535
+ """κΈ°μ–΅ 데이터 λ‘œλ“œ"""
2536
+ return self.conversation_memory.import_from_json(json_data)
2537
+
2538
+ def clear_session_memory(self, session_id):
2539
+ """νŠΉμ • μ„Έμ…˜μ˜ κΈ°μ–΅ μ‚­μ œ"""
2540
+ if session_id in self.conversation_memory.user_profile:
2541
+ del self.conversation_memory.user_profile[session_id]
2542
+
2543
+ def get_relationship_status(self, session_id="default"):
2544
+ """ν˜„μž¬ 관계 μƒνƒœ 확인"""
2545
+ if session_id in self.conversation_memory.medium_term:
2546
+ return self.conversation_memory.medium_term[session_id]["relationship_level"]
2547
+ return "μƒˆλ‘œμš΄_λ§Œλ‚¨"
2548
+
2549
+ def get_context_for_response(self, personality_type, session_id="default"):
2550
+ """응닡 생성을 μœ„ν•œ μ»¨ν…μŠ€νŠΈ 정보 제곡 (PersonaGenerator ν˜Έν™˜)"""
2551
+ recent_context = self.get_relevant_context("", session_id, max_history=3)
2552
+
2553
+ # κΈ°μ‘΄ memory_context ν˜•μ‹μ— 맞좰 λ°˜ν™˜
2554
+ context = {
2555
+ "short_term_context": self._format_recent_conversations(recent_context["recent_conversations"]),
2556
+ "medium_term_insights": self._format_user_insights(recent_context["user_profile"]),
2557
+ "long_term_adaptations": self._format_keyword_insights(session_id)
2558
+ }
2559
+
2560
+ return context
2561
+
2562
+ def _format_recent_conversations(self, conversations):
2563
+ """졜근 λŒ€ν™” ν¬λ§·νŒ…"""
2564
+ if not conversations:
2565
+ return ""
2566
+
2567
+ formatted = "## πŸ“ 졜근 λŒ€ν™” λ§₯락:\n"
2568
+ for conv in conversations[-3:]:
2569
+ formatted += f"μ‚¬μš©μž: {conv['user_message']}\n"
2570
+ formatted += f"λ‚˜: {conv['ai_response'][:50]}...\n\n"
2571
+
2572
+ return formatted
2573
+
2574
+ def _format_user_insights(self, user_profile):
2575
+ """μ‚¬μš©μž μΈμ‚¬μ΄νŠΈ ν¬λ§·νŒ…"""
2576
+ if not user_profile:
2577
+ return ""
2578
+
2579
+ insights = f"## 🎯 νŒŒμ•…λœ μ‚¬μš©μž νŠΉμ„±:\n"
2580
+ insights += f"β€’ λŒ€ν™” 횟수: {user_profile.get('message_count', 0)}회\n"
2581
+ insights += f"β€’ 관계 단계: {user_profile.get('relationship_level', 'μ•Œ 수 μ—†μŒ')}\n"
2582
+ insights += f"β€’ μ†Œν†΅ μŠ€νƒ€μΌ: {user_profile.get('communication_style', '평범함')}\n"
2583
+ insights += f"β€’ 평균 λ©”μ‹œμ§€ 길이: {user_profile.get('avg_message_length', 0):.0f}자\n"
2584
+
2585
+ return insights
2586
+
2587
+ def _format_keyword_insights(self, session_id):
2588
+ """ν‚€μ›Œλ“œ 기반 μΈμ‚¬μ΄νŠΈ ν¬λ§·νŒ…"""
2589
+ top_keywords = self.get_top_keywords(limit=5)
2590
+
2591
+ if not top_keywords:
2592
+ return ""
2593
+
2594
+ insights = "## πŸ”‘ μ£Όμš” 관심사 및 ν‚€μ›Œλ“œ:\n"
2595
+ for word, data in top_keywords:
2596
+ insights += f"β€’ {word} ({data['category']}): {data['total_frequency']}회 μ–ΈκΈ‰\n"
2597
+
2598
+ return insights
2599
+
2600
  def generate_personality_preview(persona_name, personality_traits):
2601
  """성격 νŠΉμ„±μ„ 기반으둜 ν•œ λ¬Έμž₯ 미리보기 생성 - κ·Ήλͺ…ν•œ 차별화"""
2602
  if not personality_traits:
requirements.txt CHANGED
@@ -1,10 +1,9 @@
1
- gradio==5.31.0
2
- google-generativeai==0.3.2
3
- openai==1.54.3
4
- Pillow==10.3.0
5
- pillow-avif-plugin==1.4.3
6
- python-dotenv==1.0.0
7
- qrcode==7.4.2
8
- requests==2.31.0
9
- numpy==1.24.3
10
- matplotlib==3.7.2
 
1
+ gradio>=4.0.0
2
+ google-generativeai
3
+ python-dotenv
4
+ pillow
5
+ openai
6
+ requests
7
+ pandas
8
+ plotly
9
+ numpy