Spaces:
Sleeping
Sleeping
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 |
|
|
|