humanda5
์ •๋ณด ์ธ์ง€ ๋ถ€๋ถ„ ์ˆ˜์ •
e01cc89
# npc_social_network/models/llm_helper.py
from npc_social_network.npc.emotion_config import EMOTION_LIST
from npc_social_network.npc.npc_memory import Memory
from .. import simulation_core
from typing import List
import json
import re
import time
from google.api_core import exceptions as google_exceptions
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from ..npc.npc_base import NPC
from ..manager.conversation_manager import Conversation
def query_llm_for_emotion(user_input):
"""
LLM์„ ํ†ตํ•ด ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ์—์„œ ๊ฐ์ • ์ถ”์ถœ
"""
prompt = f"""
ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ: "{user_input}"
์•„๋ž˜ ๊ฐ์ • ๋ฆฌ์ŠคํŠธ ์ค‘ ๊ฐ€์žฅ ์ž˜ ํ•ด๋‹น๋˜๋Š” ๊ฐ์ •์„ ํ•œ ๊ฐœ๋งŒ ๊ณจ๋ผ ๋ฐ˜ํ™˜ํ•˜์„ธ์š”.
๊ฐ์ • ๋ฆฌ์ŠคํŠธ: {', '.join(EMOTION_LIST)}
๋ฐ˜๋“œ์‹œ ๊ฐ์ • ์ด๋ฆ„์„ ํ•œ ๊ฐœ๋งŒ ์ถœ๋ ฅํ•˜์„ธ์š”. (์˜ˆ: joy)
"""
try:
response = simulation_core.active_llm_model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"[LLM Error] {str(e)}"
def query_llm_with_prompt(prompt: str) -> str:
"""
prompt ๋ฌธ์ž์—ด์„ ๋ฐ›์•„ LLM ํ˜ธ์ถœ
- api ์‚ฌ์šฉ๋Ÿ‰ ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ž๋™ ๋Œ€์ฒ˜
"""
if not simulation_core.active_llm_model:
return "[LLM Error] API ๋ชจ๋ธ์ด ์ •์ƒ์ ์œผ๋กœ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
try:
# 1. ์ฒซ ๋ฒˆ์งธ API ํ˜ธ์ถœ ์‹œ๋„
response = simulation_core.active_llm_model.generate_content(prompt)
return response.text.strip()
except google_exceptions.ResourceExhausted as e:
error_message = str(e)
# 2. ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์›์ธ ํŒŒ์•…
if "PerMinute" in error_message:
# -- ๋ถ„๋‹น ์‚ฌ์šฉ๋Ÿ‰ ์ดˆ๊ณผ --
simulation_core.add_log("[API ๊ฒฝ๊ณ ] ๋ถ„๋‹น ์‚ฌ์šฉ๋Ÿ‰์„ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. 60์ดˆ ํ›„ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ ํ•ฉ๋‹ˆ๋‹ค.")
time.sleep(60)
try:
# 2-1. ๋‘ ๋ฒˆ์งธ API ํ˜ธ์ถœ ์‹œ๋„
simulation_core.add_log("[API ์ •๋ณด] API ํ˜ธ์ถœ์„ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.")
response = simulation_core.active_llm_model.generate_content(prompt)
return response.text.strip()
except Exception as retry_e:
# ์žฌ์‹œ๋„๋งˆ์ € ์‹คํŒจํ•  ๊ฒฝ์šฐ, ์ตœ์ข… ์—๋Ÿฌ ๋ฐ˜ํ™˜
simulation_core.add_log("========= [API Error] =========")
# ์‹œ๋ฌผ๋ ˆ์ด์…˜์„ ์•ˆ์ „ํ•˜๊ฒŒ '์ผ์‹œ์ •์ง€' ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
with simulation_core.simulation_lock:
simulation_core.simulation_paused = True
return f"[API ์—๋Ÿฌ] ์žฌ์‹œ๋„์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {retry_e}"
elif "PerDay" in error_message:
# -- ์ผ์ผ ์‚ฌ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์‹œ --
simulation_core.add_log("[API Error] ์ผ์ผ ์‚ฌ์šฉ๋Ÿ‰์„ ๋ชจ๋‘ ์†Œ์ง„ํ–ˆ์Šต๋‹ˆ๋‹ค.")
simulation_core.add_log("๋‹ค๋ฅธ API ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ๋‚ด์ผ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.")
# ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์•ˆ์ „ํ•˜๊ฒŒ '์ผ์‹œ์ •์ง€' ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
with simulation_core.simulation_lock:
simulation_core.simulation_paused = True
return "[LLM Error] Daily quota exceeded"
else:
# ๊ทธ ์™ธ ๋‹ค๋ฅธ ResourceExhausted ์˜ค๋ฅ˜
with simulation_core.simulation_lock:
simulation_core.simulation_paused = True
return f"[LLM Error] {error_message}"
except Exception as e:
# ๊ทธ ์™ธ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ์˜ค๋ฅ˜
return f"[LLM Error] {str(e)}"
def summarize_text(text: str) -> str:
"""์ฃผ์–ด์ง„ ๊ธด ํ…์ŠคํŠธ(๋Œ€ํ™” ๋‚ด์šฉ ๋“ฑ)๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝ"""
if not text.strip():
return "์•„๋ฌด ๋‚ด์šฉ ์—†๋Š” ๋Œ€ํ™”์˜€์Œ"
prompt = f"""
# Instruction
๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ ๊ฐ€์žฅ ํ•ต์‹ฌ์ ์ธ ๋‚ด์šฉ์„ ๋‹ด์•„ ํ•œ๊ตญ์–ด ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ด์ฃผ์„ธ์š”.
# Text
{text}
# Summary (a single sentence):
"""
summary = query_llm_with_prompt(prompt)
return summary if summary and "[LLM Error]" not in summary else "๋Œ€ํ™” ์š”์•ฝ์— ์‹คํŒจํ•จ"
def summarize_memories(memories: List[Memory]) -> str:
"""
Memory ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ›์•„ LLM์„ ํ†ตํ•ด 1~2๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝ
"""
if not memories:
return ""
# Memory ๊ฐ์ฒด์—์„œ .content๋ฅผ ์ถ”์ถœํ•˜์—ฌ ์‚ฌ์šฉ
memory_contents = [mem.content for mem in memories]
joined = "\n".join([f"- {content}" for content in memory_contents])
prompt = f"""๋‹ค์Œ์€ ํ•œ ์ธ๋ฌผ์˜ ๊ณผ๊ฑฐ ๊ธฐ์–ต๋“ค์ž…๋‹ˆ๋‹ค:
{joined}
โ†’ ์ด ์‚ฌ๋žŒ์€ ์–ด๋–ค ๊ฒฝํ—˜์„ ํ–ˆ๋Š”์ง€ 1~2๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ด ์ฃผ์„ธ์š”."""
# LLM ํ˜ธ์ถœ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์•ฝ ์š”์ฒญ
summary = query_llm_with_prompt(prompt)
return summary
def analyze_gossip(gossip_content: str) -> dict:
"""LLM์„ ์ด์šฉํ•ด ์†Œ๋ฌธ์„ ๋ถ„์„ํ•˜๊ณ , ๊ด€๋ จ ์ธ๋ฌผ๊ณผ ๊ธ/๋ถ€์ •์„ JSON์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค."""
prompt="""
# ์ง€์‹œ์‚ฌํ•ญ
๋‹ค์Œ ์†Œ๋ฌธ ๋‚ด์šฉ์—์„œ ํ•ต์‹ฌ ์ธ๋ฌผ 2๋ช…๊ณผ, ๊ทธ๋“ค ์‚ฌ์ด์˜ ์ƒํ˜ธ์ž‘์šฉ์ด ๊ธ์ •์ ์ธ์ง€, ๋ถ€์ •์ ์ธ์ง€, ์ค‘๋ฆฝ์ ์ธ์ง€ ๋ถ„์„ํ•ด์ค˜.
๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ JSON ํ˜•์‹์œผ๋กœ๋งŒ ์‘๋‹ตํ•ด์ค˜, ๋‹ค๋ฅธ ์„ค๋ช…์€ ๋ถ™์ด์ง€ ๋งˆ.
# ์†Œ๋ฌธ ๋‚ด์šฉ
"{gossip_content}"
# ๋ถ„์„ ๊ฒฐ๊ณผ (JSON ํ˜•์‹)
{{
"person1": "์ฒซ ๋ฒˆ์งธ ์ธ๋ฌผ ์ด๋ฆ„",
"person2": "๋‘ ๋ฒˆ์งธ ์ธ๋ฌผ ์ด๋ฆ„",
"sentiment": "positive or negative or neutral",
"reason": "ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•œ ์ด์œ "
}}
"""
try:
response_text = query_llm_with_prompt(prompt)
# LLM ์‘๋‹ต์—์„œ JSON ๋ถ€๋ถ„๋งŒ ์ถ”์ถœ
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return {}
except (json.JSONDecodeError, Exception) as e:
print(f"์†Œ๋ฌธ ๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
return {}
def classify_and_extract_plan_details(plan_step: str) -> dict:
"""
๊ณ„ํš ๋‹จ๊ณ„๋ฅผ ๋ถ„์„ํ•˜์—ฌ ํ–‰๋™ ํƒ€์ž…, ๋Œ€์ƒ, ์ฃผ์ œ๋ฅผ ์ถ”์ถœ.
"""
prompt = f"""
# Instruction
Analyze the following action plan. Classify its type and extract the target NPC and topic if applicable.
Your response MUST be in JSON format.
Action Types can be "TALK" (for conversing with others), "HELP" (for helping someone), or "SOLO_ACTION" (for acting alone).
# Action Plan
"{plan_step}"
# Analysis (JSON format only)
{{
"action_type": "TALK or SOLO_ACTION",
"target": "NPC's name or null",
"topic": "topic of conversation or null"
}}
"""
try:
response_text = query_llm_with_prompt(prompt)
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
if json_match:
return json.loads(json_match.group())
# ๋ถ„์„ ์‹คํŒจ ์‹œ ํ˜ผ์ž ํ•˜๋Š” ํ–‰๋™์œผ๋กœ ๊ฐ„์ฃผ
return {"action_type": "SOLO_ACTION", "target": None, "topic": plan_step}
except Exception:
return {"action_type": "SOLO_ACTION", "target": None, "topic": plan_step}
def _query_llm_for_json_robustly(prompt: str) -> dict | list:
"""LLM์— JSON์„ ์š”์ฒญํ•˜๊ณ , ์‹คํŒจ ์‹œ ์ž๊ฐ€ ๊ต์ •์„ ์‹œ๋„ํ•˜๋Š” '๊ฐ€๋””์–ธ' ํ•จ์ˆ˜"""
# 1๋‹จ๊ณ„: LLM์— JSON ํ˜•์‹ ์š”์ฒญ
response_text = query_llm_with_prompt(prompt)
# Markdown ์ฝ”๋“œ ๋ธ”๋ก(```json ... ```) ์•ˆ์˜ ๋‚ด์šฉ๋ถ€ํ„ฐ ์ถ”์ถœ ์‹œ๋„
markdown_match = re.search(r'```(?:json)?\s*(\{.*\}|\[.*\])\s*```', response_text, re.DOTALL)
if markdown_match:
text_to_parse = markdown_match.group(1) # ์ฝ”๋“œ ๋ธ”๋ก ์•ˆ์˜ ๋‚ด์šฉ๋งŒ ์‚ฌ์šฉ
else:
# Markdown์ด ์—†์œผ๋ฉด, ์ „์ฒด ํ…์ŠคํŠธ์—์„œ JSON์„ ์ง์ ‘ ์ฐพ์Œ
# ์‘๋‹ต์ด Markdown ์ฝ”๋“œ ๋ธ”๋ก(```json...```)์œผ๋กœ ๊ฐ์‹ธ์—ฌ ์žˆ์„ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„
json_match = re.search(r'\{.*\}|\[.*\]', response_text, re.DOTALL)
if json_match:
text_to_parse = json_match.group(0)
else:
text_to_parse = response_text # ์ตœํ›„์˜ ๊ฒฝ์šฐ ์›๋ณธ ํ…์ŠคํŠธ ์‚ฌ์šฉ
# ๋ถˆ๋ฆฌ์–ธ ๊ฐ’์˜ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ํ‘œ์ค€์— ๋งž๊ฒŒ ์ˆ˜์ • (True -> true)
text_to_parse = text_to_parse.replace(": True", ": true").replace(": False", ": false")
# ๋”•์…”๋„ˆ๋ฆฌ๋‚˜ ๋ฆฌ์ŠคํŠธ์˜ ๋งˆ์ง€๋ง‰ ์š”์†Œ ๋’ค์— ๋ถ™์€ ๊ผฌ๋ฆฌํ‘œ ์‰ผํ‘œ ์ œ๊ฑฐ
text_to_parse = re.sub(r',\s*(\}|\])', r'\1', text_to_parse)
# ์ถ”์ถœ๋œ ํ…์ŠคํŠธ๋กœ ํŒŒ์‹ฑ ๋ฐ ์ž๊ฐ€ ๊ต์ • ์‹œ๋„
try:
return json.loads(text_to_parse) # ํŒŒ์‹ฑ ์„ฑ๊ณต ์‹œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜
except json.JSONDecodeError:
# 3๋‹จ๊ณ„: ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ, LLM์—๊ฒŒ '์ž๊ฐ€ ๊ต์ •' ์š”์ฒญ
correction_prompt = f"""
# Instruction
The following text is not a valid JSON.
Correct the syntax errors and return ONLY the valid JSON object or array.
Do not include any other text, explanations, or markdown.
# Invalid Text
"{response_text}"
# Corrected JSON:
"""
corrected_response = query_llm_with_prompt(correction_prompt)
# ์ž๊ฐ€ ๊ต์ •๋œ ํ…์ŠคํŠธ๋„ ํ•œ๋ฒˆ ๋” ์ •๋ฆฌ
corrected_response = corrected_response.replace(": True", ": true").replace(": False", ": false")
corrected_response = re.sub(r',\s*(\}|\])', r'\1', corrected_response)
try:
return json.loads(corrected_response) # ๊ต์ •๋œ ์‘๋‹ต ํŒŒ์‹ฑ ์‹œ๋„
except json.JSONDecodeError:
# 4๋‹จ๊ณ„: ์ตœ์ข… ์‹คํŒจ ์‹œ, ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
print(f"LLM ์ž๊ฐ€ ๊ต์ • ์‹คํŒจ. ์›๋ณธ ์‘๋‹ต: {response_text}")
return {}
def generate_plan_from_goal(npc_name: str, goal: str) -> List[str]:
"""๋ชฉํ‘œ๊ฐ€ ์ฃผ์–ด์ง€๋ฉด, ๊ฐ€๋””์–ธ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์•ˆ์ •์ ์œผ๋กœ ๊ณ„ํš์„ ์ƒ์„ฑ"""
prompt = f"""
# Instruction
- ๋‹น์‹ ์€ '{npc_name}'์ด๋ผ๋Š” NPC๋ฅผ ์œ„ํ•œ AI ์กฐ์ˆ˜์ž…๋‹ˆ๋‹ค.
- ์ด NPC์˜ ๋ชฉํ‘œ๋Š” "{goal}" ์ž…๋‹ˆ๋‹ค.
- ์ด ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ 3~5๋‹จ๊ณ„์˜ ๊ตฌ์ฒด์ ์ธ ํ–‰๋™ ๊ณ„ํš์„ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
- ๋‹น์‹ ์˜ ์‘๋‹ต์€ ๋ฐ˜๋“œ์‹œ ๊ฐ ๋‹จ๊ณ„๊ฐ€ ๋ฌธ์ž์—ด๋กœ ์ด๋ฃจ์–ด์ง„ ์œ ํšจํ•œ JSON ๋ฐฐ์—ด์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- ํ–‰๋™ ๊ณ„ํš์— ๋Œ€ํ•œ ๋‚ด์šฉ๋งŒ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”. ('1๋‹จ๊ณ„'์™€ ๊ฐ™์€ ๋ถˆํ•„์š”ํ•œ ๋‹จ์–ด๋Š” ํ•„์š”์—†์Šต๋‹ˆ๋‹ค.)
# ๊ณ„ํš (๋ฌธ์ž์—ด๋กœ ๊ตฌ์„ฑ๋œ JSON ๋ฐฐ์—ด ํ˜•์‹):
"""
result = _query_llm_for_json_robustly(prompt)
if isinstance(result, list) and all(isinstance(s, str) for s in result):
return result
return ["[๊ณ„ํš ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค]"] # ์ตœ์ข… ์‹คํŒจ ์‹œ ์•ˆ์ „ํ•œ ๊ฐ’ ๋ฐ˜ํ™˜
def generate_dialogue_action(speaker: "NPC", target: "NPC", conversation: "Conversation",
memories: List["Memory"], is_final_turn: bool = False) -> dict:
"""NPC์˜ ๋ชจ๋“  ๋‚ด๋ฉด ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ค์Œ ๋Œ€์‚ฌ์™€ ํ–‰๋™์„ ์ƒ์„ฑ."""
history_str = "\n".join(conversation.conversation_history)
memory_str = "\n".join([f"- {m.content}" for m in memories]) if memories else "๊ด€๋ จ๋œ ํŠน๋ณ„ํ•œ ๊ธฐ์–ต ์—†์Œ"
# ํ‰ํŒ ์ •๋ณด ์ƒ์„ฑ
reputation_info = ""
reputation_score = speaker.reputation.get(target.name, 0)
if reputation_score > 5:
reputation_info = f"- '{target.korean_name}'์— ๋Œ€ํ•œ ๋‚˜์˜ ํ‰ํŒ: ์‹ ๋ขฐํ•  ๋งŒํ•œ ์‚ฌ๋žŒ ๊ฐ™๋‹ค. (์ ์ˆ˜: {reputation_score})"
elif reputation_score < -5:
reputation_info = f"- '{target.korean_name}'์— ๋Œ€ํ•œ ๋‚˜์˜ ํ‰ํŒ: ์ข‹์ง€ ์•Š์€ ์†Œ๋ฌธ์„ ๋“ค์—ˆ๋‹ค. (์ ์ˆ˜: {reputation_score})"
# ์ƒ์ง• ๊ธฐ์–ต ์กฐํšŒ
symbolic_memories = [mem for mem in speaker.memory_store.get_all_memories() if mem.memory_type == "Symbolic"]
symbolic_section = ""
if symbolic_memories:
symbolic_section_title = "# Core Symbolic Values (Symbolic Memory)"
symbolic_contents = "\n".join([f"- {mem.content}" for mem in symbolic_memories])
# ํ”„๋กฌํ”„ํŠธ์— ์˜ˆ์˜๊ฒŒ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ์•ž๋’ค๋กœ ์ค„๋ฐ”๊ฟˆ ์ถ”๊ฐ€
symbolic_section = f"\n{symbolic_section_title}\n{symbolic_contents}"
# ์ƒ๋Œ€๋ฐฉ์— ๋Œ€ํ•ด ์•Œ๊ณ  ์žˆ๋Š” ์ •๋ณด๋ฅผ ํ”„๋กฌํ”„ํŠธ์— ์ถ”๊ฐ€
known_info_summary = "์•„์ง ์•„๋Š” ์ •๋ณด๊ฐ€ ์—†์Œ"
if target.name in speaker.knowledge:
known_facts = []
if "job" in speaker.knowledge[target.name]:
known_facts.append(f"์ง์—…์€ {speaker.knowledge[target.name]['job']}์ด๋‹ค.")
if "age" in speaker.knowledge[target.name]:
known_facts.append(f"๋‚˜์ด๋Š” {speaker.knowledge[target.name]['age']}์ด๋‹ค.")
if known_facts:
known_info_summary = ", ".join(known_facts)
if is_final_turn:
task_instruction = """
- ์ƒ๋Œ€๋ฐฉ์ด ๋Œ€ํ™”๋ฅผ ๋งˆ๋ฌด๋ฆฌํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ƒ๋Œ€๋ฐฉ์˜ ๋งˆ์ง€๋ง‰ ๋ง์— ์–ด์šธ๋ฆฌ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ์ž‘๋ณ„ ์ธ์‚ฌ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”. (์˜ˆ: "๋„ค, ์กฐ์‹ฌํžˆ ๊ฐ€์„ธ์š”.", "๋‹ค์Œ์— ๋˜ ๋ดฌ์š”.", "์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ข‹์€ ํ•˜๋ฃจ ๋ณด๋‚ด์„ธ์š”.")
- ์ด ๋Œ€์‚ฌ๋ฅผ ๋์œผ๋กœ ๋Œ€ํ™”๋ฅผ ๋งˆ๋ฌด๋ฆฌ("END")ํ•˜์„ธ์š”.
"""
else:
task_instruction = """
- ์œ„ ๋Œ€ํ™”์˜ ํ๋ฆ„๊ณผ ์ฃผ์ œ์— ๋งž์ถฐ, ๋‹น์‹ ์ด ์ด๋ฒˆ ํ„ด์— ํ•  ์ž์—ฐ์Šค๋Ÿฌ์šด ๋Œ€์‚ฌ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.
- ๋Œ€ํ™”๋ฅผ ๊ณ„์† ์ด์–ด๊ฐˆ์ง€(`action: "CONTINUE"`), ์•„๋‹ˆ๋ฉด ์ด ๋Œ€์‚ฌ๋ฅผ ๋์œผ๋กœ ๋Œ€ํ™”๋ฅผ ๋งˆ๋ฌด๋ฆฌํ• ์ง€(`action: "END"`) ๊ฒฐ์ •ํ•˜์„ธ์š”.
- ๋งŒ์•ฝ ์ƒ๋Œ€๋ฐฉ์˜ ๋ง์— ์˜ˆ์‹œ์™€ ๊ฐ™์ด ์ž‘๋ณ„ ์ธ์‚ฌ ๋˜๋Š” ๋Œ€ํ™” ์ข…๋ฃŒ๋ฅผ ์˜๋ฏธํ•œ๋‹ค๋ฉด, ๋ฐ˜๋“œ์‹œ ๋‹น์‹ ๋„ ๊ทธ์— ๋งž๋Š” ์ž‘๋ณ„ ์ธ์‚ฌ๋ฅผ ํ•˜๊ณ  "action"์„ "END"๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ์‹œ: "๊ทธ๋Ÿผ ์ด๋งŒ.", "๋‹ค์Œ์— ๋˜ ๋ดฌ์š”.", "์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์กฐ์‹ฌํžˆ๊ฐ€์„ธ์š”.", "๋„ค ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.", "์•ˆ๋…•ํžˆ ๊ฐ€์„ธ์š”.", "์•ˆ๋…•ํžˆ ๊ณ„์„ธ์š”.")
- **๋Œ€ํ™”๋ฅผ ๋๋‚ด๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค๋ฉด (`action: "END"`), ๋‹น์‹ ์˜ ๋Œ€์‚ฌ๋Š” ๋ฐ˜๋“œ์‹œ "์ด์ œ ๊ฐ€๋ด์•ผ๊ฒ ์–ด์š”", "๋‹ค์Œ์— ๋˜ ์ด์•ผ๊ธฐ ๋‚˜๋ˆ ์š”" ์™€ ๊ฐ™์ด ๋Œ€ํ™”์˜ ๋งˆ๋ฌด๋ฆฌ๋ฅผ ์•”์‹œํ•˜๋Š” ๋‚ด์šฉ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.**
- ๋‹จ์ˆœํžˆ ์ƒ๋Œ€๋ฐฉ์˜ ๋ง์— ๋™์˜๋งŒ ํ•˜์ง€ ๋ง๊ณ , ๋‹น์‹ ์˜ ๊ธฐ์–ต์ด๋‚˜ ์„ฑ๊ฒฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ƒˆ๋กœ์šด ์ƒ๊ฐ์ด๋‚˜ ์งˆ๋ฌธ, ํ™”์ œ๋ฅผ ๊บผ๋‚ด์–ด ๋Œ€ํ™”๋ฅผ ํ’๋ถ€ํ•˜๊ฒŒ ๋งŒ๋“œ์„ธ์š”.
"""
# ๋Œ€ํ™” ์ „๋žต(Conversation Strategies) ํ”„๋กฌํ”„ํŠธ ๋„์ž…
prompt = f"""
# Persona
๋‹น์‹ ์€ ๋‹ค์Œ ํ”„๋กœํ•„์„ ๊ฐ€์ง„ "{speaker.korean_name}"์ด๋ผ๋Š” ์ธ๋ฌผ์ž…๋‹ˆ๋‹ค.
- ์ง์—…: {speaker.job}
- ์„ฑ๊ฒฉ: {speaker.personality.get_narrative_summary()}
๋‹น์‹ ์€ ์ง€๊ธˆ "{target.korean_name}"์™€ ๋Œ€ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹น์‹ ์˜ ์„ฑ๊ฒฉ๊ณผ ์ƒ๋Œ€๋ฐฉ๊ณผ์˜ ๊ด€๊ณ„, ๊ด€๋ จ๋œ ๊ณผ๊ฑฐ ๊ธฐ์–ต, ๊ทธ๋ฆฌ๊ณ  ์ง€๊ธˆ๊นŒ์ง€์˜ ๋Œ€ํ™” ํ๋ฆ„์„ ๋ชจ๋‘ ๊ณ ๋ คํ•˜์—ฌ ๊ฐ€์žฅ ํ˜„์‹ค์ ์ด๊ณ  ๊นŠ์ด ์žˆ๋Š” ๋‹ค์Œ ๋Œ€์‚ฌ๋ฅผ ๋งŒ๋“œ์„ธ์š”.
์•„๋ž˜ ์„ค์ •์— ์™„๋ฒฝํ•˜๊ฒŒ ๋ชฐ์ž…ํ•˜์—ฌ ๋Œ€๋‹ตํ•˜์„ธ์š”.
# Inner State
- "{target.korean_name}"์™€์˜ ๊ด€๊ณ„: {speaker.relationships.get_relationship_summary(target.name)}
- {reputation_info}
- "{target.korean_name}"์— ๋Œ€ํ•ด ๋‚ด๊ฐ€ ์•„๋Š” ์ •๋ณด: {known_info_summary}
{symbolic_section}
# Relevant Memories
{memory_str}
# Conversation Context
- ํ˜„์žฌ ๋Œ€ํ™” ์ฃผ์ œ: "{conversation.topic}"
- ์ง€๊ธˆ๊นŒ์ง€์˜ ๋Œ€ํ™” ๋‚ด์šฉ:
{history_str}
# Instruction
{task_instruction}
- ๋‹น์‹ ์˜ ์‘๋‹ต์€ ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ JSON ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- ๋Œ€์‚ฌ์—๋Š” ์ด๋ฆ„, ํ–‰๋™ ๋ฌ˜์‚ฌ, ๋”ฐ์˜ดํ‘œ๋ฅผ ์ ˆ๋Œ€ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”.
- 'Relevant Memories'์€ ๋‹น์‹ ์˜ ์ƒ๊ฐ๊ณผ ๊ฐ์ •์˜ ๋ฐฐ๊ฒฝ์ด ๋˜๋Š” ์ •๋ณด์ž…๋‹ˆ๋‹ค. ๊ธฐ์–ต์˜ ๋‚ด์šฉ ์ž์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋งํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๊ทธ ๊ธฐ์–ต์„ ํ†ตํ•ด ํ˜•์„ฑ๋œ ๋‹น์‹ ์˜ ์ƒ๊ฐ์ด๋‚˜ ๋А๋‚Œ์„ ๋Œ€ํ™”์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋…น์—ฌ๋‚ด์„ธ์š”.
- ๋งŒ์•ฝ 'Relevant Memories'์ด ํ˜„์žฌ ๋Œ€ํ™” ์ƒ๋Œ€('{target.korean_name}')์™€ ์ง์ ‘์ ์ธ ๊ด€๋ จ์ด ์—†๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์— ๋Œ€ํ•œ ๊ฒƒ์ด๋ผ๋ฉด, ์ ˆ๋Œ€๋กœ ํ˜„์žฌ ๋Œ€ํ™” ์ƒ๋Œ€์—๊ฒŒ ์ผ์–ด๋‚œ ์ผ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ์ฐฉ๊ฐํ•ด์„œ ๋งํ•˜์ง€ ๋งˆ์„ธ์š”.** (์˜ˆ: ๋ฐฅ์— ๋Œ€ํ•œ ๊ธฐ์–ต์„ ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๋งํ•  ๋•Œ๋Š” "์‚ฌ์‹ค ์–ผ๋งˆ ์ „์— ๋ฐฅ ์”จ ๋•Œ๋ฌธ์—..." ์™€ ๊ฐ™์ด ๋ช…ํ™•ํ•˜๊ฒŒ ์ฃผ์ฒด๋ฅผ ๋ฐํžˆ์„ธ์š”.)
- ์•„๋ž˜์˜ 'Conversation Strategies'๊ณผ ๊ฐ™์ด ํ˜„์žฌ ๋Œ€ํ™”์˜ ๋งฅ๋ฝ๊ณผ ๋‹น์‹ ์˜ ์„ฑ๊ฒฉ์— ๊ฐ€์žฅ ์ ์ ˆํ•œ ํ–‰๋™ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.
# Conversation Strategies
1. **๊ณต๊ฐ ๋ฐ ์œ„๋กœ (Empathy & Comfort):** ์ƒ๋Œ€๋ฐฉ์˜ ๊ฐ์ •์— ๊ณต๊ฐํ•˜๊ฑฐ๋‚˜ ๋”ฐ๋œปํ•œ ๋ง๋กœ ์œ„๋กœํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "์ •๋ง ํž˜๋“œ์…จ๊ฒ ์–ด์š”. ์ €๋ผ๋„ ๊ทธ๋žฌ์„ ๊ฑฐ์˜ˆ์š”.")
2. **๊ฒฝํ—˜ ๊ณต์œ  (Share Experience):** ์ž์‹ ์˜ ๋น„์Šทํ•œ ๊ฒฝํ—˜์„ ์ด์•ผ๊ธฐํ•˜๋ฉฐ ์œ ๋Œ€๊ฐ์„ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "์ €๋„ ์˜ˆ์ „์— ๋น„์Šทํ•œ ์ผ๋กœ ๊ณ ์ƒํ•œ ์ ์ด ์žˆ์–ด์„œ ๊ทธ ๋งˆ์Œ ์ž˜ ์•Œ์•„์š”.")
3. **ํ•ด๊ฒฐ์ฑ… ์ œ์•ˆ (Suggest Solution):** ๋ฌธ์ œ ์ƒํ™ฉ์— ๋Œ€ํ•ด ๊ตฌ์ฒด์ ์ธ ์กฐ์–ธ์ด๋‚˜ ํ•ด๊ฒฐ์ฑ…์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "ํ˜น์‹œ ์ด๋ ‡๊ฒŒ ํ•ด๋ณด๋Š” ๊ฑด ์–ด๋–จ๊นŒ์š”? ์ œ๊ฐ€ ๋„์™€๋“œ๋ฆด ์ˆ˜๋„ ์žˆ๊ณ ์š”.")
4. **์‹ฌํ™” ์งˆ๋ฌธ (Deeper Question):** ์ƒ๋Œ€๋ฐฉ์˜ ๋ง์— ๋Œ€ํ•ด ๋” ๊นŠ์ด ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์งˆ๋ฌธ์„ ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "๊ทธ๋ ‡๊ฒŒ ์ƒ๊ฐํ•˜๊ฒŒ ๋œ ํŠน๋ณ„ํ•œ ๊ณ„๊ธฐ๊ฐ€ ์žˆ์œผ์…จ๋‚˜์š”?")
5. **ํ™”์ œ ์ „ํ™˜ (Change Topic):** ๋Œ€ํ™”๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ด๋ฃจ์–ด์กŒ๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‹ค๋ฅธ ์ฃผ์ œ๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค. (์˜ˆ: "๊ทธ๊ฑด ๊ทธ๋ ‡๊ณ , ํ˜น์‹œ ๋‹ค๋ฅธ ์žฌ๋ฏธ์žˆ๋Š” ์†Œ์‹์€ ์—†๋‚˜์š”?")
6. **์œ ๋จธ ์‚ฌ์šฉ (Use of Humor):** ๊ธด์žฅ๋œ ๋ถ„์œ„๊ธฐ๋ฅผ ์™„ํ™”ํ•˜๊ฑฐ๋‚˜ ๋ถ„์œ„๊ธฐ๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ฐ”๊พธ๊ธฐ ์œ„ํ•ด ์›ƒ์Œ์„ ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "์ด์ฏค ๋˜๋ฉด ์šฐ๋ฆฌ ๋‘˜ ๋‹ค ์ž๊ฒฉ์ฆ ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€์š”? ๊ณ ๋ฏผ ์ „๋ฌธ๊ฐ€!)
7. **ํšŒํ”ผ ๋ฐ ๋ฌด์‹œ (Avoidance/Deflection):** ๋ถˆํŽธํ•˜๊ฑฐ๋‚˜ ๋ณต์žกํ•œ ์ฃผ์ œ๋ฅผ ์ผ๋ถ€๋Ÿฌ ํ”ผํ•˜๊ฑฐ๋‚˜ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "๊ทธ ์–˜๊ธด ๋‹ค์Œ์— ํ•˜์ž. ์ง€๊ธˆ ๊ทธ ์–˜๊ธฐ๋ฅผ ํ•  ๊ธฐ๋ถ„ ์•„๋‹ˆ์•ผ.")
8. **๊ฐ์ • ๋ฌด์‹œ (Invalidation):** ์ƒ๋Œ€์˜ ๊ฐ์ •์„ ์‚ฌ์†Œํ•˜๊ฒŒ ์—ฌ๊ธฐ๊ฑฐ๋‚˜ ์ธ์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (์˜ˆ: "๊ทธ ์ •๋„ ๊ฐ€์ง€๊ณ  ๋ญ˜ ๊ทธ๋ž˜.")
9. **๋น„๋‚œ/์ฑ…์ž„ ์ „๊ฐ€ (Blame/Accusation):** ๋ฌธ์ œ์˜ ์›์ธ์„ ์ƒ๋Œ€์—๊ฒŒ ๋Œ๋ฆฌ๋ฉฐ ๊ณต๊ฒฉ์ ์œผ๋กœ ๋ฐ˜์‘ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "๊ทธ๊ฑด ๋„ˆ ์ž˜๋ชป์ด์ž–์•„. ์™œ ๋‚˜ํ•œํ…Œ ๊ทธ๋ž˜?")
10. **๋น„๊ผฌ๊ธฐ ๋ฐ ์กฐ๋กฑ (Sarcasm/Mockery):** ์ง์ ‘์ ์œผ๋กœ ๊ณต๊ฒฉํ•˜์ง€ ์•Š์ง€๋งŒ, ๋น„๊ผฌ๋Š” ๋งํˆฌ๋กœ ์ƒ๋Œ€๋ฅผ ๊นŽ์•„๋‚ด๋ฆฝ๋‹ˆ๋‹ค. (์˜ˆ: "์˜ค, ์ด์ œ์•ผ ๊ทธ๊ฑธ ๊นจ๋‹ฌ์•˜๋‹ค๊ณ ? ์—ญ์‹œ ๋น ๋ฅด์‹œ๋„ค.")
11. **ํ™”์ œ ํšŒํ”ผ๋ฅผ ์œ„ํ•œ ๊ณผ๋„ํ•œ ์ผ๋ฐ˜ํ™” (Over-Generalization):** ๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์—์„œ ๋ฒ—์–ด๋‚˜๊ธฐ ์œ„ํ•ด '์›๋ž˜ ๋‹ค ๊ทธ๋ž˜' ์‹์œผ๋กœ ์ผ๋ฐ˜ํ™” ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "์‚ฌ๋žŒ๋“ค์ด ๋‹ค ๊ทธ๋ ‡์ง€ ๋ญ. ๊ธฐ๋Œ€ํ•˜์ง€ ๋งˆ.")
# Response (JSON format only)
{{
"dialogue": "Conversation Strategies ์ „๋žต์— ๋”ฐ๋ผ ์ƒ์„ฑ๋œ ์‹ค์ œ ๋งํ•  ๋Œ€์‚ฌ ๋‚ด์šฉ",
"action": "CONTINUE or END"
}}
"""
result = _query_llm_for_json_robustly(prompt) # 3์ค‘ ๋ฐฉ์–ด ์‹œ์Šคํ…œ ์žฌํ™œ์šฉ
# ๊ฒฐ๊ณผ ํฌ๋งท ๊ฒ€์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ’ ์ฒ˜๋ฆฌ
if isinstance(result, dict) and "dialogue" in result and "action" in result:
# ํ›„์ฒ˜๋ฆฌ ๋กœ์ง (์ด๋ฆ„ ์ค‘๋ณต์ด๋‚˜ ๋”ฐ์˜ดํ‘œ ์ œ๊ฑฐ)
dialogue = result.get("dialogue", "...").strip().strip('"')
name_prefix = f"{speaker.name}:"
if dialogue.startswith(name_prefix):
dialogue = dialogue[len(name_prefix):].strip().strip('"')
result["dialogue"] = dialogue
return result
return {"dialogue": "...", "action": "END"} # ์ตœ์ข… ์‹คํŒจ ์‹œ ์•ˆ์ „ํ•˜๊ฒŒ ๋Œ€ํ™” ์ข…๋ฃŒ
def evaluate_social_action_outcome(initiator: "NPC", target: "NPC", action_type: str) -> dict:
"""๋‘ NPC์˜ ์„ฑ๊ฒฉ๊ณผ ๊ด€๊ณ„๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌํšŒ์  ํ–‰๋™์˜ ๊ฒฐ๊ณผ๋ฅผ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค."""
prompt = f"""
# Context
- ํ–‰๋™์ž: "{initiator.korean_name}" (์„ฑ๊ฒฉ: {initiator.personality.get_narrative_summary()})
- ๋Œ€์ƒ์ž: "{target.korean_name}" (์„ฑ๊ฒฉ: {target.personality.get_narrative_summary()})
- ๋‘˜์˜ ๊ด€๊ณ„: {initiator.relationships.get_relationship_summary(target.name)}
- ํ–‰๋™: ํ–‰๋™์ž('{initiator.korean_name}')๋Š” ๋Œ€์ƒ์ž('{target.korean_name}')๋ฅผ ๋„์™€์ฃผ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. (Action: {action_type})
# Your Task
์œ„ ์ƒํ™ฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, ์ด ํ–‰๋™์˜ ๊ฒฐ๊ณผ๋ฅผ ํ˜„์‹ค์ ์œผ๋กœ ์ถ”๋ก ํ•ด์ฃผ์„ธ์š”.
๊ฒฐ๊ณผ๋Š” ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ JSON ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
# Outcome (JSON format only)
{{
"success": "True or False"
"outcome_summary": "ํ–‰๋™์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝ (์˜ˆ: '๋ฐฅ์€ ์ฐฐ๋ฆฌ์˜ ์„œํˆฌ๋ฅธ ๋„์›€์— ๊ณ ๋งˆ์›Œํ•˜๋ฉด์„œ๋„ ์กฐ๊ธˆ ๋‹ต๋‹ตํ•ดํ–ˆ๋‹ค.')",
"initiator_emotion": "ํ–‰๋™์„ ํ•œ ํ›„ ํ–‰๋™์ž๊ฐ€ ๋А๋‚„ ๊ฐ์ • (์•„๋ž˜ 'Emotion_List์—์„œ ์ฐพ์•„์„œ ํ•œ ๋‹จ์–ด๋กœ ํ‘œํ˜„)",
"target_emotion": "ํ–‰๋™์„ ๋ฐ›์€ ํ›„ ๋Œ€์ƒ์ž๊ฐ€ ๋А๋‚„ ๊ฐ์ • (์•„๋ž˜ 'Emotion_List์—์„œ ์ฐพ์•„์„œ ํ•œ ๋‹จ์–ด๋กœ ํ‘œํ˜„)"
}}
# Emotion_List
{EMOTION_LIST}
"""
result = _query_llm_for_json_robustly(prompt) # 3์ค‘ ๋ฐฉ์–ด ์‹œ์Šคํ…œ ์žฌํ™œ์šฉ
if isinstance(result, dict) and "success" in result:
return result
return {# ์ตœ์ข… ์‹คํŒจ ์‹œ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
"success": False,
"outcome_summary": "ํ–‰๋™์˜ ๊ฒฐ๊ณผ๋ฅผ ํ‰๊ฐ€ํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.",
"initiator_emotion": "confusion",
"target_emotion": "neutral"
}
def evaluate_goal_achievement(initiator_name: str, goal: str, conversation_transcript: str) -> dict:
"""๋Œ€ํ™”์˜ ๋ชฉํ‘œ์™€ ์ „์ฒด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, ๋ชฉํ‘œ ๋‹ฌ์„ฑ ์—ฌ๋ถ€๋ฅผ ํ‰๊ฐ€"""
prompt = f"""
# Context
- ๋Œ€ํ™” ์‹œ์ž‘์ž: "{initiator_name}"
- ๋Œ€ํ™” ์‹œ์ž‘์ž์˜ ๋ชฉํ‘œ (๋Œ€ํ™” ์ฃผ์ œ): "{goal}"
- ์‹ค์ œ ๋Œ€ํ™” ๋‚ด์šฉ:
{conversation_transcript}
# Your Task
์œ„ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, ๋Œ€ํ™” ์‹œ์ž‘์ž("{initiator_name}")๊ฐ€ ์ž์‹ ์˜ ์›๋ž˜ ๋ชฉํ‘œ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋‹ฌ์„ฑํ–ˆ๋Š”์ง€ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š”.
๊ฒฐ๊ณผ๋Š” ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ JSON ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
# Evaluation (JSON format only)
{{
"goal_achieved": True,
"reason": "๋ชฉํ‘œ ๋‹ฌ์„ฑ ๋˜๋Š” ์‹คํŒจ์— ๋Œ€ํ•œ ๊ฐ„๊ฒฐํ•œ ์ด์œ  (์˜ˆ: '์ƒ๋Œ€๋ฐฉ์„ ์œ„๋กœํ•˜๊ณ  ์ง€์ง€ํ•˜๋ฉฐ ๊ธ์ •์ ์ธ ๋ฐ˜์‘์„ ์ด๋Œ์–ด๋ƒˆ๋‹ค.)",
"initiator_emotion": "์ด ๊ฒฐ๊ณผ๋ฅผ ์–ป์€ ํ›„ ๋Œ€ํ™” ์‹œ์ž‘์ž๊ฐ€ ๋А๋‚„ ๊ฐ์ • (์•„๋ž˜ 'Emotion_List์—์„œ ์ฐพ์•„์„œ ํ•œ ๋‹จ์–ด๋กœ ํ‘œํ˜„)
}}
# Emotion_List
{EMOTION_LIST}
"""
result = _query_llm_for_json_robustly(prompt)
if isinstance(result, dict) and "goal_achieved" in result:
return result
return {
# ์ตœ์ข… ์‹คํŒจ ์‹œ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
"goal_achieved": False,
"reason": "๋ชฉํ‘œ ๋‹ฌ์„ฑ ์—ฌ๋ถ€ ํ‰๊ฐ€์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.",
"initiator_emotion": "confusion"
}
def generate_reflection_topic(memories: List["Memory"], personality_summary: str) -> str:
"""์ตœ๊ทผ ๊ธฐ์–ต๊ณผ ์„ฑ๊ฒฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, ๊นŠ์ด ์žˆ๋Š” ์„ฑ์ฐฐ ์ฃผ์ œ๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑ"""
if not memories:
return "๋‚˜์˜ ์ธ์ƒ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ–ˆ๋˜ ์ˆœ๊ฐ„์€ ์–ธ์ œ์˜€์„๊นŒ?"
# ๊ธฐ์–ต ๋ชฉ๋ก์„ ํ”„๋กฌํ”„ํŠธ์— ๋„ฃ๊ธฐ ์ข‹์€ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜
memory_details = "\n".join([f"- {mem.content} (๊ฐ์ •: {mem.emotion})" for mem in memories])
prompt = f"""
# Instructions
- ์•„๋ž˜ 'Personality'์™€ 'Recent Memory List'๋Š” ๊ฐ๊ฐ ์–ด๋–ค ์‚ฌ๋žŒ์˜ '์„ฑ๊ฒฉ'๊ณผ '์ตœ๊ทผ ๊ธฐ์–ต ๋ชฉ๋ก'์ž…๋‹ˆ๋‹ค.
- ์ด ๊ธฐ์–ต๋“ค์„ ์ข…ํ•ฉ์ ์œผ๋กœ ๋ถ„์„ํ•˜์—ฌ, ์ด ์‚ฌ๋žŒ์ด ์ง€๊ธˆ ์ž์‹ ์— ๋Œ€ํ•ด ๊นŠ์ด ์„ฑ์ฐฐํ•ด๋ณผ ๋งŒํ•œ ๊ฐ€์žฅ ์˜๋ฏธ ์žˆ๋Š” '์งˆ๋ฌธ'์„ ํ•œ๊ตญ์–ด๋กœ ํ•œ ๋ฌธ์žฅ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
# Personality
{personality_summary}
# Recent Memory List
{memory_details}
# Self-Reflective Question (in Korean, as a single sentence):
"""
topic = query_llm_with_prompt(prompt).strip()
return topic if topic and "[LLM Error]" not in topic else "๋‚˜์˜ ์ธ์ƒ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฐ€์น˜๋Š” ๋ฌด์—‡์ผ๊นŒ?"