diff --git a/app/routers/chat.py b/app/routers/chat.py index 1cb7b6a..c646ffa 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -21,9 +21,9 @@ def _get_public_chatbot(chatbot_id: str, supabase) -> dict: @router.post("/chat/{chatbot_id}", response_model=ChatResponse) async def chat( - chatbot_id: str, - message: ChatMessage, - user=Depends(get_optional_user), + chatbot_id: str, + message: ChatMessage, + user=Depends(get_optional_user), ): supabase = get_supabase() chatbot = _get_public_chatbot(chatbot_id, supabase) @@ -97,9 +97,9 @@ async def chat( @router.get("/chat/{chatbot_id}/history/{session_id}", response_model=List[MessageResponse]) async def get_chat_history( - chatbot_id: str, - session_id: str, - user=Depends(get_optional_user), + chatbot_id: str, + session_id: str, + user=Depends(get_optional_user), ): supabase = get_supabase() @@ -114,7 +114,7 @@ async def get_chat_history( conv_id = conversation.data[0]["id"] messages = supabase.table("messages").select("*") \ .eq("conversation_id", conv_id) \ - .order("created_at", asc=True) \ + .order("created_at", desc=False) \ .execute() return [ @@ -140,7 +140,8 @@ async def get_analytics(chatbot_id: str, user=Depends(get_current_user)): if not company.data: raise HTTPException(status_code=404, detail="Company not found") - chatbot = supabase.table("chatbots").select("id").eq("id", chatbot_id).eq("company_id", company.data[0]["id"]).execute() + chatbot = supabase.table("chatbots").select("id").eq("id", chatbot_id).eq("company_id", + company.data[0]["id"]).execute() if not chatbot.data: raise HTTPException(status_code=404, detail="Chatbot not found") @@ -159,11 +160,11 @@ async def get_analytics(chatbot_id: str, user=Depends(get_current_user)): # ── Helpers ─────────────────────────────────────────────────────────────────── def _get_or_create_conversation( - chatbot_id: str, - session_id: str, - user_id: Optional[str], - language: str, - supabase, + chatbot_id: str, + session_id: str, + user_id: Optional[str], + language: str, + supabase, ) -> dict: existing = supabase.table("conversations").select("*") \ .eq("chatbot_id", chatbot_id) \ @@ -186,21 +187,29 @@ def _get_or_create_conversation( def _get_conversation_history(conversation_id: str, supabase) -> List[dict]: + """ + FIX: Changed from desc=True to desc=False (ascending/chronological order). + + The conversation history MUST be in chronological order (oldest first) + for the LLM to correctly understand the conversation flow. + Previously, messages were returned newest-first, which reversed the + conversation and confused the model. + """ messages = supabase.table("messages").select("role, content") \ .eq("conversation_id", conversation_id) \ - .order("created_at", desc=True) \ + .order("created_at", desc=False) \ .limit(20) \ .execute() return messages.data or [] def _save_message( - conversation_id: str, - role: str, - content: str, - supabase, - sources: Optional[list] = None, - model: str = "", + conversation_id: str, + role: str, + content: str, + supabase, + sources: Optional[list] = None, + model: str = "", ): supabase.table("messages").insert({ "id": str(uuid.uuid4()), @@ -209,4 +218,4 @@ def _save_message( "content": content, "sources": sources, "model": model, - }).execute() + }).execute() \ No newline at end of file diff --git a/app/services/rag.py b/app/services/rag.py index 3c854bf..8fe5b2a 100644 --- a/app/services/rag.py +++ b/app/services/rag.py @@ -11,11 +11,11 @@ RAG_SYSTEM_PROMPT = """You are a helpful AI assistant for {company_name}. Your role is to answer questions based on the provided context from company documents. IMPORTANT RULES: -1. Only answer based on the provided context -2. If information is not in the context, say "I don't have information about that in my knowledge base" +1. Answer based on the provided context below +2. If the context does not contain enough information, say so, but also try to be helpful with what IS available 3. Be concise and helpful 4. Always maintain a professional, friendly tone -5. If asked about topics outside the context, politely redirect to relevant topics +5. If asked about topics completely outside the context, politely redirect to relevant topics {custom_instructions} @@ -47,8 +47,9 @@ class RAGEngine: # Step 1: Embed the query try: query_embedding = self.embedding_svc.embed_text(query) + logger.info(f"[RAG] Query embedded successfully. Vector length: {len(query_embedding)}") except Exception as e: - logger.error(f"Embedding error: {e}") + logger.error(f"[RAG] Embedding error: {e}", exc_info=True) return { "response": "I'm having trouble processing your request. Please try again.", "sources": [], @@ -57,13 +58,22 @@ class RAGEngine: } # Step 2: Retrieve relevant chunks + # FIX: Lowered score_threshold from 0.3 to 0.1 to avoid filtering out + # all results. With cosine similarity, 0.3 can be too aggressive for + # many document types and query patterns. retrieved = self.vector_svc.search( collection_name=collection_name, query_vector=query_embedding, limit=5, - score_threshold=0.3, + score_threshold=0.1, # FIX: was 0.3, now 0.1 to avoid over-filtering ) + logger.info(f"[RAG] Retrieved {len(retrieved)} chunks from collection '{collection_name}'") + for i, item in enumerate(retrieved): + score = item.get("score", 0) + text_preview = item.get("payload", {}).get("text", "")[:80] + logger.info(f"[RAG] Chunk {i+1}: score={score:.4f}, preview='{text_preview}...'") + # Step 3: Build sources sources = [] context_parts = [] @@ -84,7 +94,12 @@ class RAGEngine: ) ) - context = "\n\n---\n\n".join(context_parts) if context_parts else "No relevant information found." + if context_parts: + context = "\n\n---\n\n".join(context_parts) + logger.info(f"[RAG] Built context from {len(context_parts)} chunks ({len(context)} chars)") + else: + context = "No relevant information found in the knowledge base." + logger.warning(f"[RAG] No context found for query: '{query}' in collection '{collection_name}'") # Step 4: Build messages system_prompt = RAG_SYSTEM_PROMPT.format( @@ -95,13 +110,18 @@ class RAGEngine: messages = [{"role": "system", "content": system_prompt}] - # Add conversation history (last 10 messages) - for msg in conversation_history[-10:]: + # FIX: Conversation history must be in CHRONOLOGICAL order (oldest first). + # The history should already come sorted ascending from the chat router. + # We take the last 10 messages for context window management. + history_to_use = conversation_history[-10:] if conversation_history else [] + for msg in history_to_use: messages.append({"role": msg["role"], "content": msg["content"]}) # Add current query messages.append({"role": "user", "content": query}) + logger.info(f"[RAG] Sending {len(messages)} messages to LLM (model: {chatbot_config.get('model')})") + # Step 5: Generate response model = chatbot_config.get("model", "accounts/fireworks/models/kimi-k2-instruct-0905") try: @@ -111,6 +131,7 @@ class RAGEngine: max_tokens=chatbot_config.get("max_tokens", 1000), temperature=chatbot_config.get("temperature", 0.7), ) + logger.info(f"[RAG] LLM response generated. Tokens used: {result.get('tokens_used', 0)}") return { "response": result["content"], "sources": sources, @@ -118,7 +139,7 @@ class RAGEngine: "model": result.get("model", model), } except Exception as e: - logger.error(f"LLM generation error: {e}") + logger.error(f"[RAG] LLM generation error: {e}", exc_info=True) return { "response": "I'm having trouble generating a response. Please try again later.", "sources": sources, @@ -127,4 +148,4 @@ class RAGEngine: } -rag_engine = RAGEngine() +rag_engine = RAGEngine() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9f89073..10cf84b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "qdrant-client>=1.17.0", "sentry-sdk>=2.53.0", "spglib>=2.7.0", + "stripe>=14.3.0", "supabase>=2.28.0", "uvicorn>=0.41.0", ] diff --git a/uv.lock b/uv.lock index 3fe1b5e..46d8fd8 100644 --- a/uv.lock +++ b/uv.lock @@ -202,6 +202,7 @@ dependencies = [ { name = "qdrant-client" }, { name = "sentry-sdk" }, { name = "spglib" }, + { name = "stripe" }, { name = "supabase" }, { name = "uvicorn" }, ] @@ -214,6 +215,7 @@ requires-dist = [ { name = "qdrant-client", specifier = ">=1.17.0" }, { name = "sentry-sdk", specifier = ">=2.53.0" }, { name = "spglib", specifier = ">=2.7.0" }, + { name = "stripe", specifier = ">=14.3.0" }, { name = "supabase", specifier = ">=2.28.0" }, { name = "uvicorn", specifier = ">=0.41.0" }, ] @@ -1448,6 +1450,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/7c/a81ef5ef10978dd073a854e0fa93b5d8021d0594b639cc8f6453c3c78a1d/strictyaml-1.7.3-py3-none-any.whl", hash = "sha256:fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7", size = 123917, upload-time = "2023-03-10T12:50:17.242Z" }, ] +[[package]] +name = "stripe" +version = "14.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/67/8a38222a57fc2ba359c4dcb66528d94c00d803c7fde8f8d8470ad6bdccbb/stripe-14.3.0.tar.gz", hash = "sha256:4c76137d741bd43e8bb433a596c198ca20f4cdf17a8fe04604faf37c74b01978", size = 1463618, upload-time = "2026-01-28T21:20:29.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/4b/0b7d5920f2be5e42d72bdfc44a9fae57b422668bfc8dacdf2f74886f6daa/stripe-14.3.0-py3-none-any.whl", hash = "sha256:3e36b68b256c8970e99b703e195d947e2a2919095758788c7074ac4485ac255e", size = 2106980, upload-time = "2026-01-28T21:20:27.566Z" }, +] + [[package]] name = "supabase" version = "2.28.0"