Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -8,18 +8,16 @@ from fastapi.responses import JSONResponse
|
|
8 |
from pydantic import BaseModel
|
9 |
from datetime import datetime, timedelta
|
10 |
|
11 |
-
# Lade RecipeBERT Modell (für semantische Zutat-Kombination)
|
12 |
bert_model_name = "alexdseo/RecipeBERT"
|
13 |
bert_tokenizer = AutoTokenizer.from_pretrained(bert_model_name)
|
14 |
bert_model = AutoModel.from_pretrained(bert_model_name)
|
15 |
-
bert_model.eval() # Setze das Modell in den Evaluationsmodus
|
16 |
|
17 |
-
|
|
|
18 |
MODEL_NAME_OR_PATH = "flax-community/t5-recipe-generation"
|
19 |
t5_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH, use_fast=True)
|
20 |
t5_model = FlaxAutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)
|
21 |
|
22 |
-
# Token Mapping für die T5 Modell-Ausgabe
|
23 |
special_tokens = t5_tokenizer.all_special_tokens
|
24 |
tokens_map = {
|
25 |
"<sep>": "--",
|
@@ -39,10 +37,10 @@ def get_embedding(text):
|
|
39 |
sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
40 |
return (sum_embeddings / sum_mask).squeeze(0)
|
41 |
|
42 |
-
def
|
43 |
-
"""
|
44 |
-
|
45 |
-
|
46 |
|
47 |
def get_cosine_similarity(vec1, vec2):
|
48 |
"""Berechnet die Cosinus-Ähnlichkeit zwischen zwei Vektoren"""
|
@@ -86,52 +84,31 @@ def calculate_age_bonus(date_added_str: str, category: str) -> float:
|
|
86 |
bonus = days_since_added * daily_bonus
|
87 |
return min(bonus, 0.10) # Max 10% (0.10)
|
88 |
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
Jetzt inklusive Altersbonus.
|
93 |
-
embedding_list_with_details: Liste von Tupeln (Name, Embedding, DateAddedStr, Category)
|
94 |
-
"""
|
95 |
-
results = []
|
96 |
-
for name, emb, date_added_str, category in embedding_list_with_details:
|
97 |
-
avg_similarity = get_cosine_similarity(query_vector, emb)
|
98 |
-
individual_similarities = [get_cosine_similarity(good_emb, emb)
|
99 |
-
for _, good_emb in all_good_embeddings]
|
100 |
-
avg_individual_similarity = sum(individual_similarities) / len(individual_similarities) if individual_similarities else 0
|
101 |
-
|
102 |
-
base_combined_score = avg_weight * avg_similarity + (1 - avg_weight) * avg_individual_similarity
|
103 |
-
|
104 |
-
# NEU: Altersbonus hinzufügen
|
105 |
-
age_bonus = calculate_age_bonus(date_added_str, category)
|
106 |
-
final_combined_score = base_combined_score + age_bonus
|
107 |
-
|
108 |
-
results.append((name, emb, final_combined_score, date_added_str, category))
|
109 |
-
results.sort(key=lambda x: x[2], reverse=True)
|
110 |
-
return results
|
111 |
-
|
112 |
-
def find_best_ingredients(required_ingredients_names, available_ingredients_details, max_ingredients=6, avg_weight=0.6):
|
113 |
"""
|
114 |
-
Findet die besten Zutaten basierend auf RecipeBERT Embeddings
|
|
|
115 |
required_ingredients_names: Liste von Strings (nur Namen)
|
116 |
available_ingredients_details: Liste von IngredientDetail-Objekten
|
117 |
"""
|
118 |
required_ingredients_names = list(set(required_ingredients_names))
|
119 |
|
120 |
# Filtern der verfügbaren Zutaten, um sicherzustellen, dass keine Pflichtzutaten dabei sind
|
121 |
-
# Korrektur hier: Zugriff auf item.name statt item['name']
|
122 |
available_ingredients_filtered_details = [
|
123 |
item for item in available_ingredients_details
|
124 |
-
if item.name not in required_ingredients_names
|
125 |
]
|
126 |
|
127 |
# Wenn keine Pflichtzutaten vorhanden sind, aber verfügbare, wähle eine zufällig als Pflichtzutat
|
128 |
if not required_ingredients_names and available_ingredients_filtered_details:
|
129 |
random_item = random.choice(available_ingredients_filtered_details)
|
130 |
-
required_ingredients_names = [random_item.name]
|
131 |
# Entferne die zufällig gewählte Zutat aus den verfügbaren Details
|
132 |
available_ingredients_filtered_details = [
|
133 |
item for item in available_ingredients_filtered_details
|
134 |
-
if item.name != random_item.name
|
135 |
]
|
136 |
print(f"No required ingredients provided. Randomly selected: {required_ingredients_names[0]}")
|
137 |
|
@@ -141,39 +118,67 @@ def find_best_ingredients(required_ingredients_names, available_ingredients_deta
|
|
141 |
if not available_ingredients_filtered_details:
|
142 |
return required_ingredients_names
|
143 |
|
144 |
-
|
145 |
-
|
|
|
146 |
|
147 |
-
|
148 |
-
|
149 |
-
embed_available_with_details = [
|
150 |
-
(item.name, get_embedding(item.name), item.dateAdded, item.category) # <--- KORREKTUR
|
151 |
-
for item in available_ingredients_filtered_details
|
152 |
-
]
|
153 |
|
154 |
-
num_to_add = min(max_ingredients - len(required_ingredients_names), len(
|
155 |
|
156 |
-
|
157 |
-
|
|
|
158 |
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
|
|
|
|
|
|
|
|
174 |
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
|
|
|
177 |
|
178 |
def skip_special_tokens(text, special_tokens):
|
179 |
"""Entfernt spezielle Tokens aus dem Text"""
|
@@ -341,4 +346,3 @@ async def generate_recipe_api(request_data: RecipeRequest):
|
|
341 |
async def read_root():
|
342 |
return {"message": "AI Recipe Generator API is running (FastAPI only)!"}
|
343 |
|
344 |
-
print("INFO: Pure FastAPI application script finished execution and defined 'app' variable.")
|
|
|
8 |
from pydantic import BaseModel
|
9 |
from datetime import datetime, timedelta
|
10 |
|
|
|
11 |
bert_model_name = "alexdseo/RecipeBERT"
|
12 |
bert_tokenizer = AutoTokenizer.from_pretrained(bert_model_name)
|
13 |
bert_model = AutoModel.from_pretrained(bert_model_name)
|
|
|
14 |
|
15 |
+
|
16 |
+
|
17 |
MODEL_NAME_OR_PATH = "flax-community/t5-recipe-generation"
|
18 |
t5_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH, use_fast=True)
|
19 |
t5_model = FlaxAutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)
|
20 |
|
|
|
21 |
special_tokens = t5_tokenizer.all_special_tokens
|
22 |
tokens_map = {
|
23 |
"<sep>": "--",
|
|
|
37 |
sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
38 |
return (sum_embeddings / sum_mask).squeeze(0)
|
39 |
|
40 |
+
def format_ingredients_for_bert(ingredients_list):
|
41 |
+
"""Formatiert Zutatenliste für BERT"""
|
42 |
+
return f"Ingredients: {', '.join(ingredients_list)}"
|
43 |
+
|
44 |
|
45 |
def get_cosine_similarity(vec1, vec2):
|
46 |
"""Berechnet die Cosinus-Ähnlichkeit zwischen zwei Vektoren"""
|
|
|
84 |
bonus = days_since_added * daily_bonus
|
85 |
return min(bonus, 0.10) # Max 10% (0.10)
|
86 |
|
87 |
+
|
88 |
+
|
89 |
+
def find_best_ingredients(required_ingredients_names, available_ingredients_details, max_ingredients=6):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
"""
|
91 |
+
Findet die besten Zutaten basierend auf RecipeBERT Embeddings
|
92 |
+
|
93 |
required_ingredients_names: Liste von Strings (nur Namen)
|
94 |
available_ingredients_details: Liste von IngredientDetail-Objekten
|
95 |
"""
|
96 |
required_ingredients_names = list(set(required_ingredients_names))
|
97 |
|
98 |
# Filtern der verfügbaren Zutaten, um sicherzustellen, dass keine Pflichtzutaten dabei sind
|
|
|
99 |
available_ingredients_filtered_details = [
|
100 |
item for item in available_ingredients_details
|
101 |
+
if item.name not in required_ingredients_names
|
102 |
]
|
103 |
|
104 |
# Wenn keine Pflichtzutaten vorhanden sind, aber verfügbare, wähle eine zufällig als Pflichtzutat
|
105 |
if not required_ingredients_names and available_ingredients_filtered_details:
|
106 |
random_item = random.choice(available_ingredients_filtered_details)
|
107 |
+
required_ingredients_names = [random_item.name]
|
108 |
# Entferne die zufällig gewählte Zutat aus den verfügbaren Details
|
109 |
available_ingredients_filtered_details = [
|
110 |
item for item in available_ingredients_filtered_details
|
111 |
+
if item.name != random_item.name
|
112 |
]
|
113 |
print(f"No required ingredients provided. Randomly selected: {required_ingredients_names[0]}")
|
114 |
|
|
|
118 |
if not available_ingredients_filtered_details:
|
119 |
return required_ingredients_names
|
120 |
|
121 |
+
print(f"\n=== Suche passende Zutaten für Basis: {required_ingredients_names} ===")
|
122 |
+
print(f"Verfügbare Zutaten: {[item.name for item in available_ingredients_filtered_details]}")
|
123 |
+
print("-" * 50)
|
124 |
|
125 |
+
current_combination = required_ingredients_names.copy()
|
126 |
+
remaining_ingredients_details = available_ingredients_filtered_details.copy()
|
|
|
|
|
|
|
|
|
127 |
|
128 |
+
num_to_add = min(max_ingredients - len(required_ingredients_names), len(remaining_ingredients_details))
|
129 |
|
130 |
+
for round_num in range(num_to_add):
|
131 |
+
best_ingredient_detail = None
|
132 |
+
best_score = -1
|
133 |
|
134 |
+
# Formatiere aktuelle Kombination für BERT
|
135 |
+
current_text = format_ingredients_for_bert(current_combination)
|
136 |
+
current_embedding = get_embedding(current_text)
|
137 |
+
|
138 |
+
print(f"\nRunde {round_num + 1} - Aktuelle Kombination: {current_combination}")
|
139 |
+
print("Teste verbleibende Zutaten:")
|
140 |
+
|
141 |
+
for ingredient_detail in remaining_ingredients_details:
|
142 |
+
# Berechne semantische Ähnlichkeit mit BERT
|
143 |
+
ingredient_text = format_ingredients_for_bert([ingredient_detail.name])
|
144 |
+
ingredient_embedding = get_embedding(ingredient_text)
|
145 |
+
similarity = get_cosine_similarity(current_embedding, ingredient_embedding)
|
146 |
+
|
147 |
+
# Berechne Altersbonus
|
148 |
+
age_bonus = calculate_age_bonus(ingredient_detail.dateAdded, ingredient_detail.category)
|
149 |
|
150 |
+
# Kombiniere Ähnlichkeit und Altersbonus
|
151 |
+
final_score = similarity + age_bonus
|
152 |
+
|
153 |
+
print(f" - '{ingredient_detail.name}': Ähnlichkeit = {similarity:.4f}, Altersbonus = {age_bonus:.4f}, Gesamt = {final_score:.4f}")
|
154 |
+
|
155 |
+
if final_score > best_score:
|
156 |
+
best_score = final_score
|
157 |
+
best_ingredient_detail = ingredient_detail
|
158 |
+
|
159 |
+
if best_ingredient_detail:
|
160 |
+
current_combination.append(best_ingredient_detail.name)
|
161 |
+
remaining_ingredients_details.remove(best_ingredient_detail)
|
162 |
|
163 |
+
# Berechne die Komponenten für die Ausgabe
|
164 |
+
best_similarity = get_cosine_similarity(
|
165 |
+
current_embedding,
|
166 |
+
get_embedding(format_ingredients_for_bert([best_ingredient_detail.name]))
|
167 |
+
)
|
168 |
+
best_age_bonus = calculate_age_bonus(best_ingredient_detail.dateAdded, best_ingredient_detail.category)
|
169 |
+
|
170 |
+
print(f"\n-> Runde {round_num + 1} abgeschlossen: Beste Zutat ist '{best_ingredient_detail.name}' mit Gesamtscore {best_score:.4f}")
|
171 |
+
print(f" (Ähnlichkeit: {best_similarity:.4f} + Altersbonus: {best_age_bonus:.4f})")
|
172 |
+
print(f" Neue Kombination: {current_combination}")
|
173 |
+
print("-" * 50)
|
174 |
+
else:
|
175 |
+
print("Keine weiteren passenden Zutaten gefunden.")
|
176 |
+
break
|
177 |
+
|
178 |
+
print(f"\nEndgültige Zutatenkombination: {current_combination}")
|
179 |
+
return current_combination
|
180 |
|
181 |
+
# --- Chef Transformer-spezifische Funktionen ---
|
182 |
|
183 |
def skip_special_tokens(text, special_tokens):
|
184 |
"""Entfernt spezielle Tokens aus dem Text"""
|
|
|
346 |
async def read_root():
|
347 |
return {"message": "AI Recipe Generator API is running (FastAPI only)!"}
|
348 |
|
|