NurseCitizenDeveloper commited on
Commit
4e63106
·
1 Parent(s): ec039c4

feat: Integrate Real AI (Gemini 1.5 Pro) - Replaced mock logic with GeminiClient - Added google-generativeai dependency - Enabled real MST classification and wound staging

Browse files
Files changed (3) hide show
  1. assessment_agent.py +94 -159
  2. gemini_client.py +192 -0
  3. requirements.txt +3 -0
assessment_agent.py CHANGED
@@ -6,31 +6,19 @@
6
  EWAAST Assessment Agent
7
 
8
  Core assessment logic with streaming output.
9
- Adapted from Google's interview_simulator.py pattern.
10
  """
11
 
12
  import json
13
  import time
14
  import os
 
 
15
  from typing import Generator
 
16
 
17
- # ===== DEMO MODE =====
18
- # Set DEMO_MODE=true to use simulated responses (no model loading)
19
- DEMO_MODE = os.environ.get("DEMO_MODE", "false").lower() == "true"
20
-
21
- # Try to import MedGemma client, fall back to mock (or use mock if DEMO_MODE)
22
- if DEMO_MODE:
23
- MEDGEMMA_AVAILABLE = False
24
- print("🎭 DEMO_MODE enabled: Using simulated MST-aware responses")
25
- else:
26
- try:
27
- from medgemma_client import medgemma_get_text_response
28
- MEDGEMMA_AVAILABLE = True
29
- except ImportError:
30
- MEDGEMMA_AVAILABLE = False
31
-
32
- from cache import cache
33
-
34
 
35
  # ===== MST VISUAL GUIDANCE =====
36
 
@@ -47,138 +35,93 @@ MST_GUIDANCE = {
47
  10: {"category": "Deep", "visual": "CRITICAL: Do NOT rely on redness. Look for subtle purple/blue undertones; induration, warmth, localized heat"}
48
  }
49
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  def classify_skin_tone(image_b64: str, context: str = "") -> dict:
52
  """
53
- Classify the Monk Skin Tone from an image.
54
-
55
- Args:
56
- image_b64: Base64-encoded image data
57
- context: Optional patient context (may contain MST info for demo mode)
58
-
59
- Returns:
60
- Dictionary with mst_value, category, visual_guidance, confidence
61
  """
62
- if MEDGEMMA_AVAILABLE:
63
- # Use MedGemma VQA for classification
64
- messages = [
65
- {
66
- "role": "system",
67
- "content": [{
68
- "type": "text",
69
- "text": """You are a clinical assistant trained to identify the Monk Skin Tone (MST) scale value.
70
- The MST scale ranges from 1 (lightest) to 10 (deepest).
71
- Analyze the provided image and return ONLY a JSON object with:
72
- {"mst_value": <1-10>, "confidence": <0.0-1.0>}"""
73
- }]
74
- },
75
- {
76
- "role": "user",
77
- "content": [
78
- {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
79
- {"type": "text", "text": "What is the Monk Skin Tone value for this patient?"}
80
- ]
81
- }
82
- ]
83
-
84
- response = medgemma_get_text_response(messages)
85
  try:
86
- result = json.loads(response)
 
 
87
  mst_value = result.get("mst_value", 5)
88
- except:
89
- mst_value = 5 # Default to medium if parsing fails
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  else:
91
- # Demo/Mock mode: extract MST from context if available
92
- import re
93
- mst_match = re.search(r'MST[:\s]*(\d+)', context, re.IGNORECASE)
94
- if mst_match:
95
- mst_value = int(mst_match.group(1))
96
- mst_value = max(1, min(10, mst_value)) # Clamp to 1-10
97
- else:
98
- # Default based on keywords in context
99
- if 'deep' in context.lower() or 'dark' in context.lower():
100
- mst_value = 9
101
- elif 'medium' in context.lower():
102
- mst_value = 5
103
- else:
104
- mst_value = 2 # Default to light for demo
105
-
106
  guidance = MST_GUIDANCE.get(mst_value, MST_GUIDANCE[5])
107
-
108
  return {
109
  "mst_value": mst_value,
110
  "category": guidance["category"],
111
  "visual_guidance": guidance["visual"],
112
- "confidence": 0.92 if DEMO_MODE else 0.85
113
  }
114
 
115
 
116
  def generate_assessment_report(image_b64: str, mst_result: dict, context: str) -> dict:
117
  """
