11import os
22import time
3- from typing import List , Optional
3+ from pathlib import Path
4+ from typing import List
45from fastapi import FastAPI , HTTPException
56from fastapi .middleware .cors import CORSMiddleware
67from fastapi .staticfiles import StaticFiles
@@ -59,6 +60,15 @@ async def serve_css():
5960
6061qdrant_client = QdrantClient (url = QDRANT_URL , api_key = QDRANT_API_KEY , timeout = 30 )
6162
63+ # Load master summary once at startup
64+ MASTER_SUMMARY = ""
65+ master_summary_path = Path ("data/clean/master_summary.txt" )
66+ if master_summary_path .exists ():
67+ MASTER_SUMMARY = master_summary_path .read_text (encoding = "utf-8" )
68+ print (f"✅ Master summary loaded: { len (MASTER_SUMMARY )} characters" )
69+ else :
70+ print ("⚠️ Master summary not found. Run generate_master_summary.py first." )
71+
6272# Session memory storage
6373session_memories : dict [str , list ] = {}
6474
@@ -67,38 +77,42 @@ def get_history(session_id: str) -> list:
6777 session_memories [session_id ] = []
6878 return session_memories [session_id ]
6979
70- # ---------- Prompt Template ----------
71- SALES_PROMPT_TEMPLATE = (
72- "You are a Sales Assistant AI that helps sales representatives query information from previous client meetings. "
73- "You have access to meeting transcripts, summaries, and client interactions to provide insights about:\n \n "
74-
75- "• Client objections, concerns, and feedback\n "
76- "• Pricing discussions and negotiations\n "
77- "• Feature requests and product feedback\n "
78- "• Competitor comparisons mentioned by clients\n "
79- "• Implementation concerns and technical questions\n "
80- "• Industry-specific requirements and use cases\n "
81- "• Decision-making processes and timelines\n "
82- "• Stakeholder involvement and buying committees\n \n "
83-
84- "RESPONSE GUIDELINES:\n "
85- "• Provide specific, actionable insights from the meeting data\n "
86- "• When possible, reference specific client meetings or contexts\n "
87- "• Highlight patterns across multiple client interactions\n "
88- "• Be concise but comprehensive in your responses\n "
89- "• If you don't have relevant information, clearly state that\n "
90- "• Focus on helping sales reps prepare for future meetings\n "
91- "• Suggest follow-up questions or strategies when appropriate\n \n "
92-
93- "EXAMPLE RESPONSES:\n "
94- "• 'Based on 3 recent enterprise meetings, clients commonly ask about data security certifications...'\n "
95- "• 'In the TechCorp meeting last month, they mentioned budget constraints around Q4...'\n "
96- "• 'Several healthcare clients have requested HIPAA compliance documentation...'\n \n "
97-
98- "Context from previous meetings:\n {context}\n \n "
80+ # ---------- Query classifier ----------
81+ STRATEGIC_KEYWORDS = [
82+ "strategy" , "strategies" , "recommend" , "suggestion" , "suggest" , "improve" , "improvement" ,
83+ "pattern" , "trend" , "common" , "most" , "top" , "best" , "worst" , "typical" , "usually" ,
84+ "objection" , "objections" , "pricing" , "price" , "feature" , "features" , "competitor" ,
85+ "competitors" , "region" , "industry" , "industries" , "segment" , "marketing" , "pitch" ,
86+ "plan" , "approach" , "how should" , "what should" , "why do" , "which clients" ,
87+ "overall" , "across" , "all clients" , "all meetings" , "generally" , "insight" , "insights" ,
88+ "win" , "lose" , "lost" , "deal" , "convert" , "conversion" , "sales cycle" , "follow up"
89+ ]
90+
91+ def is_strategic_question (message : str ) -> bool :
92+ msg = message .lower ()
93+ return any (kw in msg for kw in STRATEGIC_KEYWORDS )
94+
95+ # ---------- Prompt Templates ----------
96+ STRATEGIC_PROMPT = (
97+ "You are an expert Sales Strategist AI for Enatega — a food delivery platform solution.\n "
98+ "You have comprehensive knowledge from 250+ real client sales meetings summarized below.\n \n "
99+ "Use this knowledge to give specific, actionable, data-driven answers.\n "
100+ "Reference patterns, client names, regions, and real examples from the data.\n "
101+ "If asked for a plan or strategy, provide structured step-by-step guidance.\n \n "
102+ "KNOWLEDGE BASE FROM 250+ MEETINGS:\n {master_summary}\n \n "
103+ "Chat History:\n {chat_history}\n \n "
104+ "Question: {question}\n "
105+ "Answer:"
106+ )
107+
108+ SPECIFIC_PROMPT = (
109+ "You are a Sales Assistant AI for Enatega — a food delivery platform solution.\n "
110+ "Answer the question using the relevant meeting excerpts provided below.\n "
111+ "Be specific, reference client names and meetings where relevant.\n \n "
112+ "Relevant Meeting Excerpts:\n {context}\n \n "
99113 "Chat History:\n {chat_history}\n \n "
100- "Sales Rep Question: {question}\n "
101- "Assistant :"
114+ "Question: {question}\n "
115+ "Answer :"
102116)
103117
104118# ---------- request/response ----------
@@ -115,106 +129,72 @@ class ChatResp(BaseModel):
115129@app .post ("/chat" , response_model = ChatResp )
116130async def chat_endpoint (req : ChatReq ):
117131 start_time = time .time ()
118-
132+
119133 try :
120- # Simple similarity search using qdrant client directly
121- print (f"Searching for: { req .message } " )
122-
123- try :
124- query_vector = embeddings .embed_query (req .message )
125- print ("Query vector created successfully" )
126- except Exception as embed_error :
127- print (f"Embedding error: { embed_error } " )
128- raise HTTPException (status_code = 500 , detail = f"Embedding failed: { str (embed_error )} " )
129-
130- try :
131- search_result = qdrant_client .query_points (
132- collection_name = COLLECTION_NAME ,
133- query = query_vector ,
134- limit = 5
135- ).points
136- print (f"Found { len (search_result )} results" )
137- except Exception as search_error :
138- print (f"Search error: { search_error } " )
139- raise HTTPException (status_code = 500 , detail = f"Search failed: { str (search_error )} " )
140-
141- # Extract content and metadata from search results
142- docs = []
143- sources = []
144-
145- for hit in search_result :
146- # Debug: print the structure
147- print (f"Hit payload keys: { hit .payload .keys () if hit .payload else 'No payload' } " )
148-
149- # Try different payload structures
150- content = ""
151- metadata = {}
152-
153- if hit .payload :
154- # Try direct content access
155- content = hit .payload .get ('page_content' , '' ) or hit .payload .get ('content' , '' )
156-
157- # Try nested metadata
158- if 'metadata' in hit .payload :
159- metadata = hit .payload ['metadata' ]
160- else :
161- # Use payload directly as metadata
162- metadata = hit .payload
163-
164- if content :
165- docs .append (content )
166-
167- # Extract filename from metadata
168- filename = metadata .get ('filename' , metadata .get ('source' , 'Unknown' ))
169- if filename and filename != 'Unknown' :
170- sources .append (filename )
171-
172- # Create context from documents
173- context = "\n \n " .join ([doc for doc in docs if doc .strip ()])
174-
175- # If no context found, provide a helpful message
176- if not context .strip ():
177- context = "No relevant meeting data found for this query."
178-
179- # Get history for this session
180134 history = get_history (req .session_id )
181- history_text = "" .join ([f"{ m ['role' ]} : { m ['content' ]} \n " for m in history [- 10 :]])
135+ history_text = "" .join ([f"{ m ['role' ]} : { m ['content' ]} \n " for m in history [- 6 :]])
136+ sources = []
137+ used_chunks = 0
138+
139+ if is_strategic_question (req .message ):
140+ # --- STRATEGIC MODE: use master summary ---
141+ print (f"[STRATEGIC] { req .message } " )
142+ full_prompt = STRATEGIC_PROMPT .format (
143+ master_summary = MASTER_SUMMARY ,
144+ chat_history = history_text ,
145+ question = req .message
146+ )
147+ used_chunks = 51 # all batches
148+ else :
149+ # --- SPECIFIC MODE: semantic search ---
150+ print (f"[SPECIFIC] { req .message } " )
151+ try :
152+ query_vector = embeddings .embed_query (req .message )
153+ search_result = qdrant_client .query_points (
154+ collection_name = COLLECTION_NAME ,
155+ query = query_vector ,
156+ limit = 10
157+ ).points
158+ except Exception as e :
159+ raise HTTPException (status_code = 500 , detail = f"Search failed: { str (e )} " )
160+
161+ docs = []
162+ for hit in search_result :
163+ payload = hit .payload or {}
164+ content = payload .get ('page_content' , '' ) or payload .get ('content' , '' )
165+ meta = payload .get ('metadata' , payload )
166+ if content :
167+ docs .append (content )
168+ filename = meta .get ('filename' , meta .get ('source' , '' ))
169+ if filename :
170+ sources .append (filename )
171+
172+ context = "\n \n " .join ([d for d in docs if d .strip ()]) or "No relevant meeting data found."
173+ used_chunks = len (docs )
174+ full_prompt = SPECIFIC_PROMPT .format (
175+ context = context ,
176+ chat_history = history_text ,
177+ question = req .message
178+ )
182179
183- # Create the prompt
184- full_prompt = SALES_PROMPT_TEMPLATE .format (
185- context = context ,
186- chat_history = history_text ,
187- question = req .message
188- )
189-
190- # Get response from LLM
191- print ("Sending to LLM..." )
192180 try :
193181 response = llm .invoke (full_prompt ).content
194- print ("LLM response received" )
195- except Exception as llm_error :
196- print (f"LLM error: { llm_error } " )
197- response = f"I found { len (docs )} relevant meeting excerpts but encountered an issue. Sources: { ', ' .join (sources [:3 ]) if sources else 'your query' } ."
182+ except Exception as e :
183+ print (f"LLM error: { e } " )
184+ response = "I encountered an issue generating a response. Please try again."
198185
199- # Save to history
200- history = get_history (req .session_id )
201186 history .append ({"role" : "Human" , "content" : req .message })
202187 history .append ({"role" : "Assistant" , "content" : response })
203-
204- # Remove duplicate sources
205- sources = list (set (sources ))
206-
207- latency_ms = int ((time .time () - start_time ) * 1000 )
208-
188+
209189 return ChatResp (
210190 answer = response ,
211- sources = sources ,
212- used_chunks = len ( docs ) ,
213- latency_ms = latency_ms
191+ sources = list ( set ( sources )) ,
192+ used_chunks = used_chunks ,
193+ latency_ms = int (( time . time () - start_time ) * 1000 )
214194 )
215-
195+
216196 except Exception as e :
217- print (f"Chat error: { str (e )} " ) # Debug logging
197+ print (f"Chat error: { str (e )} " )
218198 raise HTTPException (status_code = 500 , detail = f"Error: { str (e )} " )
219199
220200@app .get ("/health" )
0 commit comments