imravi commited on
Commit
7d42d5c
·
verified ·
1 Parent(s): 88e9c48

Upload 6 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official PyTorch image as a base
2
+ FROM pytorch/pytorch:2.1.0-cuda11.8-cudnn8-runtime
3
+
4
+ # Set the working directory inside the container
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ build-essential \
10
+ git \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Create a non-root user
14
+ RUN useradd -m -u 1000 appuser
15
+
16
+ # Create cache directories and set permissions
17
+ RUN mkdir -p /app/models/sentence_transformer && \
18
+ mkdir -p /app/models/qwen && \
19
+ mkdir -p /.cache/huggingface && \
20
+ mkdir -p /.cache/torch && \
21
+ mkdir -p /.cache/sentence_transformers
22
+
23
+ # Set environment variables for cache and model locations
24
+ ENV HF_HOME="/.cache/huggingface"
25
+ ENV TORCH_HOME="/.cache/torch"
26
+ ENV SENTENCE_TRANSFORMERS_HOME="/app/models/sentence_transformer"
27
+
28
+ # Install Python dependencies
29
+ RUN pip install --no-cache-dir \
30
+ pandas \
31
+ torch \
32
+ sentence-transformers \
33
+ transformers \
34
+ numpy \
35
+ faiss-cpu \
36
+ fastapi \
37
+ uvicorn[standard] \
38
+ pydantic \
39
+ python-multipart \
40
+ huggingface_hub \
41
+ accelerate>=0.26.0
42
+
43
+ # Create a script to download models
44
+ COPY <<EOF /app/download_models.py
45
+ import os
46
+ from sentence_transformers import SentenceTransformer
47
+ from transformers import AutoTokenizer, AutoModelForCausalLM
48
+ import torch
49
+
50
+ # Download and save sentence transformer model
51
+ print("Downloading sentence transformer model...")
52
+ model = SentenceTransformer("sentence-transformers/all-mpnet-base-v2")
53
+ model.save("/app/models/sentence_transformer")
54
+ print("Sentence transformer model saved successfully!")
55
+
56
+ # Download and save Qwen model and tokenizer
57
+ print("Downloading Qwen model and tokenizer...")
58
+ model_name = "Qwen/Qwen2.5-0.5B-Instruct"
59
+ tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
60
+ model = AutoModelForCausalLM.from_pretrained(
61
+ model_name,
62
+ trust_remote_code=True,
63
+ torch_dtype=torch.float16,
64
+ device_map=None
65
+ )
66
+
67
+ tokenizer.save_pretrained("/app/models/qwen")
68
+ model.save_pretrained("/app/models/qwen")
69
+ print("Qwen model and tokenizer saved successfully!")
70
+ EOF
71
+
72
+ # Download models during build
73
+ RUN python /app/download_models.py
74
+
75
+ # Only set TRANSFORMERS_OFFLINE after downloading models
76
+ ENV TRANSFORMERS_OFFLINE=1
77
+
78
+ # Copy the main.py and modify it to use local paths
79
+ COPY main.py /app/main.py
80
+ RUN sed -i 's|"sentence-transformers/all-mpnet-base-v2"|"/app/models/sentence_transformer"|g' main.py && \
81
+ sed -i 's|"Qwen/Qwen2.5-0.5B-Instruct"|"/app/models/qwen"|g' main.py
82
+
83
+ # Copy the data file
84
+ COPY updated_plant_data_chunks_and_embeddings.csv /app/updated_plant_data_chunks_and_embeddings.csv
85
+
86
+ # Set proper permissions
87
+ RUN chown -R appuser:appuser /app && \
88
+ chown -R appuser:appuser /.cache && \
89
+ chmod -R 755 /app/models
90
+
91
+ # Switch to non-root user
92
+ USER appuser
93
+
94
+ # Set the environment variable to point to the app directory
95
+ ENV PYTHONPATH=/app
96
+
97
+ # Expose the port
98
+ EXPOSE 5000
99
+
100
+ # Reduce model loading time by setting specific environment variables
101
+ ENV OMP_NUM_THREADS=1
102
+ ENV MKL_NUM_THREADS=1
103
+ ENV TORCH_NUM_THREADS=1
104
+
105
+ # Command to run the app
106
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--timeout-keep-alive", "300"]
README.md CHANGED
@@ -1,11 +1,12 @@
1
- ---
2
- title: Plant Chatbot
3
- emoji: 🐨
4
- colorFrom: pink
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
+ ---
2
+ title: Plant Chatbot
3
+ emoji:
4
+ colorFrom: pink
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ short_description: testing
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
main.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.responses import JSONResponse
3
+ from pydantic import BaseModel
4
+ import torch
5
+ import numpy as np
6
+ import pandas as pd
7
+ from sentence_transformers import SentenceTransformer, util
8
+ from transformers import AutoTokenizer, AutoModelForCausalLM
9
+ from typing import List, Dict
10
+ import json
11
+ import asyncio
12
+
13
+ app = FastAPI(title="Plant Chatbot API")
14
+
15
+ class Query(BaseModel):
16
+ query: str
17
+
18
+ class PlantChatbot:
19
+ def __init__(self, preprocessed_data_path: str):
20
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
21
+
22
+ print("Loading sentence transformer model...")
23
+ self.embedding_model = SentenceTransformer(
24
+ model_name_or_path="/app/models/sentence_transformer",
25
+ device=self.device
26
+ )
27
+
28
+ print("Loading data...")
29
+ self.load_data(preprocessed_data_path)
30
+
31
+ print("Loading Qwen tokenizer...")
32
+ self.tokenizer = AutoTokenizer.from_pretrained(
33
+ "/app/models/qwen",
34
+ trust_remote_code=True,
35
+ local_files_only=True
36
+ )
37
+
38
+ print("Loading Qwen model...")
39
+ self.model = AutoModelForCausalLM.from_pretrained(
40
+ "/app/models/qwen",
41
+ device_map="auto" if torch.cuda.is_available() else None,
42
+ trust_remote_code=True,
43
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
44
+ local_files_only=True
45
+ )
46
+
47
+ if self.tokenizer.pad_token is None:
48
+ self.tokenizer.pad_token = self.tokenizer.eos_token
49
+
50
+ print("Initialization complete!")
51
+
52
+ def load_data(self, preprocessed_data_path: str):
53
+ # [Previous load_data implementation remains the same]
54
+ df = pd.read_csv(preprocessed_data_path)
55
+
56
+ def parse_embedding(embedding_str):
57
+ try:
58
+ embedding_str = embedding_str.strip()
59
+ if embedding_str.startswith('[') and embedding_str.endswith(']'):
60
+ embedding_str = embedding_str[1:-1]
61
+ return np.fromstring(embedding_str, sep=',')
62
+ except:
63
+ print(f"Error parsing embedding: {embedding_str[:100]}...")
64
+ return None
65
+
66
+ df["embedding"] = df["embedding"].apply(parse_embedding)
67
+ df = df.dropna(subset=['embedding'])
68
+
69
+ self.chunks_data = df.to_dict(orient="records")
70
+
71
+ embeddings_array = np.stack(df["embedding"].values)
72
+ self.embeddings = torch.tensor(
73
+ embeddings_array,
74
+ dtype=torch.float32
75
+ ).to(self.device)
76
+
77
+ def retrieve_relevant_chunks(self, query: str, n_chunks: int = 5) -> List[Dict]:
78
+ # [Previous retrieve_relevant_chunks implementation remains the same]
79
+ query_embedding = self.embedding_model.encode(
80
+ query,
81
+ convert_to_tensor=True,
82
+ show_progress_bar=False
83
+ ).to(self.device)
84
+
85
+ if len(query_embedding.shape) == 1:
86
+ query_embedding = query_embedding.unsqueeze(0)
87
+
88
+ scores = util.dot_score(query_embedding, self.embeddings)[0]
89
+ _, indices = torch.topk(scores, k=min(n_chunks, len(self.chunks_data)))
90
+ indices = indices.cpu().numpy()
91
+
92
+ return [
93
+ {
94
+ "sentence_chunk": self.chunks_data[i]["sentence_chunk"],
95
+ "Reference_plant_name": self.chunks_data[i]["Reference_plant_name"],
96
+ "Reference_plant_link": self.chunks_data[i]["Reference_plant_link"]
97
+ }
98
+ for i in indices
99
+ ]
100
+
101
+ def format_prompt(self, query: str, context_chunks: List[Dict]) -> str:
102
+ # [Previous format_prompt implementation remains the same]
103
+ context = "- " + "\n- ".join([chunk["sentence_chunk"] for chunk in context_chunks])
104
+
105
+ prompt = f"""<|im_start|>system
106
+ You are a knowledgeable plant expert. Provide helpful and accurate information about plants based on the given context.
107
+ <|im_end|>
108
+ <|im_start|>user
109
+ Based on the following context about plants, please answer the query.
110
+ Please be specific and detailed in your response, using only the information provided in the context.
111
+ If you cannot answer the question based on the provided context, please say so.
112
+
113
+ Context:
114
+ {context}
115
+
116
+ User query: {query}
117
+ <|im_end|>
118
+ <|im_start|>assistant
119
+ """
120
+ return prompt
121
+
122
+ async def generate_response(self, query: str) -> Dict:
123
+ try:
124
+ context_chunks = self.retrieve_relevant_chunks(query)
125
+ prompt = self.format_prompt(query, context_chunks)
126
+ input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to(self.device)
127
+
128
+ max_length = input_ids.shape[1] + 512
129
+ generated_text = ""
130
+
131
+ with torch.no_grad():
132
+ generated = input_ids
133
+
134
+ while generated.shape[1] < max_length:
135
+ outputs = self.model.forward(
136
+ input_ids=generated,
137
+ max_new_tokens=64,
138
+ do_sample=True,
139
+ temperature=0.7,
140
+ top_p=0.8,
141
+ repetition_penalty=1.05,
142
+ pad_token_id=self.tokenizer.pad_token_id,
143
+ eos_token_id=self.tokenizer.eos_token_id,
144
+ )
145
+
146
+ next_token = torch.argmax(outputs.logits[:, -1, :], dim=-1).unsqueeze(0)
147
+
148
+ if next_token[0, 0].item() == self.tokenizer.eos_token_id:
149
+ break
150
+
151
+ generated = torch.cat([generated, next_token.T], dim=1)
152
+ new_text = self.tokenizer.decode(next_token[0], skip_special_tokens=True)
153
+
154
+ if new_text:
155
+ generated_text += new_text
156
+
157
+ return {
158
+ "Response_text": generated_text,
159
+ "context_items": context_chunks
160
+ }
161
+
162
+ except Exception as e:
163
+ print(f"Error generating response: {str(e)}")
164
+ return {
165
+ "Response_text": "I apologize, but I encountered an error while processing your query. Please try again.",
166
+ "context_items": []
167
+ }
168
+
169
+ # Initialize chatbot at startup
170
+ chatbot = PlantChatbot("updated_plant_data_chunks_and_embeddings.csv")
171
+
172
+ @app.post("/chat")
173
+ async def chat(query: Query):
174
+ """Endpoint for chat interactions"""
175
+ try:
176
+ response = await chatbot.generate_response(query.query)
177
+ return JSONResponse(content=response)
178
+ except Exception as e:
179
+ raise HTTPException(status_code=500, detail=str(e))
180
+
181
+ @app.get("/")
182
+ async def root():
183
+ """Root endpoint"""
184
+ return {"message": "Plant Chatbot API is running. Use /chat endpoint for queries."}
185
+
186
+ if __name__ == "__main__":
187
+ import uvicorn
188
+ uvicorn.run(app, host="0.0.0.0", port=7860)
plant_data_chunks_and_embeddings.csv ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ pandas
2
+ torch
3
+ sentence-transformers
4
+ transformers
5
+ numpy
6
+ faiss-cpu
7
+ gunicorn
8
+ huggingface_hub
updated_plant_data_chunks_and_embeddings.csv ADDED
The diff for this file is too large to render. See raw diff