github-actions[bot] commited on
Commit
dbc48af
·
1 Parent(s): b73b9fb

Deploy from GitHub: 0df40e607abe71d679ca5b9cb8c5743bae597100

Browse files
Files changed (4) hide show
  1. app/config.py +2 -0
  2. app/services/llm_service.py +128 -1
  3. requirements.txt +1 -0
  4. test_gemini.py +42 -0
app/config.py CHANGED
@@ -6,6 +6,8 @@ class Settings(BaseSettings):
6
  SERPER_API_KEY: str = ""
7
  SARVAM_API_KEY: str = ""
8
  OPENAI_API_KEY: str = ""
 
9
  BEARER_TOKEN: str = ""
 
10
 
11
  settings = Settings()
 
6
  SERPER_API_KEY: str = ""
7
  SARVAM_API_KEY: str = ""
8
  OPENAI_API_KEY: str = ""
9
+ GEMINI_API_KEY: str = ""
10
  BEARER_TOKEN: str = ""
11
+ API_ENDPOINT: str = ""
12
 
13
  settings = Settings()
app/services/llm_service.py CHANGED
@@ -1,6 +1,7 @@
1
  import json
2
  import base64
3
  import re
 
4
  from app.config import settings
5
 
6
  try:
@@ -20,6 +21,12 @@ def _clean_fenced_json(text: str) -> str:
20
  return response_text.strip()
21
 
22
 
 
 
 
 
 
 
23
  def _get_openai_client():
24
  if not settings.OPENAI_API_KEY:
25
  raise ValueError("OPENAI_API_KEY is required")
@@ -28,6 +35,22 @@ def _get_openai_client():
28
  return OpenAI(api_key=settings.OPENAI_API_KEY)
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  CHATBOT_SYSTEM_PROMPTS = {
32
  "en": (
33
  "You are the AgriVision Chatbot: a friendly, practical agricultural expert for farmers. "
@@ -83,6 +106,55 @@ CHATBOT_SYSTEM_PROMPTS = {
83
  ),
84
  }
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  def detect_disease(image_data: str, query: str = ""):
87
  prompt = (
88
  "You are an expert agricultural plant-pathology assistant for farmers. Analyze this crop image and provide practical advice. "
@@ -114,6 +186,19 @@ def detect_disease(image_data: str, query: str = ""):
114
  f"\n\nFarmer query/context: {query}"
115
  "\n\nRespond only in Markdown and keep language simple for farmers."
116
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  oai = _get_openai_client()
118
  img_b64 = base64.b64encode(image_data).decode("utf-8")
119
  completion = oai.chat.completions.create(
@@ -132,6 +217,10 @@ def detect_disease(image_data: str, query: str = ""):
132
  return completion.choices[0].message.content or ""
133
 
134
 
 
 
 
 
135
  def extract_agri_products(detection_text: str):
136
  def _heuristic_product_candidates(text: str):
137
  # Fallback parser from chemical solution sections when model extraction is empty/invalid.
@@ -145,7 +234,7 @@ def extract_agri_products(detection_text: str):
145
  ]
146
 
147
  for line in (text or "").splitlines():
148
- clean = line.strip(" -•*\t")
149
  if not clean:
150
  continue
151
 
@@ -176,6 +265,24 @@ def extract_agri_products(detection_text: str):
176
  f"Analysis: {detection_text}"
177
  )
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  oai = _get_openai_client()
180
  completion = oai.chat.completions.create(
181
  model="gpt-4o-mini",
@@ -195,9 +302,29 @@ def extract_agri_products(detection_text: str):
195
  except json.JSONDecodeError:
196
  return _heuristic_product_candidates(detection_text)
197
 
 
 
 
 
 
198
  def chat_bot(query: str, image_data: bytes = None, language: str = "en"):
199
  system_instruction = CHATBOT_SYSTEM_PROMPTS.get(language, CHATBOT_SYSTEM_PROMPTS["en"])
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  oai = _get_openai_client()
202
  content = [{"type": "text", "text": query}]
203
  if image_data:
 
1
  import json
2
  import base64
3
  import re
4
+ import traceback
5
  from app.config import settings
6
 
7
  try:
 
21
  return response_text.strip()
22
 
23
 
24
+ # ---------------------------------------------------------------------------
25
+ # Client helpers
26
+ # ---------------------------------------------------------------------------
27
+
28
+ GEMINI_MODEL = "gemini-3.5-flash-lite"
29
+
30
  def _get_openai_client():
31
  if not settings.OPENAI_API_KEY:
32
  raise ValueError("OPENAI_API_KEY is required")
 
35
  return OpenAI(api_key=settings.OPENAI_API_KEY)
36
 
37
 
38
+ def _get_gemini_client():
39
+ """Return an OpenAI client pointed at Gemini's OpenAI-compatible endpoint."""
40
+ if OpenAI is None:
41
+ raise ValueError("openai package is not installed.")
42
+ if not settings.GEMINI_API_KEY:
43
+ raise ValueError("GEMINI_API_KEY is required")
44
+ return OpenAI(
45
+ api_key=settings.GEMINI_API_KEY,
46
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
47
+ )
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # System prompts
52
+ # ---------------------------------------------------------------------------
53
+
54
  CHATBOT_SYSTEM_PROMPTS = {
55
  "en": (
56
  "You are the AgriVision Chatbot: a friendly, practical agricultural expert for farmers. "
 
106
  ),
107
  }
108
 
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # Gemini helpers (via OpenAI-compatible endpoint)
112
+ # ---------------------------------------------------------------------------
113
+
114
+ def _gemini_text(prompt: str, system_instruction: str = None, temperature: float = 0.4) -> str:
115
+ """Call Gemini with a text-only prompt via OpenAI-compatible API."""
116
+ client = _get_gemini_client()
117
+ messages = []
118
+ if system_instruction:
119
+ messages.append({"role": "system", "content": system_instruction})
120
+ messages.append({"role": "user", "content": prompt})
121
+
122
+ completion = client.chat.completions.create(
123
+ model=GEMINI_MODEL,
124
+ temperature=temperature,
125
+ messages=messages,
126
+ )
127
+ return completion.choices[0].message.content or ""
128
+
129
+
130
+ def _gemini_vision(prompt: str, image_data: bytes, system_instruction: str = None, temperature: float = 0.4) -> str:
131
+ """Call Gemini with an image + text prompt via OpenAI-compatible API."""
132
+ client = _get_gemini_client()
133
+ img_b64 = base64.b64encode(image_data).decode("utf-8")
134
+
135
+ messages = []
136
+ if system_instruction:
137
+ messages.append({"role": "system", "content": system_instruction})
138
+ messages.append({
139
+ "role": "user",
140
+ "content": [
141
+ {"type": "text", "text": prompt},
142
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}},
143
+ ],
144
+ })
145
+
146
+ completion = client.chat.completions.create(
147
+ model=GEMINI_MODEL,
148
+ temperature=temperature,
149
+ messages=messages,
150
+ )
151
+ return completion.choices[0].message.content or ""
152
+
153
+
154
+ # ---------------------------------------------------------------------------
155
+ # Disease detection
156
+ # ---------------------------------------------------------------------------
157
+
158
  def detect_disease(image_data: str, query: str = ""):
