chinmayjha commited on
Commit
150cd80
Β·
unverified Β·
1 Parent(s): 0e61755

Improve RAG agent response quality and UX

Browse files

- De-duplicate conversation insights to avoid repetition across chunks
- Group chunks by document ID and show insights only once per conversation
- Update summarizer prompt to generate cleaner answers without customer names
- Add inline citations [Doc X] in answers with numbered sources section
- Format sources with clean spacing (no bold/italic markdown)
- Include conversation insights (summary + key findings) in sources
- Add progress indicators to Gradio UI for better user feedback
- Reduce retrieved documents from 10 to 5 for faster responses
- Switch from XML to compact text format for token optimization
- Add conversation context from multiple chunks when available

Makefile CHANGED
@@ -10,7 +10,7 @@ export PYTHONPATH = .
10
  # --- Default Values ---
11
 
12
  CHECK_DIRS := .
13
- RETRIEVER_CONFIG ?= configs/compute_rag_vector_index_openai_contextual_simple.yaml
14
 
15
  # --- Utilities ---
16
 
@@ -53,6 +53,12 @@ evaluate_agent: check-config
53
  run_conversation_analysis_ui: # Launch Conversation Analysis Dashboard
54
  uv run python conversation_analysis_app.py
55
 
 
 
 
 
 
 
56
  # --- QA ---
57
 
58
  format-fix:
 
10
  # --- Default Values ---
11
 
12
  CHECK_DIRS := .
13
+ RETRIEVER_CONFIG ?= configs/compute_rag_vector_index_openai_contextual.yaml
14
 
15
  # --- Utilities ---
16
 
 
53
  run_conversation_analysis_ui: # Launch Conversation Analysis Dashboard
54
  uv run python conversation_analysis_app.py
55
 
56
+ run_customer_profile_dashboard: # Launch Customer Profile Analysis Dashboard
57
+ uv run python -m tools.customer_profile_app
58
+
59
+ run_user_interaction_dashboard: # Launch User Interaction Analysis Dashboard
60
+ uv run python tools/user_interaction_ui.py
61
+
62
  # --- QA ---
63
 
64
  format-fix:
configs/compute_rag_vector_index_openai_contextual.yaml CHANGED
@@ -1,17 +1,20 @@
 
 
 
1
  parameters:
2
- extract_collection_name: raw
3
- fetch_limit: 30
4
- load_collection_name: rag
5
- content_quality_score_threshold: 0.6
6
- retriever_type: contextual
7
- embedding_model_id: text-embedding-3-small
8
  embedding_model_type: openai
9
  embedding_model_dim: 1536
10
- chunk_size: 3072
11
  contextual_summarization_type: contextual
12
- contextual_agent_model_id: gpt-4o
13
- contextual_agent_max_characters: 128
14
  mock: false
15
- processing_batch_size: 2
16
- processing_max_workers: 2
17
- device: mps # or cuda (for Nvidia GPUs) or mps (for Apple M1/M2/M3 chips)
 
1
+ # RAG Configuration for Conversation Data (Agent UI)
2
+ # This config matches the settings used to create rag_conversations collection
3
+
4
  parameters:
5
+ extract_collection_name: test_conversation_documents
6
+ fetch_limit: 0
7
+ load_collection_name: rag_conversations # Query conversation data
8
+ content_quality_score_threshold: 0.0
9
+ retriever_type: contextual # Hybrid vector + full-text search
10
+ embedding_model_id: text-embedding-3-small # Must match offline pipeline
11
  embedding_model_type: openai
12
  embedding_model_dim: 1536
13
+ chunk_size: 640 # Match offline pipeline
14
  contextual_summarization_type: contextual
15
+ contextual_agent_model_id: gpt-4o-mini
16
+ contextual_agent_max_characters: 200
17
  mock: false
18
+ processing_batch_size: 5
19
+ processing_max_workers: 4
20
+ device: mps
configs/compute_rag_vector_index_openai_contextual_simple.yaml CHANGED
@@ -1,7 +1,7 @@
1
  parameters:
2
- extract_collection_name: test_intercom_data
3
  fetch_limit: 200
