feat: add real-time agronomy chatbot with LLM integration (closes #55)#57
feat: add real-time agronomy chatbot with LLM integration (closes #55)#57Aharshi3614 wants to merge 4 commits into
Conversation
|
@Aharshi3614 is attempting to deploy a commit to the karan3431's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
🎉 Thanks for your contribution, @Aharshi3614! Please make sure CI passes and the checklist in the PR template is complete. A maintainer will review this soon. — The AgroNavis team |
|
Warning Review limit reached
More reviews will be available in 48 minutes and 36 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughImplements an agronomy chatbot: FastAPI endpoint accepting message, crop/soil/location and history, calls Gemini (or keyword fallback) for replies; adds a React AgronomyChatbot UI that posts to the endpoint; integrates the component into the dashboard and adds English localization. ChangesAgronomy Chatbot Feature
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as AgronomyChatbot
participant ChatAPI as Chatbot API
participant GeminiAPI as Gemini API
participant Fallback as Fallback Rules
User->>UI: Enter message + Send
UI->>ChatAPI: POST /api/v1/chatbot/chat (message, crop, soil_type, location, history)
ChatAPI->>ChatAPI: Build system prompt
alt GEMINI_API_KEY present
ChatAPI->>GeminiAPI: POST generateContent (prompt + history)
alt 200 OK
GeminiAPI-->>ChatAPI: Generated reply
ChatAPI-->>UI: ChatResponse {reply}
else non-200
ChatAPI->>Fallback: Apply keyword rules
Fallback-->>ChatAPI: Rule-based reply
ChatAPI-->>UI: ChatResponse {reply}
end
else API key missing
ChatAPI->>Fallback: Apply keyword rules
Fallback-->>ChatAPI: Rule-based reply
ChatAPI-->>UI: ChatResponse {reply}
end
UI->>User: Display bot reply or error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/chatbot.py`:
- Around line 120-121: Replace the current exception propagation that uses
detail=str(exc) with a generic error message and log the full exception on the
server: in the except block in chatbot.py (the place that currently does raise
HTTPException(status_code=500, detail=str(exc))), call a server logger (e.g.,
logging.exception or your existing app logger) to record the full exc/traceback,
then raise HTTPException(status_code=500, detail="Internal server error") (or
similar generic text) so internal/provider details are not leaked to clients.
- Around line 77-82: The current code assumes a well-formed Gemini JSON and
directly indexes data["candidates"][0]["content"]["parts"][0]["text"], which
raises on empty/filtered responses; update the block around the
httpx.AsyncClient call to validate the response body before returning: after
confirming resp.status_code == 200, parse resp.json() into data and check
data.get("candidates") is a non-empty list and that the nested keys ("content"
-> "parts" -> [0] -> "text") exist and are non-empty; if any check fails (or if
parsing raises), call and return _fallback_response(message) instead; you can
implement this either with explicit conditional checks on data or by wrapping
the extraction in a try/except catching KeyError/IndexError/TypeError/ValueError
and returning _fallback_response(message) on exception (referencing resp, data,
and _fallback_response).
- Line 10: The chatbot router defined as router =
APIRouter(prefix="/api/v1/chatbot", ...) in backend/chatbot.py is never mounted,
so /api/v1/chatbot/chat 404s; update backend/main.py where the FastAPI app is
created (symbol: app) to include the router by calling
app.include_router(router) (or app.include_router(router, prefix=...) if you
prefer) after importing router from backend.chatbot, ensuring the router
registration happens before the app is returned/launched.
In `@frontend/src/components/AgronomyChatbot.tsx`:
- Around line 19-24: The initial greeting is captured once in the
AgronomyChatbot component's messages state (messages / setMessages via useState)
so it doesn't update when the app language changes; update the greeting by
deriving the initial messages from t and adding an effect that listens for i18n
language (or t) changes and calls setMessages to replace the first bot message's
content with t('chatbot.greeting', ...) while preserving existing messages;
reference the messages array, setMessages and the greeting object so the effect
only updates the bot greeting text when language/t changes.
- Around line 47-67: The fetch response handling should distinguish HTTP errors
from network exceptions: in the block using
fetch(`${API_BASE}/api/v1/chatbot/chat`), check res.ok before calling
res.json(); if !res.ok, call setMessages(...) with the chatbot error message via
t('chatbot.error', ...) (using the same shape as the successful branch) rather
than falling into the catch that shows the network message; only use the catch
to handle true network failures and keep setMessages for network errors
(t('chatbot.networkError', ...)). Ensure you reference the existing symbols
fetch call, res, res.ok, res.json(), setMessages, and t so the change is
localized to the current try/catch flow.
In `@frontend/src/pages/dashboard.tsx`:
- Around line 244-248: The conditional JSX rendering block using activeTab
currently wraps the false branch in extra JSX braces causing invalid TSX; update
the ternary so the true branch renders <AgronomyChatbot /> and the false branch
directly renders <DashboardContent activeTab={activeTab}
setActiveTab={setActiveTab} /> (remove the surrounding { ... } around the
DashboardContent expression) to restore valid TSX.
- Line 245: The AgronomyChatbot on this page is rendered without the required
farm context (crop/soilType/location) so outgoing requests are empty; update the
dashboard.tsx render to obtain the current farm data (from the existing farm
state/store/context/provider used elsewhere on the page) and pass those fields
into AgronomyChatbot as props (e.g., crop, soilType, location), handling
null/undefined safely (fallbacks or conditional render) and keeping prop names
matching the AgronomyChatbot component signature so the backend receives the
populated context.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c28b8d2a-d98a-4d89-a5a3-d40c469adc24
📒 Files selected for processing (4)
backend/chatbot.pyfrontend/src/components/AgronomyChatbot.tsxfrontend/src/locales/en.jsonfrontend/src/pages/dashboard.tsx
| async with httpx.AsyncClient(timeout=15.0) as client: | ||
| resp = await client.post(url, json=payload) | ||
| if resp.status_code != 200: | ||
| return _fallback_response(message) | ||
| data = resp.json() | ||
| return data["candidates"][0]["content"]["parts"][0]["text"] |
There was a problem hiding this comment.
Handle malformed/empty Gemini responses the same way as non-200s.
A 200 response is not enough here. If Gemini returns an empty candidates array or a filtered payload, Line 82 raises and the endpoint returns 500 instead of the fallback response.
Suggested fix
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload)
if resp.status_code != 200:
return _fallback_response(message)
- data = resp.json()
- return data["candidates"][0]["content"]["parts"][0]["text"]
+ data = resp.json()
+ try:
+ return data["candidates"][0]["content"]["parts"][0]["text"]
+ except (KeyError, IndexError, TypeError):
+ return _fallback_response(message)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/chatbot.py` around lines 77 - 82, The current code assumes a
well-formed Gemini JSON and directly indexes
data["candidates"][0]["content"]["parts"][0]["text"], which raises on
empty/filtered responses; update the block around the httpx.AsyncClient call to
validate the response body before returning: after confirming resp.status_code
== 200, parse resp.json() into data and check data.get("candidates") is a
non-empty list and that the nested keys ("content" -> "parts" -> [0] -> "text")
exist and are non-empty; if any check fails (or if parsing raises), call and
return _fallback_response(message) instead; you can implement this either with
explicit conditional checks on data or by wrapping the extraction in a
try/except catching KeyError/IndexError/TypeError/ValueError and returning
_fallback_response(message) on exception (referencing resp, data, and
_fallback_response).
| except Exception as exc: | ||
| raise HTTPException(status_code=500, detail=str(exc)) No newline at end of file |
There was a problem hiding this comment.
Do not expose raw exception text in the API response.
detail=str(exc) leaks internal failure details to clients, including provider-side error text. Return a generic 500 message and log the exception server-side instead.
Suggested fix
+import logging
+
+logger = logging.getLogger(__name__)
+
`@router.post`("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
try:
system_prompt = _build_system_prompt(
req.crop or "",
@@
)
reply = await _call_gemini(system_prompt, req.history or [], req.message)
return ChatResponse(reply=reply, success=True)
except Exception as exc:
- raise HTTPException(status_code=500, detail=str(exc))
+ logger.exception("Chatbot request failed")
+ raise HTTPException(status_code=500, detail="Failed to process chatbot request") from exc🧰 Tools
🪛 Ruff (0.15.15)
[warning] 120-120: Do not catch blind exception: Exception
(BLE001)
[warning] 121-121: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/chatbot.py` around lines 120 - 121, Replace the current exception
propagation that uses detail=str(exc) with a generic error message and log the
full exception on the server: in the except block in chatbot.py (the place that
currently does raise HTTPException(status_code=500, detail=str(exc))), call a
server logger (e.g., logging.exception or your existing app logger) to record
the full exc/traceback, then raise HTTPException(status_code=500,
detail="Internal server error") (or similar generic text) so internal/provider
details are not leaked to clients.
Source: Linters/SAST tools
| const [messages, setMessages] = useState<Message[]>([ | ||
| { | ||
| role: 'bot', | ||
| content: t('chatbot.greeting', 'Hello! I am AgroBot. Ask me anything about your crops, soil, or farming practices.'), | ||
| }, | ||
| ]); |
There was a problem hiding this comment.
The greeting won't retranslate after a language switch.
Because the initial bot message is captured in state once, changing the app language leaves the greeting in the old locale while the rest of the chatbot updates.
Suggested fix
- const [messages, setMessages] = useState<Message[]>([
- {
- role: 'bot',
- content: t('chatbot.greeting', 'Hello! I am AgroBot. Ask me anything about your crops, soil, or farming practices.'),
- },
- ]);
+ const greeting = t(
+ 'chatbot.greeting',
+ 'Hello! I am AgroBot. Ask me anything about your crops, soil, or farming practices.',
+ );
+ const [messages, setMessages] = useState<Message[]>([
+ { role: 'bot', content: greeting },
+ ]);
+
+ useEffect(() => {
+ setMessages(prev =>
+ prev.length > 0 && prev[0].role === 'bot'
+ ? [{ ...prev[0], content: greeting }, ...prev.slice(1)]
+ : prev,
+ );
+ }, [greeting]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/AgronomyChatbot.tsx` around lines 19 - 24, The
initial greeting is captured once in the AgronomyChatbot component's messages
state (messages / setMessages via useState) so it doesn't update when the app
language changes; update the greeting by deriving the initial messages from t
and adding an effect that listens for i18n language (or t) changes and calls
setMessages to replace the first bot message's content with
t('chatbot.greeting', ...) while preserving existing messages; reference the
messages array, setMessages and the greeting object so the effect only updates
the bot greeting text when language/t changes.
| const res = await fetch(`${API_BASE}/api/v1/chatbot/chat`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| message: text, | ||
| crop: crop ?? '', | ||
| soil_type: soilType ?? '', | ||
| location: location ?? '', | ||
| history, | ||
| }), | ||
| }); | ||
|
|
||
| const data = await res.json(); | ||
| setMessages(prev => [...prev, { | ||
| role: 'bot', | ||
| content: data.reply ?? t('chatbot.error', 'Sorry, I could not process that. Please try again.'), | ||
| }]); | ||
| } catch { | ||
| setMessages(prev => [...prev, { | ||
| role: 'bot', | ||
| content: t('chatbot.networkError', 'Network error. Please check your connection.'), |
There was a problem hiding this comment.
Differentiate server failures from actual network failures.
Right now a 500/404 with a non-JSON body falls into the catch block and shows the network-error string, even though the request reached the server. Check res.ok before parsing so backend failures use the existing chatbot.error message.
Suggested fix
const res = await fetch(`${API_BASE}/api/v1/chatbot/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: text,
@@
history,
}),
});
+ if (!res.ok) {
+ throw new Error('chatbot_request_failed');
+ }
+
const data = await res.json();
setMessages(prev => [...prev, {
role: 'bot',
content: data.reply ?? t('chatbot.error', 'Sorry, I could not process that. Please try again.'),
}]);
- } catch {
+ } catch (err) {
setMessages(prev => [...prev, {
role: 'bot',
- content: t('chatbot.networkError', 'Network error. Please check your connection.'),
+ content:
+ err instanceof Error && err.message === 'chatbot_request_failed'
+ ? t('chatbot.error', 'Sorry, I could not process that. Please try again.')
+ : t('chatbot.networkError', 'Network error. Please check your connection.'),
}]);
} finally {
setLoading(false);
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/AgronomyChatbot.tsx` around lines 47 - 67, The fetch
response handling should distinguish HTTP errors from network exceptions: in the
block using fetch(`${API_BASE}/api/v1/chatbot/chat`), check res.ok before
calling res.json(); if !res.ok, call setMessages(...) with the chatbot error
message via t('chatbot.error', ...) (using the same shape as the successful
branch) rather than falling into the catch that shows the network message; only
use the catch to handle true network failures and keep setMessages for network
errors (t('chatbot.networkError', ...)). Ensure you reference the existing
symbols fetch call, res, res.ok, res.json(), setMessages, and t so the change is
localized to the current try/catch flow.
| ) : ( | ||
| <DashboardContent activeTab={activeTab} setActiveTab={setActiveTab} /> | ||
| {activeTab === 'chatbot' ? ( | ||
| <AgronomyChatbot /> |
There was a problem hiding this comment.
Wire farm context into the chatbot before shipping this flow.
The backend and component both support crop/soilType/location, but this page renders <AgronomyChatbot /> without any of them. From this entry point the request body will always send empty context, so the feature misses the issue requirement for crop/soil/location awareness.
Suggested direction
- <AgronomyChatbot />
+ <AgronomyChatbot
+ crop={/* current farm crop */}
+ soilType={/* current farm soil type */}
+ location={/* current farm location */}
+ />As per coding guidelines, trace cross-file contracts and validate the actual integration points, not just the presence of supporting code.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <AgronomyChatbot /> | |
| <AgronomyChatbot | |
| crop={/* current farm crop */} | |
| soilType={/* current farm soil type */} | |
| location={/* current farm location */} | |
| /> |
🧰 Tools
🪛 Biome (2.4.16)
[error] 245-245: expected , but instead found <
(parse)
[error] 245-245: expected , but instead found /
(parse)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/dashboard.tsx` at line 245, The AgronomyChatbot on this
page is rendered without the required farm context (crop/soilType/location) so
outgoing requests are empty; update the dashboard.tsx render to obtain the
current farm data (from the existing farm state/store/context/provider used
elsewhere on the page) and pass those fields into AgronomyChatbot as props
(e.g., crop, soilType, location), handling null/undefined safely (fallbacks or
conditional render) and keeping prop names matching the AgronomyChatbot
component signature so the backend receives the populated context.
|
The frontend build fails: |
|
@jpdevhub The frontend build is now running! |
|
Dont make another endpoint attach the chatbot thing in the main.py |
61f58af to
be8ed25
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
backend/main.py (1)
1027-1030: 🏗️ Heavy liftConsider rate limiting and monitoring for the chatbot endpoint.
The chatbot endpoint makes external API calls to Gemini with a 15-second timeout. Even with authentication added, consider implementing:
- Per-user rate limiting to prevent individual users from exhausting quotas
- Monitoring and alerting on Gemini API failures and latency
- Circuit breaker pattern if Gemini becomes unavailable to fail fast
- Request queuing if you expect high concurrency to avoid blocking request threads
These protections help ensure the chatbot feature doesn't degrade the overall API availability.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/main.py` around lines 1027 - 1030, The chatbot router currently exposed via app.include_router(chatbot_router) lacks protections against abuse and external API failures; update the chatbot router (chatbot_router in chatbot.py) and app initialization to add per-user rate limiting (e.g., middleware or dependency that enforces token/user-id based limits), integrate monitoring/metrics around the Gemini call (record latency, success/failure counts and expose them to your metrics system), wrap the Gemini client calls in a circuit breaker (or use a library) to short-circuit when error rate/latency crosses thresholds, and add a request queue/worker or async semaphore around the Gemini call to bound concurrency; also emit/log distinct alerts on repeated Gemini failures/latency so ops can be notified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/main.py`:
- Line 31: Remove the duplicate import of the chatbot router: keep the top-level
import "from chatbot import router as chatbot_router" (already present near the
top of the file) and delete the redundant second occurrence of the same import
that appears immediately before use; no other code changes are needed since
references use chatbot_router from the top-level import.
- Line 1030: The chatbot POST handler is publicly exposed because
app.include_router(chatbot_router) is mounted without authentication and
`@router.post`("/chat")'s async def chat(req: ChatRequest) lacks a
Depends(verify_token); secure it by requiring verify_token — either add
dependencies=[Depends(verify_token)] to the app.include_router(chatbot_router)
call or annotate the chat handler with a Depends(verify_token) parameter so
requests must be authenticated, and add a rate-limiting layer (e.g., middleware
or a per-route dependency) to the chatbot route to prevent quota/cost abuse.
---
Nitpick comments:
In `@backend/main.py`:
- Around line 1027-1030: The chatbot router currently exposed via
app.include_router(chatbot_router) lacks protections against abuse and external
API failures; update the chatbot router (chatbot_router in chatbot.py) and app
initialization to add per-user rate limiting (e.g., middleware or dependency
that enforces token/user-id based limits), integrate monitoring/metrics around
the Gemini call (record latency, success/failure counts and expose them to your
metrics system), wrap the Gemini client calls in a circuit breaker (or use a
library) to short-circuit when error rate/latency crosses thresholds, and add a
request queue/worker or async semaphore around the Gemini call to bound
concurrency; also emit/log distinct alerts on repeated Gemini failures/latency
so ops can be notified.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dae3937a-3eea-4779-b317-d155cdbbc09b
⛔ Files ignored due to path filters (1)
1NPDPRO5.JPGis excluded by!**/*.jpg
📒 Files selected for processing (2)
backend/main.pyfrontend/src/pages/dashboard.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/dashboard.tsx
|
@jpdevhub I have already attached the chatbot router in main.py. |
Summary
Adds a real-time agronomy chatbot (AgroBot) that helps farmers with crop diseases, fertilizers, irrigation, and harvest planning using Gemini LLM.
Related Issue
Closes #55
Changes
backend/chatbot.py— FastAPI endpoint for Gemini LLM integrationmain.pyAgronomyChatbot.tsxcomponent with chat UIen.jsonTesting
Checklist
.envvalues are committedSummary by CodeRabbit
New Features
Reliability
Localization
UI