kikikita commited on
Commit
ee968a7
·
1 Parent(s): dca13c2

feat: implement Redis-backed user state management and update app structure

Browse files
README.md CHANGED
@@ -1,12 +1,12 @@
1
  ---
2
- title: Test
3
- emoji: 🎮
4
  colorFrom: yellow
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.32.0
8
  python_version: "3.11"
9
- app_file: src/main.py
10
  pinned: false
11
  license: mit
12
  ---
 
1
  ---
2
+ title: LLMGameHub
3
+ emoji: "🎮"
4
  colorFrom: yellow
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.32.0
8
  python_version: "3.11"
9
+ app_file: src/app.py
10
  pinned: false
11
  license: mit
12
  ---
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ redis-server
requirements.txt CHANGED
@@ -7,6 +7,7 @@ asyncio
7
  aiohttp==3.9.1
8
  pygame==2.5.2
9
  numpy
 
10
  langchain==0.3.17
11
  langchain-core==0.3.58
12
  langchain-community==0.3.16
@@ -14,4 +15,6 @@ langchain-google-genai==2.1.4
14
  pydantic-core==2.23.4
15
  pydantic-settings==2.7.1
16
  pydantic==2.9.2
17
- langgraph
 
 
 
7
  aiohttp==3.9.1
8
  pygame==2.5.2
9
  numpy
10
+ langgraph
11
  langchain==0.3.17
12
  langchain-core==0.3.58
13
  langchain-community==0.3.16
 
15
  pydantic-core==2.23.4
16
  pydantic-settings==2.7.1
17
  pydantic==2.9.2
18
+ redis[hiredis]>=5
19
+ aioredis>=2
20
+ msgpack
src/agent/llm_graph.py CHANGED
@@ -14,7 +14,7 @@ from agent.tools import (
14
  generate_story_frame,
15
  update_state_with_choice,
16
  )
17
- from agent.state import get_user_state
18
  from audio.audio_generator import change_music_tone
19
  logger = logging.getLogger(__name__)
20
 
 
14
  generate_story_frame,
15
  update_state_with_choice,
16
  )
17
+ from agent.redis_state import get_user_state
18
  from audio.audio_generator import change_music_tone
19
  logger = logging.getLogger(__name__)
20
 
src/agent/redis_state.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Async Redis-backed user state storage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import msgpack
7
+ import redis.asyncio as redis
8
+
9
+ from agent.models import UserState
10
+
11
+
12
+ class UserRepository:
13
+ """Repository for storing UserState objects in Redis."""
14
+
15
+ def __init__(self, redis_url: str = "redis://localhost") -> None:
16
+ self.redis = redis.from_url(redis_url)
17
+
18
+ async def get(self, user_id: str) -> UserState:
19
+ """Return user state for the given id, creating it if absent."""
20
+ key = f"llmgamehub:{user_id}"
21
+ data = await self.redis.hget(key, "data")
22
+ if data is None:
23
+ return UserState()
24
+ state_dict = msgpack.unpackb(data, raw=False)
25
+ return UserState.parse_obj(state_dict)
26
+
27
+ async def set(self, user_id: str, state: UserState) -> None:
28
+ """Persist updated user state."""
29
+ key = f"llmgamehub:{user_id}"
30
+ packed = msgpack.packb(json.loads(state.json()))
31
+ await self.redis.hset(key, mapping={"data": packed})
32
+
33
+ async def reset(self, user_id: str) -> None:
34
+ """Remove stored state for a user."""
35
+ key = f"llmgamehub:{user_id}"
36
+ await self.redis.delete(key)
37
+
38
+
39
+ _repo = UserRepository()
40
+
41
+
42
+ async def get_user_state(user_hash: str) -> UserState:
43
+ return await _repo.get(user_hash)
44
+
45
+
46
+ async def set_user_state(user_hash: str, state: UserState) -> None:
47
+ await _repo.set(user_hash, state)
48
+
49
+
50
+ async def reset_user_state(user_hash: str) -> None:
51
+ await _repo.reset(user_hash)
src/agent/runner.py CHANGED
@@ -10,7 +10,7 @@ from agent.tools import generate_scene_image
10
 
11
  from agent.llm_graph import GraphState, llm_game_graph
12
  from agent.models import UserState
