m97j's picture
Initial commit
5fc69e4
raw
history blame
7.64 kB
import os
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from manager.dialogue_manager import handle_dialogue
from rag.rag_generator import chroma_initialized, load_game_docs_from_disk, add_docs
from contextlib import asynccontextmanager
from models.model_loader import load_emotion_model, load_fallback_model, load_embedder
from schemas import AskReq, AskRes
from pathlib import Path
from rag.rag_generator import set_embedder
# ๋ชจ๋ธ ์ด๋ฆ„
EMOTION_MODEL_NAME = "tae898/emoberta-base-ko"
FALLBACK_MODEL_NAME = "skt/ko-gpt-trinity-1.2B-v0.5"
EMBEDDER_MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
# ์ ˆ๋Œ€ ๊ฒฝ๋กœ ๊ธฐ์ค€ ๋ชจ๋ธ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
BASE_DIR = Path(__file__).resolve().parent # ai_server/
EMOTION_MODEL_DIR = Path(os.getenv("EMOTION_MODEL_DIR", BASE_DIR / "models" / "emotion-classification-model"))
FALLBACK_MODEL_DIR = Path(os.getenv("FALLBACK_MODEL_DIR", BASE_DIR / "models" / "fallback-npc-model"))
EMBEDDER_MODEL_DIR = Path(os.getenv("EMBEDDER_MODEL_DIR", BASE_DIR / "models" / "sentence-embedder"))
@asynccontextmanager
async def lifespan(app: FastAPI):
# Emotion
emo_tokenizer, emo_model = load_emotion_model(EMOTION_MODEL_NAME, EMOTION_MODEL_DIR)
app.state.emotion_tokenizer = emo_tokenizer
app.state.emotion_model = emo_model
# Fallback
fb_tokenizer, fb_model = load_fallback_model(FALLBACK_MODEL_NAME, FALLBACK_MODEL_DIR)
app.state.fallback_tokenizer = fb_tokenizer
app.state.fallback_model = fb_model
# Embedder
embedder = load_embedder(EMBEDDER_MODEL_NAME, EMBEDDER_MODEL_DIR)
app.state.embedder = embedder
set_embedder(embedder) # ์ถ”๊ฐ€
print("โœ… ๋ชจ๋“  ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ")
# RAG ์ดˆ๊ธฐํ™”
docs_path = BASE_DIR / "rag" / "docs"
if not chroma_initialized():
docs = load_game_docs_from_disk(str(docs_path))
add_docs(docs)
print(f"โœ… RAG ๋ฌธ์„œ {len(docs)}๊ฐœ ์‚ฝ์ž… ์™„๋ฃŒ")
else:
print("๐Ÿ”„ RAG DB ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ")
yield # ์•ฑ ์‹คํ–‰
print("๐Ÿ›‘ ์„œ๋ฒ„ ์ข…๋ฃŒ ์ค‘...")
app = FastAPI(title="ai-server", lifespan=lifespan)
# CORS ์„ค์ • (game-server์—์„œ ์š”์ฒญ ๊ฐ€๋Šฅํ•˜๋„๋ก)
app.add_middleware(
CORSMiddleware,
allow_origins=["https://fpsgame-rrbc.onrender.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.post("/ask", response_model=AskRes)
async def ask(request: Request, req: AskReq):
context = req.context or {}
npc_config = context.npc_config
if not (req.session_id and req.npc_id and req.user_input and npc_config):
raise HTTPException(status_code=400, detail="missing fields")
result = await handle_dialogue(
request=request,
session_id=req.session_id,
npc_id=req.npc_id,
user_input=req.user_input,
context=context.dict(),
npc_config=npc_config.dict()
)
return result
@app.post("/wake")
async def wake(request: Request):
body = await request.json()
session_id = body.get("session_id", "unknown")
print(f"๐Ÿ“ก Wake signal received for session: {session_id}")
return {"status": "awake", "session_id": session_id}
'''
์ตœ์ข… gameโ€‘server โ†’ aiโ€‘server ์š”์ฒญ ์˜ˆ์‹œ
{
"session_id": "abc123",
"npc_id": "mother_abandoned_factory",
"user_input": "์•„! ๋จธ๋ฆฌ๊ฐ€โ€ฆ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”.",
/* game-server์—์„œ ํ•„ํ„ฐ๋งํ•œ ํ•„์ˆ˜/์„ ํƒ require ์š”์†Œ๋งŒ ํฌํ•จ */
"context": {
"require": {
"items": ["photo_forgotten_party"], // ํ•„์ˆ˜/์„ ํƒ ๊ตฌ๋ถ„์€ npc_config.json์—์„œ
"actions": ["visited_factory"],
"game_state": ["box_opened"], // ํ•„์š” ์‹œ
"delta": { "trust": 0.35, "relationship": 0.1 }
},
"player_state": {
"level": 7,
"reputation": "helpful",
"location": "map1"
/* ์ „์ฒด ์ธ๋ฒคํ† ๋ฆฌ/ํ–‰๋™ ๋กœ๊ทธ๋Š” ํ•„์š” ์‹œ ๋ณ„๋„ ์ „๋‹ฌ */
},
"game_state": {
"current_quest": "search_jason",
"quest_stage": "in_progress",
"location": "map1",
"time_of_day": "evening"
},
"npc_state": {
"id": "mother_abandoned_factory",
"name": "์‹ค๋น„์•„",
"persona_name": "Silvia",
"dialogue_style": "emotional",
"relationship": 0.35,
"npc_mood": "grief"
},
"dialogue_history": [
{
"player": "ํ˜น์‹œ ์ด ๊ณต์žฅ์—์„œ ๋ณธ ๊ฑธ ๋งํ•ด์ค˜์š”.",
"npc": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."
}
]
}
}
'''
'''
{
"session_id": "abc123",
"npc_id": "mother_abandoned_factory",
"user_input": "์•„! ๋จธ๋ฆฌ๊ฐ€โ€ฆ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”.",
"precheck_passed": true,
"context": {
"player_status": {
"level": 7,
"reputation": "helpful",
"location": "map1",
"trigger_items": ["photo_forgotten_party"], // game-server์—์„œ ์กฐ๊ฑด ํ•„ํ„ฐ ํ›„ key๋กœ ๋ณ€ํ™˜
"trigger_actions": ["visited_factory"] // ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ key ๋ฌธ์ž์—ด
/* ์›๋ณธ ์ „์ฒด inventory/actions ๋ฐฐ์—ด์€ ์„œ๋น„์Šค ํ•„์š” ์‹œ ๋ณ„๋„ ์ „๋‹ฌ ๊ฐ€๋Šฅ
ํ•˜์ง€๋งŒ ai-server ์กฐ๊ฑด ํŒ์ •์—๋Š” trigger_*๋งŒ ์‚ฌ์šฉ */
},
"game_state": {
"current_quest": "search_jason",
"quest_stage": "in_progress",
"location": "map1",
"time_of_day": "evening"
},
"npc_config": {
"id": "mother_abandoned_factory",
"name": "์‹ค๋น„์•„",
"persona_name": "Silvia",
"dialogue_style": "emotional",
"relationship": 0.35,
"npc_mood": "grief",
"trigger_values": {
"in_progress": ["๊ธฐ์–ต", "์‚ฌ์ง„", "ํŒŒํ‹ฐ"]
},
"trigger_definitions": {
"in_progress": {
"required_text": ["๊ธฐ์–ต", "์‚ฌ์ง„"],
"required_items": ["photo_forgotten_party"], // trigger_items์™€ ๋งค์นญ
"required_actions": ["visited_factory"], // trigger_actions์™€ ๋งค์นญ
"emotion_threshold": { "sad": 0.2 },
"fallback_style": {
"style": "guarded",
"npc_emotion": "suspicious"
}
}
}
},
"dialogue_history": [
{
"player": "ํ˜น์‹œ ์ด ๊ณต์žฅ์—์„œ ๋ณธ ๊ฑธ ๋งํ•ด์ค˜์š”.",
"npc": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."
}
]
}
}
------------------------------------------------------------------------------------------------------
์ด์ „ game-server ์š”์ฒญ ๊ตฌ์กฐ ์˜ˆ์‹œ:
{
"session_id": "abc123",
"npc_id": "mother_abandoned_factory",
"user_input": "์•„! ๋จธ๋ฆฌ๊ฐ€โ€ฆ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”.",
"context": {
"player_status": {
"level": 7,
"reputation": "helpful",
"location": "map1",
"items": ["photo_forgotten_party"],
"actions": ["visited_factory", "talked_to_guard"]
},
"game_state": {
"current_quest": "search_jason",
"quest_stage": "in_progress",
"location": "map1",
"time_of_day": "evening"
},
"npc_config": {
"id": "mother_abandoned_factory",
"name": "์‹ค๋น„์•„",
"persona_name": "Silvia",
"dialogue_style": "emotional",
"relationship": 0.35,
"npc_mood": "grief",
"trigger_values": {
"in_progress": ["๊ธฐ์–ต", "์‚ฌ์ง„", "ํŒŒํ‹ฐ"]
},
"trigger_definitions": {
"in_progress": {
"required_text": ["๊ธฐ์–ต", "์‚ฌ์ง„"],
"emotion_threshold": {"sad": 0.2},
"fallback_style": {"style": "guarded", "npc_emotion": "suspicious"}
}
}
},
"dialogue_history": [
{"player": "ํ˜น์‹œ ์ด ๊ณต์žฅ์—์„œ ๋ณธ ๊ฑธ ๋งํ•ด์ค˜์š”.", "npc": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."}
]
}
}
'''