m97j commited on
Commit
bbb241c
ยท
1 Parent(s): c915682

test case update

Browse files
config.py CHANGED
@@ -17,7 +17,7 @@ MAX_LENGTH = int(os.getenv("MAX_LENGTH", 1024))
17
  NUM_FLAGS = int(os.getenv("NUM_FLAGS", 7)) # flags.json ๊ธธ์ด์™€ ์ผ์น˜
18
 
19
  # ์ƒ์„ฑ ํŒŒ๋ผ๋ฏธํ„ฐ
20
- GEN_MAX_NEW_TOKENS = int(os.getenv("GEN_MAX_NEW_TOKENS", 200))
21
  GEN_TEMPERATURE = float(os.getenv("GEN_TEMPERATURE", 0.7))
22
  GEN_TOP_P = float(os.getenv("GEN_TOP_P", 0.9))
23
 
 
17
  NUM_FLAGS = int(os.getenv("NUM_FLAGS", 7)) # flags.json ๊ธธ์ด์™€ ์ผ์น˜
18
 
19
  # ์ƒ์„ฑ ํŒŒ๋ผ๋ฏธํ„ฐ
20
+ GEN_MAX_NEW_TOKENS = int(os.getenv("GEN_MAX_NEW_TOKENS", 400))
21
  GEN_TEMPERATURE = float(os.getenv("GEN_TEMPERATURE", 0.7))
22
  GEN_TOP_P = float(os.getenv("GEN_TOP_P", 0.9))
23
 
