j-js commited on
Commit
3b94624
·
verified ·
1 Parent(s): 082c4a7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +294 -5
app.py CHANGED
@@ -1,7 +1,296 @@
1
- from fastapi import FastAPI
2
 
3
- app = FastAPI()
4
 
5
- @app.get("/")
6
- def root():
7
- return {"ok": True}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
 
3
+ from typing import Any, Dict, List, Optional
4
 
5
+ from fastapi import FastAPI, Request
6
+
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+
9
+ from fastapi.responses import HTMLResponse, JSONResponse
10
+
11
+ from context_parser import (
12
+ detect_intent,
13
+ extract_game_context_fields,
14
+ intent_to_help_mode,
15
+ split_unity_message,
16
+ )
17
+
18
+ from conversation_logic import ConversationEngine
19
+
20
+ from generator_engine import GeneratorEngine
21
+
22
+ from logging_store import LoggingStore
23
+
24
+ from models import ChatRequest, EventLogRequest, SessionFinalizeRequest, SessionStartRequest
25
+
26
+ from question_support_loader import question_support_bank
27
+
28
+ from retrieval_engine import RetrievalEngine
29
+
30
+ from ui_html import HOME_HTML
31
+
32
+ from utils import clamp01, get_user_text
33
+
34
+ from pathlib import Path
35
+ import subprocess
36
+ import sys
37
+
38
+ retriever = RetrievalEngine()
39
+ generator = GeneratorEngine()
40
+ engine = ConversationEngine(retriever=retriever, generator=generator)
41
+ store = LoggingStore()
42
+ question_support_bank.load()
43
+
44
+ app = FastAPI(title="GameAI")
45
+ app.add_middleware(
46
+ CORSMiddleware,
47
+ allow_origins=["*"],
48
+ allow_credentials=True,
49
+ allow_methods=["*"],
50
+ allow_headers=["*"],
51
+ )
52
+
53
+ # Lightweight in-memory chat session state cache.
54
+ CHAT_SESSION_STATE: Dict[str, Dict[str, Any]] = {}
55
+
56
+
57
+ def _as_dict(value: Any) -> Dict[str, Any]:
58
+ return value if isinstance(value, dict) else {}
59
+
60
+
61
+ def _extract_session_id(req_data: Dict[str, Any], req: ChatRequest) -> Optional[str]:
62
+ candidates = [
63
+ req_data.get("session_id"),
64
+ getattr(req, "session_id", None),
65
+ req_data.get("conversation_id"),
66
+ getattr(req, "conversation_id", None),
67
+ ]
68
+ for candidate in candidates:
69
+ if isinstance(candidate, str) and candidate.strip():
70
+ return candidate.strip()
71
+ return None
72
+
73
+
74
+ def _extract_chat_history(req_data: Dict[str, Any], req: ChatRequest) -> List[Dict[str, Any]]:
75
+ candidates = [
76
+ req_data.get("chat_history"),
77
+ req_data.get("history"),
78
+ getattr(req, "chat_history", None),
79
+ getattr(req, "history", None),
80
+ ]
81
+ for candidate in candidates:
82
+ if isinstance(candidate, list):
83
+ return [item for item in candidate if isinstance(item, dict)]
84
+ return []
85
+
86
+
87
+ def _recover_session_state_from_history(chat_history: List[Dict[str, Any]]) -> Dict[str, Any]:
88
+ for item in reversed(chat_history):
89
+ if not isinstance(item, dict):
90
+ continue
91
+ direct_state = item.get("session_state")
92
+ if isinstance(direct_state, dict) and direct_state:
93
+ return dict(direct_state)
94
+ meta = item.get("meta")
95
+ if isinstance(meta, dict):
96
+ meta_state = meta.get("session_state")
97
+ if isinstance(meta_state, dict) and meta_state:
98
+ return dict(meta_state)
99
+ return {}
100
+
101
+
102
+ def _merge_session_state(
103
+ cached_state: Dict[str, Any],
104
+ incoming_state: Dict[str, Any],
105
+ history_state: Dict[str, Any],
106
+ parsed_question_text: str,
107
+ parsed_hint_stage: int,
108
+ parsed_help_mode: str,
109
+ parsed_intent: str,
110
+ parsed_topic: str,
111
+ parsed_category: str,
112
+ parsed_user_last_input_type: str,
113
+ parsed_built_on_previous_turn: bool,
114
+ ) -> Dict[str, Any]:
115
+ state: Dict[str, Any] = {}
116
+ if cached_state:
117
+ state.update(cached_state)
118
+ if history_state:
119
+ state.update(history_state)
120
+ if incoming_state:
121
+ state.update(incoming_state)
122
+
123
+ if parsed_question_text:
124
+ state["question_text"] = parsed_question_text
125
+ if parsed_hint_stage:
126
+ state["hint_stage"] = parsed_hint_stage
127
+ if parsed_user_last_input_type:
128
+ state["user_last_input_type"] = parsed_user_last_input_type
129
+ if parsed_built_on_previous_turn:
130
+ state["built_on_previous_turn"] = parsed_built_on_previous_turn
131
+ if parsed_help_mode:
132
+ state["help_mode"] = parsed_help_mode
133
+ if parsed_intent:
134
+ state["intent"] = parsed_intent
135
+ if parsed_topic:
136
+ state["topic"] = parsed_topic
137
+ if parsed_category:
138
+ state["category"] = parsed_category
139
+ return state
140
+
141
+
142
+ @app.get("/health")
143
+ def health() -> Dict[str, Any]:
144
+ return {
145
+ "ok": True,
146
+ "app": "GameAI",
147
+ "generator_available": generator.available(),
148
+ "question_support_loaded": True,
149
+ }
150
+
151
+
152
+ @app.get("/", response_class=HTMLResponse)
153
+ def home() -> str:
154
+ return HOME_HTML
155
+
156
+
157
+ @app.post("/chat")
158
+ async def chat(request: Request) -> JSONResponse:
159
+ try:
160
+ try:
161
+ raw_body: Any = await request.json()
162
+ except Exception:
163
+ try:
164
+ raw_body = (await request.body()).decode("utf-8", errors="ignore")
165
+ except Exception:
166
+ raw_body = None
167
+
168
+ req_data: Dict[str, Any] = raw_body if isinstance(raw_body, dict) else {}
169
+ req = ChatRequest(**req_data) if isinstance(req_data, dict) else ChatRequest()
170
+
171
+ full_text = get_user_text(req, raw_body)
172
+ parsed = split_unity_message(full_text)
173
+
174
+ hidden_context = parsed.get("hidden_context", "")
175
+ actual_user_message = (parsed.get("user_text", "") or "").strip()
176
+ parsed_question_text = (parsed.get("question_text", "") or "").strip()
177
+ parsed_hint_stage = int(parsed.get("hint_stage", 0) or 0)
178
+ parsed_help_mode = (parsed.get("help_mode", "") or "").strip()
179
+ parsed_intent = (parsed.get("intent", "") or "").strip()
180
+ parsed_topic = (parsed.get("topic", "") or "").strip()
181
+ parsed_category = (parsed.get("category", "") or "").strip()
182
+ parsed_user_last_input_type = (parsed.get("user_last_input_type", "") or "").strip()
183
+ parsed_built_on_previous_turn = bool(parsed.get("built_on_previous_turn", False))
184
+
185
+ game_fields = extract_game_context_fields(hidden_context)
186
+ chat_history = _extract_chat_history(req_data, req)
187
+ incoming_session_state = _as_dict(req_data.get("session_state", getattr(req, "session_state", None)))
188
+ history_session_state = _recover_session_state_from_history(chat_history)
189
+
190
+ session_id = _extract_session_id(req_data, req)
191
+ cached_session_state = CHAT_SESSION_STATE.get(session_id, {}) if session_id else {}
192
+
193
+ session_state = _merge_session_state(
194
+ cached_state=_as_dict(cached_session_state),
195
+ incoming_state=incoming_session_state,
196
+ history_state=history_session_state,
197
+ parsed_question_text=parsed_question_text,
198
+ parsed_hint_stage=parsed_hint_stage,
199
+ parsed_help_mode=parsed_help_mode,
200
+ parsed_intent=parsed_intent,
201
+ parsed_topic=parsed_topic,
202
+ parsed_category=parsed_category,
203
+ parsed_user_last_input_type=parsed_user_last_input_type,
204
+ parsed_built_on_previous_turn=parsed_built_on_previous_turn,
205
+ )
206
+
207
+ question_text = (
208
+ (getattr(req, "question_text", None) or "").strip()
209
+ or parsed_question_text
210
+ or game_fields.get("question", "")
211
+ or str(session_state.get("question_text", "") or "").strip()
212
+ )
213
+ options_text = getattr(req, "options_text", None) or game_fields.get("options", [])
214
+ question_id = req_data.get("question_id") or getattr(req, "question_id", None) or session_state.get("question_id")
215
+
216
+ category = (
217
+ req_data.get("category")
218
+ or getattr(req, "category", None)
219
+ or parsed_category
220
+ or game_fields.get("category")
221
+ or session_state.get("category")
222
+ )
223
+
224
+ tone = clamp01(req_data.get("tone", getattr(req, "tone", 0.5)), 0.5)
225
+ verbosity = clamp01(req_data.get("verbosity", getattr(req, "verbosity", 0.5)), 0.5)
226
+ transparency = clamp01(req_data.get("transparency", getattr(req, "transparency", 0.5)), 0.5)
227
+
228
+ incoming_help_mode = req_data.get("help_mode") or getattr(req, "help_mode", None) or parsed_help_mode or None
229
+ explicit_intent = req_data.get("intent") or getattr(req, "intent", None) or parsed_intent or None
230
+
231
+ resolved_user_text = req_data.get("raw_user_text") or actual_user_message or full_text or ""
232
+ resolved_user_text = str(resolved_user_text).strip()
233
+
234
+ intent = explicit_intent or detect_intent(resolved_user_text, incoming_help_mode)
235
+ help_mode = incoming_help_mode or intent_to_help_mode(intent)
236
+
237
+ result = engine.generate_response(
238
+ raw_user_text=resolved_user_text,
239
+ tone=tone,
240
+ verbosity=verbosity,
241
+ transparency=transparency,
242
+ intent=intent,
243
+ help_mode=help_mode,
244
+ chat_history=chat_history,
245
+ question_text=question_text,
246
+ options_text=options_text,
247
+ question_id=question_id,
248
+ session_state=session_state,
249
+ category=category,
250
+ )
251
+
252
+ meta: Dict[str, Any] = {
253
+ "domain": result.domain,
254
+ "solved": result.solved,
255
+ "help_mode": result.help_mode,
256
+ "answer_letter": result.answer_letter,
257
+ "answer_value": result.answer_value,
258
+ "topic": result.topic,
259
+ "used_retrieval": result.used_retrieval,
260
+ "used_generator": result.used_generator,
261
+ }
262
+ if isinstance(result.meta, dict):
263
+ meta.update(result.meta)
264
+
265
+ returned_session_state = _as_dict(meta.get("session_state"))
266
+ if session_id and returned_session_state:
267
+ CHAT_SESSION_STATE[session_id] = dict(returned_session_state)
268
+
269
+ return JSONResponse({"reply": result.reply, "meta": meta})
270
+ except Exception as e:
271
+ return JSONResponse({"error": type(e).__name__, "detail": str(e)}, status_code=500)
272
+
273
+
274
+ @app.post("/log/session/start")
275
+ def log_session_start(payload: SessionStartRequest) -> Dict[str, Any]:
276
+ return store.start_session(payload.session_id, payload.user_id, payload.condition, payload.metadata)
277
+
278
+
279
+ @app.post("/log/event")
280
+ def log_event(payload: EventLogRequest) -> Dict[str, Any]:
281
+ return store.log_event(payload.session_id, payload.event_type, payload.payload, payload.timestamp)
282
+
283
+
284
+ @app.post("/log/session/finalize")
285
+ def log_session_finalize(payload: SessionFinalizeRequest) -> Dict[str, Any]:
286
+ return store.finalize_session(payload.session_id, payload.summary)
287
+
288
+
289
+ @app.get("/research/sessions")
290
+ def research_sessions() -> Dict[str, Any]:
291
+ return {"sessions": store.list_sessions()}
292
+
293
+
294
+ @app.get("/research/session/{session_id}")
295
+ def research_session(session_id: str) -> Dict[str, Any]:
296
+ return store.get_session(session_id)