dolphinium commited on
Commit
dae6a10
Β·
1 Parent(s): 131ad34

dynamic core selection according to agentic api's output.

Browse files
Files changed (4) hide show
  1. data_processing.py +51 -35
  2. extract_results.py +52 -13
  3. llm_prompts.py +6 -3
  4. ui.py +59 -30
data_processing.py CHANGED
@@ -40,32 +40,50 @@ def parse_suggestions_from_report(report_text):
40
 
41
 
42
  def llm_generate_analysis_plan_with_history(llm_model, natural_language_query, chat_history):
43
- """
44
- Generates a complete analysis plan from a user query, considering chat history
45
- and dynamic field suggestions from an external API.
46
- """
47
- search_fields, search_name = [], ""
48
- try:
49
- # Call the external API to get dynamic field suggestions
50
- search_fields, search_name = get_search_list_params(natural_language_query)
51
- print(f"Successfully retrieved {len(search_fields)} dynamic fields.")
52
- except Exception as e:
53
- print(f"Warning: Could not retrieve dynamic search fields. Proceeding without them. Error: {e}")
54
-
55
- # Generate the prompt, including the (potentially empty) search_fields
56
- prompt = get_analysis_plan_prompt(natural_language_query, chat_history, search_fields)
57
-
58
- try:
59
- response = llm_model.generate_content(prompt)
60
- cleaned_text = re.sub(r'```json\s*|\s*```', '', response.text, flags=re.MULTILINE | re.DOTALL).strip()
61
- plan = json.loads(cleaned_text)
62
- # Return the plan and the retrieved fields for UI display
63
- return plan, search_fields
64
- except Exception as e:
65
- raw_response_text = response.text if 'response' in locals() else 'N/A'
66
- print(f"Error in llm_generate_analysis_plan_with_history: {e}\nRaw Response:\n{raw_response_text}")
67
- # Return None for the plan but still return search_fields for debugging in the UI
68
- return None, search_fields
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
 
71
 
@@ -79,17 +97,15 @@ def execute_quantitative_query(solr_client, plan):
79
  "rows": 0,
80
  "json.facet": json.dumps(plan['quantitative_request']['json.facet'])
81
  }
82
-
83
- # Build the full Solr URL manually (for logging)
84
- base_url = "http://69.167.186.48:8983/solr/news/select"
85
  query_string = urllib.parse.urlencode(params)
86
  full_url = f"{base_url}?{query_string}"
87
-
88
- print(f"[DEBUG] Solr QUANTITIVE query URL: {full_url}")
89
  results = solr_client.search(**params)
90
  return results.raw_response.get("facets", {}), full_url
91
  except Exception as e:
92
- print(f"Error in quantitative query: {e}")
93
  return None, None
94
 
95
  def execute_qualitative_query(solr_client, plan):
@@ -104,15 +120,15 @@ def execute_qualitative_query(solr_client, plan):
104
  "fl": "*,score",
105
  **qual_request
106
  }
107
- base_url = "http://69.167.186.48:8983/solr/news/select"
 
108
  query_string = urllib.parse.urlencode(params)
109
  full_url = f"{base_url}?{query_string}"
110
-
111
  print(f"[DEBUG] Solr QUALITATIVE query URL: {full_url}")
112
  results = solr_client.search(**params)
113
  return results.grouped, full_url
114
  except Exception as e:
115
- print(f"Error in qualitative query: {e}")
116
  return None, None
117
 
118
  def llm_synthesize_enriched_report_stream(llm_model, query, quantitative_data, qualitative_data, plan):
 
40
 
41
 
42
  def llm_generate_analysis_plan_with_history(llm_model, natural_language_query, chat_history):
