Commit
·
f2961b7
1
Parent(s):
8bb4446
mm2
Browse files
app.py
CHANGED
|
@@ -417,6 +417,40 @@ def execute_sql_query(query, db_connection):
|
|
| 417 |
except Exception as e:
|
| 418 |
return f"Error ejecutando la consulta: {str(e)}"
|
| 419 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
def generate_plot(data, x_col, y_col, title, x_label, y_label):
|
| 421 |
"""Generate a plot from data and return the file path."""
|
| 422 |
plt.figure(figsize=(10, 6))
|
|
@@ -563,18 +597,8 @@ async def stream_agent_response(question: str, chat_history: List[List[str]]) ->
|
|
| 563 |
data.append(dict(zip(columns, values)))
|
| 564 |
|
| 565 |
if data and len(columns) >= 2:
|
| 566 |
-
# Determine chart type from user's question
|
| 567 |
-
|
| 568 |
-
if any(k in q_lower for k in ["gráfico circular", "grafico circular", "pie", "pastel"]):
|
| 569 |
-
desired_type = 'pie'
|
| 570 |
-
elif any(k in q_lower for k in ["línea", "linea", "line"]):
|
| 571 |
-
desired_type = 'line'
|
| 572 |
-
elif any(k in q_lower for k in ["dispersión", "dispersion", "scatter"]):
|
| 573 |
-
desired_type = 'scatter'
|
| 574 |
-
elif any(k in q_lower for k in ["histograma", "histogram"]):
|
| 575 |
-
desired_type = 'histogram'
|
| 576 |
-
else:
|
| 577 |
-
desired_type = 'bar'
|
| 578 |
|
| 579 |
# Choose x/y columns (assume first is category, second numeric)
|
| 580 |
x_col = columns[0]
|
|
@@ -608,8 +632,7 @@ async def stream_agent_response(question: str, chat_history: List[List[str]]) ->
|
|
| 608 |
# If we still have no chart but the user clearly wants one,
|
| 609 |
# try a second pass to get ONLY a SQL query from the agent and execute it.
|
| 610 |
if chart_fig is None:
|
| 611 |
-
|
| 612 |
-
wants_chart = any(k in q_lower for k in ["gráfico", "grafico", "chart", "graph", "pastel", "pie"])
|
| 613 |
if wants_chart:
|
| 614 |
try:
|
| 615 |
logger.info("Second pass: asking agent for ONLY SQL query in fenced block.")
|
|
@@ -648,7 +671,7 @@ async def stream_agent_response(question: str, chat_history: List[List[str]]) ->
|
|
| 648 |
except Exception:
|
| 649 |
continue
|
| 650 |
if y_col:
|
| 651 |
-
desired_type =
|
| 652 |
chart_fig = generate_chart(
|
| 653 |
data=data,
|
| 654 |
chart_type=desired_type,
|
|
@@ -663,11 +686,10 @@ async def stream_agent_response(question: str, chat_history: List[List[str]]) ->
|
|
| 663 |
except Exception as e:
|
| 664 |
logger.error(f"Second-pass SQL synthesis failed: {e}")
|
| 665 |
|
| 666 |
-
# Fallback: if user asked for a chart
|
| 667 |
# parse the most recent assistant text for lines like "LABEL: NUMBER" (bulleted or plain).
|
| 668 |
if chart_fig is None:
|
| 669 |
-
|
| 670 |
-
wants_chart = any(k in q_lower for k in ["gráfico", "grafico", "chart", "graph", "pastel", "pie"])
|
| 671 |
if wants_chart:
|
| 672 |
# Find the most recent assistant message with usable numeric pairs
|
| 673 |
candidate_text = ""
|
|
@@ -705,7 +727,6 @@ async def stream_agent_response(question: str, chat_history: List[List[str]]) ->
|
|
| 705 |
data.append({"label": label, "value": val})
|
| 706 |
logger.info(f"Fallback parse from text: extracted {len(data)} items for potential chart")
|
| 707 |
if len(data) >= 2:
|
| 708 |
-
desired_type = 'pie' if any(k in q_lower for k in ["gráfico circular", "grafico circular", "pie", "pastel"]) else 'bar'
|
| 709 |
chart_fig = generate_chart(
|
| 710 |
data=data,
|
| 711 |
chart_type=desired_type,
|
|
@@ -1013,6 +1034,37 @@ def create_application():
|
|
| 1013 |
# Append assistant message back into messages history
|
| 1014 |
chat_history.append({"role": "assistant", "content": assistant_message})
|
| 1015 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1016 |
logger.info("Response generation complete")
|
| 1017 |
return chat_history, chart_fig
|
| 1018 |
|
|
|
|
| 417 |
except Exception as e:
|
| 418 |
return f"Error ejecutando la consulta: {str(e)}"
|
| 419 |
|
| 420 |
+
def detect_chart_preferences(question: str) -> Tuple[bool, str]:
|
| 421 |
+
"""Detect whether the user is asking for a chart and infer desired type.
|
| 422 |
+
|
| 423 |
+
Returns (wants_chart, chart_type) where chart_type is one of
|
| 424 |
+
{'bar', 'pie', 'line', 'scatter', 'histogram'}.
|
| 425 |
+
Defaults to 'bar' when ambiguous.
|
| 426 |
+
"""
|
| 427 |
+
try:
|
| 428 |
+
q = (question or "").lower()
|
| 429 |
+
|
| 430 |
+
# Broad triggers indicating any chart request
|
| 431 |
+
chart_triggers = [
|
| 432 |
+
"grafico", "gráfico", "grafica", "gráfica", "chart", "graph",
|
| 433 |
+
"visualizacion", "visualización", "plot", "plotly", "diagrama"
|
| 434 |
+
]
|
| 435 |
+
wants_chart = any(k in q for k in chart_triggers)
|
| 436 |
+
|
| 437 |
+
# Specific type hints
|
| 438 |
+
if any(k in q for k in ["pastel", "pie", "circular", "donut", "dona", "anillo"]):
|
| 439 |
+
return wants_chart or True, "pie"
|
| 440 |
+
if any(k in q for k in ["linea", "línea", "line", "tendencia"]):
|
| 441 |
+
return wants_chart or True, "line"
|
| 442 |
+
if any(k in q for k in ["dispersión", "dispersion", "scatter", "puntos"]):
|
| 443 |
+
return wants_chart or True, "scatter"
|
| 444 |
+
if any(k in q for k in ["histograma", "histogram"]):
|
| 445 |
+
return wants_chart or True, "histogram"
|
| 446 |
+
if any(k in q for k in ["barra", "barras", "columnas", "column"]):
|
| 447 |
+
return wants_chart or True, "bar"
|
| 448 |
+
|
| 449 |
+
# Default
|
| 450 |
+
return wants_chart, "bar"
|
| 451 |
+
except Exception:
|
| 452 |
+
return False, "bar"
|
| 453 |
+
|
| 454 |
def generate_plot(data, x_col, y_col, title, x_label, y_label):
|
| 455 |
"""Generate a plot from data and return the file path."""
|
| 456 |
plt.figure(figsize=(10, 6))
|
|
|
|
| 597 |
data.append(dict(zip(columns, values)))
|
| 598 |
|
| 599 |
if data and len(columns) >= 2:
|
| 600 |
+
# Determine chart type from user's question
|
| 601 |
+
_, desired_type = detect_chart_preferences(question)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
|
| 603 |
# Choose x/y columns (assume first is category, second numeric)
|
| 604 |
x_col = columns[0]
|
|
|
|
| 632 |
# If we still have no chart but the user clearly wants one,
|
| 633 |
# try a second pass to get ONLY a SQL query from the agent and execute it.
|
| 634 |
if chart_fig is None:
|
| 635 |
+
wants_chart, default_type = detect_chart_preferences(question)
|
|
|
|
| 636 |
if wants_chart:
|
| 637 |
try:
|
| 638 |
logger.info("Second pass: asking agent for ONLY SQL query in fenced block.")
|
|
|
|
| 671 |
except Exception:
|
| 672 |
continue
|
| 673 |
if y_col:
|
| 674 |
+
desired_type = default_type
|
| 675 |
chart_fig = generate_chart(
|
| 676 |
data=data,
|
| 677 |
chart_type=desired_type,
|
|
|
|
| 686 |
except Exception as e:
|
| 687 |
logger.error(f"Second-pass SQL synthesis failed: {e}")
|
| 688 |
|
| 689 |
+
# Fallback: if user asked for a chart and we didn't get SQL or chart yet,
|
| 690 |
# parse the most recent assistant text for lines like "LABEL: NUMBER" (bulleted or plain).
|
| 691 |
if chart_fig is None:
|
| 692 |
+
wants_chart, desired_type = detect_chart_preferences(question)
|
|
|
|
| 693 |
if wants_chart:
|
| 694 |
# Find the most recent assistant message with usable numeric pairs
|
| 695 |
candidate_text = ""
|
|
|
|
| 727 |
data.append({"label": label, "value": val})
|
| 728 |
logger.info(f"Fallback parse from text: extracted {len(data)} items for potential chart")
|
| 729 |
if len(data) >= 2:
|
|
|
|
| 730 |
chart_fig = generate_chart(
|
| 731 |
data=data,
|
| 732 |
chart_type=desired_type,
|
|
|
|
| 1034 |
# Append assistant message back into messages history
|
| 1035 |
chat_history.append({"role": "assistant", "content": assistant_message})
|
| 1036 |
|
| 1037 |
+
# If user asked for a chart but none was produced, try to build one
|
| 1038 |
+
# from the latest assistant text using the same fallback logic.
|
| 1039 |
+
if chart_fig is None:
|
| 1040 |
+
wants_chart, desired_type = detect_chart_preferences(question)
|
| 1041 |
+
if wants_chart and isinstance(assistant_message, str):
|
| 1042 |
+
candidate_text = assistant_message
|
| 1043 |
+
raw_lines = candidate_text.split('\n')
|
| 1044 |
+
norm_lines = []
|
| 1045 |
+
for l in raw_lines:
|
| 1046 |
+
s = l.strip().lstrip("•*\t -")
|
| 1047 |
+
if s:
|
| 1048 |
+
norm_lines.append(s)
|
| 1049 |
+
data = []
|
| 1050 |
+
for l in norm_lines:
|
| 1051 |
+
m = re.match(r"^(.+?):\s*([0-9][0-9.,]*)$", l)
|
| 1052 |
+
if m:
|
| 1053 |
+
label = re.sub(r"[*_`]+", "", m.group(1)).strip()
|
| 1054 |
+
try:
|
| 1055 |
+
val = float(m.group(2).replace(',', ''))
|
| 1056 |
+
except Exception:
|
| 1057 |
+
continue
|
| 1058 |
+
data.append({"label": label, "value": val})
|
| 1059 |
+
if len(data) >= 2:
|
| 1060 |
+
chart_fig = generate_chart(
|
| 1061 |
+
data=data,
|
| 1062 |
+
chart_type=desired_type,
|
| 1063 |
+
x="label",
|
| 1064 |
+
y="value",
|
| 1065 |
+
title="Distribución"
|
| 1066 |
+
)
|
| 1067 |
+
|
| 1068 |
logger.info("Response generation complete")
|
| 1069 |
return chat_history, chart_fig
|
| 1070 |
|