TimInf commited on
Commit
3df2cca
·
verified ·
1 Parent(s): 73b9c83

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -25
app.py CHANGED
@@ -1,37 +1,219 @@
 
 
 
 
 
1
  from fastapi import FastAPI
2
  from fastapi.responses import JSONResponse
3
- from pydantic import BaseModel # Bleibt, da FastAPI es für Request Body Parsing nutzt
4
 
5
- # Deine FastAPI-Instanz
6
- app = FastAPI(title="Minimal FastAPI Test App")
 
 
 
7
 
8
- # Eine einfache Request-Modell-Klasse (auch wenn wir sie hier nicht wirklich nutzen,
9
- # zeigt es, dass Pydantic funktioniert)
10
- class TestRequest(BaseModel):
11
- message: str = "Hello"
12
 
13
- # Ein einfacher "Hello World"-Endpunkt, der auf POST-Anfragen reagiert
14
- @app.post("/test")
15
- async def test_api_endpoint(request_data: TestRequest):
 
 
 
 
 
 
 
 
 
16
  """
17
- Ein einfacher Test-Endpunkt.
18
- Gibt eine Begrüßungsnachricht zurück, die die empfangene Nachricht enthält.
 
19
  """
20
- print(f"INFO: Received test request with message: {request_data.message}") # Log für den Space
21
- return JSONResponse(content={"status": "success", "message": f"Hello from FastAPI! You sent: {request_data.message}"})
22
 
23
- # Ein optionaler Root-Endpunkt (oft gut für Health-Checks)
24
- @app.get("/")
25
- async def read_root():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  """
27
- Root-Endpunkt für grundlegenden Health-Check.
28
  """
29
- return {"message": "FastAPI is running!"}
 
 
30
 