flags.json CHANGED
@@ -2,7 +2,7 @@
2
  "ALL_FLAGS": [
3
  "give_item",
4
  "give_hint",
5
- "quest_stage_change",
6
  "change_game_state",
7
  "change_player_state",
8
  "npc_action",
 
2
  "ALL_FLAGS": [
3
  "give_item",
4
  "give_hint",
5
+ "change_npc_state",
6
  "change_game_state",
7
  "change_player_state",
8
  "npc_action",
inference.py CHANGED
@@ -3,7 +3,7 @@ from config import DEVICE, MAX_LENGTH, GEN_MAX_NEW_TOKENS, GEN_TEMPERATURE, GEN_
3
  from model_loader import ModelWrapper
4
 
5
  # ์ „์—ญ ๋กœ๋“œ (์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ 1ํšŒ)
6
- wrapper = ModelWrapper()
7
  tokenizer, model, flags_order = wrapper.get()
8
 
9
  GEN_PARAMS = {
@@ -18,14 +18,17 @@ def run_inference(prompt: str):
18
  inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=MAX_LENGTH).to(DEVICE)
19
 
20
  with torch.no_grad():
 
21
  gen_ids = model.generate(**inputs, **GEN_PARAMS)
22
  generated_text = tokenizer.decode(
23
  gen_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True
24
  )
25
 
 
26
  outputs = model(**inputs, output_hidden_states=True)
27
  h = outputs.hidden_states[-1]
28
 
 
29
  STATE_ID = tokenizer.convert_tokens_to_ids("<STATE>")
30
  ids = inputs["input_ids"]
31
  mask = (ids == STATE_ID).unsqueeze(-1)
@@ -35,6 +38,7 @@ def run_inference(prompt: str):
35
  else:
36
  pooled = h[:, -1, :]
37
 
 
38
  delta_pred = torch.tanh(model.delta_head(pooled))[0].cpu().tolist()
39
  flag_prob = torch.sigmoid(model.flag_head(pooled))[0].cpu().tolist()
40
  flag_thr = torch.sigmoid(model.flag_threshold_head(pooled))[0].cpu().tolist()
 
3
  from model_loader import ModelWrapper
4
 
5
  # ์ „์—ญ ๋กœ๋“œ (์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ 1ํšŒ)
6
+ wrapper = ModelWrapper() # ๊ธฐ๋ณธ์€ latest ๋ธŒ๋žœ์น˜
7
  tokenizer, model, flags_order = wrapper.get()
8
 
9
  GEN_PARAMS = {
 
18
  inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=MAX_LENGTH).to(DEVICE)
19
 
20
  with torch.no_grad():
21
+ # ํ…์ŠคํŠธ ์ƒ์„ฑ
22
  gen_ids = model.generate(**inputs, **GEN_PARAMS)
23
  generated_text = tokenizer.decode(
24
  gen_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True
25
  )
26
 
27
+ # ํžˆ๋“  ์Šคํ…Œ์ดํŠธ ์ถ”์ถœ
28
  outputs = model(**inputs, output_hidden_states=True)
29
  h = outputs.hidden_states[-1]
30
 
31
+ # <STATE> ํ† ํฐ ์œ„์น˜ ํ’€๋ง
32
  STATE_ID = tokenizer.convert_tokens_to_ids("<STATE>")
33
  ids = inputs["input_ids"]
34
  mask = (ids == STATE_ID).unsqueeze(-1)
 
38
  else:
39
  pooled = h[:, -1, :]
40
 
41
+ # ์ปค์Šคํ…€ ํ—ค๋“œ ์ถ”๋ก 
42
  delta_pred = torch.tanh(model.delta_head(pooled))[0].cpu().tolist()
43
  flag_prob = torch.sigmoid(model.flag_head(pooled))[0].cpu().tolist()
44
  flag_thr = torch.sigmoid(model.flag_threshold_head(pooled))[0].cpu().tolist()
model_loader.py CHANGED
@@ -1,10 +1,7 @@
1
  import os, json, torch
2
  import torch.nn as nn
3
  from transformers import AutoTokenizer, AutoModelForCausalLM
4
- from peft import PeftModel
5
- from config import BASE_MODEL, ADAPTERS, DEVICE, HF_TOKEN
6
-
7
- ADAPTER_VOCAB_SIZE = 151672 # ํ•™์Šต ์‹œ์  vocab size (๋กœ๊ทธ ๊ธฐ์ค€)
8
 
9
  SPECIALS = ["<SYS>", "<CTX>", "<PLAYER>", "<NPC>", "<STATE>", "<RAG>", "<PLAYER_STATE>"]
10
 
@@ -21,9 +18,12 @@ class ModelWrapper:
21
  self.flags_order = json.load(open(flags_path, encoding="utf-8"))["ALL_FLAGS"]
22
  self.num_flags = len(self.flags_order)
23
 
24
- # 1) ํ† ํฌ๋‚˜์ด์ € (ํ•™์Šต๊ณผ ๋™์ผ ์˜ต์…˜ + SPECIALS)
 
 
25
  self.tokenizer = AutoTokenizer.from_pretrained(
26
- BASE_MODEL,
 
27
  use_fast=True,
28
  token=HF_TOKEN,
29
  trust_remote_code=True
@@ -31,39 +31,25 @@ class ModelWrapper:
31
  if self.tokenizer.pad_token is None:
32
  self.tokenizer.pad_token = self.tokenizer.eos_token
33
  self.tokenizer.padding_side = "right"
34
- # ํ•™์Šต ์‹œ ์ถ”๊ฐ€ํ–ˆ๋˜ ํŠน์ˆ˜ ํ† ํฐ ์žฌํ˜„
35
  self.tokenizer.add_special_tokens({"additional_special_tokens": SPECIALS})
36
 
37
- # 2) ๋ฒ ์ด์Šค ๋ชจ๋ธ (์˜คํ”„๋กœ๋”ฉ ๋„๊ณ  ๋กœ๋“œ)
38
- base = AutoModelForCausalLM.from_pretrained(
39
- BASE_MODEL,
40
- device_map=None, # โœ… ์˜คํ”„๋กœ๋”ฉ ๋น„ํ™œ์„ฑํ™”
41
- low_cpu_mem_usage=False, # โœ… meta ํ…์„œ ์ƒ์„ฑ ๋ฐฉ์ง€
42
- trust_remote_code=True,
43
- token=HF_TOKEN
44
- )
45
-
46
- # 3) ํ•™์Šต ์‹œ vocab size๋กœ ๊ฐ•์ œ ๋ฆฌ์‚ฌ์ด์ฆˆ (์–ด๋Œ‘ํ„ฐ ๋กœ๋“œ ์ „์—)
47
- base.resize_token_embeddings(ADAPTER_VOCAB_SIZE)
48
-
49
- # 4) LoRA ์–ด๋Œ‘ํ„ฐ ์ ์šฉ (์˜คํ”„๋กœ๋”ฉ ๋„๊ณ  ๋กœ๋“œ)
50
- branch = get_current_branch()
51
- self.model = PeftModel.from_pretrained(
52
- base,
53
- ADAPTERS,
54
  revision=branch,
55
- device_map=None, # โœ… ์˜คํ”„๋กœ๋”ฉ ๋น„ํ™œ์„ฑํ™”
56
- low_cpu_mem_usage=False, # โœ… meta ํ…์„œ ์ƒ์„ฑ ๋ฐฉ์ง€
 
57
  token=HF_TOKEN
58
  )
59
 
60
- # 5) ์ปค์Šคํ…€ ํ—ค๋“œ
61
  hidden_size = self.model.config.hidden_size
62
  self.model.delta_head = nn.Linear(hidden_size, 2).to(DEVICE)
63
  self.model.flag_head = nn.Linear(hidden_size, self.num_flags).to(DEVICE)
64
  self.model.flag_threshold_head = nn.Linear(hidden_size, self.num_flags).to(DEVICE)
65
 
66
- # 6) ์ปค์Šคํ…€ ํ—ค๋“œ ๊ฐ€์ค‘์น˜ ๋กœ๋“œ(์žˆ์„ ๊ฒฝ์šฐ)
67
  for head_name, file_name in [
68
  ("delta_head", "delta_head.pt"),
69
  ("flag_head", "flag_head.pt"),
@@ -77,7 +63,7 @@ class ModelWrapper:
77
  except Exception as e:
78
  print(f"[WARN] Failed to load {file_name}: {e}")
79
 
80
- # 7) ๋””๋ฐ”์ด์Šค ๋ฐฐ์น˜
81
  self.model.to(DEVICE)
82
  self.model.eval()
83
 
 
1
  import os, json, torch
2
  import torch.nn as nn
3
  from transformers import AutoTokenizer, AutoModelForCausalLM
4
+ from config import DEVICE, HF_TOKEN
 
 
 
5
 
6
  SPECIALS = ["<SYS>", "<CTX>", "<PLAYER>", "<NPC>", "<STATE>", "<RAG>", "<PLAYER_STATE>"]
7
 
 
18
  self.flags_order = json.load(open(flags_path, encoding="utf-8"))["ALL_FLAGS"]
19
  self.num_flags = len(self.flags_order)
20
 
21
+ branch = get_current_branch()
22
+
23
+ # 1) ํ† ํฌ๋‚˜์ด์ € (ํ•™์Šต ๋‹น์‹œ vocab + SPECIALS)
24
  self.tokenizer = AutoTokenizer.from_pretrained(
25
+ "m97j/npc_LoRA-fps", # ๋ณ‘ํ•ฉ๋œ ๋ชจ๋ธ์ด ์˜ฌ๋ผ๊ฐ„ repo
26
+ revision=branch,
27
  use_fast=True,
28
  token=HF_TOKEN,
29
  trust_remote_code=True
 
31
  if self.tokenizer.pad_token is None:
32
  self.tokenizer.pad_token = self.tokenizer.eos_token
33
  self.tokenizer.padding_side = "right"
 
34
  self.tokenizer.add_special_tokens({"additional_special_tokens": SPECIALS})
35
 
36
+ # 2) ๋ณ‘ํ•ฉ๋œ ๋ชจ๋ธ ๋กœ๋“œ (์ƒค๋“œ ์ž๋™ ์ธ์‹)
37
+ self.model = AutoModelForCausalLM.from_pretrained(
38
+ "m97j/npc_LoRA-fps",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  revision=branch,
40
+ device_map=None, # ์˜คํ”„๋กœ๋”ฉ ๋น„ํ™œ์„ฑํ™”
41
+ low_cpu_mem_usage=False,
42
+ trust_remote_code=True,
43
  token=HF_TOKEN
44
  )
45
 
46
+ # 3) ์ปค์Šคํ…€ ํ—ค๋“œ ์ถ”๊ฐ€
47
  hidden_size = self.model.config.hidden_size
48
  self.model.delta_head = nn.Linear(hidden_size, 2).to(DEVICE)
49
  self.model.flag_head = nn.Linear(hidden_size, self.num_flags).to(DEVICE)
50
  self.model.flag_threshold_head = nn.Linear(hidden_size, self.num_flags).to(DEVICE)
51
 
52
+ # 4) ์ปค์Šคํ…€ ํ—ค๋“œ ๊ฐ€์ค‘์น˜ ๋กœ๋“œ
53
  for head_name, file_name in [
54
  ("delta_head", "delta_head.pt"),
55
  ("flag_head", "flag_head.pt"),
 
63
  except Exception as e:
64
  print(f"[WARN] Failed to load {file_name}: {e}")
65
 
66
+ # 5) ๋””๋ฐ”์ด์Šค ๋ฐฐ์น˜
67
  self.model.to(DEVICE)
68
  self.model.eval()
69
 
modules/case_loader.py CHANGED
@@ -9,14 +9,18 @@ with open(TEST_CASES_PATH, "r", encoding="utf-8") as f:
9
  TEST_CASES = json.load(f)
10
 
11
  def get_case_names():
12
- return [f"{i+1}. {c['description']}" for i, c in enumerate(TEST_CASES)]
 
 
 
 
13
 
14
  def load_case(idx):
15
- case = TEST_CASES[idx]
16
- return json.dumps(case, ensure_ascii=False, indent=2), case["player_utterance"]
17
 
18
  def run_case(idx, player_utt):
19
- case = TEST_CASES[idx].copy()
20
  case["player_utterance"] = player_utt
21
  prompt = build_webtest_prompt(case["npc_id"], case["npc_location"], player_utt)
22
  result = run_inference(prompt)
 
9
  TEST_CASES = json.load(f)
10
 
11
  def get_case_names():
12
+ # description์€ input ์•ˆ์— ์žˆ์Œ
13
+ return [f"{i+1}. {c['input'].get('description','')}" for i, c in enumerate(TEST_CASES)]
14
+
15
+ def load_cases():
16
+ return TEST_CASES
17
 
18
  def load_case(idx):
19
+ cases = load_cases()
20
+ return cases[idx]
21
 
22
  def run_case(idx, player_utt):
23
+ case = TEST_CASES[idx]["input"].copy()
24
  case["player_utterance"] = player_utt
25
  prompt = build_webtest_prompt(case["npc_id"], case["npc_location"], player_utt)
26
  result = run_inference(prompt)
modules/ui_components.py CHANGED
@@ -1,9 +1,39 @@
1
  import gradio as gr
2
- from .case_loader import get_case_names, load_case, run_case
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  def build_ui():
5
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple")) as demo:
6
- # ์ƒ๋‹จ ์†Œ๊ฐœ
7
  gr.Markdown("""
8
  # ๐Ÿ‘พ PersonaChatEngine HF-Serve
9
  **๊ฒŒ์ž„ ๋‚ด NPC ๋ฉ”์ธ ๋ชจ๋ธ ์ถ”๋ก  ์„œ๋ฒ„**
@@ -13,31 +43,68 @@ def build_ui():
13
  with gr.Row():
14
  gr.Button("๐Ÿ“„ ์ƒ์„ธ ๋ฌธ์„œ ๋ณด๊ธฐ",
15
  link="https://huggingface.co/spaces/m97j/PersonaChatEngine_HF-serve/blob/main/README.md")
16
- gr.Button("๐Ÿ’ป Colab ํ…Œ์ŠคํŠธ ์—ด๊ธฐ",
17
  link="https://colab.research.google.com/drive/1_-qH8kdoU2Jj58TdaSnswHex-BFefInq?usp=sharing#scrollTo=cFJGv8BJ8oPD")
18
 
19
  gr.Markdown("### ๐ŸŽฏ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ๊ธฐ๋ฐ˜ ๊ฐ„๋‹จ ์‹คํ–‰")
 
20
 
21
  with gr.Row():
22
- case_dropdown = gr.Dropdown(choices=get_case_names(), label="ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์„ ํƒ", value=get_case_names()[0])
23
  load_btn = gr.Button("์ผ€์ด์Šค ๋ถˆ๋Ÿฌ์˜ค๊ธฐ")
24
 
25
- case_info = gr.Textbox(label="์ผ€์ด์Šค ์ •๋ณด", lines=10)
26
- player_input = gr.Textbox(label="Player Utterance ์ˆ˜์ •", lines=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  run_btn = gr.Button("๐Ÿš€ Run Inference", variant="primary")
29
  npc_resp = gr.Textbox(label="NPC Response")
30
  deltas = gr.JSON(label="Deltas")
31
  flags = gr.JSON(label="Flags Probabilities")
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  load_btn.click(
34
- fn=lambda name: load_case(get_case_names().index(name)),
35
  inputs=[case_dropdown],
36
- outputs=[case_info, player_input]
 
 
 
 
37
  )
38
 
 
39
  run_btn.click(
40
- fn=lambda name, utt: run_case(get_case_names().index(name), utt),
41
  inputs=[case_dropdown, player_input],
42
  outputs=[npc_resp, deltas, flags]
43
  )
 
1
  import gradio as gr
2
+ from .case_loader import load_case, run_case
3
+
4
+ # ์‹œ์—ฐ์šฉ ์ผ€์ด์Šค ์ด๋ฆ„ ํ•˜๋“œ์ฝ”๋”ฉ
5
+ CASE_NAMES = [
6
+ "ํ๊ณต์žฅ์—์„œ NPC์™€ ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
7
+ "๋งˆ์„ ๋Œ€์žฅ์žฅ์ด์™€ ๋ฌด๊ธฐ ์ˆ˜๋ฆฌ์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
8
+ "์ˆฒ์† ์€๋‘”์ž์™€ ํฌ๊ท€ ์•ฝ์ดˆ์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
9
+ "ํ•ญ๊ตฌ ๊ด€๋ฆฌ๊ด€๊ณผ ์ถœํ•ญ ํ—ˆ๊ฐ€์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
10
+ "๋งˆ๋ฒ•์‚ฌ ๊ฒฌ์Šต์ƒ๊ณผ ๊ณ ๋Œ€ ์ฃผ๋ฌธ์„œ์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด"
11
+ ]
12
+
13
+ def format_case_info(case: dict) -> dict:
14
+ """์ผ€์ด์Šค ์ •๋ณด๋ฅผ ๋ณด๊ธฐ ์ข‹๊ฒŒ ์ •๋ฆฌํ•ด์„œ ๋ฐ˜ํ™˜"""
15
+ inp = case["input"]
16
+ tags = inp.get("tags", {})
17
+ context_lines = [f"{h['role'].upper()}: {h['text']}" for h in inp.get("context", [])]
18
+
19
+ return {
20
+ "npc_id": inp.get("npc_id", ""),
21
+ "npc_location": inp.get("npc_location", ""),
22
+ "quest_stage": tags.get("quest_stage", ""),
23
+ "relationship": tags.get("relationship", ""),
24
+ "trust": tags.get("trust", ""),
25
+ "npc_mood": tags.get("npc_mood", ""),
26
+ "player_reputation": tags.get("player_reputation", ""),
27
+ "style": tags.get("style", ""),
28
+ "lore": inp.get("lore", ""),
29
+ "description": inp.get("description", ""),
30
+ "player_state": inp.get("player_state", {}),
31
+ "context": "\n".join(context_lines),
32
+ "player_utterance": inp.get("player_utterance", "")
33
+ }
34
 
35
  def build_ui():
36
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple")) as demo:
 
37
  gr.Markdown("""
38
  # ๐Ÿ‘พ PersonaChatEngine HF-Serve
39
  **๊ฒŒ์ž„ ๋‚ด NPC ๋ฉ”์ธ ๋ชจ๋ธ ์ถ”๋ก  ์„œ๋ฒ„**
 
43
  with gr.Row():
44
  gr.Button("๐Ÿ“„ ์ƒ์„ธ ๋ฌธ์„œ ๋ณด๊ธฐ",
45
  link="https://huggingface.co/spaces/m97j/PersonaChatEngine_HF-serve/blob/main/README.md")
46
+ gr.Button("๐Ÿ’ป Colab ๋…ธํŠธ๋ถ ์—ด๊ธฐ",
47
  link="https://colab.research.google.com/drive/1_-qH8kdoU2Jj58TdaSnswHex-BFefInq?usp=sharing#scrollTo=cFJGv8BJ8oPD")
48
 
49
  gr.Markdown("### ๐ŸŽฏ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ๊ธฐ๋ฐ˜ ๊ฐ„๋‹จ ์‹คํ–‰")
50
+ gr.Markdown("โš ๏ธ ์ถ”๋ก ์—๋Š” ์ˆ˜ ์ดˆ ~ ์ตœ๋Œ€ 1๋ถ„ ์ •๋„ ์†Œ์š”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”.")
51
 
52
  with gr.Row():
53
+ case_dropdown = gr.Dropdown(choices=CASE_NAMES, label="ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์„ ํƒ", value=CASE_NAMES[0])
54
  load_btn = gr.Button("์ผ€์ด์Šค ๋ถˆ๋Ÿฌ์˜ค๊ธฐ")
55
 
56
+ # ์ผ€์ด์Šค ์ •๋ณด ํ‘œ์‹œ ์˜์—ญ
57
+ with gr.Row():
58
+ with gr.Column():
59
+ npc_id = gr.Textbox(label="NPC ID", interactive=False)
60
+ npc_loc = gr.Textbox(label="NPC Location", interactive=False)
61
+ quest_stage = gr.Textbox(label="Quest Stage", interactive=False)
62
+ relationship = gr.Textbox(label="Relationship", interactive=False)
63
+ trust = gr.Textbox(label="Trust", interactive=False)
64
+ npc_mood = gr.Textbox(label="NPC Mood", interactive=False)
65
+ player_rep = gr.Textbox(label="Player Reputation", interactive=False)
66
+ style = gr.Textbox(label="Style", interactive=False)
67
+
68
+ with gr.Column():
69
+ lore = gr.Textbox(label="Lore", lines=3, interactive=False)
70
+ desc = gr.Textbox(label="Description", lines=3, interactive=False)
71
+ player_state = gr.JSON(label="Player State", interactive=False)
72
+ context = gr.Textbox(label="Context", lines=6, interactive=False)
73
+
74
+ # Player Utterance๋Š” ๋ณ„๋„ ์ž…๋ ฅ์ฐฝ
75
+ player_input = gr.Textbox(label="Player Utterance", lines=2)
76
 
77
  run_btn = gr.Button("๐Ÿš€ Run Inference", variant="primary")
78
  npc_resp = gr.Textbox(label="NPC Response")
79
  deltas = gr.JSON(label="Deltas")
80
  flags = gr.JSON(label="Flags Probabilities")
81
 
82
+ # ์ผ€์ด์Šค ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ๋™์ž‘
83
+ def on_load_case(name):
84
+ idx = CASE_NAMES.index(name)
85
+ case = load_case(idx) # TEST_CASES ์ง์ ‘ ์ ‘๊ทผ ๋Œ€์‹  load_case ์‚ฌ์šฉ
86
+ info = format_case_info(case)
87
+ return (
88
+ info["npc_id"], info["npc_location"], info["quest_stage"],
89
+ info["relationship"], info["trust"], info["npc_mood"],
90
+ info["player_reputation"], info["style"], info["lore"],
91
+ info["description"], info["player_state"], info["context"],
92
+ info["player_utterance"]
93
+ )
94
+
95
  load_btn.click(
96
+ fn=on_load_case,
97
  inputs=[case_dropdown],
98
+ outputs=[
99
+ npc_id, npc_loc, quest_stage, relationship, trust,
100
+ npc_mood, player_rep, style, lore, desc, player_state, context,
101
+ player_input
102
+ ]
103
  )
104
 
105
+ # ์ถ”๋ก  ์‹คํ–‰
106
  run_btn.click(
107
+ fn=lambda name, utt: run_case(CASE_NAMES.index(name), utt),
108
  inputs=[case_dropdown, player_input],
109
  outputs=[npc_resp, deltas, flags]
110
  )
test_cases.json CHANGED
@@ -1,100 +1,143 @@
1
  [
2
  {
3
- "id": "case1",
4
- "npc_id": "mother_abandoned_factory",
5
- "npc_location": "map1",
6
- "description": "ํ๊ณต์žฅ์—์„œ NPC์™€ ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
7
- "player_utterance": "์•„! ๋จธ๋ฆฌ๊ฐ€!!! ๊ฐ‘์ž๊ธฐ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”...",
8
- "tags": {
9
- "quest_stage": "in_progress",
10
- "relationship": 0.35,
11
- "trust": 0.35,
12
- "npc_mood": "grief",
13
- "player_reputation": "helpful",
14
- "style": "emotional"
15
- },
16
- "lore": "์ด ๊ณต์žฅ์€ ์ˆ˜์‹ญ ๋…„ ์ „ ํ™”์žฌ๋กœ ํ์‡„๋˜์—ˆ๋‹ค.",
17
- "context": [
18
- {"role": "player", "text": "์‚ฌ์‹ค ์ด ๊ณต์žฅ์„ ๋Œ์•„๋‹ค๋‹ˆ๋ฉด์„œ..."},
19
- {"role": "npc", "text": "ํ˜น์‹œ ๊ทธ ํŒŒํ‹ฐ์— Jason๋„ ์žˆ์—ˆ๋‚˜์š”..."}
20
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  },
22
  {
23
- "id": "case2",
24
- "npc_id": "blacksmith_village_center",
25
- "npc_location": "village_square",
26
- "description": "๋งˆ์„ ๋Œ€์žฅ์žฅ์ด์™€ ๋ฌด๊ธฐ ์ˆ˜๋ฆฌ์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
27
- "player_utterance": "์ด ๊ฒ€์„ ๋‹ค์‹œ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ๊ณ ์ณ์ค„ ์ˆ˜ ์žˆ๋‚˜์š”?",
28
- "tags": {
29
- "quest_stage": "not_started",
30
- "relationship": 0.2,
31
- "trust": 0.4,
32
- "npc_mood": "neutral",
33
- "player_reputation": "unknown",
34
- "style": "direct"
35
- },
36
- "lore": "๋งˆ์„์˜ ๋Œ€์žฅ์žฅ์ด๋Š” ์„ธ๋Œ€๋ฅผ ์ด์–ด ๋ฌด๊ธฐ๋ฅผ ์ œ์ž‘ํ•ด์™”๋‹ค.",
37
- "context": [
38
- {"role": "npc", "text": "์˜ค, ์—ฌํ–‰์ž๊ตฐ. ๋ฌด์Šจ ์ผ๋กœ ์™”๋‚˜?"}
39
- ]
 
 
 
 
 
 
 
 
40
  },
41
  {
42
- "id": "case3",
43
- "npc_id": "forest_hermit",
44
- "npc_location": "deep_forest",
45
- "description": "์ˆฒ์† ์€๋‘”์ž์™€ ํฌ๊ท€ ์•ฝ์ดˆ์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
46
- "player_utterance": "ํ˜น์‹œ ์ด ๊ทผ์ฒ˜์—์„œ ํ‘ธ๋ฅธ๋น› ์•ฝ์ดˆ๋ฅผ ๋ณธ ์  ์žˆ๋‚˜์š”?",
47
- "tags": {
48
- "quest_stage": "in_progress",
49
- "relationship": 0.5,
50
- "trust": 0.6,
51
- "npc_mood": "curious",
52
- "player_reputation": "friendly",
53
- "style": "polite"
54
- },
55
- "lore": "์€๋‘”์ž๋Š” ์ˆฒ์† ๊นŠ์€ ๊ณณ์—์„œ ์•ฝ์ดˆ์™€ ๋ฒ„์„ฏ์„ ์—ฐ๊ตฌํ•œ๋‹ค.",
56
- "context": [
57
- {"role": "player", "text": "์•ˆ๋…•ํ•˜์„ธ์š”, ํ˜น์‹œ ์ž ์‹œ ์ด์•ผ๊ธฐ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์„๊นŒ์š”?"},
58
- {"role": "npc", "text": "์—ฌ๊ธฐ๊นŒ์ง€ ์˜ค๋Š” ์‚ฌ๋žŒ์€ ๋“œ๋ฌผ์ง€์š”."}
59
- ]
 
 
 
 
 
 
 
60
  },
61
  {
62
- "id": "case4",
63
- "npc_id": "captain_port_authority",
64
- "npc_location": "harbor",
65
- "description": "ํ•ญ๊ตฌ ๊ด€๋ฆฌ๊ด€๊ณผ ์ถœํ•ญ ํ—ˆ๊ฐ€์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
66
- "player_utterance": "์ด ๋ฐฐ๋ฅผ ์˜ค๋Š˜ ์•ˆ์— ์ถœํ•ญ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ—ˆ๊ฐ€๋ฅผ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.",
67
- "tags": {
68
- "quest_stage": "urgent",
69
- "relationship": 0.45,
70
- "trust": 0.3,
71
- "npc_mood": "suspicious",
72
- "player_reputation": "neutral",
73
- "style": "persuasive"
74
- },
75
- "lore": "ํ•ญ๊ตฌ ๊ด€๋ฆฌ๊ด€์€ ๋ชจ๋“  ์„ ๋ฐ•์˜ ์ถœํ•ญ์„ ์—„๊ฒฉํžˆ ๊ด€๋ฆฌํ•œ๋‹ค.",
76
- "context": [
77
- {"role": "npc", "text": "์„œ๋ฅ˜๋Š” ๋‹ค ์ค€๋น„๋๋‚˜?"}
78
- ]
 
 
 
 
 
 
 
 
79
  },
80
  {
81
- "id": "case5",
82
- "npc_id": "young_apprentice_mage",
83
- "npc_location": "mage_tower_library",
84
- "description": "๋งˆ๋ฒ•์‚ฌ ๊ฒฌ์Šต์ƒ๊ณผ ๊ณ ๋Œ€ ์ฃผ๋ฌธ์„œ์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋Š” ์žฅ๋ฉด",
85
- "player_utterance": "์ด ์ฃผ๋ฌธ์„œ์— ์ ํžŒ ๋ฌธ์žฅ์„ ํ•ด์„ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?",
86
- "tags": {
87
- "quest_stage": "research",
88
- "relationship": 0.6,
89
- "trust": 0.7,
90
- "npc_mood": "excited",
91
- "player_reputation": "scholar",
92
- "style": "inquisitive"
93
- },
94
- "lore": "๋งˆ๋ฒ•์‚ฌ ํƒ‘์˜ ๋„์„œ๊ด€์—๋Š” ์ˆ˜๋ฐฑ ๋…„ ๋œ ๊ณ ์„œ๋“ค์ด ๋ณด๊ด€๋˜์–ด ์žˆ๋‹ค.",
95
- "context": [
96
- {"role": "player", "text": "์ด ์ฑ…์€ ๊ต‰์žฅํžˆ ์˜ค๋ž˜๋œ ๊ฒƒ ๊ฐ™์•„์š”."},
97
- {"role": "npc", "text": "๋งž์•„์š”! ์ด๋Ÿฐ ๊ฑด ์ •๋ง ๋“œ๋ฌผ์ฃ ."}
98
- ]
 
 
 
 
 
 
 
99
  }
100
  ]
 
1
  [
2
  {
3
+ "instruction": "ํ”Œ๋ ˆ์ด์–ด ๋ฐœํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ NPC์˜ ๋‹ค์Œ ๋Œ€์‚ฌ์™€ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธกํ•˜์„ธ์š”.",
4
+ "input": {
5
+ "npc_id": "mother_abandoned_factory",
6
+ "npc_location": "map1",
7
+ "tags": {
8
+ "quest_stage": "in_progress",
9
+ "relationship": 0.35,
10
+ "trust": 0.35,
11
+ "npc_mood": "grief",
12
+ "player_reputation": "helpful",
13
+ "style": "emotional"
14
+ },
15
+ "lore": "",
16
+ "description": "",
17
+ "player_state": {
18
+ "items": ["forgotten_picture", "Jason's ID card"],
19
+ "actions": [],
20
+ "position": ""
21
+ },
22
+ "context": [
23
+ {
24
+ "role": "player",
25
+ "text": "์‚ฌ์‹ค ์ด ๊ณต์žฅ์„ ๋Œ์•„๋‹ค๋‹ˆ๋ฉด์„œ Jason์˜ ํ”์ ์„ ๋ฐœ๊ฒฌํ–ˆ์–ด์š” ๊ทผ๋ฐ ID ์นด๋“œ์˜ ์‚ฌ์ง„์„ ๋ณด๋‹ˆ ์ œ ์ง€๊ฐ‘์— ์žˆ๋Š” ์‚ฌ์ง„์— ์žˆ๋Š” ์–ผ๊ตด์ธ ๊ฑธ ๋ณด๊ณ  Jason๊ณผ ์ €๋Š” ์•„๋Š” ์‚ฌ์ด์˜€๋˜ ๊ฑธ ์•Œ๊ฒŒ ๋˜์—ˆ์ฃ . ํ•˜์ง€๋งŒ ์ œ ๊ธฐ์–ต์€ ๋Œ€๋ถ€๋ถ„ ์‚ฌ๋ผ์กŒ๊ณ  ๋ช‡๋‹ฌ ์ „ ํŒŒํ‹ฐ๋ฅผ ํ–ˆ๋˜ ๊ธฐ์–ต๋งŒ ํ๋ฆฌ๊ฒŒ ๋‚จ์•„์žˆ์–ด์š”"
26
+ },
27
+ {
28
+ "role": "npc",
29
+ "text": "ํ˜น์‹œ ๊ทธ ํŒŒํ‹ฐ์— Jason๋„ ์žˆ์—ˆ๋‚˜์š”?"
30
+ }
31
+ ],
32
+ "player_utterance": "์•„!! ๋จธ๋ฆฌ๊ฐ€!! ๊ฐ‘์ž๊ธฐ ๊ธฐ์–ต๋‚ฌ์–ด์š”! ์ด ์ง€๊ฐ‘์†์˜ ์‚ฌ์ง„์ด ๊ทธ๋•Œ ํŒŒํ‹ฐ์—์„œ ์ฐ์—ˆ๋˜ ์‚ฌ์ง„์ด์—์š” Jason๋„ ๊ทธ๊ณณ์— ์žˆ์—ˆ๋„ค์š”."
33
+ }
34
  },
35
  {
36
+ "instruction": "ํ”Œ๋ ˆ์ด์–ด ๋ฐœํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ NPC์˜ ๋‹ค์Œ ๋Œ€์‚ฌ์™€ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธกํ•˜์„ธ์š”.",
37
+ "input": {
38
+ "npc_id": "blacksmith_village_center",
39
+ "npc_location": "village_square",
40
+ "tags": {
41
+ "quest_stage": "not_started",
42
+ "relationship": 0.0,
43
+ "trust": 0.0,
44
+ "npc_mood": "neutral",
45
+ "player_reputation": "unknown",
46
+ "style": "direct"
47
+ },
48
+ "lore": "",
49
+ "description": "",
50
+ "player_state": {
51
+ "items": ["broken sward"],
52
+ "actions": [],
53
+ "position": ""
54
+ },
55
+ "context": [
56
+ {"role": "player", "text": "์ €๊ธฐ.."},
57
+ {"role": "npc", "text": "์˜ค, ์—ฌํ–‰์ž๊ตฐ. ๋ฌด์Šจ ์ผ๋กœ ์™”๋‚˜?"}
58
+ ],
59
+ "player_utterance": "์ด ๊ฒ€์„ ๋‹ค์‹œ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ๊ณ ์ณ์ค„ ์ˆ˜ ์žˆ๋‚˜์š”?"
60
+ }
61
  },
62
  {
63
+ "instruction": "ํ”Œ๋ ˆ์ด์–ด ๋ฐœํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ NPC์˜ ๋‹ค์Œ ๋Œ€์‚ฌ์™€ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธกํ•˜์„ธ์š”.",
64
+ "input": {
65
+ "npc_id": "forest_hermit",
66
+ "npc_location": "deep_forest",
67
+ "tags": {
68
+ "quest_stage": "not_started",
69
+ "relationship": 0.0,
70
+ "trust": 0.0,
71
+ "npc_mood": "curious",
72
+ "player_reputation": "friendly",
73
+ "style": "polite"
74
+ },
75
+ "lore": "",
76
+ "description": "",
77
+ "player_state": {
78
+ "items": [],
79
+ "actions": [],
80
+ "position": ""
81
+ },
82
+ "context": [
83
+ {"role": "player", "text": "์•ˆ๋…•ํ•˜์„ธ์š”, ํ˜น์‹œ ์ž ์‹œ ์ด์•ผ๊ธฐ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์„๊นŒ์š”?"},
84
+ {"role": "npc", "text": "์—ฌ๊ธฐ๊นŒ์ง€ ์ฐพ์•„์˜ค๋Š” ์‚ฌ๋žŒ์€ ๋“œ๋ฌผ์ง€์š”.. ๋ฌด์Šจ ์šฉ๊ฑด์ด์‹œ์ฃ ? ์—ฌํ–‰์ž์—ฌ.."}
85
+ ],
86
+ "player_utterance": "ํ˜น์‹œ ์ด ๊ทผ์ฒ˜์—์„œ ํ‘ธ๋ฅธ๋น› ์•ฝ์ดˆ๋ฅผ ๋ณด์‹  ์  ์žˆ๋‚˜์š”?"
87
+ }
88
  },
89
  {
90
+ "instruction": "ํ”Œ๋ ˆ์ด์–ด ๋ฐœํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ NPC์˜ ๋‹ค์Œ ๋Œ€์‚ฌ์™€ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธกํ•˜์„ธ์š”.",
91
+ "input": {
92
+ "npc_id": "captain_port_authority",
93
+ "npc_location": "harbor",
94
+ "tags": {
95
+ "quest_stage": "in_progress",
96
+ "relationship": 0.0,
97
+ "trust": 0.0,
98
+ "npc_mood": "suspicious",
99
+ "player_reputation": "neutral",
100
+ "style": "persuasive"
101
+ },
102
+ "lore": "",
103
+ "description": "",
104
+ "player_state": {
105
+ "items": [],
106
+ "actions": [],
107
+ "position": ""
108
+ },
109
+ "context": [
110
+ {"role": "player", "text": "์ œ ๋ฐฐ๋ฅผ ์˜ค๋Š˜ ๊ผญ ์ถœํ•ญ์‹œํ‚ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค"},
111
+ {"role": "npc", "text": "์–ด๋ ต๋‹ค. ์›์น™์ ์œผ๋กœ ๋‹น์ผ์˜ ์ถœํ•ญ์ผ์ •์€ ๊ทธ ์ „๋‚ ์— ๊ฒฐ์ •ํ•œ๋‹ค ์„œ๋ฅ˜๋Š” ๋‹ค ์ค€๋น„๋๋‚˜? ๊ทธ๋Ÿผ ๋‚ด์ผ ์ผ์ •์„ ์žก์•„์ฃผ์ง€"}
112
+ ],
113
+ "player_utterance": "๋ฌด์Šจ ์ผ์ด ์žˆ์–ด๋„ ์ด ๋ฐฐ๋ฅผ ์˜ค๋Š˜ ์•ˆ์— ๊ผญ ์ถœํ•ญ์‹œ์ผœ์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ์ œ๋ฐœ ํ—ˆ๊ฐ€๋ฅผ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค."
114
+ }
115
  },
116
  {
117
+ "instruction": "ํ”Œ๋ ˆ์ด์–ด ๋ฐœํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ NPC์˜ ๋‹ค์Œ ๋Œ€์‚ฌ์™€ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธกํ•˜์„ธ์š”.",
118
+ "input": {
119
+ "npc_id": "young_apprentice_mage",
120
+ "npc_location": "mage_tower_library",
121
+ "tags": {
122
+ "quest_stage": "in_progress",
123
+ "relationship": 0.3,
124
+ "trust": 0.35,
125
+ "npc_mood": "excited",
126
+ "player_reputation": "scholar",
127
+ "style": "inquisitive"
128
+ },
129
+ "lore": "",
130
+ "description": "",
131
+ "player_state": {
132
+ "items": ["magic spell scroll"],
133
+ "actions": [],
134
+ "position": ""
135
+ },
136
+ "context": [
137
+ {"role": "player", "text": "์ด ์ฑ…์€ ๊ต‰์žฅํžˆ ์˜ค๋ž˜๋œ ๊ฒƒ ๊ฐ™์•„์š”.."},
138
+ {"role": "npc", "text": "๋งž์•„์š”! ์ด ์ฑ…์€ ๊ณ ๋Œ€๋กœ๋ถ€ํ„ฐ ์ „ํ•ด์ง„๋‹ค๊ณ  ์•Œ๋ ค์ง„ ์ฑ…์ด์ฃ "}
139
+ ],
140
+ "player_utterance": "๊ทธ๋Ÿผ ํ˜น์‹œ ์ด ์ฃผ๋ฌธ์„œ์— ์ ํžŒ ๋ฌธ์žฅ์„ ํ•ด์„ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?"
141
+ }
142
  }
143
  ]
webtest_prompt.py CHANGED
@@ -4,12 +4,11 @@ def build_webtest_prompt(npc_id: str, npc_location: str, player_utt: str) -> str
4
  """
5
  Web Test ์ „์šฉ: ์ตœ์†Œ ์ž…๋ ฅ๊ฐ’(NPC ID, Location, Player ๋ฐœํ™”)์œผ๋กœ
6
  ๋ชจ๋ธ ํ•™์Šต ํฌ๋งท์— ๋งž๋Š” prompt ๋ฌธ์ž์—ด์„ ์ƒ์„ฑ.
7
- ์‹ค์ œ API/๊ฒŒ์ž„ ์„œ๋น„์Šค ๊ฒฝ๋กœ์—์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ.
8
  """
9
  pre = {
 
 
10
  "tags": {
11
- "npc_id": npc_id,
12
- "location": npc_location,
13
  "quest_stage": "",
14
  "relationship": "",
15
  "trust": "",
@@ -17,7 +16,11 @@ def build_webtest_prompt(npc_id: str, npc_location: str, player_utt: str) -> str
17
  "player_reputation": "",
18
  "style": ""
19
  },
20
- "player_state": {},
 
 
 
 
21
  "rag_main_docs": [],
22
  "context": [],
23
  "player_utterance": player_utt
@@ -43,7 +46,6 @@ def _assemble_prompt_for_model(pre: Dict[str, Any]) -> str:
43
  elif "DESCRIPTION:" in doc:
44
  desc_text += doc + "\n"
45
  else:
46
- # fallback: type ๊ธฐ๋ฐ˜ ๋ถ„๋ฆฌ ๊ฐ€๋Šฅ
47
  if "lore" in doc.lower():
48
  lore_text += doc + "\n"
49
  elif "description" in doc.lower():
@@ -51,8 +53,8 @@ def _assemble_prompt_for_model(pre: Dict[str, Any]) -> str:
51
 
52
  prompt = [
53
  "<SYS>",
54
- f"NPC_ID={tags.get('npc_id','')}",
55
- f"NPC_LOCATION={tags.get('location','')}",
56
  "TAGS:",
57
  f" quest_stage={tags.get('quest_stage','')}",
58
  f" relationship={tags.get('relationship','')}",
@@ -65,18 +67,14 @@ def _assemble_prompt_for_model(pre: Dict[str, Any]) -> str:
65
  f"LORE: {lore_text.strip() or '(์—†์Œ)'}",
66
  f"DESCRIPTION: {desc_text.strip() or '(์—†์Œ)'}",
67
  "</RAG>",
68
- "<PLAYER_STATE>"
 
 
 
 
 
69
  ]
70
 
71
- if ps.get("items"):
72
- prompt.append(f"items={','.join(ps['items'])}")
73
- if ps.get("actions"):
74
- prompt.append(f"actions={','.join(ps['actions'])}")
75
- if ps.get("position"):
76
- prompt.append(f"position={ps['position']}")
77
- prompt.append("</PLAYER_STATE>")
78
-
79
- prompt.append("<CTX>")
80
  for h in pre.get("context", []):
81
  prompt.append(f"{h['role']}: {h['text']}")
82
  prompt.append("</CTX>")
@@ -85,4 +83,4 @@ def _assemble_prompt_for_model(pre: Dict[str, Any]) -> str:
85
  prompt.append("<STATE>")
86
  prompt.append("<NPC>")
87
 
88
- return "\n".join(prompt)
 
4
  """
5
  Web Test ์ „์šฉ: ์ตœ์†Œ ์ž…๋ ฅ๊ฐ’(NPC ID, Location, Player ๋ฐœํ™”)์œผ๋กœ
6
  ๋ชจ๋ธ ํ•™์Šต ํฌ๋งท์— ๋งž๋Š” prompt ๋ฌธ์ž์—ด์„ ์ƒ์„ฑ.
 
7
  """
8
  pre = {
9
+ "npc_id": npc_id,
10
+ "npc_location": npc_location,
11
  "tags": {
 
 
12
  "quest_stage": "",
13
  "relationship": "",
14
  "trust": "",
 
16
  "player_reputation": "",
17
  "style": ""
18
  },
19
+ "player_state": {
20
+ "items": [],
21
+ "actions": [],
22
+ "position": ""
23
+ },
24
  "rag_main_docs": [],
25
  "context": [],
26
  "player_utterance": player_utt
 
46
  elif "DESCRIPTION:" in doc:
47
  desc_text += doc + "\n"
48
  else:
 
49
  if "lore" in doc.lower():
50
  lore_text += doc + "\n"
51
  elif "description" in doc.lower():
 
53
 
54
  prompt = [
55
  "<SYS>",
56
+ f"NPC_ID={pre.get('npc_id','')}",
57
+ f"NPC_LOCATION={pre.get('npc_location','')}",
58
  "TAGS:",
59
  f" quest_stage={tags.get('quest_stage','')}",
60
  f" relationship={tags.get('relationship','')}",
 
67
  f"LORE: {lore_text.strip() or '(์—†์Œ)'}",
68
  f"DESCRIPTION: {desc_text.strip() or '(์—†์Œ)'}",
69
  "</RAG>",
70
+ "<PLAYER_STATE>",
71
+ f"items={','.join(ps.get('items', []))}" if ps.get("items") else "items=",
72
+ f"actions={','.join(ps.get('actions', []))}" if ps.get("actions") else "actions=",
73
+ f"position={ps.get('position','')}",
74
+ "</PLAYER_STATE>",
75
+ "<CTX>"
76
  ]
77
 
 
 
 
 
 
 
 
 
 
78
  for h in pre.get("context", []):
79
  prompt.append(f"{h['role']}: {h['text']}")
80
  prompt.append("</CTX>")
 
83
  prompt.append("<STATE>")
84
  prompt.append("<NPC>")
85
 
86
+ return "\n".join(prompt)