Spaces:
Sleeping
Sleeping
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 +15 -11
- modules/llm/card_interpreter.py +29 -41
- modules/llm/rules.py +23 -0
- requirements.txt +1 -0
- scripts/get_cards.py +4 -17
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
|
21 |
-
deck
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
# Initialize pipeline with smaller model and CPU
|
18 |
-
self.pipeline = pipeline(
|
19 |
"text-generation",
|
20 |
-
model="
|
21 |
-
device_map="
|
22 |
pad_token_id=2,
|
23 |
-
model_kwargs={
|
24 |
-
"low_cpu_mem_usage": False,
|
25 |
-
"use_cache": False
|
26 |
-
}
|
27 |
)
|
28 |
-
|
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 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
38 |
|
39 |
-
|
40 |
-
|
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 |
-
|
47 |
-
|
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 +=
|
|
|
|
|
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=
|
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 |
-
#
|
31 |
with zipfile.ZipFile(zip_file, "r") as zip_ref:
|
32 |
-
#
|
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 |
-
#
|
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 |
-
|
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}")
|