4
- load_collection_name: rag_intercom
5
  content_quality_score_threshold: 0.6
6
  retriever_type: contextual
7
  embedding_model_id: text-embedding-3-small
 
1
  parameters:
2
+ extract_collection_name: test_conversation_documents
3
  fetch_limit: 200
4
+ load_collection_name: rag_conversations
5
  content_quality_score_threshold: 0.6
6
  retriever_type: contextual
7
  embedding_model_id: text-embedding-3-small
src/second_brain_online/application/agents/agents.py CHANGED
@@ -61,9 +61,9 @@ class AgentWrapper:
61
  )
62
 
63
  agent = ToolCallingAgent(
64
- tools=[what_can_i_do, retriever_tool], # Remove summarizer - it's redundant
65
  model=model,
66
- max_steps=2, # Reduce steps since we removed summarizer
67
  verbosity_level=2,
68
  )
69
 
 
61
  )
62
 
63
  agent = ToolCallingAgent(
64
+ tools=[what_can_i_do, retriever_tool, summarizer_tool],
65
  model=model,
66
+ max_steps=3, # Retrieval β†’ answer_with_sources β†’ final_answer (pass-through)
67
  verbosity_level=2,
68
  )
69
 
src/second_brain_online/application/agents/tools/mongodb_retriever.py CHANGED
@@ -5,8 +5,10 @@ import yaml
5
  from loguru import logger
6
  from opik import opik_context, track
7
  from smolagents import Tool
 
8
 
9
  from second_brain_online.application.rag import get_retriever
 
10
 
11
 
12
  class MongoDBRetrieverTool(Tool):
@@ -33,6 +35,11 @@ class MongoDBRetrieverTool(Tool):
33
 
34
  self.config_path = config_path
35
  self.retriever = self.__load_retriever(config_path)
 
 
 
 
 
36
 
37
  def __load_retriever(self, config_path: Path):
38
  config = yaml.safe_load(config_path.read_text())
@@ -42,13 +49,58 @@ class MongoDBRetrieverTool(Tool):
42
  embedding_model_id=config["embedding_model_id"],
43
  embedding_model_type=config["embedding_model_type"],
44
  retriever_type=config["retriever_type"],
45
- k=5,
46
  device=config["device"],
47
  enable_reranking=config.get("enable_reranking", False),
48
  rerank_model_name=config.get("rerank_model_name", "cross-encoder/ms-marco-MiniLM-L-2-v2"),
49
  stage1_limit=config.get("stage1_limit", 50),
50
- final_k=config.get("final_k", 10),
51
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  @track(name="MongoDBRetrieverTool.forward")
54
  def forward(self, query: str) -> str:
@@ -78,62 +130,91 @@ class MongoDBRetrieverTool(Tool):
78
  query = self.__parse_query(query)
79
  relevant_docs = self.retriever.invoke(query)
80
 
81
- formatted_docs = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  for i, doc in enumerate(relevant_docs, 1):
83
- # Extract metadata
84
- title = doc.metadata.get("title", "Untitled")
85
- datetime = doc.metadata.get("datetime", "unknown")
86
- contextual_summary = doc.metadata.get("contextual_summary", "")
87
- marketing_insights = doc.metadata.get("marketing_insights", {})
88
- content = doc.page_content.strip()
89
 
90
- # Format marketing insights if available
91
- marketing_insights_text = ""
92
- if marketing_insights:
93
- marketing_insights_text = "\n<marketing_insights>\n"
94
-
95
- # Add quotes
96
- quotes = marketing_insights.get("quotes", [])
97
- if quotes:
98
- marketing_insights_text += "<quotes>\n"
99
- for quote in quotes:
100
- marketing_insights_text += f"- \"{quote.get('quote', '')}\" (Sentiment: {quote.get('sentiment', 'Unknown')})\n"
101
- marketing_insights_text += "</quotes>\n"
102
-
103
- # Add key findings
104
- findings = marketing_insights.get("key_findings", [])
105
- if findings:
106
- marketing_insights_text += "<key_findings>\n"
107
- for finding in findings:
108
- marketing_insights_text += f"- {finding.get('finding', '')} (Impact: {finding.get('impact', 'Unknown')})\n"
109
- marketing_insights_text += "</key_findings>\n"
110
-
111
- marketing_insights_text += "</marketing_insights>\n"
112
 