159
  prompt = (
160
  "You are an expert agricultural plant-pathology assistant for farmers. Analyze this crop image and provide practical advice. "
 
186
  f"\n\nFarmer query/context: {query}"
187
  "\n\nRespond only in Markdown and keep language simple for farmers."
188
  )
189
+
190
+ # --- Gemini (primary) ---
191
+ try:
192
+ print("[LLM] detect_disease: trying Gemini...")
193
+ result = _gemini_vision(prompt, image_data, temperature=0.4)
194
+ print("[LLM] detect_disease: Gemini succeeded")
195
+ return result
196
+ except Exception as e:
197
+ print(f"[LLM] detect_disease: Gemini FAILED: {e}")
198
+ traceback.print_exc()
199
+ print("[LLM] detect_disease: falling back to OpenAI...")
200
+
201
+ # --- OpenAI (fallback) ---
202
  oai = _get_openai_client()
203
  img_b64 = base64.b64encode(image_data).decode("utf-8")
204
  completion = oai.chat.completions.create(
 
217
  return completion.choices[0].message.content or ""
218
 
219
 
220
+ # ---------------------------------------------------------------------------
221
+ # Product extraction
222
+ # ---------------------------------------------------------------------------
223
+
224
  def extract_agri_products(detection_text: str):
225
  def _heuristic_product_candidates(text: str):
226
  # Fallback parser from chemical solution sections when model extraction is empty/invalid.
 
234
  ]
235
 
236
  for line in (text or "").splitlines():
237
+ clean = line.strip(" -*\t")
238
  if not clean:
239
  continue
240
 
 
265
  f"Analysis: {detection_text}"
266
  )
267
 
