AlekhyaC2005 commited on
Commit
46fb1fc
·
1 Parent(s): 7b9b535

first commit

Browse files
.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------
2
+ # Environment variables
3
+ # -----------------------------
4
+ .env
5
+
6
+ # -----------------------------
7
+ # Python cache files
8
+ # -----------------------------
9
+ __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+
13
+ # -----------------------------
14
+ # Virtual environments
15
+ # -----------------------------
16
+ myvenv/
17
+ env/
18
+ venv/
19
+ ENV/
20
+
21
+ # -----------------------------
22
+ # VS Code / IDE
23
+ # -----------------------------
24
+ .vscode/
25
+ .idea/
26
+
27
+ # -----------------------------
28
+ # Test Files
29
+ # -----------------------------
30
+ test.py
Dockerfile ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =========================================================
2
+ # BASE IMAGE
3
+ # =========================================================
4
+
5
+ FROM python:3.11-slim
6
+
7
+ # =========================================================
8
+ # ENV VARIABLES
9
+ # =========================================================
10
+
11
+ ENV PYTHONDONTWRITEBYTECODE=1
12
+ ENV PYTHONUNBUFFERED=1
13
+
14
+ # =========================================================
15
+ # WORK DIRECTORY
16
+ # =========================================================
17
+
18
+ WORKDIR /app
19
+
20
+ # =========================================================
21
+ # INSTALL SYSTEM DEPENDENCIES
22
+ # =========================================================
23
+
24
+ RUN apt-get update && apt-get install -y \
25
+ gcc \
26
+ g++ \
27
+ && rm -rf /var/lib/apt/lists/*
28
+
29
+ # =========================================================
30
+ # COPY REQUIREMENTS
31
+ # =========================================================
32
+
33
+ COPY requirements.txt .
34
+
35
+ # =========================================================
36
+ # INSTALL PYTHON DEPENDENCIES
37
+ # =========================================================
38
+
39
+ RUN pip install --upgrade pip
40
+
41
+ RUN pip install --no-cache-dir -r requirements.txt
42
+
43
+ # =========================================================
44
+ # COPY PROJECT FILES
45
+ # =========================================================
46
+
47
+ COPY . .
48
+
49
+ # =========================================================
50
+ # EXPOSE PORT
51
+ # =========================================================
52
+
53
+ EXPOSE 7860
54
+
55
+ # =========================================================
56
+ # START FASTAPI
57
+ # =========================================================
58
+
59
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/core/config.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/core/config.py
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ HF_TOKEN = os.getenv("HF_TOKEN")
8
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
9
+ QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")
10
+ QDRANT_URL = os.getenv("QDRANT_URL")
app/core/llm.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from openai import AsyncOpenAI
2
+ from app.core.config import HF_TOKEN
3
+
4
+ def get_llm():
5
+
6
+ return AsyncOpenAI(
7
+ base_url="https://router.huggingface.co/v1",
8
+ api_key=HF_TOKEN
9
+ )
app/main.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+
3
+ from app.routes.predict import router as predict_router
4
+ from app.routes.chat import router as chat_router
5
+
6
+ app = FastAPI(
7
+ title="NeuroFlora API"
8
+ )
9
+
10
+ app.include_router(predict_router)
11
+ app.include_router(chat_router)
12
+
13
+ @app.get("/")
14
+
15
+ async def root():
16
+
17
+ return {
18
+ "message": "NeuroFlora API Running"
19
+ }
app/memory/qdrant_memory.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from qdrant_client import QdrantClient
2
+ from mem0 import Memory
3
+
4
+ from app.core.config import *
5
+
6
+
7
+ # =========================================================
8
+ # MEM0 + QDRANT CONFIG
9
+ # =========================================================
10
+
11
+ config = {
12
+ "version": "v1.1",
13
+
14
+ "llm": {
15
+ "provider": "gemini",
16
+ "config": {
17
+ "api_key": GEMINI_API_KEY,
18
+ "model": "gemini-2.5-flash"
19
+ }
20
+ },
21
+
22
+ "embedder": {
23
+ "provider": "gemini",
24
+ "config": {
25
+ "api_key": GEMINI_API_KEY,
26
+ "model": "gemini-embedding-001"
27
+ }
28
+ },
29
+
30
+ "vector_store": {
31
+ "provider": "qdrant",
32
+ "config": {
33
+ "url": QDRANT_URL,
34
+ "api_key": QDRANT_API_KEY,
35
+ "collection_name": "mem0_memory",
36
+ "embedding_model_dims": 768
37
+ }
38
+ }
39
+ }
40
+
41
+ mem_client = Memory.from_config(config)
42
+
43
+ # =========================================================
44
+ # SEARCH MEMORY
45
+ # =========================================================
46
+
47
+ def search_memory(user_id, user_query):
48
+
49
+ try:
50
+
51
+ results = mem_client.search(
52
+ query=user_query,
53
+ filters={"user_id": user_id}
54
+ )
55
+
56
+ memories = [
57
+ f"ID:{mem.get('id')}\nMemory:{mem.get('memory')}"
58
+ for mem in results.get("results", [])
59
+ ]
60
+
61
+ return memories
62
+
63
+ except Exception as e:
64
+
65
+ print(f"[MEMORY SEARCH ERROR]: {e}")
66
+
67
+ return []
68
+
69
+ # =========================================================
70
+ # ADD MEMORY
71
+ # =========================================================
72
+
73
+ def add_memory(user_id, user_query, ai_response):
74
+
75
+ try:
76
+
77
+ mem_client.add(
78
+ user_id=user_id,
79
+ messages=[
80
+ {
81
+ "role": "user",
82
+ "content": user_query
83
+ },
84
+ {
85
+ "role": "assistant",
86
+ "content": ai_response
87
+ }
88
+ ]
89
+ )
90
+
91
+ except Exception as e:
92
+
93
+ print(f"[MEMORY ADD ERROR]: {e}")
app/model/model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e4ad08428b471d099fe46c5422d4740ab6cfe11c7f97536faf4f9c73110cfa19
3
+ size 243992671
app/model/predictor.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torchvision import transforms
4
+ from torchvision.models import efficientnet_v2_s
5
+ from PIL import Image
6
+ from collections import OrderedDict
7
+
8
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
9
+
10
+ MODEL_PATH = "app/model/model.pth"
11
+ IMG_SIZE = 224
12
+
13
+ checkpoint = torch.load(
14
+ MODEL_PATH,
15
+ map_location=DEVICE,
16
+ weights_only=False
17
+ )
18
+
19
+ class_names = checkpoint["class_names"]
20
+ num_classes = len(class_names)
21
+
22
+ model = efficientnet_v2_s(weights=None)
23
+
24
+ in_features = model.classifier[1].in_features
25
+
26
+ model.classifier = nn.Sequential(
27
+ nn.Dropout(p=0.3, inplace=True),
28
+ nn.Linear(in_features, num_classes),
29
+ )
30
+
31
+ state_dict = checkpoint["model_state_dict"]
32
+
33
+ new_state_dict = OrderedDict()
34
+
35
+ for k, v in state_dict.items():
36
+ name = k.replace("module.", "")
37
+ new_state_dict[name] = v
38
+
39
+ if "n_averaged" in new_state_dict:
40
+ del new_state_dict["n_averaged"]
41
+
42
+ model.load_state_dict(new_state_dict)
43
+
44
+ model.to(DEVICE)
45
+ model.eval()
46
+
47
+ transform = transforms.Compose([
48
+ transforms.Resize((IMG_SIZE, IMG_SIZE)),
49
+ transforms.ToTensor(),
50
+ transforms.Normalize(
51
+ [0.485, 0.456, 0.406],
52
+ [0.229, 0.224, 0.225]
53
+ ),
54
+ ])
55
+
56
+ def predict_image(image):
57
+
58
+ image = image.convert("RGB")
59
+
60
+ image_tensor = transform(image).unsqueeze(0).to(DEVICE)
61
+
62
+ with torch.no_grad():
63
+
64
+ outputs = model(image_tensor)
65
+
66
+ probabilities = torch.softmax(outputs, dim=1)
67
+
68
+ confidence, predicted = torch.max(probabilities, 1)
69
+
70
+ return {
71
+ "prediction": class_names[predicted.item()],
72
+ "confidence": round(confidence.item() * 100, 2)
73
+ }
app/routes/chat.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from fastapi.responses import StreamingResponse
3
+ from pydantic import BaseModel
4
+
5
+ from app.services.chat_service import generate_response
6
+ from app.utils.streaming import stream_response
7
+
8
+ router = APIRouter()
9
+
10
+ class ChatRequest(BaseModel):
11
+ user_id: str
12
+ message: str
13
+
14
+ @router.post("/chat")
15
+
16
+ async def chat(request: ChatRequest):
17
+
18
+ response = await generate_response(
19
+ request.user_id,
20
+ request.message
21
+ )
22
+
23
+ return StreamingResponse(
24
+ stream_response(
25
+ response,
26
+ request.user_id,
27
+ request.message
28
+ ),
29
+ media_type="text/plain"
30
+ )
app/routes/predict.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File
2
+ from PIL import Image
3
+ import io
4
+
5
+ from app.model.predictor import predict_image
6
+
7
+ router = APIRouter()
8
+
9
+ @router.post("/predict")
10
+
11
+ async def predict(
12
+ file: UploadFile = File(...)
13
+ ):
14
+
15
+ image_bytes = await file.read()
16
+
17
+ image = Image.open(
18
+ io.BytesIO(image_bytes)
19
+ )
20
+
21
+ result = predict_image(image)
22
+
23
+ return result
app/services/chat_service.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.core.llm import get_llm
2
+ from app.memory.qdrant_memory import search_memory
3
+
4
+ async def generate_response(
5
+ user_id,
6
+ user_query
7
+ ):
8
+
9
+ memories = search_memory(
10
+ user_id,
11
+ user_query
12
+ )
13
+
14
+ memory_context = "\n".join(memories)
15
+
16
+ SYSTEM_PROMPT = f"""
17
+ You are NeuroFlora,
18
+ an intelligent plant disease assistant.
19
+
20
+ Previous user memories:
21
+ {memory_context}
22
+
23
+ Help users with:
24
+ - plant diseases
25
+ - crop health
26
+ - farming guidance
27
+ - pesticide awareness
28
+ - plant care
29
+ """
30
+
31
+ client = get_llm()
32
+
33
+ response = await client.chat.completions.create(
34
+ model="meta-llama/Llama-3.1-70B-Instruct:scaleway",
35
+ messages=[
36
+ {
37
+ "role": "system",
38
+ "content": SYSTEM_PROMPT
39
+ },
40
+ {
41
+ "role": "user",
42
+ "content": user_query
43
+ }
44
+ ],
45
+ stream=True
46
+ )
47
+
48
+ return response
app/utils/streaming.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import random
3
+ from functools import partial
4
+ from app.memory.qdrant_memory import add_memory
5
+
6
+ async def _store_memory(user_id, user_query, full_response):
7
+ try:
8
+ loop = asyncio.get_event_loop()
9
+ await loop.run_in_executor(
10
+ None, # uses default ThreadPoolExecutor
11
+ partial(add_memory, user_id, user_query, full_response)
12
+ )
13
+ except Exception as e:
14
+ print(f"[MEMORY STORE ERROR]: {e}")
15
+
16
+
17
+ async def stream_response(response, user_id, user_query):
18
+ full_response = ""
19
+
20
+ try:
21
+ async for chunk in response:
22
+ delta = chunk.choices[0].delta
23
+
24
+ if delta and delta.content:
25
+ token = delta.content
26
+
27
+ for char in token:
28
+ full_response += char
29
+ yield char
30
+ await asyncio.sleep(random.uniform(0.01, 0.02))
31
+
32
+ except Exception as e:
33
+ yield f"\n[ERROR]: {str(e)}"
34
+
35
+ finally:
36
+ if full_response:
37
+ asyncio.create_task(
38
+ _store_memory(user_id, user_query, full_response)
39
+ )
requirements.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.115.0
2
+ uvicorn==0.30.6
3
+
4
+ torch==2.5.1
5
+ torchvision==0.20.1
6
+
7
+ pillow==10.4.0
8
+
9
+ openai==1.51.0
10
+
11
+ python-multipart==0.0.9
12
+
13
+ qdrant-client==1.11.3
14
+
15
+ mem0ai==0.1.48
16
+
17
+ google-generativeai==0.8.3
18
+
19
+ numpy==1.26.4
20
+
21
+ pydantic==2.9.2
22
+
23
+ httpx==0.27.2
24
+
25
+ aiohttp==3.10.10
26
+
27
+ dotenv