m97j's picture
Initial commit
320c4f1
raw
history blame
7.65 kB
import asyncio
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from contextlib import asynccontextmanager
from manager.dialogue_manager import handle_dialogue
from rag.rag_manager import chroma_initialized, load_game_docs_from_disk, add_docs, set_embedder
from models.model_loader import load_fallback_model, load_embedder
from schemas import AskReq, AskRes
from config import (
FALLBACK_MODEL_NAME, FALLBACK_MODEL_DIR,
EMBEDDER_MODEL_NAME, EMBEDDER_MODEL_DIR,
HF_TOKEN, BASE_DIR
)
model_ready = False # ๋ชจ๋ธ ๋กœ๋”ฉ ์ƒํƒœ ํ”Œ๋ž˜๊ทธ
async def load_models(app: FastAPI):
global model_ready
print("๐Ÿš€ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹œ์ž‘...")
fb_tokenizer, fb_model = load_fallback_model(FALLBACK_MODEL_NAME, FALLBACK_MODEL_DIR, token=HF_TOKEN)
app.state.fallback_tokenizer = fb_tokenizer
app.state.fallback_model = fb_model
embedder = load_embedder(EMBEDDER_MODEL_NAME, EMBEDDER_MODEL_DIR, token=HF_TOKEN)
app.state.embedder = embedder
set_embedder(embedder)
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 ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ")
model_ready = True
print("โœ… ๋ชจ๋“  ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ")
@asynccontextmanager
async def lifespan(app: FastAPI):
asyncio.create_task(load_models(app)) # ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋กœ๋”ฉ
yield
print("๐Ÿ›‘ ์„œ๋ฒ„ ์ข…๋ฃŒ ์ค‘...")
app = FastAPI(title="ai-server", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["https://fpsgame-rrbc.onrender.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/", include_in_schema=False)
async def root():
return HTMLResponse("""
<h1>Persona Chat Engine API</h1>
<p>์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.</p>
<p><a href="/docs">Swagger UI๋กœ ์ด๋™</a></p>
""")
@app.get("/status")
async def status():
return {"ready": model_ready}
@app.post("/wake")
async def wake(request: Request):
session_id = (await request.json()).get("session_id", "unknown")
print(f"๐Ÿ“ก Wake signal received for session: {session_id}")
if not model_ready:
asyncio.create_task(load_models(app))
return {"status": "awake", "model_ready": model_ready}
@app.post("/ask", response_model=AskRes)
async def ask(request: Request, req: AskReq):
if not model_ready:
raise HTTPException(status_code=503, detail="Model not ready")
if not req.context:
raise HTTPException(status_code=400, detail="missing context")
if not (req.session_id and req.npc_id and req.user_input):
raise HTTPException(status_code=400, detail="missing fields")
context = req.context
npc_config_dict = context.npc_config.model_dump() if context.npc_config else None
return await handle_dialogue(
request=request,
session_id=req.session_id,
npc_id=req.npc_id,
user_input=req.user_input,
context=context.model_dump(),
npc_config=npc_config_dict
)
'''
์ตœ์ข… 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": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."}
]
}
}
'''