13
- from agent.state import get_user_state
14
 
15
  logger = logging.getLogger(__name__)
16
 
 
10
 
11
  from agent.llm_graph import GraphState, llm_game_graph
12
  from agent.models import UserState
13
+ from agent.redis_state import get_user_state
14
 
15
  logger = logging.getLogger(__name__)
16
 
src/agent/state.py DELETED
@@ -1,24 +0,0 @@
1
- """Simple in-memory user state storage."""
2
-
3
- from typing import Dict
4
-
5
- from agent.models import UserState
6
-
7
- _USER_STATE: Dict[str, UserState] = {}
8
-
9
-
10
- def get_user_state(user_hash: str) -> UserState:
11
- """Return user state for the given id, creating it if necessary."""
12
- if user_hash not in _USER_STATE:
13
- _USER_STATE[user_hash] = UserState()
14
- return _USER_STATE[user_hash]
15
-
16
-
17
- def set_user_state(user_hash: str, state: UserState) -> None:
18
- """Persist updated user state."""
19
- _USER_STATE[user_hash] = state
20
-
21
-
22
- def reset_user_state(user_hash: str) -> None:
23
- """Reset stored state for a user."""
24
- _USER_STATE[user_hash] = UserState()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agent/tools.py CHANGED
@@ -17,7 +17,7 @@ from agent.models import (
17
  UserChoice,
18
  )
19
  from agent.prompts import ENDING_CHECK_PROMPT, SCENE_PROMPT, STORY_FRAME_PROMPT
20
- from agent.state import get_user_state, set_user_state
21
  from images.image_generator import modify_image, generate_image
22
  from agent.image_agent import ChangeScene
23
 
 
17
  UserChoice,
18
  )
19
  from agent.prompts import ENDING_CHECK_PROMPT, SCENE_PROMPT, STORY_FRAME_PROMPT
20
+ from agent.redis_state import get_user_state, set_user_state
21
  from images.image_generator import modify_image, generate_image
22
  from agent.image_agent import ChangeScene
23
 
src/app.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import time
3
+ import atexit
4
+ import shutil
5
+ from redis import Redis, ConnectionError
6
+
7
+
8
+ REDIS_BIN = shutil.which("redis-server")
9
+
10
+ if not REDIS_BIN:
11
+ raise RuntimeError("redis-server not found. Ensure redis is installed via packages.txt")
12
+
13
+
14
+ redis_cmd = [
15
+ REDIS_BIN,
16
+ "--save",
17
+ "",
18
+ "--appendonly",
19
+ "no",
20
+ "--dir",
21
+ "/tmp",
22
+ "--pidfile",
23
+ "/tmp/redis.pid",
24
+ ]
25
+ redis_process = subprocess.Popen(redis_cmd)
26
+
27
+
28
+ redis_client = Redis()
29
+ for _ in range(20):
30
+ try:
31
+ redis_client.ping()
32
+ break
33
+ except ConnectionError:
34
+ time.sleep(0.5)
35
+ else:
36
+ raise RuntimeError("Failed to start redis-server")
37
+
38
+
39
+ atexit.register(redis_process.terminate)
40
+
41
+
42
+ time.sleep(0.5)
43
+
44
+ import main
src/main.py CHANGED
@@ -21,14 +21,15 @@ import asyncio
21
  from game_setting import get_user_story
22
  from config import settings
23
 
 
24
  logger = logging.getLogger(__name__)
25
 
26
 
27
  async def return_to_constructor(user_hash: str):
28
  """Return to the constructor and reset user state and audio."""
29
- from agent.state import reset_user_state
30
 
31
- reset_user_state(user_hash)
32
  await cleanup_music_session(user_hash)
33
  # Generate a new hash to avoid stale state
34
  new_hash = str(uuid.uuid4())
 
21
  from game_setting import get_user_story
22
  from config import settings
23
 
24
+
25
  logger = logging.getLogger(__name__)
26
 
27
 
28
  async def return_to_constructor(user_hash: str):
29
  """Return to the constructor and reset user state and audio."""
30
+ from agent.redis_state import reset_user_state
31
 
32
+ await reset_user_state(user_hash)
33
  await cleanup_music_session(user_hash)
34
  # Generate a new hash to avoid stale state
35
  new_hash = str(uuid.uuid4())