nivakaran commited on
Commit
fe5bbf8
·
verified ·
1 Parent(s): c272330

Upload 6 files

Browse files
Files changed (6) hide show
  1. .env +2 -0
  2. labeled_image.jpg +0 -0
  3. max.pdf +0 -0
  4. max.py +503 -0
  5. max2.pdf +0 -0
  6. max3.pdf +0 -0
.env ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ GROQ_API_KEY="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"
2
+ HF_TOKEN="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"
labeled_image.jpg ADDED
max.pdf ADDED
File without changes
max.py ADDED
@@ -0,0 +1,503 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import json
4
+ import re
5
+ import logging
6
+ import tempfile
7
+ import base64
8
+ from uuid import uuid4
9
+ from typing import Optional, List
10
+ from fastapi import FastAPI, UploadFile, File, HTTPException
11
+ from fastapi.responses import JSONResponse
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from pydantic import BaseModel
14
+ from PIL import Image, UnidentifiedImageError
15
+ from dotenv import load_dotenv
16
+ from langchain.chains import create_history_aware_retriever, create_retrieval_chain
17
+ from langchain.chains.combine_documents import create_stuff_documents_chain
18
+ from langchain_community.chat_message_histories import ChatMessageHistory
19
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
20
+ from langchain_groq import ChatGroq
21
+ from langchain_huggingface import HuggingFaceEmbeddings
22
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
23
+ from langchain_community.document_loaders import PyPDFLoader
24
+ from langchain_chroma import Chroma
25
+ from langchain.tools import Tool
26
+
27
+ from core.predict import ImageClassifier # Assumed to be implemented
28
+
29
+ # Configure logging
30
+ logging.basicConfig(level=logging.INFO)
31
+ logger = logging.getLogger(__name__)
32
+
33
+ # Load environment variables
34
+ load_dotenv()
35
+ HF_TOKEN = os.getenv("HF_TOKEN")
36
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
37
+ MODEL_PATH = os.getenv("MODEL_PATH", os.path.join(os.getcwd(), "model", "best_model.pth"))
38
+ HOST = os.getenv("HOST", "0.0.0.0")
39
+ PORT = int(os.getenv("PORT", 5000))
40
+ PDF_PATH = os.getenv("PDF_PATH", "max3.pdf")
41
+
42
+ # Validate environment variables
43
+ if not all([HF_TOKEN, GROQ_API_KEY, MODEL_PATH, PDF_PATH]):
44
+ logger.error("Missing required environment variables")
45
+ raise RuntimeError("Environment variables not set")
46
+
47
+ # Initialize FastAPI app
48
+ app = FastAPI(
49
+ title="EcoHarvest Combined API",
50
+ description="API for food image classification and e-commerce assistance.",
51
+ version="1.0.0",
52
+ )
53
+
54
+ # Configure CORS
55
+ app.add_middleware(
56
+ CORSMiddleware,
57
+ allow_origins=["*"], # Restrict to specific origins in production
58
+ allow_credentials=True,
59
+ allow_methods=["GET", "POST"],
60
+ allow_headers=["*"],
61
+ )
62
+
63
+ # Constants
64
+ MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
65
+ ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png"}
66
+
67
+ # Food classification class names
68
+ class_name = {
69
+ 0: 'apple_pie',
70
+ 1: 'baby_back_ribs',
71
+ 2: 'baklava',
72
+ 3: 'beef_carpaccio',
73
+ 4: 'beef_tartare',
74
+ 5: 'beet_salad',
75
+ 6: 'beignets',
76
+ 7: 'bibimbap',
77
+ 8: 'bread_pudding',
78
+ 9: 'breakfast_burrito',
79
+ 10: 'bruschetta',
80
+ 11: 'caesar_salad',
81
+ 12: 'cannoli',
82
+ 13: 'caprese_salad',
83
+ 14: 'carrot_cake',
84
+ 15: 'ceviche',
85
+ 16: 'cheese_plate',
86
+ 17: 'cheesecake',
87
+ 18: 'chicken_curry',
88
+ 19: 'chicken_quesadilla',
89
+ 20: 'chicken_wings',
90
+ 21: 'chocolate_cake',
91
+ 22: 'chocolate_mousse',
92
+ 23: 'churros',
93
+ 24: 'clam_chowder',
94
+ 25: 'club_sandwich',
95
+ 26: 'crab_cakes',
96
+ 27: 'creme_brulee',
97
+ 28: 'croque_madame',
98
+ 29: 'cup_cakes',
99
+ 30: 'deviled_eggs',
100
+ 31: 'donuts',
101
+ 32: 'dumplings',
102
+ 33: 'edamame',
103
+ 34: 'eggs_benedict',
104
+ 35: 'escargots',
105
+ 36: 'falafel',
106
+ 37: 'filet_mignon',
107
+ 38: 'fish_and_chips',
108
+ 39: 'foie_gras',
109
+ 40: 'french_fries',
110
+ 41: 'french_onion_soup',
111
+ 42: 'french_toast',
112
+ 43: 'fried_calamari',
113
+ 44: 'fried_rice',
114
+ 45: 'frozen_yogurt',
115
+ 46: 'garlic_bread',
116
+ 47: 'gnocchi',
117
+ 48: 'greek_salad',
118
+ 49: 'grilled_cheese_sandwich',
119
+ 50: 'grilled_salmon',
120
+ 51: 'guacamole',
121
+ 52: 'gyoza',
122
+ 53: 'hamburger',
123
+ 54: 'hot_and_sour_soup',
124
+ 55: 'hot_dog',
125
+ 56: 'huevos_rancheros',
126
+ 57: 'hummus',
127
+ 58: 'ice_cream',
128
+ 59: 'lasagna',
129
+ 60: 'lobster_bisque',
130
+ 61: 'lobster_roll_sandwich',
131
+ 62: 'macaroni_and_cheese',
132
+ 63: 'macarons',
133
+ 64: 'miso_soup',
134
+ 65: 'mussels',
135
+ 66: 'nachos',
136
+ 67: 'omelette',
137
+ 68: 'onion_rings',
138
+ 69: 'oysters',
139
+ 70: 'pad_thai',
140
+ 71: 'paella',
141
+ 72: 'pancakes',
142
+ 73: 'panna_cotta',
143
+ 74: 'peking_duck',
144
+ 75: 'pho',
145
+ 76: 'pizza',
146
+ 77: 'pork_chop',
147
+ 78: 'poutine',
148
+ 79: 'prime_rib',
149
+ 80: 'pulled_pork_sandwich',
150
+ 81: 'ramen',
151
+ 82: 'ravioli',
152
+ 83: 'red_velvet_cake',
153
+ 84: 'risotto',
154
+ 85: 'samosa',
155
+ 86: 'sashimi',
156
+ 87: 'scallops',
157
+ 88: 'seaweed_salad',
158
+ 89: 'shrimp_and_grits',
159
+ 90: 'spaghetti_bolognese',
160
+ 91: 'spaghetti_carbonara',
161
+ 92: 'spring_rolls',
162
+ 93: 'steak',
163
+ 94: 'strawberry_shortcake',
164
+ 95: 'sushi',
165
+ 96: 'tacos',
166
+ 97: 'takoyaki',
167
+ 98: 'tiramisu',
168
+ 99: 'tuna_tartare',
169
+ 100: 'waffles'
170
+ }
171
+
172
+ # Initialize image classifier
173
+ try:
174
+ classifier = ImageClassifier(model_path=MODEL_PATH, class_name=class_name)
175
+ logger.info("Image classifier initialized successfully")
176
+ except Exception as e:
177
+ logger.error(f"Failed to load image classifier model: {str(e)}")
178
+ raise RuntimeError("Image classifier initialization failed")
179
+
180
+ # Initialize RAG components
181
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
182
+ llm = ChatGroq(model_name="Deepseek-R1-Distill-Llama-70b")
183
+ session_store = {}
184
+
185
+ def process_pdf(file_path: str):
186
+ try:
187
+ loader = PyPDFLoader(file_path)
188
+ documents = loader.load()
189
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=5000, chunk_overlap=500)
190
+ splits = text_splitter.split_documents(documents)
191
+ vectorstore = Chroma.from_documents(
192
+ documents=splits,
193
+ embedding=embeddings,
194
+ persist_directory="./max.db"
195
+ )
196
+ logger.info(f"PDF {file_path} processed successfully")
197
+ return vectorstore
198
+ except Exception as e:
199
+ logger.error(f"Failed to process PDF: {str(e)}")
200
+ raise RuntimeError("PDF processing failed")
201
+
202
+ # Initialize vectorstore
203
+ try:
204
+ vectorstore = process_pdf(PDF_PATH)
205
+ retriever = vectorstore.as_retriever()
206
+ logger.info("Vectorstore initialized successfully")
207
+ except Exception as e:
208
+ logger.error(f"Vectorstore initialization failed: {str(e)}")
209
+ raise RuntimeError("Vectorstore initialization failed")
210
+
211
+ # Define tools for e-commerce assistant
212
+ def get_customer_feedback(customerName: str, email: str, feedback: str) -> str:
213
+ if not all(isinstance(x, str) for x in [customerName, email, feedback]):
214
+ raise ValueError("All parameters must be strings")
215
+ if "@" not in email or "." not in email.split("@")[-1]:
216
+ raise ValueError("Invalid email format")
217
+ return f"Feedback from {customerName} ({email}) recorded: {feedback[:200]}"
218
+
219
+ def calculate_math(expression: str) -> str:
220
+ allowed_chars = set("0123456789+-*/(). ")
221
+ if not all(c in allowed_chars for c in expression):
222
+ raise ValueError("Invalid characters in expression")
223
+ try:
224
+ return str(eval(expression))
225
+ except Exception as e:
226
+ return f"Calculation error: {str(e)}"
227
+
228
+ get_customer_feedback_tool = Tool(
229
+ name="get_customer_feedback",
230
+ func=get_customer_feedback,
231
+ description="Record customer feedback. Parameters: customerName (string), email (string), feedback (string)."
232
+ )
233
+
234
+ calculate_math_tool = Tool(
235
+ name="calculate_math",
236
+ func=calculate_math,
237
+ description="Perform math calculations. Parameters: expression (string)."
238
+ )
239
+
240
+ tools = [get_customer_feedback_tool, calculate_math_tool]
241
+
242
+ # Pydantic models
243
+ class PredictionResponse(BaseModel):
244
+ label: str
245
+ category: str
246
+ output_image: Optional[str] = None # Base64-encoded output image
247
+
248
+ class QuestionRequest(BaseModel):
249
+ session_id: str
250
+ question: str
251
+
252
+ class QuestionResponse(BaseModel):
253
+ answer: str
254
+
255
+ class FoodCategory(BaseModel):
256
+ label: str
257
+ category: str
258
+
259
+ # Food categorization dictionary
260
+ food_categories = {
261
+ "Meals & Main Courses": [
262
+ 'bibimbap', 'breakfast_burrito', 'chicken_curry', 'chicken_quesadilla', 'clam_chowder',
263
+ 'club_sandwich', 'croque_madame', 'dumplings', 'eggs_benedict', 'filet_mignon', 'fish_and_chips',
264
+ 'french_onion_soup', 'gyoza', 'hamburger', 'hot_and_sour_soup', 'hot_dog', 'huevos_rancheros',
265
+ 'lasagna', 'lobster_bisque', 'lobster_roll_sandwich', 'macaroni_and_cheese', 'miso_soup',
266
+ 'omelette', 'pad_thai', 'paella', 'peking_duck', 'pho', 'pizza', 'pork_chop', 'prime_rib',
267
+ 'pulled_pork_sandwich', 'spaghetti_bolognese', 'spaghetti_carbonara', 'tacos', 'takoyaki'
268
+ ],
269
+ "Baked Goods & Pastries": [
270
+ 'apple_pie', 'baklava', 'beignets', 'bread_pudding', 'cannoli', 'carrot_cake', 'chocolate_cake',
271
+ 'churros', 'cup_cakes', 'donuts', 'french_toast', 'macarons', 'pancakes', 'red_velvet_cake',
272
+ 'strawberry_shortcake', 'waffles'
273
+ ],
274
+ "Appetizer & Side Dishes": [
275
+ 'beet_salad', 'bruschetta', 'caesar_salad', 'caprese_salad', 'ceviche', 'deviled_eggs', 'edamame',
276
+ 'falafel', 'french_fries', 'fried_calamari', 'garlic_bread', 'greek_salad', 'grilled_cheese_sandwich',
277
+ 'hummus', 'nachos', 'onion_rings', 'poutine', 'samosa', 'seaweed_salad', 'spring_rolls'
278
+ ],
279
+ "Meat & Seafood": [
280
+ 'baby_back_ribs', 'beef_carpaccio', 'beef_tartare', 'chicken_wings', 'crab_cakes', 'escargots',
281
+ 'foie_gras', 'grilled_salmon', 'mussels', 'oysters', 'sashimi', 'scallops', 'shrimp_and_grits',
282
+ 'steak', 'sushi', 'tuna_tartare'
283
+ ],
284
+ "Dairy Products & Desserts": [
285
+ 'cheese_plate', 'cheesecake', 'chocolate_mousse', 'creme_brulee', 'frozen_yogurt', 'ice_cream',
286
+ 'panna_cotta', 'tiramisu'
287
+ ],
288
+ "Rice Grains & Noodles": [
289
+ 'fried_rice', 'gnocchi', 'ravioli', 'risotto', 'ramen'
290
+ ],
291
+ "Beverages": [],
292
+ "Fruits & Vegetables": [],
293
+ "Sauce Condiments and Seasonings": ['guacamole' ],
294
+ }
295
+
296
+ # Image classification endpoints
297
+ @app.post(
298
+ "/predict",
299
+ response_model=PredictionResponse,
300
+ summary="Classify a food image",
301
+ description="Upload a food image (JPG/PNG) to classify it into one of 101 food categories and return its category."
302
+ )
303
+ async def predict_image(file: UploadFile = File(..., description="A food image in JPG/PNG format")):
304
+ logger.info(f"Received image file: {file.filename}")
305
+
306
+ # Validate file extension
307
+ ext = os.path.splitext(file.filename)[1].lower()
308
+ if ext not in ALLOWED_EXTENSIONS:
309
+ logger.warning(f"Invalid file extension: {ext}")
310
+ raise HTTPException(status_code=400, detail="Only JPG/PNG files are allowed")
311
+
312
+ # Validate file size
313
+ contents = await file.read()
314
+ if len(contents) > MAX_FILE_SIZE:
315
+ logger.warning(f"File size too large: {len(contents)} bytes")
316
+ raise HTTPException(status_code=400, detail="File size exceeds 5MB")
317
+
318
+ # Process image
319
+ try:
320
+ image = Image.open(io.BytesIO(contents)).convert("RGB")
321
+ except UnidentifiedImageError:
322
+ logger.error("Invalid image file")
323
+ raise HTTPException(status_code=400, detail="Invalid image file")
324
+ except Exception as e:
325
+ logger.error(f"Error processing image: {str(e)}")
326
+ raise HTTPException(status_code=500, detail="Error processing image")
327
+
328
+ # Predict
329
+ try:
330
+ # Create a temporary file using mkstemp
331
+ fd, temp_file_path = tempfile.mkstemp(suffix=".jpg")
332
+ try:
333
+ image.save(temp_file_path)
334
+ label, output_image_path = classifier.predict(temp_file_path)
335
+ finally:
336
+ os.close(fd)
337
+ if os.path.exists(temp_file_path):
338
+ os.remove(temp_file_path) # Clean up
339
+ except Exception as e:
340
+ logger.error(f"Prediction error: {str(e)}")
341
+ raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
342
+
343
+ # Determine category
344
+ category = next((cat for cat, foods in food_categories.items() if label in foods), "Uncategorized")
345
+
346
+ # Encode output image as base64 if available
347
+ output_image = None
348
+ if output_image_path and os.path.exists(output_image_path):
349
+ try:
350
+ with open(output_image_path, "rb") as f:
351
+ output_image = base64.b64encode(f.read()).decode("utf-8")
352
+ except Exception as e:
353
+ logger.warning(f"Failed to encode output image: {str(e)}")
354
+
355
+ logger.info(f"Prediction for {file.filename}: {label} (Category: {category})")
356
+ return PredictionResponse(label=label, category=category, output_image=output_image)
357
+
358
+ # E-commerce assistant endpoints
359
+ def execute_function_call(raw_response: str) -> str:
360
+ think_end = raw_response.find('</think>')
361
+ answer_section = raw_response[think_end + len('</think>'):].strip() if think_end != -1 else raw_response.strip()
362
+
363
+ function_calls = []
364
+ while '<function>' in answer_section:
365
+ start_idx = answer_section.find('<function>') + len('<function>')
366
+ end_idx = answer_section.find('</function>', start_idx) or len(answer_section)
367
+ function_call = answer_section[start_idx:end_idx].strip()
368
+ function_calls.append(function_call)
369
+ answer_section = answer_section[end_idx + len('</function>'):]
370
+
371
+ results = []
372
+ for call in function_calls:
373
+ try:
374
+ tool_name, param_str = call.split(':', 1)
375
+ params = json.loads(param_str.replace("'", '"').strip())
376
+ tool = next(t for t in tools if t.name == tool_name.strip())
377
+ result = tool.run(**params)
378
+ results.append(f"{tool_name.strip()} result: {result}")
379
+ except Exception as e:
380
+ results.append(f"Error in {call[:24]}: {str(e)}")
381
+
382
+ clean_answer = re.sub(r'<\/?function>', '', answer_section).strip()
383
+ final_answer = clean_answer + ('\n\n' + '\n'.join(results) if results else '')
384
+ return final_answer
385
+
386
+ @app.post(
387
+ "/ask",
388
+ response_model=QuestionResponse,
389
+ summary="Ask the e-commerce assistant",
390
+ description="Submit a question to the EcoHarvest assistant, which uses RAG and tools for feedback and calculations."
391
+ )
392
+ async def ask_question(request: QuestionRequest):
393
+ session_id = request.session_id
394
+ question = request.question
395
+ logger.info(f"Received question for session {session_id}: {question}")
396
+
397
+ try:
398
+ if session_id not in session_store:
399
+ session_store[session_id] = {
400
+ "history": ChatMessageHistory(),
401
+ "retriever": retriever
402
+ }
403
+
404
+ session = session_store[session_id]
405
+ history = session["history"]
406
+ last_messages = history.messages[-6:]
407
+
408
+ # RAG processing
409
+ contextualize_q_prompt = ChatPromptTemplate.from_messages([
410
+ ("system", "Rephrase questions considering chat history."),
411
+ MessagesPlaceholder("chat_history"),
412
+ ("human", "{input}")
413
+ ])
414
+
415
+ history_aware_retriever = create_history_aware_retriever(
416
+ llm, session["retriever"], contextualize_q_prompt
417
+ )
418
+
419
+ system_prompt = """You are Max, an e-commerce assistant. Strict rules:
420
+ You only provide answers based on the context provided about ecoHarvest or carry out the following functions:
421
+ **Available Functions:**
422
+ 1. calculate_math:
423
+ - Use for: Any mathematical calculations
424
+ - Parameters: "expression": "mathematical expression as string"
425
+ - Example: <function>calculate_math: {{"expression":"80*0.15"}} </function>
426
+
427
+ 2. get_customer_feedback:
428
+ - Use for: Recording customer reviews/feedback
429
+ - Parameters: "customerName": "string", "email": "string", "feedback": "string"
430
+ - Example: <function>get_customer_feedback: {{"customerName":"[customer name]","email":"[email]","feedback":"[customer feedback]"}} </function>
431
+
432
+ 3. Function calls MUST:
433
+ - Be wrapped in <function> tags
434
+ - Use EXACT parameter names
435
+ - Appear where their results should be used
436
+
437
+ 4. Never invent functions - only use the 2 listed above
438
+
439
+ 5. For ecoHarvest-related questions, use the context below:
440
+ {context}"""
441
+
442
+ qa_prompt = ChatPromptTemplate.from_messages([
443
+ ("system", system_prompt),
444
+ MessagesPlaceholder("chat_history"),
445
+ ("human", "{input}")
446
+ ])
447
+
448
+ question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
449
+ rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
450
+
451
+ # Get and process response
452
+ result = rag_chain.invoke({
453
+ "input": question,
454
+ "chat_history": last_messages
455
+ })
456
+ final_answer = execute_function_call(result["answer"])
457
+
458
+ # Update history
459
+ history.add_user_message(question)
460
+ history.add_ai_message(final_answer)
461
+
462
+ logger.info(f"Response for session {session_id}: {final_answer[:100]}...")
463
+ return QuestionResponse(answer=final_answer)
464
+
465
+ except Exception as e:
466
+ logger.error(f"Error processing question for session {session_id}: {str(e)}")
467
+ raise HTTPException(status_code=500, detail=f"Processing failed: {str(e)}")
468
+
469
+ # Food categorization endpoint
470
+ @app.get(
471
+ "/food/categories",
472
+ response_model=List[FoodCategory],
473
+ summary="Get food categories",
474
+ description="Returns a list of all food items and their respective categories."
475
+ )
476
+ async def get_food_categories():
477
+ logger.info("Received request for food categories")
478
+
479
+ # Create response list
480
+ categorized_foods = []
481
+ for label in class_name.values():
482
+ category = next((cat for cat, foods in food_categories.items() if label in foods), "Uncategorized")
483
+ categorized_foods.append(FoodCategory(label=label, category=category))
484
+
485
+ logger.info(f"Returning {len(categorized_foods)} categorized food items")
486
+ return categorized_foods
487
+
488
+ # Root endpoint
489
+ @app.get("/")
490
+ async def root():
491
+ return {
492
+ "message": "Welcome to the EcoHarvest Combined API",
493
+ "endpoints": {
494
+ "image_classification": "/predict",
495
+ "ecommerce_assistant": "/ask",
496
+ "food_categories": "/food/categories",
497
+ "docs": "/docs"
498
+ }
499
+ }
500
+
501
+ if __name__ == "__main__":
502
+ import uvicorn
503
+ uvicorn.run(app, host=HOST, port=PORT)
max2.pdf ADDED
Binary file (16.8 kB). View file
 
max3.pdf ADDED
Binary file (18.5 kB). View file