alaselababatunde commited on
Commit
80f385a
·
1 Parent(s): a0de8cc
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ AgriCopilot_OPENROUTER_MODEL_KEY=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
__pycache__/app.cpython-311.pyc ADDED
Binary file (11.9 kB). View file
 
__pycache__/vector.cpython-311.pyc ADDED
Binary file (4.68 kB). View file
 
app.py CHANGED
@@ -5,12 +5,12 @@
5
  import os
6
  import logging
7
  import io
8
- import torch
9
  from fastapi import FastAPI, Request, Header, HTTPException, UploadFile, File
10
  from fastapi.responses import JSONResponse
11
  from pydantic import BaseModel
12
- from transformers import pipeline
13
- from PIL import Image
14
  from vector import query_vector
15
 
16
  # ==============================
@@ -67,102 +67,99 @@ class VectorRequest(BaseModel):
67
  query: str
68
 
69
  # ==============================
70
- # Hugging Face Config
71
  # ==============================
72
- HF_TOKEN = os.getenv("HUGGINGFACEHUB_API_TOKEN")
 
 
73
 
74
- if not HF_TOKEN:
75
- logger.warning("⚠️ No Hugging Face token found. Gated models may fail to load.")
76
- else:
77
- logger.info("✅ Hugging Face token detected.")
78
-
79
- # Device setup (GPU if available)
80
- device = 0 if torch.cuda.is_available() else -1
81
- logger.info(f"🧠 Using device: {'GPU' if device == 0 else 'CPU'}")
82
-
83
- # ==============================
84
- # Pipelines
85
- # ==============================
86
- # Conversational + reasoning models (Meta LLaMA)
87
- chat_pipe = pipeline(
88
- "text-generation",
89
- model="meta-llama/Llama-3.1-8B-Instruct",
90
- token=HF_TOKEN,
91
- device=device,
92
- )
93
-
94
- disaster_pipe = pipeline(
95
- "text-generation",
96
- model="meta-llama/Llama-3.1-8B-Instruct",
97
- token=HF_TOKEN,
98
- device=device,
99
- )
100
-
101
- market_pipe = pipeline(
102
- "text-generation",
103
- model="meta-llama/Llama-3.1-8B-Instruct",
104
- token=HF_TOKEN,
105
- device=device,
106
- )
107
-
108
- # Lightweight Meta Vision backbone (ConvNeXt-Tiny)
109
- crop_vision = pipeline(
110
- "image-classification",
111
- model="facebook/convnext-tiny-224",
112
- token=HF_TOKEN,
113
- device=device,
114
  )
 
115
 
116
  # ==============================
117
  # Helper Functions
118
  # ==============================
119
- def run_conversational(pipe, prompt: str):
120
- """Handles conversational tasks safely."""
 
 
121
  try:
122
- output = pipe(prompt, max_new_tokens=200, temperature=0.7, do_sample=True)
123
- if isinstance(output, list) and len(output) > 0:
124
- return output[0].get("generated_text", str(output))
125
- return str(output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  except Exception as e:
127
  logger.error(f"Conversational pipeline error: {e}")
128
  return f"⚠️ Model error: {str(e)}"
129
 
130
  def run_crop_doctor(image_bytes: bytes, symptoms: str):
131
- """
132
- Hybrid Crop Doctor System:
133
- 1. Uses ConvNeXt to classify plant visuals.
134
- 2. Pulls related info from vector dataset.
135
- 3. LLaMA 3.1 generates a short diagnosis and treatment guide.
136
- """
137
  try:
138
- # --- Step 1: Vision Classification ---
139
- image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
140
- vision_results = crop_vision(image)
141
- if not vision_results or "label" not in vision_results[0]:
142
- raise ValueError("No vision classification result received.")
143
- top_label = vision_results[0]["label"]
144
-
145
- # --- Step 2: Vector Knowledge Recall ---
146
  vector_matches = query_vector(symptoms)
147
  related_knowledge = " ".join(vector_matches[:3]) if isinstance(vector_matches, list) else str(vector_matches)
148
 
149
- # --- Step 3: Reasoning via LLaMA ---
150
  prompt = (
151
- f"A farmer uploaded a maize image showing signs of '{top_label}'. "
152
- f"Reported symptoms: {symptoms}. "
153
  f"Knowledge base reference: {related_knowledge}. "
154
  "Generate a structured diagnostic report with:\n"
155
  "1. Disease Name\n2. Cause\n3. Treatment\n4. Prevention Tips\n"
156
  "Keep the explanation short and easy for farmers to understand."
157
  )
158
 
159
- response = chat_pipe(prompt, max_new_tokens=250, temperature=0.6, do_sample=False, truncation=True)
 
 
 
 
 
 
 
 
 
 
 
160
 
161
- # Extract text output
162
- if isinstance(response, list) and len(response) > 0:
163
- text = response[0].get("generated_text", "").strip()
164
- return text if text else "⚠️ No response generated. Try again with clearer image or symptoms."
165
- return "⚠️ Unexpected response format from reasoning model."
166
 
167
  except Exception as e:
168
  logger.error(f"Crop Doctor error: {e}")
@@ -186,19 +183,22 @@ async def crop_doctor(
186
  @app.post("/multilingual-chat")
187
  async def multilingual_chat(req: ChatRequest, authorization: str | None = Header(None)):
188
  check_auth(authorization)
189
- reply = run_conversational(chat_pipe, req.query)
 
190
  return {"reply": reply}
191
 
192
  @app.post("/disaster-summarizer")
193
  async def disaster_summarizer(req: DisasterRequest, authorization: str | None = Header(None)):
194
  check_auth(authorization)
195
- summary = run_conversational(disaster_pipe, req.report)
 
196
  return {"summary": summary}
197
 
198
  @app.post("/marketplace")
199
  async def marketplace(req: MarketRequest, authorization: str | None = Header(None)):
200
  check_auth(authorization)
201
- recommendation = run_conversational(market_pipe, req.product)
 
202
  return {"recommendation": recommendation}
203
 
204
  @app.post("/vector-search")
 
5
  import os
6
  import logging
7
  import io
8
+ import base64
9
  from fastapi import FastAPI, Request, Header, HTTPException, UploadFile, File
10
  from fastapi.responses import JSONResponse
11
  from pydantic import BaseModel
12
+ from openai import OpenAI
13
+ from duckduckgo_search import DDGS
14
  from vector import query_vector
15
 
16
  # ==============================
 
67
  query: str
68
 
69
  # ==============================
70
+ # OpenRouter Config
71
  # ==============================
72
+ OPENROUTER_API_KEY = os.getenv("AgriCopilot_OPENROUTER_MODEL_KEY")
73
+ if not OPENROUTER_API_KEY:
74
+ logger.error("🛑 Missing AgriCopilot_OPENROUTER_MODEL_KEY")
75
 
76
+ client = OpenAI(
77
+ base_url="https://openrouter.ai/api/v1",
78
+ api_key=OPENROUTER_API_KEY,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  )
80
+ MODEL_NAME = "qwen/qwen3-vl-235b-a22b-thinking"
81
 
82
  # ==============================
83
  # Helper Functions
84
  # ==============================
85
+ def format_ai_response(text: str) -> str:
86
+ return f"{text}\n\n---\n**AI: AgriCopilot | Provider: Team Astra**"
87
+
88
+ def run_with_fallback(system_prompt: str, user_prompt: str) -> str:
89
  try:
90
+ # First pass
91
+ response = client.chat.completions.create(
92
+ model=MODEL_NAME,
93
+ messages=[
94
+ {"role": "system", "content": system_prompt + " If you do not know the answer and lack sufficient knowledge, explicitly reply with only 'WEB_SEARCH_REQUIRED'."},
95
+ {"role": "user", "content": user_prompt}
96
+ ],
97
+ temperature=0.7,
98
+ max_tokens=800
99
+ )
100
+ reply = response.choices[0].message.content.strip()
101
+
102
+ if "WEB_SEARCH_REQUIRED" in reply.upper():
103
+ logger.info("🌍 Web search triggered!")
104
+ ddgs = DDGS()
105
+ search_results = list(ddgs.text(user_prompt, max_results=3))
106
+ context = " ".join([r['body'] for r in search_results]) if search_results else "No relevant search results found online."
107
+
108
+ # Second pass with search context
109
+ second_prompt = f"Using this web search context: {context}\n\nAnswer the user: {user_prompt}"
110
+ response2 = client.chat.completions.create(
111
+ model=MODEL_NAME,
112
+ messages=[
113
+ {"role": "system", "content": system_prompt},
114
+ {"role": "user", "content": second_prompt}
115
+ ],
116
+ temperature=0.7,
117
+ max_tokens=800
118
+ )
119
+ reply = response2.choices[0].message.content.strip()
120
+ reply += "\n\n*(Information augmented with real-time web search)*"
121
+
122
+ return format_ai_response(reply)
123
  except Exception as e:
124
  logger.error(f"Conversational pipeline error: {e}")
125
  return f"⚠️ Model error: {str(e)}"
126
 
127
  def run_crop_doctor(image_bytes: bytes, symptoms: str):
 
 
 
 
 
 
128
  try:
129
+ # Extract base64 encoded image
130
+ base64_image = base64.b64encode(image_bytes).decode('utf-8')
131
+ image_url = f"data:image/jpeg;base64,{base64_image}"
132
+
 
 
 
 
133
  vector_matches = query_vector(symptoms)
134
  related_knowledge = " ".join(vector_matches[:3]) if isinstance(vector_matches, list) else str(vector_matches)
135
 
 
136
  prompt = (
137
+ "Analyze this crop image. "
138
+ f"The farmer reported these symptoms: {symptoms}. "
139
  f"Knowledge base reference: {related_knowledge}. "
140
  "Generate a structured diagnostic report with:\n"
141
  "1. Disease Name\n2. Cause\n3. Treatment\n4. Prevention Tips\n"
142
  "Keep the explanation short and easy for farmers to understand."
143
  )
144
 
145
+ response = client.chat.completions.create(
146
+ model=MODEL_NAME,
147
+ messages=[
148
+ {"role": "system", "content": "You are AgriCopilot, an expert agricultural AI assistant."},
149
+ {"role": "user", "content": [
150
+ {"type": "text", "text": prompt},
151
+ {"type": "image_url", "image_url": {"url": image_url}}
152
+ ]}
153
+ ],
154
+ temperature=0.6,
155
+ max_tokens=800
156
+ )
157
 
158
+ reply = response.choices[0].message.content.strip()
159
+ if not reply:
160
+ return "⚠️ No response generated. Try again with clearer image or symptoms."
161
+
162
+ return format_ai_response(reply)
163
 
164
  except Exception as e:
165
  logger.error(f"Crop Doctor error: {e}")
 
183
  @app.post("/multilingual-chat")
184
  async def multilingual_chat(req: ChatRequest, authorization: str | None = Header(None)):
185
  check_auth(authorization)
186
+ sys_prompt = "You are AgriCopilot, an expert agricultural AI assistant. Answer the farmer's question helpfully."
187
+ reply = run_with_fallback(sys_prompt, req.query)
188
  return {"reply": reply}
189
 
190
  @app.post("/disaster-summarizer")
191
  async def disaster_summarizer(req: DisasterRequest, authorization: str | None = Header(None)):
192
  check_auth(authorization)
193
+ sys_prompt = "You are AgriCopilot. Summarize the following disaster report for a farmer and provide actionable immediate next steps."
194
+ summary = run_with_fallback(sys_prompt, req.report)
195
  return {"summary": summary}
196
 
197
  @app.post("/marketplace")
198
  async def marketplace(req: MarketRequest, authorization: str | None = Header(None)):
199
  check_auth(authorization)
200
+ sys_prompt = "You are AgriCopilot. Provide marketplace recommendations, pricing strategies, and selling tips for the specified agricultural product."
201
+ recommendation = run_with_fallback(sys_prompt, req.product)
202
  return {"recommendation": recommendation}
203
 
204
  @app.post("/vector-search")
requirements.txt CHANGED
@@ -2,14 +2,9 @@ fastapi
2
  uvicorn[standard]
3
  langchain-community
4
  faiss-cpu
5
- huggingface-hub
6
- sentence-transformers
7
  pandas
8
  datasets
9
- transformers
10
- accelerate
11
- torch
12
- sentencepiece
13
  kagglehub
14
- langdetect
15
- python-multipart
 
 
2
  uvicorn[standard]
3
  langchain-community
4
  faiss-cpu
 
 
5
  pandas
6
  datasets
 
 
 
 
7
  kagglehub
8
+ python-multipart
9
+ duckduckgo-search
10
+ openai