31
- print("INFO: FastAPI application script finished execution and defined 'app' variable.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- # Dieser Block wird in Hugging Face Spaces nicht direkt ausgeführt, da Uvicorn
34
- # die App direkt lädt, aber er ist für lokale Tests nützlich.
35
- if __name__ == "__main__":
36
- import uvicorn
37
- uvicorn.run(app, host="0.0.0.0", port=7860) # Lokaler Port 7860, wie in Space
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import FlaxAutoModelForSeq2SeqLM, AutoTokenizer # AutoModel entfernt
2
+ import torch # Beibehalten
3
+ import numpy as np # Beibehalten
4
+ import random
5
+ import json
6
  from fastapi import FastAPI
7
  from fastapi.responses import JSONResponse
8
+ from pydantic import BaseModel
9
 
10
+ # Lade RecipeBERT Modell (KOMPLETT ENTFERNT für diesen Schritt)
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
+ # bert_model.eval()
15
 
16
+ # Lade T5 Rezeptgenerierungsmodell
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
+ # Token Mapping für die T5 Modell-Ausgabe
22
+ special_tokens = t5_tokenizer.all_special_token
23
+ tokens_map = {
24
+ "<sep>": "--",
25
+ "<section>": "\n"
26
+ }
27
+
28
+ # --- RecipeBERT-spezifische Funktionen sind entfernt oder vereinfacht ---
29
+ # get_embedding, average_embedding, get_cosine_similarity, get_combined_scores sind entfernt.
30
+
31
+ # find_best_ingredients (modifiziert, um KEINE Embeddings zu nutzen)
32
+ def find_best_ingredients(required_ingredients, available_ingredients, max_ingredients=6, avg_weight=0.6):
33
  """
34
+ Findet die besten Zutaten. Für diesen einfachen Test wird nur
35
+ die Liste der benötigten Zutaten um zufällig ausgewählte
36
+ verfügbare Zutaten ergänzt, OHNE Embeddings zu nutzen.
37
  """
38
+ required_ingredients = list(set(required_ingredients))
39
+ available_ingredients = list(set([i for i in available_ingredients if i not in required_ingredients]))
40
 
41
+ # Sonderfall: Wenn keine benötigten Zutaten vorhanden sind, wähle zufällig eine aus den verfügbaren Zutaten
42
+ if not required_ingredients and available_ingredients:
43
+ random_ingredient = random.choice(available_ingredients)
44
+ required_ingredients = [random_ingredient]
45
+ available_ingredients = [i for i in available_ingredients if i != random_ingredient]
46
+
47
+ # Wenn bereits maximale Kapazität erreicht ist
48
+ if len(required_ingredients) >= max_ingredients:
49
+ return required_ingredients[:max_ingredients]
50
+
51
+ # Wenn keine zusätzlichen Zutaten verfügbar sind
52
+ if not available_ingredients:
53
+ return required_ingredients
54
+
55
+ # Füge zufällig weitere Zutaten hinzu, bis max_ingredients erreicht ist
56
+ current_ingredients = required_ingredients.copy()
57
+ num_to_add = min(max_ingredients - len(current_ingredients), len(available_ingredients))
58
+
59
+ # Wähle zufällig aus den verfügbaren Zutaten
60
+ selected_from_available = random.sample(available_ingredients, num_to_add)
61
+ current_ingredients.extend(selected_from_available)
62
+
63
+ return current_ingredients
64
+
65
+
66
+ def skip_special_tokens(text, special_tokens):
67
+ """Entfernt spezielle Tokens aus dem Text"""
68
+ for token in special_tokens:
69
+ text = text.replace(token, "")
70
+ return text
71
+
72
+ def target_postprocessing(texts, special_tokens):
73
+ """Post-processed generierten Text"""
74
+ if not isinstance(texts, list):
75
+ texts = [texts]
76
+ new_texts = []
77
+ for text in texts:
78
+ text = skip_special_tokens(text, special_tokens)
79
+ for k, v in tokens_map.items():
80
+ text = text.replace(k, v)
81
+ new_texts.append(text)
82
+ return new_texts
83
+
84
+ def validate_recipe_ingredients(recipe_ingredients, expected_ingredients, tolerance=0):
85
  """
86
+ Validiert, ob das Rezept ungefähr die erwarteten Zutaten enthält.
87
  """
88
+ recipe_count = len([ing for ing in recipe_ingredients if ing and ing.strip()])
89
+ expected_count = len(expected_ingredients)
90
+ return abs(recipe_count - expected_count) == tolerance
91
 
92
+ def generate_recipe_with_t5(ingredients_list, max_retries=5):
93
+ """Generiert ein Rezept mit dem T5 Rezeptgenerierungsmodell mit Validierung."""
94
+ original_ingredients = ingredients_list.copy()
95
+ for attempt in range(max_retries):
96
+ try:
97
+ if attempt > 0:
98
+ current_ingredients = original_ingredients.copy()
99
+ random.shuffle(current_ingredients)
100
+ else:
101
+ current_ingredients = ingredients_list
102
+ ingredients_string = ", ".join(current_ingredients)
103
+ prefix = "items: "
104
+ generation_kwargs = {
105
+ "max_length": 512,
106
+ "min_length": 64,
107
+ "do_sample": True,
108
+ "top_k": 60,
109
+ "top_p": 0.95
110
+ }
111
+ inputs = t5_tokenizer(
112
+ prefix + ingredients_string,
113
+ max_length=256,
114
+ padding="max_length",
115
+ truncation=True,
116
+ return_tensors="jax"
117
+ )
118
+ output_ids = t5_model.generate(
119
+ input_ids=inputs.input_ids,
120
+ attention_mask=inputs.attention_mask,
121
+ **generation_kwargs
122
+ )
123
+ generated = output_ids.sequences
124
+ generated_text = target_postprocessing(
125
+ t5_tokenizer.batch_decode(generated, skip_special_tokens=False),
126
+ special_tokens
127
+ )[0]
128
+ recipe = {}
129
+ sections = generated_text.split("\n")
130
+ for section in sections:
131
+ section = section.strip()
132
+ if section.startswith("title:"):
133
+ recipe["title"] = section.replace("title:", "").strip().capitalize()
134
+ elif section.startswith("ingredients:"):
135
+ ingredients_text = section.replace("ingredients:", "").strip()
136
+ recipe["ingredients"] = [item.strip().capitalize() for item in ingredients_text.split("--") if item.strip()]
137
+ elif section.startswith("directions:"):
138
+ directions_text = section.replace("directions:", "").strip()
139
+ recipe["directions"] = [step.strip().capitalize() for step in directions_text.split("--") if step.strip()]
140
+ if "title" not in recipe:
141
+ recipe["title"] = f"Rezept mit {', '.join(current_ingredients[:3])}"
142
+ if "ingredients" not in recipe:
143
+ recipe["ingredients"] = current_ingredients
144
+ if "directions" not in recipe:
145
+ recipe["directions"] = ["Keine Anweisungen generiert"]
146
+ if validate_recipe_ingredients(recipe["ingredients"], original_ingredients):
147
+ return recipe
148
+ else:
149
+ if attempt == max_retries - 1:
150
+ return recipe
151
+ except Exception as e:
152
+ if attempt == max_retries - 1:
153
+ return {
154
+ "title": f"Rezept mit {original_ingredients[0] if original_ingredients else 'Zutaten'}",
155
+ "ingredients": original_ingredients,
156
+ "directions": ["Fehler beim Generieren der Rezeptanweisungen"]
157
+ }
158
+ return {
159
+ "title": f"Rezept mit {original_ingredients[0] if original_ingredients else 'Zutaten'}",
160
+ "ingredients": original_ingredients,
161
+ "directions": ["Fehler beim Generieren der Rezeptanweisungen"]
162
+ }
163
 
164
+ def process_recipe_request_logic(required_ingredients, available_ingredients, max_ingredients, max_retries):
165
+ """
166
+ Kernlogik zur Verarbeitung einer Rezeptgenerierungsanfrage.
167
+ """
168
+ if not required_ingredients and not available_ingredients:
169
+ return {"error": "Keine Zutaten angegeben"}
170
+ try:
171
+ # Hier wird die vereinfachte find_best_ingredients verwendet, die KEINE Embeddings nutzt.
172
+ optimized_ingredients = find_best_ingredients(
173
+ required_ingredients, available_ingredients, max_ingredients
174
+ )
175
+ recipe = generate_recipe_with_t5(optimized_ingredients, max_retries)
176
+ result = {
177
+ 'title': recipe['title'],
178
+ 'ingredients': recipe['ingredients'],
179
+ 'directions': recipe['directions'],
180
+ 'used_ingredients': optimized_ingredients
181
+ }
182
+ return result
183
+ except Exception as e:
184
+ return {"error": f"Fehler bei der Rezeptgenerierung: {str(e)}"}
185
+
186
+ # --- FastAPI-Implementierung ---
187
+ app = FastAPI(title="AI Recipe Generator API") # Deine FastAPI-Instanz
188
+
189
+ class RecipeRequest(BaseModel):
190
+ required_ingredients: list[str] = []
191
+ available_ingredients: list[str] = []
192
+ max_ingredients: int = 7
193
+ max_retries: int = 5
194
+ ingredients: list[str] = [] # Für Abwärtskompatibilität
195
+
196
+ @app.post("/generate_recipe") # Der API-Endpunkt für Flutter
197
+ async def generate_recipe_api(request_data: RecipeRequest):
198
+ """
199
+ Standard-REST-API-Endpunkt für die Flutter-App.
200
+ Nimmt direkt JSON-Daten an und gibt direkt JSON zurück.
201
+ """
202
+ final_required_ingredients = request_data.required_ingredients
203
+ if not final_required_ingredients and request_data.ingredients:
204
+ final_required_ingredients = request_data.ingredients
205
+
206
+ result_dict = process_recipe_request_logic(
207
+ final_required_ingredients,
208
+ request_data.available_ingredients,
209
+ request_data.max_ingredients,
210
+ request_data.max_retries
211
+ )
212
+ return JSONResponse(content=result_dict)
213
+
214
+ # Optionaler Root-Endpunkt für Health-Checks
215
+ @app.get("/")
216
+ async def read_root():
217
+ return {"message": "AI Recipe Generator API is running (T5 only)!"} # Angepasste Nachricht
218
+
219
+ print("INFO: FastAPI application script finished execution and defined 'app' variable.")