Skip to content

Commit 5760ef4

Browse files
committed
fallback gameplay move to heuristic when inference artifacts missing
1 parent 1b14a52 commit 5760ef4

2 files changed

Lines changed: 60 additions & 13 deletions

File tree

src/api/modules/gameplay/router.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,44 @@ def post_move(
137137

138138
mode = request.mode
139139
if mode in {"fast", "strong"}:
140-
inference_service = _resolve_inference_service(http_request)
141-
result = inference_service.predict(board=board, mode=mode)
142-
move_payload: MovePayload | None = None
143-
if result.move is not None:
144-
r1, c1, r2, c2 = result.move
145-
move_payload = MovePayload(r1=r1, c1=c1, r2=r2, c2=c2)
146-
147-
return MoveResponse(
148-
move=move_payload,
149-
action_idx=result.action_idx,
150-
value=result.value,
151-
mode=result.mode,
152-
)
140+
try:
141+
inference_service = _resolve_inference_service(http_request)
142+
result = inference_service.predict(board=board, mode=mode)
143+
move_payload: MovePayload | None = None
144+
if result.move is not None:
145+
r1, c1, r2, c2 = result.move
146+
move_payload = MovePayload(r1=r1, c1=c1, r2=r2, c2=c2)
147+
148+
return MoveResponse(
149+
move=move_payload,
150+
action_idx=result.action_idx,
151+
value=result.value,
152+
mode=result.mode,
153+
)
154+
except HTTPException as exc:
155+
if exc.status_code != status.HTTP_503_SERVICE_UNAVAILABLE:
156+
raise
157+
# Keep PvE matches playable when model artifacts are missing in runtime.
158+
logger.warning(
159+
"Inference unavailable on /gameplay/move; falling back to heuristic_hard",
160+
extra={"detail": exc.detail},
161+
)
162+
rng = np.random.default_rng()
163+
fallback_move = heuristic_move(board=board, rng=rng, level="hard")
164+
if fallback_move is None:
165+
return MoveResponse(
166+
move=None,
167+
action_idx=ACTION_SPACE.pass_index,
168+
value=0.0,
169+
mode="heuristic_hard",
170+
)
171+
r1, c1, r2, c2 = fallback_move
172+
return MoveResponse(
173+
move=MovePayload(r1=r1, c1=c1, r2=r2, c2=c2),
174+
action_idx=ACTION_SPACE.encode(fallback_move),
175+
value=0.0,
176+
mode="heuristic_hard",
177+
)
153178

154179
rng = np.random.default_rng()
155180
if mode == "random":

tests/test_api_move.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import unittest
55
from pathlib import Path
66

7+
from fastapi import HTTPException, status
78
from fastapi.testclient import TestClient
89

910
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
@@ -101,6 +102,27 @@ def _unavailable_inference() -> _StubInferenceService:
101102
self.assertEqual(body["mode"], "heuristic_normal")
102103
self.assertIsInstance(body["action_idx"], int)
103104

105+
def test_move_endpoint_falls_back_to_heuristic_when_inference_is_unavailable(self) -> None:
106+
app = create_app()
107+
108+
def _unavailable_inference() -> _StubInferenceService:
109+
raise HTTPException(
110+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
111+
detail="Inference unavailable in test",
112+
)
113+
114+
app.dependency_overrides[get_inference_service_dep] = _unavailable_inference
115+
client = TestClient(app)
116+
117+
board = AtaxxBoard()
118+
payload = MoveRequest(board=board_to_state(board), mode="fast").model_dump()
119+
response = client.post("/api/v1/gameplay/move", json=payload)
120+
self.assertEqual(response.status_code, 200)
121+
122+
body = response.json()
123+
self.assertEqual(body["mode"], "heuristic_hard")
124+
self.assertIsInstance(body["action_idx"], int)
125+
104126

105127
if __name__ == "__main__":
106128
unittest.main()

0 commit comments

Comments
 (0)