Ignore WS heartbeats and reject empty chat messages (#32)
Deploy Beta / unit-tests (push) Successful in 10s
Unit Tests / test (push) Successful in 9s
Deploy Beta / docker (push) Successful in 25s
Deploy Beta / deploy-beta (push) Successful in 56s

## Summary
- Closes #31
- Ignore WebSocket `type: ping` heartbeats so keepalives no longer create conversations or hit title/LLM pipelines
- Reject empty/whitespace user messages in both chat consumers, `PromptSerializer`, and REST conversation prompt POST

## Test plan
- [x] `UserPromptGuardTestCase`, `PromptSerializerTestCase` blank/whitespace cases
- [x] `WebSocketReceiveGuardTestCase` ping ignore + empty message rejection (both WS routes)
- [ ] Deploy to beta; leave idle tab open and confirm no new rogue conversations
- [ ] Confirm normal chat send still works

Related FE: https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/51Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
westfarn committed 2026-07-28 05:12:13 -07:00
1 parent 5d5b448868
commit ee3d47c8c3
8 files changed
+170 -4

No files matched your search

+19 -2
View File
@@ -22,6 +22,7 @@ from .services.title_generator import title_generator
from .services.moderation_classifier import moderation_classifier, ModerationLabel
from .services.prompt_classifier.prompt_classifier import PromptClassifier, PromptType
from .services.data_analysis_service import AsyncDataAnalysisService
from .utils import has_usable_user_prompt, is_heartbeat_payload, normalize_user_message
logger = logging.getLogger(__name__)
@@ -288,13 +289,29 @@ class ChatConsumerGraph(AsyncWebsocketConsumer):
print("Text Data: ", text_data)
if text_data:
data = json.loads(text_data)
# Keepalive frames must not create conversations or hit the LLM.
if is_heartbeat_payload(data):
return
model = data.get("modelName", "Turbo")
message = data.get("message", None)
message = normalize_user_message(data.get("message", None))
conversation_id = data.get("conversation_id", None)
email = data.get("email", None)
file = data.get("file", None)
file_type = data.get("fileType", "")
if not has_usable_user_prompt(message, file):
logger.info("Ignoring websocket payload with empty message")
await self.send_json_message(
json.dumps(
{
"type": "error",
"content": "Message text cannot be empty.",
}
)
)
return
if not conversation_id:
title = await title_generator.generate_async(message)
conversation_id = await create_conversation(message, email, title)