118
- Generate the clinical assessment report using MedGemma.
119
-
120
- Args:
121
- image_b64: Base64-encoded wound image
122
- mst_result: MST classification result
123
- context: Optional patient context
124
-
125
- Returns:
126
- Dictionary with stage, rationale, care_plan, urgency
127
  """
128
- mst_value = mst_result["mst_value"]
129
- visual_guidance = mst_result["visual_guidance"]
130
 
131
- if MEDGEMMA_AVAILABLE:
132
- messages = [
133
- {
134
- "role": "system",
135
- "content": [{
136
- "type": "text",
137
- "text": f"""You are EWAAST, an equitable wound assessment agent.
138
-
139
- The patient has been classified as MST {mst_value} ({mst_result['category']} skin tone).
140
- Visual Guidance: {visual_guidance}
141
-
142
- CRITICAL: For MST 7-10, you MUST NOT use "redness" or "erythema" as primary indicators.
143
- Instead, look for purple/blue discoloration, warmth, induration, and texture changes.
144
-
145
- Assess the wound and provide:
146
- 1. Stage (Stage 1-4, Unstageable, or DTI)
147
- 2. Clinical rationale (explain what visual features you observed)
148
- 3. Recommended care plan
149
- 4. Urgency level (routine, urgent, immediate)
150
-
151
- Return as JSON: {{"stage": "...", "rationale": "...", "care_plan": "...", "urgency": "..."}}"""
152
- }]
153
- },
154
- {
155
- "role": "user",
156
- "content": [
157
- {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
158
- {"type": "text", "text": f"Patient context: {context or 'Not provided'}. Assess this wound."}
159
- ]
160
- }
161
- ]
162
-
163
- response = medgemma_get_text_response(messages)
164
  try:
165
- return json.loads(response)
166
- except:
167
- pass
168
-
169
- # Mock response for development
 
 
170
  if mst_value >= 7:
171
  return {
172
- "stage": "Stage 2",
173
- "rationale": f"Observed purple-blue discoloration consistent with early tissue damage on MST {mst_value} skin. Warmth and slight induration noted. No use of 'redness' criteria due to unreliability on deep skin tones.",
174
- "care_plan": "1. Offload pressure immediately\n2. Apply appropriate dressing\n3. Monitor for progression\n4. Document with good lighting to capture color changes",
175
  "urgency": "urgent"
176
  }
177
  else:
178
  return {
179
- "stage": "Stage 1",
180
- "rationale": f"Non-blanchable erythema observed on MST {mst_value} skin. Area is warm to touch with intact epidermis.",
181
- "care_plan": "1. Relieve pressure on affected area\n2. Keep skin clean and dry\n3. Apply barrier cream if indicated\n4. Reposition schedule every 2 hours",
182
  "urgency": "routine"
183
  }
184
 
@@ -186,73 +129,65 @@ Return as JSON: {{"stage": "...", "rationale": "...", "care_plan": "...", "urgen
186
  def stream_assessment(image_b64: str, context: str) -> Generator[str, None, None]:
187
  """
188
  Stream the wound assessment process.
