-
Notifications
You must be signed in to change notification settings - Fork 36
Add AI Text Category Suggestion #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d772a5a
3f53165
3983663
171f555
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,9 @@ | |||||||||||||||||||||||||||
| # Speech-to-Text Model (Whisper) | ||||||||||||||||||||||||||||
| WHISPER_API_URL = "https://router.huggingface.co/models/openai/whisper-large-v3-turbo" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Zero-Shot Text Classification Model | ||||||||||||||||||||||||||||
| ZERO_SHOT_TEXT_API_URL = "https://router.huggingface.co/models/facebook/bart-large-mnli" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| async def _make_request(client, url, payload): | ||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| response = await client.post(url, headers=headers, json=payload, timeout=20.0) | ||||||||||||||||||||||||||||
|
|
@@ -456,3 +459,40 @@ async def detect_abandoned_vehicle_clip(image: Union[Image.Image, bytes], client | |||||||||||||||||||||||||||
| labels = ["abandoned car", "rusted vehicle", "car with flat tires", "wrecked car", "normal parked car"] | ||||||||||||||||||||||||||||
| targets = ["abandoned car", "rusted vehicle", "car with flat tires", "wrecked car"] | ||||||||||||||||||||||||||||
| return await _detect_clip_generic(image, labels, targets, client) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| async def classify_text_category(text: str, client: httpx.AsyncClient = None): | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| Classifies text into civic issue categories using Zero-Shot Classification. | ||||||||||||||||||||||||||||
| Returns the top category and confidence. | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| labels = [ | ||||||||||||||||||||||||||||
| "pothole", "garbage dump", "water leak", "broken street light", | ||||||||||||||||||||||||||||
| "broken infrastructure", "traffic congestion", "fire accident", | ||||||||||||||||||||||||||||
| "stray animal threat", "fallen tree hazard", "pest infestation", | ||||||||||||||||||||||||||||
| "clean area", "noise pollution" | ||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| payload = { | ||||||||||||||||||||||||||||
| "inputs": text, | ||||||||||||||||||||||||||||
| "parameters": {"candidate_labels": labels} | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if client: | ||||||||||||||||||||||||||||
| result = await _make_request(client, ZERO_SHOT_TEXT_API_URL, payload) | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| async with httpx.AsyncClient() as new_client: | ||||||||||||||||||||||||||||
| result = await _make_request(new_client, ZERO_SHOT_TEXT_API_URL, payload) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Result format: {'sequence': '...', 'labels': ['pothole', ...], 'scores': [0.9, ...]} | ||||||||||||||||||||||||||||
| if isinstance(result, dict) and 'labels' in result and 'scores' in result: | ||||||||||||||||||||||||||||
| top_label = result['labels'][0] | ||||||||||||||||||||||||||||
| top_score = result['scores'][0] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+487
to
+490
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against empty At Line 488 and Line 489, direct 💡 Suggested fix- if isinstance(result, dict) and 'labels' in result and 'scores' in result:
- top_label = result['labels'][0]
- top_score = result['scores'][0]
+ if (
+ isinstance(result, dict)
+ and isinstance(result.get("labels"), list)
+ and isinstance(result.get("scores"), list)
+ and len(result["labels"]) > 0
+ and len(result["scores"]) > 0
+ ):
+ top_label = result["labels"][0]
+ top_score = result["scores"][0]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| # Simple mapping to internal categories if needed, or return raw | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| "category": top_label, | ||||||||||||||||||||||||||||
| "confidence": top_score, | ||||||||||||||||||||||||||||
| "all_scores": dict(zip(result['labels'][:3], result['scores'][:3])) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return {"category": "unknown", "confidence": 0} | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,10 @@ | ||||||||||||||||||||||||||||||||||
| from fastapi import APIRouter, HTTPException | ||||||||||||||||||||||||||||||||||
| from fastapi import APIRouter, HTTPException, Request | ||||||||||||||||||||||||||||||||||
| from pydantic import BaseModel | ||||||||||||||||||||||||||||||||||
| from typing import List, Optional, Any | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| from backend.priority_engine import priority_engine | ||||||||||||||||||||||||||||||||||
| from backend.hf_api_service import classify_text_category | ||||||||||||||||||||||||||||||||||
| from backend.dependencies import get_http_client | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| router = APIRouter() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -36,3 +38,22 @@ def analyze_issue(request: AnalyzeIssueRequest): | |||||||||||||||||||||||||||||||||
| suggested_categories=result["suggested_categories"], | ||||||||||||||||||||||||||||||||||
| reasoning=result["reasoning"] | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| class CategorySuggestionRequest(BaseModel): | ||||||||||||||||||||||||||||||||||
| text: str | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @router.post("/api/suggest-category-text") | ||||||||||||||||||||||||||||||||||
| async def suggest_category_text(request: Request, body: CategorySuggestionRequest): | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| Suggests a category based on the text description using Zero-Shot Classification. | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| if not body.text or len(body.text) < 5: | ||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=400, detail="Text must be at least 5 characters long") | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||
| client = get_http_client(request) | ||||||||||||||||||||||||||||||||||
| result = await classify_text_category(body.text, client=client) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normalize input before length validation. Line 50 validates untrimmed text, so whitespace-only payloads can pass and still hit the classifier. 💡 Suggested fix- if not body.text or len(body.text) < 5:
+ cleaned_text = body.text.strip()
+ if len(cleaned_text) < 5:
raise HTTPException(status_code=400, detail="Text must be at least 5 characters long")
@@
- result = await classify_text_category(body.text, client=client)
+ result = await classify_text_category(cleaned_text, client=client)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||
| # Fallback or error | ||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=500, detail=f"Classification service unavailable: {str(e)}") | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+57
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not expose raw exception text in API responses. Line 59 returns 💡 Suggested fix- except Exception as e:
- # Fallback or error
- raise HTTPException(status_code=500, detail=f"Classification service unavailable: {str(e)}")
+ except Exception as e:
+ # log e server-side
+ raise HTTPException(status_code=500, detail="Classification service unavailable") from e🧰 Tools🪛 Ruff (0.15.2)[warning] 57-57: Do not catch blind exception: (BLE001) [warning] 59-59: Within an (B904) [warning] 59-59: Use explicit conversion flag Replace with conversion flag (RUF010) 🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Avoid returning raw exception details in the HTTP response; return a generic error message instead. Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Use
.get()to safely ensure lists are non-empty before accessing index 0.Prompt for AI agents