113
- # Create optimized document structure - truncate content to avoid token overload
114
- content_preview = content[:500] + "..." if len(content) > 500 else content
115
- formatted_docs.append(
116
- f"""
117
- <document id="{i}">
118
- <title>{title}</title>
119
- <date>{datetime}</date>
120
- <contextual_summary>
121
- {contextual_summary}
122
- </contextual_summary>
123
- {marketing_insights_text}
124
- <content>
125
- {content_preview}
126
- </content>
127
- </document>
128
- """
129
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  result = "\n".join(formatted_docs)
132
- result = f"""
133
- <search_results>
 
134
  {result}
135
- </search_results>
136
- When using context from any document, reference the document title and date for attribution.
137
  """
138
  return result
139
  except Exception:
 
5
  from loguru import logger
6
  from opik import opik_context, track
7
  from smolagents import Tool
8
+ from pymongo import MongoClient
9
 
10
  from second_brain_online.application.rag import get_retriever
11
+ from second_brain_online.config import settings
12
 
13
 
14
  class MongoDBRetrieverTool(Tool):
 
35
 
36
  self.config_path = config_path
37
  self.retriever = self.__load_retriever(config_path)
38
+
39
+ # Setup MongoDB client for fetching conversation insights
40
+ self.mongodb_client = MongoClient(settings.MONGODB_URI)
41
+ self.database = self.mongodb_client[settings.MONGODB_DATABASE_NAME]
42
+ self.conversation_docs_collection = self.database["test_conversation_documents"]
43
 
44
  def __load_retriever(self, config_path: Path):
45
  config = yaml.safe_load(config_path.read_text())
 
49
  embedding_model_id=config["embedding_model_id"],
50
  embedding_model_type=config["embedding_model_type"],
51
  retriever_type=config["retriever_type"],
52
+ k=5, # Reduced from 10 to 5 for faster processing
53
  device=config["device"],
54
  enable_reranking=config.get("enable_reranking", False),
55
  rerank_model_name=config.get("rerank_model_name", "cross-encoder/ms-marco-MiniLM-L-2-v2"),
56
  stage1_limit=config.get("stage1_limit", 50),
57
+ final_k=config.get("final_k", 5), # Reduced from 10 to 5
58
  )
59
+
60
+ def __fetch_conversation_insights(self, document_ids: list[str]) -> dict:
61
+ """
62
+ Fetch conversation_insights and metadata for the given document IDs from test_conversation_documents.
63
+
64
+ Args:
65
+ document_ids: List of document IDs to fetch insights for
66
+
67
+ Returns:
68
+ Dictionary mapping document_id -> {conversation_insights, url, source, user_id}
69
+ """
70
+ insights_map = {}
71
+ not_found_count = 0
72
+
73
+ # Fetch documents from MongoDB with additional metadata
74
+ cursor = self.conversation_docs_collection.find(
75
+ {"id": {"$in": document_ids}},
76
+ {
77
+ "id": 1,
78
+ "conversation_insights": 1,
79
+ "metadata.url": 1,
80
+ "metadata.source": 1,
81
+ "metadata.user_id": 1
82
+ }
83
+ )
84
+
85
+ for doc in cursor:
86
+ doc_id = doc.get("id")
87
+ insights = doc.get("conversation_insights")
88
+ metadata = doc.get("metadata", {})
89
+
90
+ if insights:
91
+ insights_map[doc_id] = {
92
+ "conversation_insights": insights,
93
+ "url": metadata.get("url"),
94
+ "source": metadata.get("source"),
95
+ "user_id": metadata.get("user_id")
96
+ }
97
+
98
+ # Track mismatches
99
+ not_found_count = len(document_ids) - len(insights_map)
100
+ if not_found_count > 0:
101
+ logger.warning(f"Could not find conversation_insights for {not_found_count} out of {len(document_ids)} document IDs")
102
+
103
+ return insights_map
104
 