189
-
190
- This is a generator that yields JSON messages for SSE streaming.
191
- Adapted from the interview_simulator pattern in appoint-ready.
192
-
193
- Args:
194
- image_b64: Base64-encoded wound image
195
- context: Optional patient context
196
-
197
- Yields:
198
- JSON strings with step updates and data
199
  """
200
 
201
- # Step 1: Acknowledge image received
202
  yield json.dumps({
203
  "step": "received",
204
- "thinking": "Image received. Beginning analysis...",
205
  "data": {}
206
  })
 
207
 
208
- time.sleep(0.5) # Brief pause for UX
209
-
210
- # Step 2: Classify skin tone
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  yield json.dumps({
212
  "step": "classifying_mst",
213
- "thinking": "Analyzing skin tone using Monk Skin Tone (MST) scale. This helps ensure equitable assessment across all skin tones...",
214
  "data": {}
215
  })
216
 
217
- time.sleep(1.0) # Simulate processing
218
  mst_result = classify_skin_tone(image_b64, context)
219
 
220
  yield json.dumps({
221
  "step": "mst_complete",
222
- "thinking": f"Skin tone classified as MST {mst_result['mst_value']} ({mst_result['category']}). Applying appropriate visual guidance...",
223
  "data": mst_result
224
  })
225
 
226
- time.sleep(0.5)
227
-
228
- # Step 3: Analyze wound features
229
  yield json.dumps({
230
  "step": "analyzing_wound",
231
- "thinking": f"Analyzing wound features with MST-aware criteria. For MST {mst_result['mst_value']}: {mst_result['visual_guidance']}",
232
- "data": {}
233
- })
234
-
235
- time.sleep(1.5) # Simulate processing
236
-
237
- # Step 4: Generate report
238
- yield json.dumps({
239
- "step": "generating_report",
240
- "thinking": "Generating clinical assessment report with equitable staging...",
241
  "data": {}
242
  })
243
 
244
- time.sleep(1.0)
245
  report = generate_assessment_report(image_b64, mst_result, context)
246
 
247
- # Step 5: Complete
248
  yield json.dumps({
249
  "step": "complete",
250
- "thinking": "Assessment complete. Report generated.",
251
  "data": {
252
  "mst": mst_result,
253
  "report": report
254
  }
255
  })
256
 
257
- # Signal end of stream
258
  yield json.dumps({"event": "end"})
 
6
  EWAAST Assessment Agent
7
 
8
  Core assessment logic with streaming output.
9
+ Now powered by Google Gemini 1.5 Pro (Real AI) via gemini_client.
10
  """
11
 
12
  import json
13
  import time
14
  import os
15
+ import base64
16
+ import io
17
  from typing import Generator
18
+ from PIL import Image
19
 
20
+ # Import Real AI Client
21
+ from gemini_client import get_gemini_client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  # ===== MST VISUAL GUIDANCE =====
24
 
 
35
  10: {"category": "Deep", "visual": "CRITICAL: Do NOT rely on redness. Look for subtle purple/blue undertones; induration, warmth, localized heat"}
36
  }
37
 
38
+ def _b64_to_pil(image_b64: str) -> Image.Image:
39
+ """Convert base64 string to PIL Image."""
40
+ try:
41
+ if "," in image_b64:
42
+ image_b64 = image_b64.split(",")[1]
43
+ image_data = base64.b64decode(image_b64)
44
+ return Image.open(io.BytesIO(image_data))
45
+ except Exception as e:
46
+ print(f"Error converting image: {e}")
47
+ return Image.new('RGB', (100, 100), color='gray')
48
+
49
 
50
  def classify_skin_tone(image_b64: str, context: str = "") -> dict:
51
  """
52
+ Classify the Monk Skin Tone from an image using Gemini.
 
 
 
 
 
 
 
53
  """
54
+ client = get_gemini_client()
55
+
56
+ if client.is_available():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  try:
58
+ image = _b64_to_pil(image_b64)
59
+ result = client.analyze_mst(image)
60
+
61
  mst_value = result.get("mst_value", 5)
62
+ # Normalize constraints
63
+ mst_value = max(1, min(10, int(mst_value)))
64
+
65
+ guidance = MST_GUIDANCE.get(mst_value, MST_GUIDANCE[5])
66
+
67
+ return {
68
+ "mst_value": mst_value,
69
+ "category": guidance["category"],
70
+ "visual_guidance": guidance["visual"],
71
+ "confidence": result.get("confidence", 0.85)
72
+ }
73
+ except Exception as e:
74
+ print(f"AI Classification failed: {e}")
75
+
76
+ # Fallback / Demo Logic
77
+ print("Falling back to heuristic MST classification")
78
+ import re
79
+ mst_match = re.search(r'MST[:\s]*(\d+)', context, re.IGNORECASE)
80
+ if mst_match:
81
+ mst_value = int(mst_match.group(1))
82
+ elif 'deep' in context.lower() or 'dark' in context.lower():
83
+ mst_value = 9
84
+ elif 'medium' in context.lower():
85
+ mst_value = 5
86
  else:
87
+ mst_value = 2
88
+
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  guidance = MST_GUIDANCE.get(mst_value, MST_GUIDANCE[5])
 
90
  return {
91
  "mst_value": mst_value,
92
  "category": guidance["category"],
93
  "visual_guidance": guidance["visual"],
94
+ "confidence": 0.5
95
  }
96
 
97
 
98
  def generate_assessment_report(image_b64: str, mst_result: dict, context: str) -> dict:
99
  """
100
+ Generate the clinical assessment report using Gemini.
 
 
 
 
 
 
 
 
101
  """
102
+ client = get_gemini_client()
 
103
 
104
+ if client.is_available():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  try:
106
+ image = _b64_to_pil(image_b64)
107
+ return client.assess_wound(image, mst_result, context)
108
+ except Exception as e:
109
+ print(f"AI Assessment failed: {e}")
110
+
111
+ # Fallback Logic
112
+ mst_value = mst_result["mst_value"]
113
  if mst_value >= 7:
114
  return {
115
+ "stage": "Assessment Failed (Fallback)",
116
+ "rationale": f"AI unavailable. Observed features on MST {mst_value} skin. Check for purple/blue tones.",
117
+ "care_plan": "1. Offload pressure\n2. Monitor for changes",
118
  "urgency": "urgent"
119
  }
120
  else:
121
  return {
122
+ "stage": "Assessment Failed (Fallback)",
123
+ "rationale": f"AI unavailable. Observed features on MST {mst_value} skin. Check for redness.",
124
+ "care_plan": "1. Relieve pressure\n2. Keep clean",
125
  "urgency": "routine"
126
  }
127
 
 
129
  def stream_assessment(image_b64: str, context: str) -> Generator[str, None, None]:
130
  """
131
  Stream the wound assessment process.
 
 
 
 
 
 
 
 
 
 
132
  """
133
 
134
+ # Step 1: Acknowledge
135
  yield json.dumps({
136
  "step": "received",
137
+ "thinking": "Image received. validating...",
138
  "data": {}
139
  })
140
+ time.sleep(0.5)
141
 
142
+ # Step 2: Validate Image (New Step with Gemini)
143
+ client = get_gemini_client()
144
+ if client.is_available():
145
+ try:
146
+ img = _b64_to_pil(image_b64)
147
+ val_result = client.validate_wound_image(img)
148
+ if not val_result.get("is_valid", True):
149
+ yield json.dumps({
150
+ "step": "error",
151
+ "thinking": f"Image Rejected: {val_result.get('reason')}",
152
+ "data": {"error": val_result.get('reason')}
153
+ })
154
+ yield json.dumps({"event": "end"})
155
+ return
156
+ except Exception:
157
+ pass
158
+
159
+ # Step 3: Classify skin tone
160
  yield json.dumps({
161
  "step": "classifying_mst",
162
+ "thinking": "Analyzing skin tone using Monk Skin Tone (MST) scale with Gemini Vision...",
163
  "data": {}
164
  })
165
 
 
166
  mst_result = classify_skin_tone(image_b64, context)
167
 
168
  yield json.dumps({
169
  "step": "mst_complete",
170
+ "thinking": f"Identified MST {mst_result['mst_value']} ({mst_result['category']}). Adjusted visual guidance: {mst_result['visual_guidance']}",
171
  "data": mst_result
172
  })
173
 
174
+ # Step 4: Analyze wound
 
 
175
  yield json.dumps({
176
  "step": "analyzing_wound",
177
+ "thinking": f"Gemini is analyzing wound features. Looking for MST-specific signs (e.g., discoloration vs redness)...",
 
 
 
 
 
 
 
 
 
178
  "data": {}
179
  })
180
 
181
+ # Step 5: Generate report
182
  report = generate_assessment_report(image_b64, mst_result, context)
