MilanCalegari commited on
Commit
868e142
·
1 Parent(s): 267cb21

refactor: improve session state management and enhance card interpretation logic

Browse files

- Added session state checks to initialize cards, deck, and interpreter only once.
- Updated the CardInterpreter to use a global pipeline for efficiency.
- Introduced a new rules module for better content management in interpretations.
- Enhanced the get_cards function with clearer comments and removed unnecessary print statements.
- Updated requirements to include the 'watchdog' package.

app.py CHANGED
@@ -13,13 +13,19 @@ base_dir = os.path.dirname(os.path.abspath(__file__))
13
  data_dir = os.path.join(base_dir, "data")
14
  json_file = os.path.join(data_dir, "tarot-images.json")
15
 
16
-
17
- get_cards()
 
18
 
19
  # Initialize deck and interpreter
20
- deck = TarotDeck()
21
- deck.get_cards()
22
- interpreter = CardInterpreter()
 
 
 
 
 
23
 
24
  st.set_page_config(page_title="Tarot Reading", page_icon="🔮", layout="centered")
25
 
@@ -43,8 +49,6 @@ with st.sidebar:
43
  # index=0
44
  # )
45
 
46
- reversed_prob -= 1
47
-
48
  # User interface texts
49
  welcome_text = "### Welcome to your Tarot Reading"