268
+ # --- Gemini (primary) ---
269
+ try:
270
+ print("[LLM] extract_agri_products: trying Gemini...")
271
+ extraction_text = _gemini_text(prompt, temperature=0)
272
+ extraction_text = _clean_fenced_json(extraction_text)
273
+ parsed = json.loads(extraction_text)
274
+ products = parsed.get("products", []) if isinstance(parsed, dict) else []
275
+ if isinstance(products, list) and products:
276
+ print("[LLM] extract_agri_products: Gemini succeeded")
277
+ return products
278
+ print("[LLM] extract_agri_products: Gemini returned empty, using heuristic")
279
+ return _heuristic_product_candidates(detection_text)
280
+ except Exception as e:
281
+ print(f"[LLM] extract_agri_products: Gemini FAILED: {e}")
282
+ traceback.print_exc()
283
+ print("[LLM] extract_agri_products: falling back to OpenAI...")
284
+
285
+ # --- OpenAI (fallback) ---
286
  oai = _get_openai_client()
287
  completion = oai.chat.completions.create(
288
  model="gpt-4o-mini",
 
302
  except json.JSONDecodeError:
303
  return _heuristic_product_candidates(detection_text)
304
 
305
+
306
+ # ---------------------------------------------------------------------------
307
+ # Chatbot
308
+ # ---------------------------------------------------------------------------
309
+
310
  def chat_bot(query: str, image_data: bytes = None, language: str = "en"):
311
  system_instruction = CHATBOT_SYSTEM_PROMPTS.get(language, CHATBOT_SYSTEM_PROMPTS["en"])
312
 
313
+ # --- Gemini (primary) ---
314
+ try:
315
+ print("[LLM] chat_bot: trying Gemini...")
316
+ if image_data:
317
+ result = _gemini_vision(query, image_data, system_instruction=system_instruction, temperature=0.7)
318
+ else:
319
+ result = _gemini_text(query, system_instruction=system_instruction, temperature=0.7)
320
+ print("[LLM] chat_bot: Gemini succeeded")
321
+ return result
322
+ except Exception as e:
323
+ print(f"[LLM] chat_bot: Gemini FAILED: {e}")
324
+ traceback.print_exc()
325
+ print("[LLM] chat_bot: falling back to OpenAI...")
326
+
327
+ # --- OpenAI (fallback) ---
328
  oai = _get_openai_client()
329
  content = [{"type": "text", "text": query}]
330
  if image_data:
requirements.txt CHANGED
@@ -6,3 +6,4 @@ requests
6
  sarvamai
7
  python-dotenv
8
  openai
 
 
6
  sarvamai
7
  python-dotenv
8
  openai
9
+ google-generativeai
test_gemini.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compare Gemini API speed: OpenAI-compat vs direct REST."""
2
+ import time
3
+ import requests
4
+ import os
5
+ from dotenv import load_dotenv
6
+ from openai import OpenAI
7
+
8
+ load_dotenv()
9
+ API_KEY = os.getenv("GEMINI_API_KEY")
10
+ MODEL = "gemini-3.5-flash-lite"
11
+ PROMPT = "What is the capital of France? Reply in one sentence."
12
+
13
+ # --- Method 1: Direct REST API ---
14
+ print("=== Method 1: Direct REST API ===")
15
+ t1 = time.time()
16
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent?key={API_KEY}"
17
+ payload = {"contents": [{"parts": [{"text": PROMPT}]}]}
18
+ resp = requests.post(url, json=payload, timeout=30)
19
+ data = resp.json()
20
+ print(f"Raw response: {data}")
21
+ if "candidates" in data:
22
+ text = data["candidates"][0]["content"]["parts"][0]["text"]
23
+ print(f"Response: {text}")
24
+ else:
25
+ print(f"ERROR: {data.get('error', data)}")
26
+ elapsed1 = time.time() - t1
27
+ print(f"Time: {elapsed1:.1f}s\n")
28
+
29
+ # --- Method 2: OpenAI-compatible endpoint ---
30
+ print("=== Method 2: OpenAI-compatible endpoint ===")
31
+ t2 = time.time()
32
+ client = OpenAI(
33
+ api_key=API_KEY,
34
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
35
+ )
36
+ response = client.chat.completions.create(
37
+ model=MODEL,
38
+ messages=[{"role": "user", "content": PROMPT}],
39
+ )
40
+ elapsed2 = time.time() - t2
41
+ print(f"Response: {response.choices[0].message.content}")
42
+ print(f"Time: {elapsed2:.1f}s")