183
 
 
184
  yield json.dumps({
185
  "step": "complete",
186
+ "thinking": "Assessment complete. Clinical rationale generated.",
187
  "data": {
188
  "mst": mst_result,
189
  "report": report
190
  }
191
  })
192
 
 
193
  yield json.dumps({"event": "end"})
gemini_client.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ EWAAST: Google AI Studio Client
3
+
4
+ Uses Google's Gemini API for real AI inference.
5
+ Supports Gemini 1.5 Pro (Vision) and newer models.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import base64
11
+ import io
12
+ from typing import Optional, Dict, Any
13
+ from PIL import Image
14
+
15
+ # Google AI Studio API key
16
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
17
+
18
+ # Model selection - Gemini 1.5 Pro is standard for vision
19
+ DEFAULT_MODEL = "gemini-1.5-pro"
20
+ FALLBACK_MODEL = "gemini-1.5-flash"
21
+
22
+
23
+ class GeminiClient:
24
+ """
25
+ Client for Google AI Studio / Gemini API.
26
+
27
+ Uses Gemini Pro for vision-based wound assessment.
28
+ """
29
+
30
+ def __init__(self, model_name: str = DEFAULT_MODEL):
31
+ self.model_name = model_name
32
+ self._api_available = False
33
+ self._genai = None
34
+
35
+ # Initialize
36
+ self._initialize()
37
+
38
+ def _initialize(self):
39
+ """Initialize the Google GenAI client."""
40
+ if not GOOGLE_API_KEY:
41
+ print("⚠️ GOOGLE_API_KEY not set - AI features disabled")
42
+ return
43
+
44
+ try:
45
+ import google.generativeai as genai
46
+ genai.configure(api_key=GOOGLE_API_KEY)
47
+ self._genai = genai
48
+ self._api_available = True
49
+ print(f"✅ Google AI Studio connected: {self.model_name}")
50
+ except ImportError:
51
+ print("⚠️ google-generativeai not installed")
52
+ except Exception as e:
53
+ print(f"⚠️ Google AI init failed: {e}")
54
+
55
+ def is_available(self) -> bool:
56
+ """Check if Gemini API is available."""
57
+ return self._api_available
58
+
59
+ def _image_to_part(self, image: Image.Image) -> dict:
60
+ """Convert PIL Image to Gemini-compatible format."""
61
+ # Convert to RGB if necessary
62
+ if image.mode != 'RGB':
63
+ image = image.convert('RGB')
64
+
65
+ buffer = io.BytesIO()
66
+ image.save(buffer, format="JPEG", quality=85)
67
+ image_bytes = buffer.getvalue()
68
+
69
+ # Determine strict MIME type for API
70
+ return {
71
+ "mime_type": "image/jpeg",
72
+ "data": base64.b64encode(image_bytes).decode("utf-8")
73
+ }
74
+
75
+ def validate_wound_image(self, image: Image.Image) -> Dict[str, Any]:
76
+ """
77
+ Validate if the image is actually a wound/medical image.
78
+ """
79
+ if not self._api_available:
80
+ return {"is_valid": True, "reason": "AI validation unavailable"}
81
+
82
+ prompt = """Analyze this image and determine:
83
+ 1. Is this a medical/clinical image of human skin or a wound?
84
+ 2. If NOT medical, what type of image is it?
85
+
86
+ Respond in JSON format ONLY:
87
+ {"is_medical": true/false, "reason": "brief explanation"}"""
88
+
89
+ try:
90
+ response = self.generate(image, prompt)
91
+ # Try to parse JSON from response
92
+ try:
93
+ # Find JSON object
94
+ start = response.find('{')
95
+ end = response.rfind('}') + 1
96
+ if start != -1 and end != -1:
97
+ result = json.loads(response[start:end])
98
+ return {
99
+ "is_valid": result.get("is_medical", False),
100
+ "reason": result.get("reason", "Analysis complete")
101
+ }
102
+ except:
103
+ pass
104
+
105
+ # Fallback keyword check
106
+ is_valid = "true" in response.lower() or "medical" in response.lower()
107
+ return {"is_valid": is_valid, "reason": response[:100]}
108
+
109
+ except Exception as e:
110
+ print(f"Validation error: {e}")
111
+ return {"is_valid": True, "reason": "Validation failed, proceeding"}
112
+
113
+ def analyze_mst(self, image: Image.Image) -> Dict[str, Any]:
114
+ """Classify Monk Skin Tone."""
115
+ prompt = """Analyze the skin tone in this image using the Monk Skin Tone (MST) scale (1-10).
116
+ Return ONLY a JSON object:
117
+ {"mst_value": <1-10 integer>, "confidence": <0.0-1.0 float>}"""
118
+
119
+ response = self.generate(image, prompt)
120
+ try:
121
+ start = response.find('{')
122
+ end = response.rfind('}') + 1
123
+ if start != -1 and end != -1:
124
+ return json.loads(response[start:end])
125
+ except:
126
+ pass
127
+ return {"mst_value": 5, "confidence": 0.0}
128
+
129
+ def assess_wound(self, image: Image.Image, mst_data: Dict[str, Any], context: str = "") -> Dict[str, Any]:
130
+ """Full wound assessment."""
131
+ mst_val = mst_data.get('mst_value', 5)
132
+
133
+ prompt = f"""You are EWAAST, an equitable wound assessment specialist.
134
+ Patient Context: {context}
135
+ Skin Tone: MST {mst_val}
136
+
137
+ Analyze this wound image. CRITICAL: For deeper skin tones (MST 7-10), DO NOT rely on redness. Look for purple/blue tones, texture changes, or darkening.
138
+
139
+ Provide a JSON response with:
140
+ 1. "stage": Assessment (e.g., "Stage 1", "Stage 2", "Unstageable", "DTI", "Healing")
141
+ 2. "rationale": Clinical reasoning describing visual evidence
142
+ 3. "care_plan": 3-4 bullet points of immediate recommendations
143
+ 4. "urgency": "routine", "urgent", or "emergency"
144
+
145
+ JSON Format:
146
+ {{
147
+ "stage": "string",
148
+ "rationale": "string",
149
+ "care_plan": "string",
150
+ "urgency": "string"
151
+ }}"""
152
+
153
+ response = self.generate(image, prompt)
154
+ try:
155
+ start = response.find('{')
156
+ end = response.rfind('}') + 1
157
+ if start != -1 and end != -1:
158
+ return json.loads(response[start:end])
159
+ except:
160
+ pass
161
+ return {
162
+ "stage": "Assessment Failed",
163
+ "rationale": f"Could not parse AI response: {response[:100]}...",
164
+ "care_plan": "Consult human specialist.",
165
+ "urgency": "urgent"
166
+ }
167
+
168
+ def generate(self, image: Image.Image, prompt: str) -> str:
169
+ """Generate response from Gemini."""
170
+ if not self._api_available:
171
+ raise RuntimeError("Google AI Studio not available.")
172
+
173
+ try:
174
+ model = self._genai.GenerativeModel(self.model_name)
175
+ response = model.generate_content([prompt, image])
176
+ return response.text
177
+ except Exception:
178
+ # Fallback
179
+ if self.model_name != FALLBACK_MODEL:
180
+ print(f"Falling back to {FALLBACK_MODEL}")
181
+ model = self._genai.GenerativeModel(FALLBACK_MODEL)
182
+ response = model.generate_content([prompt, image])
183
+ return response.text
184
+ raise
185
+
186
+ _client = None
187
+
188
+ def get_gemini_client() -> GeminiClient:
189
+ global _client
190
+ if _client is None:
191
+ _client = GeminiClient()
192
+ return _client
requirements.txt CHANGED
@@ -14,6 +14,9 @@ peft>=0.10.0
14
  # Image Processing
15
  Pillow>=10.0.0
16
 
 
 
 
17
  # Web Interface (Flask + React Architecture)
18
  flask>=3.0.0
19
  flask-cors>=4.0.0
 
14
  # Image Processing
15
  Pillow>=10.0.0
16
 
17
+ # Gemini API (Real AI)
18
+ google-generativeai>=0.4.0
19
+
20
  # Web Interface (Flask + React Architecture)
21
  flask>=3.0.0
22
  flask-cors>=4.0.0