43
+ """
44
+ Generates a complete analysis plan from a user query, considering chat history
45
+ and dynamic field suggestions from an external API.
46
+ """
47
+ search_fields, search_name, field_mappings = [], "", {}
48
+ try:
49
+ # Call the external API to get dynamic fields, core name, and mappings
50
+ search_fields, search_name, field_mappings = get_search_list_params(natural_language_query)
51
+ print(f"API returned core: '{search_name}' with {len(search_fields)} fields and {len(field_mappings)} mappings.")
52
+ except Exception as e:
53
+ print(f"Warning: Could not retrieve dynamic search fields. Proceeding without them. Error: {e}")
54
+
55
+ # Determine the core name, default to 'news' if not provided by the API
56
+ core_name = search_name if search_name else 'news'
57
+
58
+ # Apply the field mappings to the suggestions before sending them to the LLM
59
+ mapped_search_fields = []
60
+ if search_fields and field_mappings:
61
+ for field in search_fields:
62
+ original_name = field.get('field_name')
63
+ # Create a new dict to avoid modifying the original
64
+ mapped_field = field.copy()
65
+ if original_name in field_mappings:
66
+ mapped_field['field_name'] = field_mappings[original_name]
67
+ print(f"Mapped field '{original_name}' to '{mapped_field['field_name']}'")
68
+ mapped_search_fields.append(mapped_field)
69
+ else:
70
+ mapped_search_fields = search_fields
71
+
72
+
73
+ # Generate the prompt, passing the mapped fields and the dynamic core name
74
+ prompt = get_analysis_plan_prompt(natural_language_query, chat_history, mapped_search_fields, core_name)
75
+
76
+ try:
77
+ response = llm_model.generate_content(prompt)
78
+ cleaned_text = re.sub(r'```json\s*|\s*```', '', response.text, flags=re.MULTILINE | re.DOTALL).strip()
79
+ plan = json.loads(cleaned_text)
80
+ # Return the plan, the mapped fields for UI display, and the core name
81
+ return plan, mapped_search_fields, core_name
82
+ except Exception as e:
83
+ raw_response_text = response.text if 'response' in locals() else 'N/A'
84
+ print(f"Error in llm_generate_analysis_plan_with_history: {e}\nRaw Response:\n{raw_response_text}")
85
+ # Return None for the plan but still return other data for debugging
86
+ return None, mapped_search_fields, core_name
87
 
88
 
89
 
 
97
  "rows": 0,
98
  "json.facet": json.dumps(plan['quantitative_request']['json.facet'])
99
  }
100
+ # Build the full Solr URL manually (for logging) from the client's current URL
101
+ base_url = f"{solr_client.url}/select"
 
102
  query_string = urllib.parse.urlencode(params)
103
  full_url = f"{base_url}?{query_string}"
104
+ print(f"[DEBUG] Solr QUANTITATIVE query URL: {full_url}")
 
105
  results = solr_client.search(**params)
106
  return results.raw_response.get("facets", {}), full_url
107
  except Exception as e:
108
+ print(f"Error in quantitative query on core specified in client ({solr_client.url}): {e}")
109
  return None, None
110
 
111
  def execute_qualitative_query(solr_client, plan):
 
120
  "fl": "*,score",
121
  **qual_request
122
  }
123
+ # Build the full Solr URL manually (for logging) from the client's current URL
124
+ base_url = f"{solr_client.url}/select"
125
  query_string = urllib.parse.urlencode(params)
126
  full_url = f"{base_url}?{query_string}"
 
127
  print(f"[DEBUG] Solr QUALITATIVE query URL: {full_url}")
128
  results = solr_client.search(**params)
129
  return results.grouped, full_url
130
  except Exception as e:
131
+ print(f"Error in qualitative query on core specified in client ({solr_client.url}): {e}")
132
  return None, None
133
 
134
  def llm_synthesize_enriched_report_stream(llm_model, query, quantitative_data, qualitative_data, plan):
extract_results.py CHANGED
@@ -1,28 +1,67 @@
1
  import requests
2
  import json
3
  import yaml
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  def get_search_list_params(query, k=20):
6
  """
7
- Returns tuple: (search_fields, search_name)
 
 
 
8
  """
9
  url = "https://aitest.ebalina.com/stream"
10
 
11
- response = requests.post(url,
12
- headers={'Content-Type': 'application/json'},
13
- json={"query": query, "k": k},
14
- stream=True)
 
 
15
 
 
 
 
 
16
  for line in response.iter_lines():
17
  if line and line.startswith(b'data: '):
18
  try:
19
- data = json.loads(line[6:])
20
- if data.get('log_title') == 'Search List Result':
21
- yaml_data = yaml.safe_load(data['content'])
22
- # As requested, ignoring 'search_name' and only returning fields
23
- return yaml_data.get('search_fields', []), yaml_data.get('search_name', '')
24
- except:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  continue
26
 
27
- # Return empty list if no valid data is found
28
- return [], ""
 
 
1
  import requests
2
  import json
3
  import yaml