105
  @track(name="MongoDBRetrieverTool.forward")
106
  def forward(self, query: str) -> str:
 
130
  query = self.__parse_query(query)
131
  relevant_docs = self.retriever.invoke(query)
132
 
133
+ # Step 1: Extract unique document IDs from chunks
134
+ document_ids = []
135
+ for doc in relevant_docs:
136
+ doc_id = doc.metadata.get("id")
137
+ if doc_id:
138
+ document_ids.append(doc_id)
139
+
140
+ # Step 2: Fetch conversation insights for unique IDs
141
+ unique_doc_ids = list(set(document_ids)) # De-duplicate
142
+ insights_map = self.__fetch_conversation_insights(unique_doc_ids)
143
+
144
+ # Step 3: Group chunks by document ID to avoid duplicating insights
145
+ docs_by_id = {}
146
+ skipped_chunks = 0
147
+
148
  for i, doc in enumerate(relevant_docs, 1):
149
+ doc_id = doc.metadata.get("id")
 
 
 
 
 
150
 
151
+ # Skip chunks without conversation insights
152
+ if not doc_id or doc_id not in insights_map:
153
+ skipped_chunks += 1
154
+ logger.debug(f"Skipping chunk {i} - no conversation insights available for doc_id: {doc_id}")
155
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ # Group chunks by document ID
158
+ if doc_id not in docs_by_id:
159
+ docs_by_id[doc_id] = {
160
+ "title": doc.metadata.get("title", "Untitled"),
161
+ "datetime": doc.metadata.get("datetime", "unknown"),
162
+ "source": insights_map[doc_id].get("source", "Unknown Source"),
163
+ "url": insights_map[doc_id].get("url", ""),
164
+ "user_id": insights_map[doc_id].get("user_id", ""),
165
+ "insights": insights_map[doc_id]["conversation_insights"],
166
+ "chunks": []
167
+ }
168
+
169
+ # Add this chunk's contextual summary to the document
170
+ docs_by_id[doc_id]["chunks"].append(doc.metadata.get("contextual_summary", ""))
171
+
172
+ # Step 4: Format unique documents with their insights
173
+ formatted_docs = []
174
+ for doc_num, (doc_id, doc_info) in enumerate(docs_by_id.items(), 1):
175
+ doc_text = f"=== DOCUMENT {doc_num} ===\n"
176
+ doc_text += f"Title: {doc_info['title']}\n"
177
+ doc_text += f"Date: {doc_info['datetime']}\n"
178
+ doc_text += f"Source: {doc_info['source']} | ID: {doc_id}"
179
+ if doc_info['user_id']:
180
+ doc_text += f" | User: {doc_info['user_id']}"
181
+ if doc_info['url']:
182
+ doc_text += f"\nURL: {doc_info['url']}"
183
+
184
+ # Add all chunk contexts from this conversation
185
+ doc_text += f"\n\nCONTEXT (from {len(doc_info['chunks'])} chunk(s)):\n"
186
+ for chunk_idx, chunk_context in enumerate(doc_info['chunks'], 1):
187
+ doc_text += f"{chunk_idx}. {chunk_context}\n"
188
+
189
+ # Add conversation insights (only once per conversation)
190
+ insights = doc_info['insights']
191
+ summary = insights.get("summary", "")
192
+ if summary:
193
+ doc_text += f"\nINSIGHTS SUMMARY: {summary}\n"
194
+
195
+ # Add key findings
196
+ key_findings = insights.get("key_findings", [])
197
+ if key_findings:
198
+ doc_text += "\nKEY FINDINGS:\n"
199
+ for finding in key_findings:
200
+ insight_type = finding.get("insight_type", "Unknown")
201
+ finding_text = finding.get("finding", "")
202
+ impact = finding.get("impact", "Unknown")
203
+ doc_text += f"- [{insight_type}/{impact}] {finding_text}\n"
204
+
205
+ doc_text += "\n---\n"
206
+ formatted_docs.append(doc_text)
207
+
208
+ # Log statistics
209
+ logger.info(f"Retrieved {len(relevant_docs)} chunks from {len(docs_by_id)} unique conversations, skipped {skipped_chunks} without insights")
210
 
