Spaces:
Sleeping
Sleeping
| 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) | |
| ) | |
| 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 | |
| } | |
| } | |