Spaces:
Sleeping
Sleeping
test case update
Browse files- config.py +1 -1
- flags.json +1 -1
- inference.py +5 -1
- model_loader.py +15 -29
- modules/case_loader.py +8 -4
- modules/ui_components.py +76 -9
- test_cases.json +131 -88
- webtest_prompt.py +16 -18
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",
|
| 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 |
-
"
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
| 25 |
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 26 |
-
|
|
|
|
| 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 |
-
|
| 39 |
-
|
| 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,
|
|
|
|
| 57 |
token=HF_TOKEN
|
| 58 |
)
|
| 59 |
|
| 60 |
-
#
|
| 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 |
-
#
|
| 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 |
-
#
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def load_case(idx):
|
| 15 |
-
|
| 16 |
-
return
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 23 |
load_btn = gr.Button("์ผ์ด์ค ๋ถ๋ฌ์ค๊ธฐ")
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 35 |
inputs=[case_dropdown],
|
| 36 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
)
|
| 38 |
|
|
|
|
| 39 |
run_btn.click(
|
| 40 |
-
fn=lambda name, utt: run_case(
|
| 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 |
-
"
|
| 4 |
-
"
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
},
|
| 22 |
{
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
},
|
| 41 |
{
|
| 42 |
-
"
|
| 43 |
-
"
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
},
|
| 61 |
{
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
},
|
| 80 |
{
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 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={
|
| 55 |
-
f"NPC_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)
|