50
  instructions_text = (
@@ -84,10 +88,10 @@ context = st.text_area(context_text, placeholder=context_placeholder)
84
  if st.button(draw_button):
85
  # Shuffle and draw cards
86
  with st.spinner(spinner_texts["shuffle"]):
87
- deck.shuffle(reversed_prob)
88
 
89
  with st.spinner(spinner_texts["channel"]):
90
- cards = deck.draw(CardReadingMethod(method))
91
 
92
  # Display cards
93
  st.markdown(spinner_texts["cards"])
@@ -111,11 +115,11 @@ if st.button(draw_button):
111
  # Generate and display interpretation
112
  with st.spinner(spinner_texts["consult"]):
113
  if context:
114
- interpretation = interpreter.generate_interpretation(
115
  cards, context, CardReadingMethod(method)
116
  )
117
  else:
118
- interpretation = interpreter.generate_interpretation(
119
  cards, None, CardReadingMethod(method)
120
  )
121
 
 
13
  data_dir = os.path.join(base_dir, "data")
14
  json_file = os.path.join(data_dir, "tarot-images.json")
15
 
16
+ if 'initialized' not in st.session_state:
17
+ get_cards()
18
+ st.session_state.initialized = True
19
 
20
  # Initialize deck and interpreter
21
+ if 'deck' not in st.session_state:
22
+ deck = TarotDeck()
23
+ deck.get_cards()
24
+ st.session_state.deck = deck
25
+
26
+ if 'interpreter' not in st.session_state:
27
+ interpreter = CardInterpreter()
28
+ st.session_state.interpreter = interpreter
29
 
30
  st.set_page_config(page_title="Tarot Reading", page_icon="🔮", layout="centered")
31
 
 
49
  # index=0
50
  # )
51
 
 
 
52
  # User interface texts
53
  welcome_text = "### Welcome to your Tarot Reading"
54
  instructions_text = (
 
88
  if st.button(draw_button):
89
  # Shuffle and draw cards
90
  with st.spinner(spinner_texts["shuffle"]):
91
+ st.session_state.deck.shuffle(reversed_prob)
92
 
93
  with st.spinner(spinner_texts["channel"]):
94
+ cards = st.session_state.deck.draw(CardReadingMethod(method))
95
 
96
  # Display cards
97
  st.markdown(spinner_texts["cards"])
 
115
  # Generate and display interpretation
116
  with st.spinner(spinner_texts["consult"]):
117
  if context:
118
+ interpretation = st.session_state.interpreter.generate_interpretation(
119
  cards, context, CardReadingMethod(method)
120
  )
121
  else:
122
+ interpretation = st.session_state.interpreter.generate_interpretation(
123
  cards, None, CardReadingMethod(method)
124
  )
125
 
modules/llm/card_interpreter.py CHANGED
@@ -1,54 +1,42 @@
1
  import os
2
  from typing import Dict, List, Optional
3
 
4
- from huggingface_hub import login
5
  from transformers import pipeline
6
 
7
  from modules.utils.commom import Card, CardReadingMethod
8
 
9
  from ..interfaces.llm_interface import CardInterpreterInterface
 
10
 
 
11
 
12
- class CardInterpreter(CardInterpreterInterface):
13
- def __init__(self) -> None:
14
- # Login to Hugging Face
15
- hf_token = os.getenv("HF_TOKEN")
16
- login(token=hf_token)
17
- # Initialize pipeline with smaller model and CPU
18
- self.pipeline = pipeline(
19
  "text-generation",
20
- model="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
21
- device_map="cpu", # Force CPU for HF Spaces compatibility
22
  pad_token_id=2,
23
- model_kwargs={
24
- "low_cpu_mem_usage": False,
25
- "use_cache": False
26
- }
27
  )
28
- # Base prompt template
29
- self._base_content = """
30
- You are a powerful occultist and exceptional tarot reader. Provide a concise reading based on the given cards.
31
 
32
- Focus on Rider Waite Tarot symbolism and card imagery.
33
 
34
- The possible methods are:
35
- - PAST_PRESENT_FUTURE: Three cards, past, present and future;
36
- - CELTIC_CROSS: Ten cards, the situation, challenges, what to focus on, your past, your strengths, near future, suggested approach, what you need to know, your hopes and fears, and outcomes;
37
- - HAND_OF_ERIS: Five cards, about your question, what may help you, what may hinder you, possible outcome number 1, and possible outcome number 2;
 
 
38
 
39
- Reading Guidelines:
40
- - Keep answers brief and focused;
41
- - Provide a summary overview;
42
- - Only interpret reversed cards when specified;
43
- - With context: Focus on context-specific interpretation;
44
- - Without context: Give practical daily guidance;
45
 
46
- If other context is provided:
47
- - Focus on the context provided;
48
- - Provide a reading related to the context;
49
- - Focus primarily on interpreting within the given context
50
- - Keep the symbolism of the cards first, but make sure to use the context to interpret the cards.
51
- """
52
 
53
  def _format_card(self, card: Card) -> str:
54
  # Format card name with reversed state
@@ -63,7 +51,6 @@ class CardInterpreter(CardInterpreterInterface):
63
  Present: {self._format_card(cards[1])}
64
  Future: {self._format_card(cards[2])}
65
  """,
66
-
67
  CardReadingMethod.CELTIC_CROSS: lambda: f"""The provided cards are:
68
  The situation: {self._format_card(cards[0])}
69
  Challenges: {self._format_card(cards[1])}
@@ -76,22 +63,23 @@ class CardInterpreter(CardInterpreterInterface):
76
  Your hopes and fears: {self._format_card(cards[8])}
77
  Outcomes: {self._format_card(cards[9])}
78
  """,
79
-
80
  CardReadingMethod.HAND_OF_ERIS: lambda: f"""The provided cards are:
81
  About your question: {self._format_card(cards[0])}
82
  What may help you: {self._format_card(cards[1])}
83
  What may hinder you: {self._format_card(cards[2])}
84
  Possible outcome number 1: {self._format_card(cards[3])}
85
  Possible outcome number 2: {self._format_card(cards[4])}
86
- """
87
  }
88
 
89
  question = method_templates[method]()
90
- question += f"\nIn the context of: {context}\nDrawn with the method: {method.value}"
 
 
91
 
92
  return [
93
  {"role": "system", "content": self._base_content},
94
- {"role": "user", "content": question}
95
  ]
96
 
97
  def generate_interpretation(
@@ -99,8 +87,8 @@ class CardInterpreter(CardInterpreterInterface):
99
  ) -> str:
100
  prompt = self.generate_prompt(cards, context or "General reading", method)
101
  result = self.pipeline(
102
- prompt,
103
- max_new_tokens=256, # Reduced token limit for faster inference
104
  num_return_sequences=1,
105
  do_sample=False,
106
  )
 
1
  import os
2
  from typing import Dict, List, Optional
3
 
4
+ from huggingface_hub import HfFolder, login
5
  from transformers import pipeline
6
 
7
  from modules.utils.commom import Card, CardReadingMethod
8
 
9
  from ..interfaces.llm_interface import CardInterpreterInterface
10
+ from .rules import content
11
 
12
+ PIPELINE = None
13
 
14
+
15
+ def get_pipeline():
16
+ global PIPELINE
17
+ if PIPELINE is None:
18
+ PIPELINE = pipeline(
 
 
19
  "text-generation",
20
+ model="meta-llama/Llama-3.2-1B-Instruct",
21
+ device_map="auto",
22
  pad_token_id=2,
23
+ model_kwargs={"low_cpu_mem_usage": True, "use_cache": False},
 
 
 
24
  )
25
+ return PIPELINE
 
 
26
 
 
27
 
28
+ class CardInterpreter(CardInterpreterInterface):
29
+ def __init__(self) -> None:
30
+ # Login to Hugging Face
31
+ hf_token = os.getenv("HF_TOKEN")
32
+ if not HfFolder.get_token():
33
+ login(token=hf_token)
34
 
35
+ # Initialize pipeline
36
+ self.pipeline = get_pipeline()
 
 
 
 
37
 
38
+ # Base prompt template
39
+ self._base_content = content
 
 
 
 
40
 
41
  def _format_card(self, card: Card) -> str:
42
  # Format card name with reversed state
 
51
  Present: {self._format_card(cards[1])}
52
  Future: {self._format_card(cards[2])}
53
  """,
 
54
  CardReadingMethod.CELTIC_CROSS: lambda: f"""The provided cards are:
55
  The situation: {self._format_card(cards[0])}
56
  Challenges: {self._format_card(cards[1])}
 
63
  Your hopes and fears: {self._format_card(cards[8])}
64
  Outcomes: {self._format_card(cards[9])}
65
  """,
 
66
  CardReadingMethod.HAND_OF_ERIS: lambda: f"""The provided cards are:
67
  About your question: {self._format_card(cards[0])}
68
  What may help you: {self._format_card(cards[1])}
69
  What may hinder you: {self._format_card(cards[2])}
70
  Possible outcome number 1: {self._format_card(cards[3])}
71
  Possible outcome number 2: {self._format_card(cards[4])}
72
+ """,
73
  }
74
 
75
  question = method_templates[method]()
76
+ question += (
77
+ f"\nIn the context of: {context}\nDrawn with the method: {method.value}"
78
+ )
79
 
80
  return [
81
  {"role": "system", "content": self._base_content},
82
+ {"role": "user", "content": question},
83
  ]
84
 
85
  def generate_interpretation(
 
87
  ) -> str:
88
  prompt = self.generate_prompt(cards, context or "General reading", method)
89
  result = self.pipeline(
90
+ prompt,
91
+ max_new_tokens=512,
92
  num_return_sequences=1,
93
  do_sample=False,
94
  )
modules/llm/rules.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ content = """
2
+ You are a masterful tarot reader with deep esoteric knowledge. Provide an insightful and focused reading based on the drawn cards.
3
+
4
+ Reading Guidelines:
5
+ - Keep answers brief and focused;
6
+ - Provide a summary overview at the end;
7
+ - Only interpret reversed cards when specified;
8
+ - With context: Focus on context-specific interpretation;
9
+ - Without context: Give practical daily guidance;
10
+ - Do not mention the method name in the reading overview;
11
+ - Be direct and concise.
12
+
13
+ If other context is provided:
14
+ - Focus on the context provided;
15
+ - Provide a reading related to the context;
16
+ - Focus primarily on interpreting within the given context
17
+ - Keep the symbolism of the cards first, but make sure to use the context to interpret the cards.
18
+
19
+ Focus on Rider Waite Tarot symbolism and card imagery.
20
+ Format example:
21
+ 1. Cards meaning;
22
+ 2. If the context is provided, give a practical and short guidance;
23
+ 3. Reading overview."""
requirements.txt CHANGED
@@ -57,3 +57,4 @@ transformers==4.47.1
57
  typing_extensions==4.12.2
58
  tzdata==2024.2
59
  urllib3==2.3.0
 
 
57
  typing_extensions==4.12.2
58
  tzdata==2024.2
59
  urllib3==2.3.0
60
+ watchdog==2.3.1
scripts/get_cards.py CHANGED
@@ -27,41 +27,28 @@ def get_cards():
27
  with open(zip_file, "wb") as f:
28
  f.write(response.content)
29
 
30
- # Extrair e renomear se necessário
31
  with zipfile.ZipFile(zip_file, "r") as zip_ref:
32
- # Listar conteúdo do ZIP
33
  files = zip_ref.namelist()
34
  json_files = [f for f in files if f.endswith(".json")]
35
 
36
- print(f"Arquivos encontrados no ZIP: {files}")
37
- print(f"Arquivos JSON encontrados: {json_files}")
38
-
39
  if json_files:
40
- # Extrair todos os arquivos
41
  zip_ref.extractall(data_dir)
42
 
43
- # Renomear o primeiro arquivo JSON encontrado para tarot-images.json
44
  old_path = os.path.join(data_dir, json_files[0])
45
  new_path = os.path.join(data_dir, "tarot-images.json")
46
- print(f"Renomeando de {old_path} para {new_path}")
47
 
48
  if old_path != new_path:
49
  if os.path.exists(old_path):
50
  os.rename(old_path, new_path)
51
- else:
52
- print(f"ERRO: Arquivo original {old_path} não encontrado!")
53
  else:
54
  raise Exception("No JSON file found in the ZIP archive")
55
 
56
  os.remove(zip_file)
57
 
58
- # Verificar se o arquivo final existe
59
- if os.path.exists(new_path):
60
- print(f"Arquivo final encontrado em: {new_path}")
61
- else:
62
- print(f"ERRO: Arquivo final não encontrado em: {new_path}")
63
-
64
- print("Files downloaded and extracted successfully!")
65
 
66
  except Exception as e:
67
  print(f"Error downloading/extracting files: {e}")
 
27
  with open(zip_file, "wb") as f:
28
  f.write(response.content)
29
 
30
+ # Extract and rename if necessary
31
  with zipfile.ZipFile(zip_file, "r") as zip_ref:
32
+ # List all files in the ZIP
33
  files = zip_ref.namelist()
34
  json_files = [f for f in files if f.endswith(".json")]
35
 
 
 
 
36
  if json_files:
37
+ # Extract all files
38
  zip_ref.extractall(data_dir)
39
 
 
40
  old_path = os.path.join(data_dir, json_files[0])
41
  new_path = os.path.join(data_dir, "tarot-images.json")
 
42
 
43
  if old_path != new_path:
44
  if os.path.exists(old_path):
45
  os.rename(old_path, new_path)
46
+
 
47
  else:
48
  raise Exception("No JSON file found in the ZIP archive")
49
 
50
  os.remove(zip_file)
51
 
 
 
 
 
 
 
 
52
 
53
  except Exception as e:
54
  print(f"Error downloading/extracting files: {e}")