4
+ import re
5
+
6
+ def _parse_mappings(mapping_str: str) -> dict:
7
+ """Parses the field mapping string into a dictionary."""
8
+ mappings = {}
9
+ if not mapping_str:
10
+ return mappings
11
+ # Example line: "Mapping 'company_territory' to 'owner_company_territory' under 'compound'."
12
+ pattern = re.compile(r"Mapping '([^']*)' to '([^']*)'")
13
+ for line in mapping_str.split('\n'):
14
+ match = pattern.search(line)
15
+ if match:
16
+ original_field, mapped_field = match.groups()
17
+ mappings[original_field] = mapped_field
18
+ return mappings
19
 
20
  def get_search_list_params(query, k=20):
21
  """
22
+ Connects to the external API, parses the stream, and returns the core name,
23
+ search fields, and field mappings.
24
+
25
+ Returns tuple: (search_fields, search_name, field_mappings)
26
  """
27
  url = "https://aitest.ebalina.com/stream"
28
 
29
+ response = requests.post(
30
+ url,
31
+ headers={'Content-Type': 'application/json'},
32
+ json={"query": query, "k": k},
33
+ stream=True
34
+ )
35
 
36
+ search_fields = []
37
+ search_name = ""
38
+ field_mappings_str = ""
39
+
40
  for line in response.iter_lines():
41
  if line and line.startswith(b'data: '):
42
  try:
43
+ line_str = line.decode('utf-8')[6:]
44
+ if not line_str or line_str.isspace():
45
+ continue
46
+
47
+ data = json.loads(line_str)
48
+ log_title = data.get('log_title')
49
+
50
+ if log_title == 'Search List Result':
51
+ content = data.get('content', '')
52
+ if content:
53
+ yaml_data = yaml.safe_load(content)
54
+ print("DEBUG:", yaml_data)
55
+ # This is the dynamic core name
56
+ search_name = yaml_data.get('search_name', '')
57
+ search_fields = yaml_data.get('search_fields', [])
58
+
59
+ elif log_title == 'Field Mapping Outputs':
60
+ field_mappings_str = data.get('content', '')
61
+
62
+ except (json.JSONDecodeError, yaml.YAMLError, AttributeError):
63
  continue
64
 
65
+ field_mappings = _parse_mappings(field_mappings_str)
66
+
67
+ return search_fields, search_name, field_mappings
llm_prompts.py CHANGED
@@ -11,15 +11,17 @@ import datetime
11
  import json
12
  from solr_metadata import format_metadata_for_prompt
13
 
14
- def get_analysis_plan_prompt(natural_language_query, chat_history, search_fields=None):
15
  """
16
  Generates the prompt for creating a Solr analysis plan from a user query.
17
  Args:
18
  natural_language_query (str): The user's query.
19
  chat_history (list): A list of previous user and bot messages.
20
  search_fields (list, optional): A list of dictionaries with 'field_name' and 'field_value'.
 
21
  """
22
- formatted_field_info = format_metadata_for_prompt()
 
23
  formatted_history = ""
24
  for user_msg, bot_msg in chat_history:
25
  if user_msg:
