LiamKhoaLe commited on
Commit
5a45a46
·
0 Parent(s):

Push chatbot to Hugging Face Space

Browse files
Files changed (7) hide show
  1. .gitattributes +35 -0
  2. .gitignore +2 -0
  3. .huggingface.yml +4 -0
  4. Dockerfile +21 -0
  5. README.md +11 -0
  6. app.py +215 -0
  7. requirements.txt +14 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .env
2
+ secrets.toml
.huggingface.yml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ sdk: docker
2
+ app_file: app.py
3
+ port: 7860
4
+ hardware: cpu-basic
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ # Create and use a non-root user (optional)
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ # Set working directory
9
+ WORKDIR /app
10
+
11
+ # Copy all project files to the container
12
+ COPY . .
13
+
14
+ # Install dependencies
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Expose port
18
+ EXPOSE 7860
19
+
20
+ # Run the application
21
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "3"]
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Tutorbot
3
+ emoji: 🧑‍🏫
4
+ colorFrom: green
5
+ colorTo: purple
6
+ sdk: docker
7
+ sdk_version: latest
8
+ pinned: false
9
+ license: apache-2.0
10
+ short_description: Simple tutor chatbot
11
+ ---
app.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://binkhoale1812-tutorbot.hf.space/chat
2
+ import os
3
+ import time
4
+ import uvicorn
5
+ import tempfile
6
+ import psutil
7
+ import logging
8
+
9
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
10
+ from fastapi.responses import JSONResponse
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+
13
+ from google import genai
14
+ from gradio_client import Client, handle_file
15
+
16
+ # —————— Logging ——————
17
+ logging.basicConfig(level=logging.DEBUG, format="%(asctime)s — %(name)s — %(levelname)s — %(message)s", force=True)
18
+ logger = logging.getLogger("tutor-chatbot")
19
+ logger.setLevel(logging.DEBUG)
20
+ logger.info("🚀 Starting Tutor Chatbot API...")
21
+
22
+ # —————— Environment ——————
23
+ gemini_flash_api_key = os.getenv("FlashAPI")
24
+ if not gemini_flash_api_key:
25
+ raise ValueError("❌ Missing Gemini Flash API key!")
26
+
27
+ # —————— System Check ——————
28
+ def check_system_resources():
29
+ memory = psutil.virtual_memory()
30
+ cpu = psutil.cpu_percent(interval=1)
31
+ disk = psutil.disk_usage("/")
32
+ logger.info(f"🔍 RAM: {memory.percent}%, CPU: {cpu}%, Disk: {disk.percent}%")
33
+ check_system_resources()
34
+
35
+ os.environ["OMP_NUM_THREADS"] = "1"
36
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
37
+
38
+ # —————— FastAPI Setup ——————
39
+ app = FastAPI(title="Tutor Chatbot API")
40
+ app.add_middleware(
41
+ CORSMiddleware,
42
+ allow_origins=[
43
+ "http://localhost:5173",
44
+ "http://localhost:3000",
45
+ "https://medical-chatbot-henna.vercel.app",
46
+ ],
47
+ allow_credentials=True,
48
+ allow_methods=["*"],
49
+ allow_headers=["*"],
50
+ )
51
+
52
+ # —————— Gemini 2.5 API Call ——————
53
+ def gemini_flash_completion(prompt, model="gemini-2.5-flash-preview-04-17", temperature=0.7):
54
+ client = genai.Client(api_key=gemini_flash_api_key)
55
+ try:
56
+ response = client.models.generate_content(model=model, contents=prompt)
57
+ return response.text
58
+ except Exception as e:
59
+ logger.error(f"❌ Gemini error: {e}")
60
+ return "Error generating response from Gemini."
61
+
62
+ # —————— Qwen 2.5 VL Client Setup ——————
63
+ qwen_client = Client("prithivMLmods/Qwen2.5-VL-7B-Instruct")
64
+ logger.info("[Qwen] Using remote API via Gradio Client")
65
+
66
+ def qwen_image_summary(image_file: UploadFile, subject: str, level: str) -> str:
67
+ from gradio_client import Client, handle_file
68
+ import tempfile
69
+ # Read file with appropriate format
70
+ if image_file.content_type not in {"image/png", "image/jpeg", "image/jpg"}:
71
+ raise HTTPException(415, "Only PNG or JPEG images are supported")
72
+ # Write/read file
73
+ try:
74
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
75
+ tmp.write(image_file.file.read())
76
+ tmp_path = tmp.name
77
+ logger.info(f"[Qwen] File saved at {tmp_path}, sending to /generate_image...")
78
+ # Prompt
79
+ instruction = f"""
80
+ You are an academic tutor.
81
+
82
+ The student has submitted an image that may contain multiple exam-style questions or study material. Your task is to:
83
+ 1. Carefully extract **each individual question** from the image (if visible), even if they are numbered (e.g., 1., 2., 3.).
84
+ 2. If any question contains **multiple-choice options** (e.g., a), b), c), d)), include them exactly as shown.
85
+ 3. Preserve the original structure and wording as much as possible — DO NOT paraphrase.
86
+ 4. Do not include commentary, analysis, or summaries — just return the extracted question(s) cleanly.
87
+
88
+ Format your output as:
89
+ 1. Question 1 text
90
+ a) option A
91
+ b) option B
92
+ c) option C
93
+ d) option D
94
+
95
+ 2. Question 2 text
96
+ a) ... (if applicable)
97
+
98
+ Only include what appears in the image. Be accurate and neat.
99
+ """
100
+ # Client spec
101
+ client = Client("prithivMLmods/Qwen2.5-VL")
102
+ # Client configs
103
+ result = client.predict(
104
+ model_name="Qwen2.5-VL-7B-Instruct",
105
+ text=instruction,
106
+ image=handle_file(tmp_path),
107
+ max_new_tokens=1024,
108
+ temperature=0.6,
109
+ top_p=0.9,
110
+ top_k=50,
111
+ repetition_penalty=1.2,
112
+ api_name="/generate_image"
113
+ )
114
+ logger.info("[Qwen] ✅ Summary returned from /generate_image")
115
+ os.remove(tmp_path)
116
+ return result.strip()
117
+ # Error
118
+ except Exception as e:
119
+ logger.error(f"[QWEN_API_ERROR] {e}")
120
+ raise HTTPException(500, "❌ Qwen image analysis failed")
121
+
122
+
123
+ # —————— Unified Chat Endpoint ——————
124
+ @app.post("/chat")
125
+ async def chat_endpoint(
126
+ query: str = Form(""),
127
+ subject: str = Form("general"),
128
+ level: str = Form("secondary"),
129
+ lang: str = Form("EN"),
130
+ image: UploadFile = File(None)
131
+ ):
132
+ start_time = time.time()
133
+ image_context = ""
134
+
135
+ # Step 1: If image is present, get transcription from Qwen
136
+ if image:
137
+ logger.info("[Router] 📸 Image uploaded — using Qwen2.5-VL for transcription")
138
+ try:
139
+ image_context = qwen_image_summary(image, subject, level)
140
+ except HTTPException as e:
141
+ return JSONResponse(status_code=e.status_code, content={"response": e.detail})
142
+
143
+ # Step 2: Build prompt for Gemini depending on presence of text and/or image
144
+ if query and image_context:
145
+ # Case: image + query
146
+ prompt = f"""
147
+ You are an academic tutor specialized in **{subject}** at **{level}** level.
148
+ Below is an image submitted by a student and transcribed by a vision model:
149
+
150
+ --- BEGIN IMAGE CONTEXT ---
151
+ {image_context}
152
+ --- END IMAGE CONTEXT ---
153
+
154
+ The student asked the following:
155
+
156
+ **Question:** {query}
157
+
158
+ Respond appropriately using markdown:
159
+ - **Bold** key ideas
160
+ - *Italic* for reasoning
161
+ - Provide examples if useful
162
+
163
+ **Response Language:** {lang}
164
+ """
165
+ elif image_context and not query:
166
+ # Case: image only — auto-answer based on content
167
+ prompt = f"""
168
+ You are an academic tutor specialized in **{subject}** at **{level}** level.
169
+ A student submitted an image with no question. Below is the vision model’s transcription:
170
+
171
+ --- BEGIN IMAGE CONTENT ---
172
+ {image_context}
173
+ --- END IMAGE CONTENT ---
174
+
175
+ Based on this image, explain its key ideas and help the student understand it.
176
+ Assume it's part of their study material.
177
+
178
+ Respond using markdown:
179
+ - **Bold** key terms
180
+ - *Italic* for explanations
181
+ - Give brief insights or examples
182
+
183
+ **Response Language:** {lang}
184
+ """
185
+ elif query and not image_context:
186
+ # Case: text only
187
+ prompt = f"""
188
+ You are an academic tutor specialized in **{subject}** at **{level}** level.
189
+
190
+ **Question:** {query}
191
+
192
+ Answer clearly using markdown:
193
+ - **Bold** key terms
194
+ - *Italic* for explanations
195
+ - Include examples if helpful
196
+
197
+ **Response Language:** {lang}
198
+ """
199
+ else:
200
+ # Nothing was sent
201
+ return JSONResponse(content={"response": "❌ Please provide either a query, an image, or both."})
202
+ # Step 3: Call Gemini
203
+ response_text = gemini_flash_completion(prompt)
204
+ end_time = time.time()
205
+ response_text += f"\n\n*(Response time: {end_time - start_time:.2f} seconds)*"
206
+ return JSONResponse(content={"response": response_text})
207
+
208
+ # —————— Launch Server ——————
209
+ if __name__ == "__main__":
210
+ logger.info("✅ Launching FastAPI server...")
211
+ try:
212
+ uvicorn.run(app, host="0.0.0.0", port=7860, log_level="debug")
213
+ except Exception as e:
214
+ logger.error(f"❌ Server startup failed: {e}")
215
+ exit(1)
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # **Agents**
2
+ google-genai
3
+ huggingface_hub
4
+ # **Environment**
5
+ python-dotenv # Not used in Streamlit deployment
6
+ python-multipart
7
+ # **Deployment**
8
+ uvicorn
9
+ fastapi
10
+ psutil # CPU/RAM logger
11
+ # **OCR&&
12
+ # OCR
13
+ gradio_client
14
+ pillow