Update app.py
Browse files
app.py
CHANGED
@@ -2,12 +2,10 @@ import streamlit as st
|
|
2 |
import pandas as pd
|
3 |
import chromadb
|
4 |
from sentence_transformers import SentenceTransformer
|
5 |
-
from transformers import pipeline,
|
6 |
from PIL import Image
|
7 |
from io import BytesIO
|
8 |
import requests
|
9 |
-
from huggingface_hub import login
|
10 |
-
|
11 |
|
12 |
# --- 1. Load Recipes Dataset ---
|
13 |
@st.cache_data
|
@@ -29,7 +27,7 @@ recipes_df = load_recipes()
|
|
29 |
# --- 2. Load SentenceTransformer Model ---
|
30 |
@st.cache_resource
|
31 |
def load_embedding_model():
|
32 |
-
return SentenceTransformer("all-
|
33 |
|
34 |
embedding_model = load_embedding_model()
|
35 |
|
@@ -43,7 +41,7 @@ def get_sentence_transformer_embeddings(text):
|
|
43 |
|
44 |
try:
|
45 |
existing_data = collection.get()
|
46 |
-
existing_ids = set(existing_data
|
47 |
except Exception as e:
|
48 |
st.error(f"⚠ ChromaDB Error: {e}")
|
49 |
existing_ids = set()
|
@@ -54,78 +52,61 @@ for index, row in recipes_df.iterrows():
|
|
54 |
continue
|
55 |
embedding = get_sentence_transformer_embeddings(row["combined_text"])
|
56 |
if embedding:
|
57 |
-
collection.add(
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
# --- 5. Retrieve Similar Recipes ---
|
60 |
def retrieve_recipes(query, top_k=3):
|
61 |
query_embedding = get_sentence_transformer_embeddings(query)
|
62 |
results = collection.query(query_embeddings=[query_embedding], n_results=top_k)
|
63 |
-
|
64 |
-
if results and
|
65 |
recipe_indices = [int(id) for id in results["ids"][0] if id.isdigit()]
|
66 |
return recipes_df.iloc[recipe_indices] if recipe_indices else None
|
67 |
return None
|
68 |
|
69 |
-
|
70 |
hf_token = st.secrets["key"]
|
71 |
if hf_token is None:
|
72 |
raise ValueError("Hugging Face token is missing. Add it as a secret in your Space.")
|
73 |
-
login(token=hf_token)
|
74 |
-
|
75 |
-
# --- 6. Load
|
76 |
-
@st.cache_resource
|
77 |
@st.cache_resource
|
78 |
-
def
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
mistral_model = load_mistral_model()
|
86 |
-
|
87 |
-
|
88 |
-
# --- 7. Answer Question Using Mistral ---
|
89 |
-
def answer_question(query, context=""):
|
90 |
-
greetings = ["hi", "hello", "hey", "greetings", "how are you", "what's up"]
|
91 |
-
query_cleaned = query.lower().strip()
|
92 |
-
|
93 |
-
# Handle greetings
|
94 |
-
if query_cleaned in greetings:
|
95 |
-
return "Hello! I'm here to assist with recipes and food-related questions. 🍽️ What would you like to know?"
|
96 |
-
|
97 |
-
# Retrieve relevant recipe
|
98 |
-
related_recipes = retrieve_recipes(query, top_k=1)
|
99 |
-
if related_recipes is None or related_recipes.empty:
|
100 |
-
return "I specialize in recipes! 🍽️ Feel free to ask me about ingredients, cooking methods, or meal ideas. 😊"
|
101 |
-
|
102 |
-
# If found, use its instructions as context
|
103 |
-
context = related_recipes.iloc[0]['instructions']
|
104 |
-
prompt = f"Context: {context}\n\nQuestion: {query}\nAnswer:"
|
105 |
-
|
106 |
-
response = mistral_model(prompt)
|
107 |
-
if isinstance(response, list) and response:
|
108 |
-
return response[0].get("generated_text", "I'm not sure, but I can help with recipes! 😊").strip()
|
109 |
-
|
110 |
-
return "I'm not sure, but I can help with recipes! 😊"
|
111 |
-
|
112 |
-
# --- 8. Classify Query Type ---
|
113 |
@st.cache_resource
|
114 |
def load_classifier():
|
115 |
-
return pipeline("zero-shot-classification", model="facebook/bart-large-mnli"
|
116 |
|
117 |
classifier = load_classifier()
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
return "Recipe Search"
|
123 |
-
|
124 |
-
labels = ["Q&A", "Recipe Search"]
|
125 |
result = classifier(query, candidate_labels=labels, multi_label=False)
|
126 |
-
return result
|
|
|
|
|
|
|
127 |
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
def display_image(image_url, recipe_name):
|
130 |
try:
|
131 |
if not isinstance(image_url, str) or not image_url.startswith("http"):
|
@@ -140,7 +121,7 @@ def display_image(image_url, recipe_name):
|
|
140 |
st.image(placeholder_url, caption=recipe_name, use_container_width=True)
|
141 |
|
142 |
# --- 10. Streamlit UI ---
|
143 |
-
st.title("🍽️ AI Recipe & Q&A Assistant
|
144 |
|
145 |
user_query = st.text_input("Enter your question or recipe search query:", "", key="main_query_input")
|
146 |
|
@@ -149,31 +130,22 @@ if "retrieved_recipes" not in st.session_state:
|
|
149 |
|
150 |
if st.button("Ask AI"):
|
151 |
if user_query:
|
152 |
-
#
|
153 |
-
|
154 |
-
|
155 |
-
st.
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
elif intent == "Recipe Search":
|
167 |
-
retrieved_recipes = retrieve_recipes(user_query)
|
168 |
-
if retrieved_recipes is not None and not retrieved_recipes.empty:
|
169 |
-
st.session_state["retrieved_recipes"] = retrieved_recipes
|
170 |
-
st.subheader("🍴 Found Recipes:")
|
171 |
-
for index, recipe in retrieved_recipes.iterrows():
|
172 |
-
st.markdown(f"### {recipe['title']}")
|
173 |
-
st.write(f"**Ingredients:** {recipe['ingredients']}")
|
174 |
-
st.write(f"**Instructions:** {recipe['instructions']}")
|
175 |
-
display_image(recipe.get('img_src', ''), recipe['title'])
|
176 |
-
else:
|
177 |
-
st.warning("⚠️ No relevant recipes found.")
|
178 |
else:
|
179 |
-
st.warning("
|
|
|
|
|
|
|
|
2 |
import pandas as pd
|
3 |
import chromadb
|
4 |
from sentence_transformers import SentenceTransformer
|
5 |
+
from transformers import pipeline, AutoModelForQuestionAnswering, AutoTokenizer
|
6 |
from PIL import Image
|
7 |
from io import BytesIO
|
8 |
import requests
|
|
|
|
|
9 |
|
10 |
# --- 1. Load Recipes Dataset ---
|
11 |
@st.cache_data
|
|
|
27 |
# --- 2. Load SentenceTransformer Model ---
|
28 |
@st.cache_resource
|
29 |
def load_embedding_model():
|
30 |
+
return SentenceTransformer("all-MiniLM-L6-v2") # Smaller & optimized model
|
31 |
|
32 |
embedding_model = load_embedding_model()
|
33 |
|
|
|
41 |
|
42 |
try:
|
43 |
existing_data = collection.get()
|
44 |
+
existing_ids = set(existing_data.get("ids", [])) # Use `.get()` for safety
|
45 |
except Exception as e:
|
46 |
st.error(f"⚠ ChromaDB Error: {e}")
|
47 |
existing_ids = set()
|
|
|
52 |
continue
|
53 |
embedding = get_sentence_transformer_embeddings(row["combined_text"])
|
54 |
if embedding:
|
55 |
+
collection.add(
|
56 |
+
embeddings=[embedding],
|
57 |
+
documents=[row["combined_text"]],
|
58 |
+
ids=[recipe_id],
|
59 |
+
metadatas=[{"title": row["title"], "ingredients": row["ingredients"], "instructions": row["instructions"], "img_src": row["img_src"]}]
|
60 |
+
)
|
61 |
|
62 |
# --- 5. Retrieve Similar Recipes ---
|
63 |
def retrieve_recipes(query, top_k=3):
|
64 |
query_embedding = get_sentence_transformer_embeddings(query)
|
65 |
results = collection.query(query_embeddings=[query_embedding], n_results=top_k)
|
66 |
+
|
67 |
+
if results and results.get("ids"):
|
68 |
recipe_indices = [int(id) for id in results["ids"][0] if id.isdigit()]
|
69 |
return recipes_df.iloc[recipe_indices] if recipe_indices else None
|
70 |
return None
|
71 |
|
|
|
72 |
hf_token = st.secrets["key"]
|
73 |
if hf_token is None:
|
74 |
raise ValueError("Hugging Face token is missing. Add it as a secret in your Space.")
|
75 |
+
login(token=hf_token)
|
76 |
+
|
77 |
+
# --- 6. Load LLM Model (Better Model for Generation) ---
|
|
|
78 |
@st.cache_resource
|
79 |
+
def load_llm_model():
|
80 |
+
return pipeline("text-generation", model="mistralai/Mistral-7B-Instruct-v0.3")
|
81 |
+
|
82 |
+
llm_model = load_llm_model()
|
83 |
+
|
84 |
+
# --- 7. Load Classifier ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
@st.cache_resource
|
86 |
def load_classifier():
|
87 |
+
return pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
|
88 |
|
89 |
classifier = load_classifier()
|
90 |
|
91 |
+
# --- 8. Query Classification ---
|
92 |
+
def is_food_related(query):
|
93 |
+
labels = ["Food & Cooking", "General Knowledge", "Science", "Technology"]
|
|
|
|
|
|
|
94 |
result = classifier(query, candidate_labels=labels, multi_label=False)
|
95 |
+
return result["labels"][0] == "Food & Cooking"
|
96 |
+
|
97 |
+
def generate_recipe(query):
|
98 |
+
related_recipes = retrieve_recipes(query, top_k=2)
|
99 |
|
100 |
+
if not related_recipes or related_recipes.empty:
|
101 |
+
return "I couldn't find a matching recipe, but let me create one for you!"
|
102 |
+
|
103 |
+
base_text = "\n".join([f"- {r['title']}: {r['ingredients']}" for _, r in related_recipes.iterrows()])
|
104 |
+
full_prompt = f"Create a unique recipe using these ingredients: {query}.\n\nReference recipes:\n{base_text}"
|
105 |
+
|
106 |
+
response = llm_model(full_prompt, max_length=200, num_return_sequences=1)
|
107 |
+
return response[0]["generated_text"]
|
108 |
+
|
109 |
+
# --- 9. Display Image Function ---
|
110 |
def display_image(image_url, recipe_name):
|
111 |
try:
|
112 |
if not isinstance(image_url, str) or not image_url.startswith("http"):
|
|
|
121 |
st.image(placeholder_url, caption=recipe_name, use_container_width=True)
|
122 |
|
123 |
# --- 10. Streamlit UI ---
|
124 |
+
st.title("🍽️ AI Recipe & Q&A Assistant")
|
125 |
|
126 |
user_query = st.text_input("Enter your question or recipe search query:", "", key="main_query_input")
|
127 |
|
|
|
130 |
|
131 |
if st.button("Ask AI"):
|
132 |
if user_query:
|
133 |
+
if is_food_related(user_query): # Check if it's food-related
|
134 |
+
st.subheader("🤖 AI Response:")
|
135 |
+
response = generate_recipe(user_query)
|
136 |
+
st.write(response)
|
137 |
+
|
138 |
+
retrieved_recipes = retrieve_recipes(user_query)
|
139 |
+
if retrieved_recipes is not None and not retrieved_recipes.empty:
|
140 |
+
st.session_state["retrieved_recipes"] = retrieved_recipes
|
141 |
+
st.subheader("🍴 Found Recipes:")
|
142 |
+
for _, recipe in retrieved_recipes.iterrows():
|
143 |
+
st.markdown(f"### {recipe['title']}")
|
144 |
+
st.write(f"**Ingredients:** {recipe['ingredients']}")
|
145 |
+
st.write(f"**Instructions:** {recipe['instructions']}")
|
146 |
+
display_image(recipe.get('img_src', ''), recipe['title'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
else:
|
148 |
+
st.warning("⚠️ No relevant recipes found.")
|
149 |
+
else:
|
150 |
+
st.subheader("🤖 AI Answer:")
|
151 |
+
st.write("I specialize in food-related topics! 🍽️ Please ask me about recipes, cooking methods, or ingredients.")
|