@@ -27,6 +29,7 @@ def get_analysis_plan_prompt(natural_language_query, chat_history, search_fields
27
 
28
  dynamic_fields_prompt_section = ""
29
  if search_fields:
 
30
  formatted_fields = "\n".join([f" - {field['field_name']}: {field['field_value']}" for field in search_fields])
31
  dynamic_fields_prompt_section = f"""
32
  ---
@@ -84,7 +87,7 @@ This is the most critical part of your task. A bad choice leads to a useless, bo
84
  * **IMPLICIT COUNT:** If the user asks a "what," "who," "how many," or "most common" question without specifying a value metric, the measure is `count`.
85
 
86
  ---
87
- ### FIELD DEFINITIONS (Your Source of Truth)
88
 
89
  {formatted_field_info}
90
  {dynamic_fields_prompt_section}
 
11
  import json
12
  from solr_metadata import format_metadata_for_prompt
13
 
14
+ def get_analysis_plan_prompt(natural_language_query, chat_history, search_fields=None, core_name="news"):
15
  """
16
  Generates the prompt for creating a Solr analysis plan from a user query.
17
  Args:
18
  natural_language_query (str): The user's query.
19
  chat_history (list): A list of previous user and bot messages.
20
  search_fields (list, optional): A list of dictionaries with 'field_name' and 'field_value'.
21
+ core_name (str): The name of the Solr core to use for field metadata.
22
  """
23
+ # Dynamically get field info for the specified core
24
+ formatted_field_info = format_metadata_for_prompt(core_name)
25
  formatted_history = ""
26
  for user_msg, bot_msg in chat_history:
27
  if user_msg:
 
29
 
30
  dynamic_fields_prompt_section = ""
31
  if search_fields:
32
+ # The search_fields are now pre-mapped, so we can use them directly
33
  formatted_fields = "\n".join([f" - {field['field_name']}: {field['field_value']}" for field in search_fields])
34
  dynamic_fields_prompt_section = f"""
35
  ---
 
87
  * **IMPLICIT COUNT:** If the user asks a "what," "who," "how many," or "most common" question without specifying a value metric, the measure is `count`.
88
 
89
  ---
90
+ ### FIELD DEFINITIONS (Your Source of Truth for Core: {core_name})
91
 
92
  {formatted_field_info}
93
  {dynamic_fields_prompt_section}
ui.py CHANGED
@@ -21,6 +21,7 @@ from data_processing import (
21
  parse_suggestions_from_report
22
  )
23
 
 
24
  def create_ui(llm_model, solr_client):
25
  """
26
  Builds the Gradio UI and wires up all the event handlers.
@@ -36,28 +37,39 @@ def create_ui(llm_model, solr_client):
36
  with gr.Column(scale=4):
37
  gr.Markdown("# PharmaCircle AI Data Analyst")
38
  with gr.Column(scale=1):
39
- clear_button = gr.Button("πŸ”„ Start New Analysis", variant="primary")
 
40
 
41
  gr.Markdown("Ask a question to begin your analysis. I will generate an analysis plan, retrieve quantitative and qualitative data, create a visualization, and write an enriched report.")
42
 
43
  with gr.Row():
44
  with gr.Column(scale=1):
45
- chatbot = gr.Chatbot(label="Analysis Chat Log", height=700, show_copy_button=True)
46
- msg_textbox = gr.Textbox(placeholder="Ask a question, e.g., 'Show me the top 5 companies by total deal value in 2023'", label="Your Question", interactive=True)
 
 
47
 
48
  with gr.Column(scale=2):
49
  with gr.Accordion("Dynamic Field Suggestions", open=False):
50
- suggestions_display = gr.Markdown("Suggestions from the external API will appear here...", visible=True)
 
51
  with gr.Accordion("Generated Analysis Plan", open=False):
52
- plan_display = gr.Markdown("Plan will appear here...", visible=True)
 
53
  with gr.Accordion("Retrieved Quantitative Data", open=False):
54
- quantitative_url_display = gr.Markdown("Quantitative URL will appear here...", visible=False)
55
- quantitative_data_display = gr.Markdown("Aggregate data will appear here...", visible=False)
 
 
56
  with gr.Accordion("Retrieved Qualitative Data (Examples)", open=False):
57
- qualitative_url_display = gr.Markdown("Qualitative URL will appear here...", visible=False)
58
- qualitative_data_display = gr.Markdown("Example data will appear here...", visible=False)
59
- plot_display = gr.Image(label="Visualization", type="filepath", visible=False)
60
- report_display = gr.Markdown("Report will be streamed here...", visible=False)
 
 
 
 
61
 
62
  def process_analysis_flow(user_input, history, state):
63
  """
@@ -80,12 +92,12 @@ def create_ui(llm_model, solr_client):
80
  history.append((user_input, f"Analyzing: '{query_context}'\n\n*Generating analysis plan...*"))
81
  yield (history, state, None, None, None, None, None, None, None, None)
82
 
83
- # Generate plan and get search field suggestions
84
- analysis_plan, search_fields = llm_generate_analysis_plan_with_history(llm_model, query_context, history)
85
 
86
  # Update and display search field suggestions in its own accordion
87
- if search_fields:
88
- suggestions_md = "**External API Suggestions:**\n" + "\n".join([f"- `{field['field_name']}`: `{field['field_value']}`" for field in search_fields])
89
  suggestions_display_update = gr.update(value=suggestions_md, visible=True)
90
  else:
91
  suggestions_display_update = gr.update(value="No suggestions were returned from the external API.", visible=True)
@@ -95,30 +107,45 @@ def create_ui(llm_model, solr_client):
95
  yield (history, state, None, None, None, None, None, None, None, suggestions_display_update)
96
  return
97
 
98
- history.append((None, "βœ… Analysis plan generated!"))
99
  plan_summary = f"""
100
- * **Analysis Dimension:** `{analysis_plan.get('analysis_dimension')}`
101
- * **Analysis Measure:** `{analysis_plan.get('analysis_measure')}`
102
- * **Query Filter:** `{analysis_plan.get('query_filter')}`
103
- """
104
  history.append((None, plan_summary))
105
- formatted_plan = f"**Full Analysis Plan:**\n```json\n{json.dumps(analysis_plan, indent=2)}\n```"
106
  yield (history, state, None, None, gr.update(value=formatted_plan, visible=True), None, None, None, None, suggestions_display_update)
107
 
108
  history.append((None, "*Executing queries for aggregates and examples...*"))
109
  yield (history, state, None, None, gr.update(value=formatted_plan, visible=True), None, None, None, None, suggestions_display_update)
110
 
 
 
 
 
 
 
 
 
 
111
  # Execute queries in parallel
112
  aggregate_data, quantitative_url = None, None
113
  example_data, qualitative_url = None, None
114
- with concurrent.futures.ThreadPoolExecutor() as executor:
115
- future_agg = executor.submit(execute_quantitative_query, solr_client, analysis_plan)
116
- future_ex = executor.submit(execute_qualitative_query, solr_client, analysis_plan)
117
- aggregate_data, quantitative_url = future_agg.result()
118
- example_data, qualitative_url = future_ex.result()
 
 
 
 
 
 
119
 
120
  if not aggregate_data or aggregate_data.get('count', 0) == 0:
121
- history.append((None, "No data was found for your query. Please try a different question."))
122
  yield (history, state, None, None, gr.update(value=formatted_plan, visible=True), None, None, None, None, suggestions_display_update)
123
  return
124
 
@@ -178,7 +205,8 @@ def create_ui(llm_model, solr_client):
178
  msg_textbox.submit(
179
  fn=process_analysis_flow,
180
  inputs=[msg_textbox, chatbot, state],
181
- outputs=[chatbot, state, plot_display, report_display, plan_display, quantitative_url_display, quantitative_data_display, qualitative_url_display, qualitative_data_display, suggestions_display],
 
182
  ).then(
183
  lambda: gr.update(value=""),
184
  None,
@@ -189,8 +217,9 @@ def create_ui(llm_model, solr_client):
189
  clear_button.click(
190
  fn=reset_all,
191
  inputs=None,
192
- outputs=[chatbot, state, msg_textbox, plot_display, report_display, plan_display, quantitative_url_display, quantitative_data_display, qualitative_url_display, qualitative_data_display, suggestions_display],
 
193
  queue=False
194
  )
195
 
196
- return demo
 
21
  parse_suggestions_from_report
22
  )
23
 
24
+
25
  def create_ui(llm_model, solr_client):
26
  """
27
  Builds the Gradio UI and wires up all the event handlers.
 
37
  with gr.Column(scale=4):
38
  gr.Markdown("# PharmaCircle AI Data Analyst")
39
  with gr.Column(scale=1):
40
+ clear_button = gr.Button(
41
+ "πŸ”„ Start New Analysis", variant="primary")
42
 
43
  gr.Markdown("Ask a question to begin your analysis. I will generate an analysis plan, retrieve quantitative and qualitative data, create a visualization, and write an enriched report.")
44
 
45
  with gr.Row():
46
  with gr.Column(scale=1):
47
+ chatbot = gr.Chatbot(
48
+ label="Analysis Chat Log", height=700, show_copy_button=True)
49
+ msg_textbox = gr.Textbox(
50
+ placeholder="Ask a question, e.g., 'Show me the top 5 companies by total deal value in 2023'", label="Your Question", interactive=True)
51
 
52
  with gr.Column(scale=2):
53
  with gr.Accordion("Dynamic Field Suggestions", open=False):
54
+ suggestions_display = gr.Markdown(
55
+ "Suggestions from the external API will appear here...", visible=True)
56
  with gr.Accordion("Generated Analysis Plan", open=False):
57
+ plan_display = gr.Markdown(
58
+ "Plan will appear here...", visible=True)
59
  with gr.Accordion("Retrieved Quantitative Data", open=False):
60
+ quantitative_url_display = gr.Markdown(
61
+ "Quantitative URL will appear here...", visible=False)
62
+ quantitative_data_display = gr.Markdown(
63
+ "Aggregate data will appear here...", visible=False)
64
  with gr.Accordion("Retrieved Qualitative Data (Examples)", open=False):
65
+ qualitative_url_display = gr.Markdown(
66
+ "Qualitative URL will appear here...", visible=False)
67
+ qualitative_data_display = gr.Markdown(
68
+ "Example data will appear here...", visible=False)
69
+ plot_display = gr.Image(
70
+ label="Visualization", type="filepath", visible=False)
71
+ report_display = gr.Markdown(
72
+ "Report will be streamed here...", visible=False)
73
 
74
  def process_analysis_flow(user_input, history, state):
75
  """
 
92
  history.append((user_input, f"Analyzing: '{query_context}'\n\n*Generating analysis plan...*"))
93
  yield (history, state, None, None, None, None, None, None, None, None)
94
 
95
+ # Generate plan and get search field suggestions. This now returns the core name.
96
+ analysis_plan, mapped_search_fields, core_name = llm_generate_analysis_plan_with_history(llm_model, query_context, history)
97
 
98
  # Update and display search field suggestions in its own accordion
99
+ if mapped_search_fields:
100
+ suggestions_md = "**API Suggestions (with mappings applied):**\n" + "\n".join([f"- `{field['field_name']}`: `{field['field_value']}`" for field in mapped_search_fields])
101
  suggestions_display_update = gr.update(value=suggestions_md, visible=True)
102
  else:
103
  suggestions_display_update = gr.update(value="No suggestions were returned from the external API.", visible=True)
 
107
  yield (history, state, None, None, None, None, None, None, None, suggestions_display_update)
108
  return
109
 
110
+ history.append((None, f"βœ… Analysis plan generated for core: **`{core_name}`**"))
111
  plan_summary = f"""
112
+ * **Analysis Dimension:** `{analysis_plan.get('analysis_dimension')}`
113
+ * **Analysis Measure:** `{analysis_plan.get('analysis_measure')}`
114
+ * **Query Filter:** `{analysis_plan.get('query_filter')}`
115
+ """
116
  history.append((None, plan_summary))
117
+ formatted_plan = f"**Full Analysis Plan (Core: `{core_name}`):**\n```json\n{json.dumps(analysis_plan, indent=2)}\n```"
118
  yield (history, state, None, None, gr.update(value=formatted_plan, visible=True), None, None, None, None, suggestions_display_update)
119
 
120
  history.append((None, "*Executing queries for aggregates and examples...*"))
121
  yield (history, state, None, None, gr.update(value=formatted_plan, visible=True), None, None, None, None, suggestions_display_update)
122
 
123
+ # --- DYNAMIC CORE SWITCH (Thread-safe) ---
124
+ original_solr_url = solr_client.url
125
+ # Correctly construct the new URL by replacing the last component (the core name)
126
+ base_url = original_solr_url.rsplit('/', 1)[0]
127
+ new_url = f"{base_url}/{core_name}"
128
+ solr_client.url = new_url
129
+ print(f"[INFO] Switched Solr client to core: {core_name} at URL: {solr_client.url}")
130
+ # ---
131
+
132
  # Execute queries in parallel
133
  aggregate_data, quantitative_url = None, None
134
  example_data, qualitative_url = None, None
135
+ try:
136
+ with concurrent.futures.ThreadPoolExecutor() as executor:
137
+ future_agg = executor.submit(execute_quantitative_query, solr_client, analysis_plan)
138
+ future_ex = executor.submit(execute_qualitative_query, solr_client, analysis_plan)
139
+ aggregate_data, quantitative_url = future_agg.result()
140
+ example_data, qualitative_url = future_ex.result()
141
+ finally:
142
+ # --- IMPORTANT: Reset client to default URL ---
143
+ solr_client.url = original_solr_url
144
+ print(f"[INFO] Reset Solr client to default URL: {original_solr_url}")
145
+ # ---
146
 
147
  if not aggregate_data or aggregate_data.get('count', 0) == 0:
148
+ history.append((None, f"No data was found for your query in the '{core_name}' core. Please try a different question."))
149
  yield (history, state, None, None, gr.update(value=formatted_plan, visible=True), None, None, None, None, suggestions_display_update)
150
  return
151
 
 
205
  msg_textbox.submit(
206
  fn=process_analysis_flow,
207
  inputs=[msg_textbox, chatbot, state],
208
+ outputs=[chatbot, state, plot_display, report_display, plan_display, quantitative_url_display,
209
+ quantitative_data_display, qualitative_url_display, qualitative_data_display, suggestions_display],
210
  ).then(
211
  lambda: gr.update(value=""),
212
  None,
 
217
  clear_button.click(
218
  fn=reset_all,
219
  inputs=None,
220
+ outputs=[chatbot, state, msg_textbox, plot_display, report_display, plan_display, quantitative_url_display,
221
+ quantitative_data_display, qualitative_url_display, qualitative_data_display, suggestions_display],
222
  queue=False
223
  )
224
 
225
+ return demo