211
  result = "\n".join(formatted_docs)
212
+ result = f"""SEARCH RESULTS
213
+ ===============
214
+
215
  {result}
216
+
217
+ When using context, reference the document title, date, and ID for attribution.
218
  """
219
  return result
220
  except Exception:
src/second_brain_online/application/agents/tools/summarizer.py CHANGED
@@ -61,41 +61,77 @@ class HuggingFaceEndpointSummarizerTool(Tool):
61
 
62
 
63
  class OpenAISummarizerTool(Tool):
64
- name = "openai_summarizer"
65
- description = """Use this tool to summarize search results in XML format. This tool is especially useful when you need to analyze multiple documents from search results. The tool will parse XML search results, identify topics that are directly relevant to the user's query, and create a focused summary with document references. It filters out irrelevant topics to ensure the summary directly answers the user's question."""
 
 
 
 
66
 
67
  inputs = {
68
- "text": {
69
  "type": "string",
70
- "description": """The text to summarize.""",
71
  }
72
  }
73
  output_type = "string"
74
 
75
- SYSTEM_PROMPT = """You are an expert document analyst specialized in query-focused summarization.
76
-
77
- Your task is to analyze search results and create a focused summary that directly answers the user's question.
78
 
79
- When you receive XML search results, you should:
80
- 1. Parse ALL documents from the XML structure
81
- 2. Identify topics that are directly relevant to the user's query
82
- 3. Filter out irrelevant topics that don't relate to the question
83
- 4. Group related information by relevant topics
84
- 5. Extract key insights that directly answer the user's question
85
- 6. Include document references with titles and dates when available
86
-
87
- Analysis Guidelines:
88
- - Focus on information that directly answers the user's question
89
- - Only include topics that are relevant to the query
90
- - Use specific document titles and dates from the XML metadata when available
91
- - Ignore irrelevant information like cookie policies, privacy policies, HTTP errors, etc.
92
- - Create a well-structured, readable summary
93
- - Group similar topics together when appropriate
94
-
95
- Document content:
96
  {content}
97
 
98
- Generate a focused summary that directly answers the user's question, organized by relevant topics with document references. Exclude any topics that don't directly relate to the question."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  def __init__(self, *args, **kwargs) -> None:
101
  super().__init__(*args, **kwargs)
@@ -105,22 +141,31 @@ Generate a focused summary that directly answers the user's question, organized
105
  api_key=settings.OPENAI_API_KEY,
106
  )
107
 
108
- @track
109
- def forward(self, text: str) -> str:
 
 
 
 
 
 
 
 
110
  result = self.__client.chat.completions.create(
111
  model=settings.OPENAI_MODEL_ID,
112
  messages=[
113
  {
114
  "role": "system",
115
- "content": "You are an expert document analyst specialized in query-focused topic-based summarization. You excel at parsing XML search results, identifying relevant topics, and creating structured summaries with proper document references."
116
  },
117
  {
118
  "role": "user",
119
- "content": self.SYSTEM_PROMPT.format(content=text),
120
  },
121
  ],
122
- temperature=0.1, # Lower temperature for more consistent, focused output
123
- max_tokens=2000, # Increased token limit for more detailed summaries
 
124
  )
125
 
126
  return result.choices[0].message.content
 
61
 
62
 
63
  class OpenAISummarizerTool(Tool):
64
+ name = "answer_with_sources"
65
+ description = """Use this tool to generate the complete final answer to the user's question based on search results.
66
+
67
+ After retrieving documents with mongodb_vector_search_retriever, use this tool to synthesize a comprehensive answer with a Sources section.
68
+
69
+ CRITICAL: This tool's output is the complete answer - after getting results from this tool, you MUST call the built-in final_answer tool and pass this output EXACTLY as-is without any modifications."""
70
 
71
  inputs = {
72
+ "search_results": {
73
  "type": "string",
74
+ "description": """The complete search results from mongodb_vector_search_retriever to analyze and synthesize into an answer. Pass the ENTIRE output from the retriever tool.""",
75
  }
76
  }
