Message Save Flow Analysis - User Messages Saved Twice

Overview

When using assistants, user messages are being saved twice to Firestore, while assistant messages are saved only once. This document traces the complete flow to understand why this duplication occurs.

The Complete Flow

1. Frontend Initiates Request

When a user sends a message through the chat interface:

  1. Frontend saves user message immediately (StreamingContext.tsx:100):
    const messageData = EnhancedFirebaseService.createUserMessagePayload(text, currentUser, traceId);
    const savePromise = EnhancedFirebaseService.saveMessage(assistantConfig.assistantId, messageData);
    
  2. Frontend calls the streaming API (chatStreaming.ts:436):
    await vacChat({
      // ...
      apiEndpoint: `/vac/streaming/aitana3`,
      save_to_history: true,  // This flag is passed to backend
      // ...
    });
    

2. Request Routes Through Backend

The request follows this path:

  1. Frontend → Proxy (/api/proxy):
    • The proxy forwards the request to the backend with the endpoint /vac/streaming/aitana3
  2. Backend Router (app.py:104):
    • VACRoutes creates the /vac/streaming/<vector_name> endpoint
    • Uses vac_stream_with_assistant_support as the stream interpreter
  3. Wrapper Function (vac_service_wrapper.py):
    • Detects assistantId in emissaryConfig
    • Routes to assistant logic instead of regular vac_stream
    • Calls original vac_stream with assistant configuration
  4. Backend saves messages (vac_service_wrapper.py:122-143):
    # Check if we need to save messages to history
    save_to_history = emissary_config.get('save_to_history', kwargs.get('save_to_history', False))
       
    if save_to_history and response and "metadata" in response:
        # Calls save_messages_after_streaming which saves BOTH user and assistant messages
        await save_messages_after_streaming(...)
    

3. Why User Messages Are Saved Twice

  1. First Save: Frontend saves user message immediately when sending (StreamingContext.tsx:100)
  2. Second Save: Backend saves user message in save_messages_after_streaming (assistant_config.py:198)

4. Why Assistant Messages Are Only Saved Once

  • Frontend no longer saves assistant messages (comment at chatStreaming.ts:475-479)
  • Only the backend saves assistant messages in save_messages_after_streaming
  • When the wrapper save logic was removed, assistant messages were lost because nothing else saves them

The Root Cause

The duplication occurs because:

  1. Frontend proactively saves user messages for immediate UI feedback
  2. Backend also saves user messages as part of the complete conversation flow
  3. Both saves happen because save_to_history: true is passed from frontend

Alternative Flows

Direct Assistant Endpoint (/vac/assistant/<assistant_id>)

When calling the assistant endpoint directly:

  • assistant_stream in assistant_config.py handles the request
  • Also calls save_messages_after_streaming when streaming completes
  • Would still result in duplicate user messages if frontend saves first

Email Integration Flow

  • Uses process_assistant_request from assistant_utils.py
  • This function also saves both user and assistant messages when save_to_history=True
  • No duplication here because email integration doesn’t pre-save messages

Solutions

Option 1: Frontend Only Saves User Messages

  • Keep frontend saving user messages for immediate UI feedback
  • Modify backend to only save assistant messages
  • Pros: No changes to frontend, maintains immediate UI feedback
  • Cons: Split responsibility for message saving

Option 2: Backend Saves Everything

  • Remove frontend message saving
  • Let backend handle all message persistence
  • Pros: Single source of truth, consistent save logic
  • Cons: Slight delay before user message appears in Firestore

Option 3: Conditional Backend Save

  • Backend checks if user message already exists before saving
  • Only saves if not already present
  • Pros: Handles both flows gracefully
  • Cons: Additional Firestore read operations

Option 4: Pass Flag from Frontend

  • Frontend passes user_message_already_saved: true flag
  • Backend skips user message save when flag is true
  • Pros: Explicit control, no extra reads
  • Cons: Requires frontend changes

Recommendation

Option 4 (Pass Flag from Frontend) is recommended because:

  1. Maintains immediate UI feedback
  2. Avoids duplicate saves
  3. No extra Firestore reads
  4. Clear and explicit control flow
  5. Minimal code changes required

Implementation Plan

  1. Modify frontend to pass user_message_already_saved: true in the request
  2. Update backend save logic to check this flag
  3. Test all flows (streaming, direct assistant, email integration)
  4. Ensure backward compatibility for API clients