Fix chatbot: session_id validation, init failure guard, blank message check
Browse files- backend/api/routes/chatbot.py +39 -18
backend/api/routes/chatbot.py
CHANGED
|
@@ -5,9 +5,8 @@ from __future__ import annotations
|
|
| 5 |
import logging
|
| 6 |
import threading
|
| 7 |
|
| 8 |
-
from pydantic import BaseModel, Field
|
| 9 |
-
from fastapi import APIRouter, Depends, Request
|
| 10 |
-
from fastapi.responses import JSONResponse
|
| 11 |
from slowapi import Limiter
|
| 12 |
from slowapi.util import get_remote_address
|
| 13 |
|
|
@@ -21,12 +20,19 @@ router = APIRouter()
|
|
| 21 |
|
| 22 |
class ChatRequest(BaseModel):
|
| 23 |
message: str = Field(..., min_length=1, max_length=4000)
|
| 24 |
-
session_id: str = "default"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
class FeedbackRequest(BaseModel):
|
| 28 |
-
session_id: str
|
| 29 |
-
message_id: str
|
| 30 |
rating: str = Field(..., pattern=r"^(up|down|flag)$")
|
| 31 |
comment: str = Field("", max_length=2000)
|
| 32 |
|
|
@@ -34,16 +40,25 @@ class FeedbackRequest(BaseModel):
|
|
| 34 |
# Thread-safe lazy chatbot init
|
| 35 |
_chatbot = None
|
| 36 |
_chatbot_lock = threading.Lock()
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
def _get_chatbot(hub: DataHub):
|
| 40 |
-
global _chatbot
|
| 41 |
if _chatbot is not None:
|
| 42 |
return _chatbot
|
|
|
|
|
|
|
| 43 |
with _chatbot_lock:
|
| 44 |
-
if _chatbot is None:
|
|
|
|
|
|
|
| 45 |
from src.chatbot.vineyard_chatbot import VineyardChatbot
|
| 46 |
_chatbot = VineyardChatbot(hub=hub)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
return _chatbot
|
| 48 |
|
| 49 |
|
|
@@ -51,15 +66,21 @@ def _get_chatbot(hub: DataHub):
|
|
| 51 |
@limiter.limit("10/minute")
|
| 52 |
async def chat_message(request: Request, req: ChatRequest, hub: DataHub = Depends(get_datahub)):
|
| 53 |
bot = _get_chatbot(hub)
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
@router.post("/feedback")
|
|
@@ -76,4 +97,4 @@ async def chat_feedback(request: Request, req: FeedbackRequest):
|
|
| 76 |
return {"status": "ok"}
|
| 77 |
except Exception as exc:
|
| 78 |
log.error("Feedback logging failed: %s", exc)
|
| 79 |
-
|
|
|
|
| 5 |
import logging
|
| 6 |
import threading
|
| 7 |
|
| 8 |
+
from pydantic import BaseModel, Field, field_validator
|
| 9 |
+
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
|
|
| 10 |
from slowapi import Limiter
|
| 11 |
from slowapi.util import get_remote_address
|
| 12 |
|
|
|
|
| 20 |
|
| 21 |
class ChatRequest(BaseModel):
|
| 22 |
message: str = Field(..., min_length=1, max_length=4000)
|
| 23 |
+
session_id: str = Field("default", min_length=1, max_length=100, pattern=r"^[a-zA-Z0-9_\-]+$")
|
| 24 |
+
|
| 25 |
+
@field_validator("message")
|
| 26 |
+
@classmethod
|
| 27 |
+
def message_not_blank(cls, v: str) -> str:
|
| 28 |
+
if not v.strip():
|
| 29 |
+
raise ValueError("Message must not be blank")
|
| 30 |
+
return v.strip()
|
| 31 |
|
| 32 |
|
| 33 |
class FeedbackRequest(BaseModel):
|
| 34 |
+
session_id: str = Field(..., min_length=1, max_length=100)
|
| 35 |
+
message_id: str = Field(..., min_length=1, max_length=100)
|
| 36 |
rating: str = Field(..., pattern=r"^(up|down|flag)$")
|
| 37 |
comment: str = Field("", max_length=2000)
|
| 38 |
|
|
|
|
| 40 |
# Thread-safe lazy chatbot init
|
| 41 |
_chatbot = None
|
| 42 |
_chatbot_lock = threading.Lock()
|
| 43 |
+
_chatbot_init_failed = False
|
| 44 |
|
| 45 |
|
| 46 |
def _get_chatbot(hub: DataHub):
|
| 47 |
+
global _chatbot, _chatbot_init_failed
|
| 48 |
if _chatbot is not None:
|
| 49 |
return _chatbot
|
| 50 |
+
if _chatbot_init_failed:
|
| 51 |
+
return None
|
| 52 |
with _chatbot_lock:
|
| 53 |
+
if _chatbot is not None:
|
| 54 |
+
return _chatbot
|
| 55 |
+
try:
|
| 56 |
from src.chatbot.vineyard_chatbot import VineyardChatbot
|
| 57 |
_chatbot = VineyardChatbot(hub=hub)
|
| 58 |
+
except Exception as exc:
|
| 59 |
+
_chatbot_init_failed = True
|
| 60 |
+
log.error("Chatbot init failed (will not retry): %s", exc)
|
| 61 |
+
return None
|
| 62 |
return _chatbot
|
| 63 |
|
| 64 |
|
|
|
|
| 66 |
@limiter.limit("10/minute")
|
| 67 |
async def chat_message(request: Request, req: ChatRequest, hub: DataHub = Depends(get_datahub)):
|
| 68 |
bot = _get_chatbot(hub)
|
| 69 |
+
if bot is None:
|
| 70 |
+
raise HTTPException(status_code=503, detail="Chatbot unavailable")
|
| 71 |
+
try:
|
| 72 |
+
response = bot.chat(req.message)
|
| 73 |
+
return {
|
| 74 |
+
"message": response.message,
|
| 75 |
+
"confidence": getattr(response, "confidence", None),
|
| 76 |
+
"sources": getattr(response, "sources", []),
|
| 77 |
+
"caveats": getattr(response, "caveats", []),
|
| 78 |
+
"rule_violations": getattr(response, "rule_violations", []),
|
| 79 |
+
"response_mode": getattr(response, "response_mode", "info"),
|
| 80 |
+
}
|
| 81 |
+
except Exception as exc:
|
| 82 |
+
log.error("Chatbot error: %s", exc, exc_info=True)
|
| 83 |
+
raise HTTPException(status_code=500, detail="Chatbot processing failed")
|
| 84 |
|
| 85 |
|
| 86 |
@router.post("/feedback")
|
|
|
|
| 97 |
return {"status": "ok"}
|
| 98 |
except Exception as exc:
|
| 99 |
log.error("Feedback logging failed: %s", exc)
|
| 100 |
+
raise HTTPException(status_code=500, detail="Feedback logging failed")
|