77
  output_type = "string"
78
 
79
+ SYSTEM_PROMPT = """Based on the search results below, create a comprehensive answer to the user's question.
 
 
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  {content}
82
 
83
+ Create a two-part response:
84
+
85
+ 1. **ANSWER** (with inline citations):
86
+ - Focus on the core issues, concerns, or highlights identified
87
+ - DO NOT mention specific customer names or personal identifiers
88
+ - Group related insights by topic with bullet points
89
+ - Be concise and general, highlighting the problem/concern rather than individuals
90
+ - Add INLINE CITATIONS at the end of each point using format: [Doc X]
91
+ - Number each unique document sequentially (Doc 1, Doc 2, etc.)
92
+
93
+ Example:
94
+ β€’ Organizations are planning phone number porting transitions, but custom porting is expensive (~$1,000) and should be done in bulk [Doc 1]
95
+ β€’ Questions about additional license requirements for integrations ($45 per user) [Doc 1]
96
+ β€’ Ringtone volume issues in embedded Salesforce app [Doc 2]
97
+
98
+ 2. **πŸ“š Sources** (at the end):
99
+ - List ONLY UNIQUE documents (de-duplicate by Document ID)
100
+ - Number each unique source to match the inline citations (Doc 1, Doc 2, etc.)
101
+ - Format URLs as markdown links: [View Chat](url) or [View Recording](url)
102
+
103
+ For EACH unique document, use this EXACT structure with proper spacing and NO bold/italic formatting:
104
+
105
+ Doc X: [Title (Date)]
106
+ Source: [Type] | Document ID: [ID] | [Hyperlinked URL if available] | [User ID if available]
107
+
108
+ Summary: [One-line summary of the conversation]
109
+
110
+ Key Findings:
111
+ - [Type/Impact] Finding text here
112
+ - [Type/Impact] Finding text here
113
+
114
+ Example:
115
+
116
+ Doc 1: JustCall Checkin (2025-10-07)
117
+ Source: Justcall Meeting Recordings | Document ID: 4f6f9cee4f
118
+
119
+ Summary: Discussion about phone number porting timeline and costs
120
+
121
+ Key Findings:
122
+ - [Technical Issue/High] Custom porting is expensive at $1,000 per request
123
+ - [Feature Request/Medium] Need bulk porting option to reduce costs
124
+
125
+ Doc 2: Intercom Conversation (2025-10-05)
126
+ Source: Intercom Chats | Document ID: 7a6678783fea06d | [View Chat](https://app.intercom.com/...) | User ID: 432830
127
+
128
+ Summary: Customer requesting billing discount due to service interruption
129
+
130
+ Key Findings:
131
+ - [Pricing Concern/High] Request for discount due to porting delays
132
+ - [Policy Gap/Medium] No current policy for inactivity-based discounts
133
+
134
+ Provide a focused answer with inline citations followed by the well-formatted Sources section with conversation insights."""
135
 
136
  def __init__(self, *args, **kwargs) -> None:
137
  super().__init__(*args, **kwargs)
 
141
  api_key=settings.OPENAI_API_KEY,
142
  )
143
 
144
+ def forward(self, search_results: str) -> str:
145
+ """Generate final answer with sources based on search results.
146
+
147
+ Args:
148
+ search_results: The complete search results to analyze (includes the original query)
149
+
150
+ Returns:
151
+ Complete answer with Sources section
152
+ """
153
+
154
  result = self.__client.chat.completions.create(
155
  model=settings.OPENAI_MODEL_ID,
156
  messages=[
157
  {
158
  "role": "system",
159
+ "content": "You are an expert analyst. Answer the user's question based on the search results provided. Create a comprehensive answer with a Sources section."
160
  },
161
  {
162
  "role": "user",
163
+ "content": self.SYSTEM_PROMPT.format(content=search_results),
164
  },
165
  ],
166
+ temperature=0.0, # Deterministic output
167
+ max_tokens=1500, # Reduced for faster response
168
+ timeout=45.0, # Reduced timeout
169
  )
