m97j's picture
Initial commit
07f17f1
import torch, random, re
from typing import Dict, Any, List, Optional, Tuple
from fastapi import Request
from sentence_transformers import util
from models.fallback_model import generate_fallback_response
ALPHA_THR = 0.58
DELTA_CLAMP = (-1.0, 1.0)
# ----------------------------
# Utilities
# ----------------------------
def _clamp(x: float, lo: float, hi: float) -> float:
return max(lo, min(hi, x))
def _adjust_delta_with_rag(delta: Dict[str, float]) -> Dict[str, float]:
trust = _clamp(float(delta.get("trust", 0.0)), *DELTA_CLAMP)
rel = _clamp(float(delta.get("relationship", 0.0)), *DELTA_CLAMP)
return {"trust": trust, "relationship": rel}
def _embedding_similarity(embedder, text: str, examples: List[str]) -> float:
if not examples:
return 0.0
inp_emb = embedder.encode(text, convert_to_tensor=True)
ex_embs = embedder.encode(examples, convert_to_tensor=True)
cos_scores = util.cos_sim(inp_emb, ex_embs)
return float(torch.mean(cos_scores).item())
def _doc_type(doc: Dict[str, Any]) -> Optional[str]:
if "type" in doc:
return doc.get("type")
return doc.get("metadata", {}).get("type")
def _get_flag_doc(rag_docs: List[Dict[str, Any]], flag_name: str) -> Dict[str, Any]:
for doc in rag_docs:
if _doc_type(doc) == "flag_def" and doc.get("flag_name") == flag_name:
return doc
return {}
def _get_turn_doc(rag_docs: List[Dict[str, Any]], npc_id: str, quest_stage: str) -> Dict[str, Any]:
# ๋™์ผ npc_id/quest_stage์ธ ๊ฐ€์žฅ ์ตœ์‹ (turn_index ์ตœ๋Œ€) ๋ฌธ์„œ๋ฅผ ์šฐ์„  ๋ฐ˜ํ™˜
candidates = [
d for d in rag_docs
if _doc_type(d) == "dialogue_turn"
and d.get("npc_id") == npc_id
and d.get("quest_stage") == quest_stage
]
if not candidates:
return {}
return sorted(candidates, key=lambda d: d.get("turn_index", -1))[-1]
def _short_ctx_from_pre(pre_data: dict) -> str:
pairs = pre_data.get("context", []) or []
return "\n".join(f"{m.get('role', 'user')}: {m.get('text', '')}" for m in pairs)
async def fetch_response_policy_from_pre(pre_data: dict) -> str:
for doc in pre_data.get("rag_main_docs", []):
if _doc_type(doc) == "main_res_validate":
return doc.get("text", "") or doc.get("chunk", "")
return (
"์‘๋‹ต์ด NPC persona์™€ ํ˜„์žฌ ์ƒํƒœ(delta, flags)์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜์‹œ์˜ค. "
"๋ถ€์ ์ ˆํ•œ ํ‘œํ˜„์€ ์™„ํ™”ํ•˜๊ณ , ์„ธ๊ณ„๊ด€์„ ์œ ์ง€ํ•˜์‹œ์˜ค."
)
# ----------------------------
# RAG helpers
# ----------------------------
def _extract_expected_delta(rag_docs: List[Dict[str, Any]]) -> Dict[str, float]:
# trigger_def.delta_expected ์šฐ์„ , ์—†์œผ๋ฉด dialogue_turn.delta ํ‰๊ท (์„ ํƒ)
expected = {}
for doc in rag_docs:
if _doc_type(doc) == "trigger_def" and doc.get("delta_expected"):
expected.update(doc["delta_expected"])
return expected
def _collect_value_contexts(rag_docs: List[Dict[str, Any]], value: str) -> List[str]:
contexts = []
for doc in rag_docs:
# description/content/text ํ•„๋“œ์—์„œ value๊ฐ€ ์–ธ๊ธ‰๋œ ๋ฌธ์žฅ ์ˆ˜์ง‘
for key in ("content", "text", "npc", "player"):
if value and isinstance(doc.get(key), str) and value in doc[key]:
contexts.append(doc[key])
return contexts
def _weight_by_doc_type(t: str) -> float:
# ๋“ฑ์žฅ ์œ„์น˜ ๊ฐ€์ค‘์น˜(์ƒํ™ฉ์— ๋งž๊ฒŒ ์กฐ์ •)
return {
"dialogue_turn": 1.2,
"trigger_def": 1.0,
"description": 1.0,
"npc_persona": 0.9,
"lore": 0.7,
"flag_def": 0.8,
"main_res_validate": 0.8,
}.get(t, 1.0)
def _collect_positive_negative_texts(rag_docs: List[Dict[str, Any]]) -> Tuple[List[str], List[str]]:
pos, neg = [], []
for doc in rag_docs:
t = _doc_type(doc)
w = _weight_by_doc_type(t)
if isinstance(doc.get("examples_positive"), list):
pos.extend([f"[{t}] {s}" for s in doc["examples_positive"]] * int(max(1, round(w))))
if isinstance(doc.get("examples_good"), list):
pos.extend([f"[{t}] {s}" for s in doc["examples_good"]] * int(max(1, round(w))))
if isinstance(doc.get("examples_negative"), list):
neg.extend([f"[{t}] {s}" for s in doc["examples_negative"]] * int(max(1, round(w))))
if isinstance(doc.get("examples_bad"), list):
neg.extend([f"[{t}] {s}" for s in doc["examples_bad"]] * int(max(1, round(w))))
return pos, neg
# ----------------------------
# Delta ๊ฒ€์ฆ/๋ณด์ •
# ----------------------------
def _adjust_delta_with_rag_and_embedding(
delta: Dict[str, float],
rag_docs: List[Dict[str, Any]],
embedder,
player_utt: str,
npc_text: str,
flags_yes: List[str],
sim_threshold: float = 0.72,
diff_threshold: float = 0.18,
blend: float = 0.6 # expected์— ๋Œ์–ด๋‹น๊ธฐ๋Š” ๋น„์œจ
) -> Dict[str, float]:
trust = _clamp(float(delta.get("trust", 0.0)), *DELTA_CLAMP)
rel = _clamp(float(delta.get("relationship", 0.0)), *DELTA_CLAMP)
expected = _extract_expected_delta(rag_docs)
pos, neg = _collect_positive_negative_texts(rag_docs)
context_text = f"PLAYER: {player_utt}\nNPC: {npc_text}\nFLAGS: {', '.join(flags_yes) if flags_yes else 'none'}"
pos_sim = _embedding_similarity(embedder, context_text, pos) if pos else 0.0
neg_sim = _embedding_similarity(embedder, context_text, neg) if neg else 0.0
# ๋งฅ๋ฝ์ด โ€˜๊ธ์ •โ€™์— ๊ฐ€๊น๊ณ  ๊ธฐ๋Œ€์™€ ์ฐจ์ด๊ฐ€ ํฌ๋ฉด ๊ธฐ๋Œ€ ์ชฝ์œผ๋กœ ๋ณด์ •
def _pull(val, key):
if key in expected:
exp = float(expected[key])
if abs(val - exp) > diff_threshold and pos_sim - neg_sim >= (sim_threshold - 0.1):
return _clamp(blend * exp + (1 - blend) * val, *DELTA_CLAMP)
return val
trust = _pull(trust, "trust")
rel = _pull(rel, "relationship")
return {"trust": trust, "relationship": rel}
# ----------------------------
# Flag ๋ณด์ • ๋กœ์ง
# ----------------------------
def adjust_flags_with_rag_and_embedding(
flags_prob: Dict[str, float],
flags_thr: Dict[str, float],
rag_flags_score: Dict[str, float],
rag_flags_pred: Dict[str, int],
embedder,
npc_text: str,
rag_positive_examples: Dict[str, List[str]],
deltas_final: Dict[str, float], # โ† delta ๋ณด์ • ๊ฒฐ๊ณผ ๋ฐ˜์˜
rag_docs: List[Dict[str, Any]],
alpha_model: float = 0.6,
margin: float = 0.05,
sim_threshold: float = 0.8,
random_jitter: float = 0.05
) -> Dict[str, int]:
# ์ „์ฒด ํŒจํ„ด ์œ ์‚ฌ๋„
model_vector = [flags_prob.get(name, 0.0) for name in rag_flags_score.keys()]
rag_vector = [rag_flags_score.get(name, 0.0) for name in rag_flags_score.keys()]
sim = float(
embedder.encode([model_vector], convert_to_tensor=True)
@ embedder.encode([rag_vector], convert_to_tensor=True).T
)
expected = _extract_expected_delta(rag_docs)
final_preds = {}
for name in rag_flags_score.keys():
prob_model = float(flags_prob.get(name, 0.0))
thr_model = float(flags_thr.get(name, 0.5))
score_rag = float(rag_flags_score.get(name, 0.0))
_ = int(rag_flags_pred.get(name, 0))
emb_score = _embedding_similarity(embedder, npc_text, rag_positive_examples.get(name, []))
# delta ์ผ๊ด€์„ฑ ๋ณด์ •(ํ•ด๋‹น flag๊ฐ€ ์˜ˆ์ƒ๋  ๋•Œ delta์™€์˜ ๋ถˆ์ผ์น˜ ํŒจ๋„ํ‹ฐ)
delta_penalty = 0.0
if expected:
# ์‹ ํ˜ธ๊ฐ€ ์–‘์˜ ๋ณ€ํ™”์ธ๋ฐ ๋ชจ๋ธ delta๊ฐ€ ํฐ ์Œ์ˆ˜์ธ ๊ฒฝ์šฐ ๋“ฑ
if "trust" in expected and deltas_final.get("trust", 0.0) * expected["trust"] < 0:
delta_penalty += 0.08
if "relationship" in expected and deltas_final.get("relationship", 0.0) * expected["relationship"] < 0:
delta_penalty += 0.06
# ํ˜ผํ•ฉ ์ ์ˆ˜ + ์ž„๋ฒ ๋”ฉ + ๋ธํƒ€ ์ •ํ•ฉ
blended_score = (
alpha_model * prob_model
+ (1 - alpha_model) * score_rag
+ 0.2 * emb_score
- delta_penalty
)
thr_blend = alpha_model * thr_model + (1 - alpha_model) * 0.5
if abs(blended_score - thr_blend) <= margin:
adjusted_score = score_rag if sim < sim_threshold else blended_score
else:
adjusted_score = blended_score
if adjusted_score != score_rag:
adjusted_score += random.uniform(-random_jitter, random_jitter)
adjusted_score = max(0.0, min(1.0, adjusted_score))
final_preds[name] = int(adjusted_score >= thr_blend)
return final_preds
# ----------------------------
# Validators / Rewriters
# ----------------------------
async def validate_or_rewrite_response(
request: Request,
response_text: str,
description_text: str,
ctx_text: str,
player_utt: str,
deltas: Dict[str, float],
flags_yes: List[str],
flags_values: Dict[str, str], # โ† ์ถ”๊ฐ€
value_contexts: Dict[str, List[str]], # โ† ์ถ”๊ฐ€
) -> str:
flag_value_info = "\n".join(f"- {k}: {v}" for k, v in flags_values.items()) if flags_values else "none"
value_ctx_lines = []
for k, arr in value_contexts.items():
if arr:
# ๋„ˆ๋ฌด ๊ธธ์–ด์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜์—ฌ ์ƒ์œ„ 1~2๊ฐœ๋งŒ
value_ctx_lines.append(f"- {k}: {arr[0]}")
if len(arr) > 1:
value_ctx_lines.append(f" (more: {min(2, len(arr)-1)} refs)")
value_ctx_info = "\n".join(value_ctx_lines) if value_ctx_lines else "none"
prompt = (
"๋‹ค์Œ์€ ๊ฒŒ์ž„ ๋‚ด NPC ์‘๋‹ต์ž…๋‹ˆ๋‹ค.\n"
f"[RESPONSE]\n{response_text}\n[/RESPONSE]\n\n"
"์•„๋ž˜์˜ ๊ฒ€์ฆ ๊ธฐ์ค€์„ ๋งŒ์กฑํ•˜๋Š”์ง€ ํŒ๋‹จํ•˜๊ณ , ๋งŒ์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ์ค€์— ๋งž๊ฒŒ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žฌ์ž‘์„ฑํ•˜์„ธ์š”.\n"
f"[FINAL_CHECK_DESCRIPTION]\n{description_text}\n[/FINAL_CHECK_DESCRIPTION]\n\n"
"์ƒํƒœ ์ •๋ณด:\n"
f"- DELTA: trust={deltas.get('trust',0.0):.3f}, relationship={deltas.get('relationship',0.0):.3f}\n"
f"- FLAGS(YES): {', '.join(flags_yes) if flags_yes else 'none'}\n"
f"- FLAG_VALUES:\n{flag_value_info}\n"
f"- VALUE_CONTEXTS:\n{value_ctx_info}\n\n"
"๋งฅ๋ฝ:\n"
f"[CTX]\n{ctx_text}\n[/CTX]\n"
f"[PLAYER]\n{player_utt}\n[/PLAYER]\n\n"
"์š”๊ตฌ์‚ฌํ•ญ:\n"
"- ๊ธฐ์ค€์„ ๋งŒ์กฑํ•˜๋ฉด ์‘๋‹ต์„ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•˜๋˜ ๋ฏผ๊ฐํ•œ ํ‘œํ˜„์€ ์™„ํ™”ํ•˜์„ธ์š”.\n"
"- ๊ธฐ์ค€์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ์ค€์„ ์ถฉ์กฑํ•˜๋„๋ก ์‘๋‹ต์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žฌ์ž‘์„ฑํ•˜์„ธ์š”.\n"
"- ์ถœ๋ ฅ์€ NPC์˜ ์ตœ์ข… ๋Œ€์‚ฌ๋งŒ ํ•œ ์ค„๋กœ ์ œ๊ณตํ•˜์„ธ์š”."
)
fb_raw = await generate_fallback_response(request, prompt)
return fb_raw.strip()
# ----------------------------
# Main path postprocess
# ----------------------------
async def postprocess_main(
request: Request,
pre_data: dict,
model_payload: dict
) -> dict:
embedder = request.app.state.embedder
npc_id = pre_data["npc_id"]
quest_stage = pre_data["game_state"].get("quest_stage", "default")
location = pre_data["game_state"].get("location", "unknown")
rag_docs = pre_data.get("rag_main_docs", [])
npc_text_in = (model_payload.get("npc_output_text") or "").strip()
player_utt = pre_data.get("player_utterance", "")
# 1) Delta ๊ฒ€์ฆ/๋ณด์ •(์˜๋ฏธ ๊ธฐ๋ฐ˜ + ๊ธฐ๋Œ€๊ฐ’)
deltas_in = model_payload.get("deltas", {}) or {}
deltas_adj = _adjust_delta_with_rag_and_embedding(
delta=deltas_in,
rag_docs=rag_docs,
embedder=embedder,
player_utt=player_utt,
npc_text=npc_text_in,
flags_yes=[],
)
# 2) Flag ๋ณด์ •(์ž„๋ฒ ๋”ฉ/๊ธฐ๋Œ€ ๋ธํƒ€ ๋ฐ˜์˜)
flags_binary = adjust_flags_with_rag_and_embedding(
flags_prob=model_payload.get("flags_prob", {}),
flags_thr=model_payload.get("flags_thr", {}),
rag_flags_score={doc["flag_name"]: doc.get("score_rag", 0.0) for doc in rag_docs if _doc_type(doc) == "flag_def"},
rag_flags_pred={doc["flag_name"]: doc.get("pred_rag", 0) for doc in rag_docs if _doc_type(doc) == "flag_def"},
embedder=embedder,
npc_text=npc_text_in,
rag_positive_examples={doc["flag_name"]: doc.get("examples_positive", []) for doc in rag_docs if _doc_type(doc) == "flag_def"},
deltas_final=deltas_adj,
rag_docs=rag_docs,
)
# ์ƒ์„ธ ์ •๋ณด ๊ธฐ๋ก + yes ๋ฆฌ์ŠคํŠธ
flags_detail = {}
flags_yes_list: List[str] = []
for name, pred in flags_binary.items():
flag_doc = _get_flag_doc(rag_docs, name)
score_model = float(model_payload.get("flags_prob", {}).get(name, 0.0))
thr_model = float(model_payload.get("flags_thr", {}).get(name, 0.5))
rag_thr = float(flag_doc.get("threshold", 0.5)) if flag_doc else 0.5
examples_pos = flag_doc.get("examples_positive", []) if flag_doc else []
emb_score = _embedding_similarity(embedder, npc_text_in, examples_pos) if examples_pos else 0.0
thr_blend = ALPHA_THR * thr_model + (1.0 - ALPHA_THR) * rag_thr
flags_detail[name] = {
"score_model": score_model,
"thr_model": thr_model,
"thr_rag": rag_thr,
"thr_blend": thr_blend,
"emb_score": emb_score,
"pred": pred
}
if pred == 1:
flags_yes_list.append(name)
# 3) Flag value ์ถ”์ถœ(๋Œ€ํ™” ํ„ด ์‹ค์ œ ๊ฐ’ ์šฐ์„ ) + value ๋งฅ๋ฝ ์ˆ˜์ง‘
flags_values: Dict[str, str] = {}
value_contexts: Dict[str, List[str]] = {}
turn_doc = _get_turn_doc(rag_docs, npc_id, quest_stage)
def _turn_flag_value(doc: Dict[str, Any], fname: str) -> Optional[str]:
if not doc:
return None
# ๋ฆฌ์ŠคํŠธ ๊ตฌ์กฐ ์ „์ œ
flags = doc.get("flags")
if isinstance(flags, list):
for f in flags:
if f.get("flag_name") == fname:
return f.get("flag_value")
# ํ•˜์œ„ํ˜ธํ™˜: dict์ธ ๊ฒฝ์šฐ yes(1)/no(0)๋งŒ ์ œ๊ณต๋จ
if isinstance(flags, dict) and fname in flags:
return "yes" if flags.get(fname) else "no"
return None
for name in flags_yes_list:
if name in ["give_item", "npc_action", "change_player_state", "change_game_state"]:
val = _turn_flag_value(turn_doc, name)
if val:
flags_values[name] = val
value_contexts[name] = _collect_value_contexts(rag_docs, val)
# 3-1) value ์ผ์น˜์„ฑ ์ž„๋ฒ ๋”ฉ ๊ฒ€์ฆ(์‘๋‹ต๊ณผ value ๋งฅ๋ฝ์˜ ์œ ์‚ฌ๋„)
# ์œ ์‚ฌ๋„๊ฐ€ ๋‚ฎ์œผ๋ฉด response ์žฌ์ž‘์„ฑ์—์„œ ๋ณด์ •๋˜๋„๋ก ํžŒํŠธ ์ œ๊ณต
# (์—ฌ๊ธฐ์„œ ๋ฐ”๋กœ ๊ฐ’์„ ๋ฐ”๊พธ์ง€๋Š” ์•Š๊ณ , ๊ฒ€์ฆ ํ”„๋กฌํ”„ํŠธ์— context๋กœ ์ „๋‹ฌ)
# ํ•„์š” ์‹œ ํ•˜๋“œ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
# 4) ์‘๋‹ต ๊ฒ€์ฆ/์žฌ์ž‘์„ฑ(์ตœ์ข… delta/flags/value ๊ธฐ์ค€)
desc_text = await fetch_response_policy_from_pre(pre_data)
ctx_text = _short_ctx_from_pre(pre_data)
npc_text_out = await validate_or_rewrite_response(
request=request,
response_text=npc_text_in,
description_text=desc_text,
ctx_text=ctx_text,
player_utt=player_utt,
deltas=deltas_adj,
flags_yes=flags_yes_list,
flags_values=flags_values,
value_contexts=value_contexts,
)
return {
"session_id": model_payload.get("session_id"),
"npc_output_text": npc_text_out,
"deltas": deltas_adj, # ๋ณด์ • ์™„๋ฃŒ ๋ธํƒ€
"flags": {k: 1 if k in flags_yes_list else 0 for k in flags_binary.keys()},
"valid": True,
"meta": {
"npc_id": npc_id,
"quest_stage": quest_stage,
"location": location,
"additional_trigger": pre_data.get("additional_trigger", False),
"trigger_meta": pre_data.get("trigger_meta", {}),
"flags_detail": flags_detail,
"flags_values": flags_values,
"value_contexts": value_contexts,
}
}
# ----------------------------
# Fallback path postprocess
# ----------------------------
async def fallback_final_check(
request: Request,
fb_response: str,
player_utt: str,
npc_config: dict,
action_delta: dict
) -> str:
"""
fallback ์‘๋‹ต์˜ ์ตœ์ข… ๋ณด์ •:
1) npc_action / npc_emotion / delta์™€ ์˜๋ฏธ์  ์ผ์น˜
2) ์„ธ๊ณ„๊ด€ ๋ฐ ์•ˆ์ „์„ฑ(ํ‘œํ˜„ ์™„ํ™”)
"""
checks = []
npc_action = action_delta.get("npc_action")
npc_emotion = action_delta.get("npc_emotion")
delta = action_delta.get("delta", {}) or {}
if npc_action:
checks.append(f"NPC๋Š” '{npc_action}' ํ–‰๋™์„ ๋ฐ˜์˜ํ•ด์•ผ ํ•จ")
if npc_emotion:
checks.append(f"NPC๋Š” '{npc_emotion}' ๊ฐ์ •์„ ํ‘œํ˜„ํ•ด์•ผ ํ•จ")
for name, value in delta.items():
direction = "๊ธ์ •์ " if value > 0.5 else "๋ถ€์ •์ " if value < -0.5 else "์ค‘๋ฆฝ์ "
checks.append(f"{name} ๊ฐ’({value:.2f})์€ {direction} ๋ฐฉํ–ฅ์ด๋ฉฐ, ์ด์— ๋งž๋Š” ๋ฐ˜์‘์ด์–ด์•ผ ํ•จ")
checks.append("์‘๋‹ต์ด NPC persona์™€ ์„ธ๊ณ„๊ด€์— ๋ถ€ํ•ฉํ•ด์•ผ ํ•จ")
checks.append("๋ฏผ๊ฐํ•œ ํ‘œํ˜„์€ ์™„ํ™”ํ•ด์•ผ ํ•จ")
delta_desc = ", ".join([f"{k}={v:.2f}(-1.0~1.0)" for k, v in delta.items()]) or "์—†์Œ"
prompt = (
"๋‹ค์Œ์€ ๊ฒŒ์ž„ ๋‚ด NPC์˜ ์‘๋‹ต์ž…๋‹ˆ๋‹ค.\n"
f"[RESPONSE]\n{fb_response}\n[/RESPONSE]\n\n"
"๊ฒ€์ฆ ๊ธฐ์ค€:\n" + "\n".join(f"- {c}" for c in checks) + "\n\n"
f"ํ”Œ๋ ˆ์ด์–ด ๋ฐœํ™”: {player_utt}\n"
"์š”๊ตฌ์‚ฌํ•ญ:\n"
"- ๊ธฐ์ค€์„ ๋งŒ์กฑํ•˜๋ฉด ์‘๋‹ต์„ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•˜์„ธ์š”.\n"
"- ๊ธฐ์ค€์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ์ค€์— ๋ถ€ํ•ฉํ•˜๋„๋ก ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ˆ˜์ •ํ•˜์„ธ์š”.\n"
"- ์ถœ๋ ฅ์€ NPC์˜ ์ตœ์ข… ๋Œ€์‚ฌ๋งŒ ํ•œ ์ค„๋กœ ์ œ๊ณตํ•˜์„ธ์š”.\n\n"
"NPC ์ƒํƒœ ์š”์•ฝ:\n"
f"- ACTION: {npc_action or '์—†์Œ'}\n"
f"- EMOTION: {npc_emotion or '์—†์Œ'}\n"
f"- DELTA: {delta_desc}\n"
)
fb_checked = await generate_fallback_response(request, prompt)
return fb_checked.strip()
async def postprocess_fallback(
request: Request,
pre_data: dict,
fb_raw_text: str
) -> dict:
"""
Fallback ๋ชจ๋ธ ์ถœ๋ ฅ์— ๋Œ€ํ•ด:
- ํŠน์ˆ˜ fallback์ด๋ฉด action/delta ๋ฐ˜์˜ํ•˜์—ฌ ์ตœ์ข… ๋ณด์ •
- deltas๋Š” pre_data.trigger_meta.delta๋ฅผ ์ด๋ฒˆ ํ„ด ๋ณ€ํ™”๋Ÿ‰์œผ๋กœ ์‚ฌ์šฉ
- flags๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„์–ด์žˆ์Œ(ํ•„์š” ์‹œ pre์—์„œ ํ™•์ • ๊ฐ€๋Šฅ)
"""
npc_id = pre_data["npc_id"]
quest_stage = pre_data["game_state"].get("quest_stage", "default")
location = pre_data["game_state"].get("location", "unknown")
trigger_meta = pre_data.get("trigger_meta", {}) or {}
action_delta = {
"npc_action": trigger_meta.get("npc_action"),
"npc_emotion": trigger_meta.get("npc_emotion"),
"delta": trigger_meta.get("delta", {}) or {}
}
# ์ด๋ฒˆ ํ„ด ๋ณ€ํ™”๋Ÿ‰(ํŠน์ˆ˜ fallback์˜ ๊ฒฝ์šฐ trigger_meta.delta๊ฐ€ ๊ธฐ์ค€)
deltas_adj = _adjust_delta_with_rag(action_delta.get("delta", {}))
# ํŠน์ˆ˜ fallback ๋ณด์ •
player_utt = pre_data.get("player_utterance", "")
npc_config = pre_data.get("tags", {}) or {}
if pre_data.get("additional_trigger", False):
fb_checked = await fallback_final_check(
request=request,
fb_response=fb_raw_text,
player_utt=player_utt,
npc_config=npc_config,
action_delta={"npc_action": action_delta.get("npc_action"),
"npc_emotion": action_delta.get("npc_emotion"),
"delta": deltas_adj}
)
else:
fb_checked = fb_raw_text.strip()
return {
"session_id": pre_data.get("session_id"),
"npc_output_text": fb_checked,
"deltas": deltas_adj, # ์ด๋ฒˆ ํ„ด ๋ณ€ํ™”๋Ÿ‰
"flags": {}, # ๊ธฐ๋ณธ ๋น„์–ด ์žˆ์Œ(ํ•„์š” ์‹œ pre ๋‹จ๊ณ„์—์„œ ํ™•์ • ๊ฐ€๋Šฅ)
"valid": False,
"meta": {
"npc_id": npc_id,
"quest_stage": quest_stage,
"location": location,
"additional_trigger": pre_data.get("additional_trigger", False),
"trigger_meta": trigger_meta
}
}