170
 
171
  return result.choices[0].message.content
src/second_brain_online/application/ui/custom_gradio_ui.py CHANGED
@@ -128,13 +128,15 @@ class CustomGradioUI:
128
  self.submit_btn.click(
129
  fn=self.process_query,
130
  inputs=[self.query_input],
131
- outputs=[self.answer_output, self.sources_output, self.tools_output, self.debug_output, self.conversation_table]
 
132
  )
133
 
134
  self.query_input.submit(
135
  fn=self.process_query,
136
  inputs=[self.query_input],
137
- outputs=[self.answer_output, self.sources_output, self.tools_output, self.debug_output, self.conversation_table]
 
138
  )
139
 
140
  # Conversation search handlers
@@ -150,16 +152,24 @@ class CustomGradioUI:
150
  outputs=[self.conversation_search, self.conversation_table]
151
  )
152
 
153
- def process_query(self, query: str) -> Tuple[str, str, str, str, pd.DataFrame]:
154
  """Process the user query and return formatted response components."""
155
  if not query.strip():
156
  # Clear all outputs when query is empty
157
  return "", "", "", "", self.load_conversations()
158
 
159
  try:
160
- # Run the agent
 
 
 
 
 
161
  result = self.agent.run(query)
162
 
 
 
 
163
  # Parse the result with agent logs
164
  agent_logs = getattr(self.agent, 'logs', []) if hasattr(self.agent, 'logs') else []
165
  answer, sources, tools_used = self.parse_agent_response(result, agent_logs)
@@ -187,8 +197,10 @@ class CustomGradioUI:
187
  debug_text = str(result)
188
 
189
  # Filter conversations based on sources used
 
190
  filtered_conversations = self.filter_conversations_by_sources(sources)
191
 
 
192
  return answer_html, sources_html, tools_html, debug_text, filtered_conversations
193
 
194
  except Exception as e:
 
128
  self.submit_btn.click(
129
  fn=self.process_query,
130
  inputs=[self.query_input],
131
+ outputs=[self.answer_output, self.sources_output, self.tools_output, self.debug_output, self.conversation_table],
132
+ show_progress="full" # Show progress indicator
133
  )
134
 
135
  self.query_input.submit(
136
  fn=self.process_query,
137
  inputs=[self.query_input],
138
+ outputs=[self.answer_output, self.sources_output, self.tools_output, self.debug_output, self.conversation_table],
139
+ show_progress="full" # Show progress indicator
140
  )
141
 
142
  # Conversation search handlers
 
152
  outputs=[self.conversation_search, self.conversation_table]
153
  )
154
 
155
+ def process_query(self, query: str, progress=gr.Progress()) -> Tuple[str, str, str, str, pd.DataFrame]:
156
  """Process the user query and return formatted response components."""
157
  if not query.strip():
158
  # Clear all outputs when query is empty
159
  return "", "", "", "", self.load_conversations()
160
 
161
  try:
162
+ # Show progress indicator with descriptive message
163
+ progress(0, desc="πŸ” Starting query processing...")
164
+
165
+ # Run the agent (this takes 30-60 seconds)
166
+ # Use None for indeterminate progress during long operation
167
+ progress(None, desc="πŸ” Searching knowledge base and retrieving documents...")
168
  result = self.agent.run(query)
169
 
170
+ # Quick post-processing steps
171
+ progress(0.8, desc="✨ Formatting answer and sources...")
172
+
173
  # Parse the result with agent logs
174
  agent_logs = getattr(self.agent, 'logs', []) if hasattr(self.agent, 'logs') else []
175
  answer, sources, tools_used = self.parse_agent_response(result, agent_logs)
 
197
  debug_text = str(result)
198
 
199
  # Filter conversations based on sources used
200
+ progress(0.95, desc="πŸ“Š Updating conversation list...")
201
  filtered_conversations = self.filter_conversations_by_sources(sources)
202
 
203
+ progress(1.0, desc="βœ… Complete!")
204
  return answer_html, sources_html, tools_html, debug_text, filtered_conversations
205
 
206
  except Exception as e: