bi_agent / app.py
Prerna43's picture
Update app.py
d1bdcf3 verified
Raw
History Blame Contribute Delete
7.93 kB
from __future__ import annotations
import sys
from pathlib import Path
import gradio as gr
import matplotlib.pyplot as plt
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from services.service import BIService
service = BIService()
def ensure_list(value):
if isinstance(value, list):
return [str(v) for v in value if str(v).strip()]
if isinstance(value, str) and value.strip():
return [value.strip()]
return []
def build_schema_info_markdown() -> str:
try:
schema_catalog = service.clickhouse.get_schema_catalog()
except Exception as exc:
return f"## Connected Schema\nUnable to load schema info: `{exc}`"
grouped: dict[str, list[str]] = {}
for column in schema_catalog:
table_name = f"{column.database}.{column.table}"
grouped.setdefault(table_name, []).append(f"`{column.name}`")
preferred_tables = [
"amazon.amazon_reviews",
"amazon.product_dim",
"amazon.daily_review_metrics",
"amazon.product_dim_stage",
]
ordered_tables = [table for table in preferred_tables if table in grouped]
ordered_tables.extend(sorted(table for table in grouped if table not in preferred_tables))
lines = ["## Connected Schema", "**Database:** `amazon`", ""]
for table_name in ordered_tables:
lines.append(f"**{table_name}**")
lines.append(", ".join(grouped[table_name]))
lines.append("")
return "\n".join(lines)
def validate_visualization(dataframe: pd.DataFrame, viz: dict) -> dict:
if dataframe.empty:
return {}
columns = list(dataframe.columns)
chart_type = viz.get("chart_type")
x_axis = viz.get("x_axis")
y_axis = viz.get("y_axis")
if x_axis not in columns:
x_axis = columns[0]
if y_axis not in columns:
y_axis = columns[1] if len(columns) > 1 else None
if chart_type not in ["line", "bar", "scatter", "table"]:
if x_axis and any(token in x_axis.lower() for token in ["date", "time", "day", "month", "year"]):
chart_type = "line"
elif x_axis and dataframe[x_axis].dtype == "object":
chart_type = "bar"
else:
chart_type = "scatter"
return {
"chart_type": chart_type,
"x_axis": x_axis,
"y_axis": y_axis,
"orientation": viz.get("orientation", "vertical"),
"top_n": viz.get("top_n", 12),
"rotate_x_labels": viz.get("rotate_x_labels", False),
"truncate_labels": viz.get("truncate_labels", 28),
"title": viz.get("title", "Chart"),
}
def _truncate_label(value: object, limit: int) -> str:
text = str(value)
if len(text) <= limit:
return text
return text[: max(0, limit - 3)] + "..."
def render_chart_gradio(dataframe: pd.DataFrame, viz: dict):
if dataframe.empty:
return None
viz = validate_visualization(dataframe, viz)
chart_type = viz.get("chart_type")
x_axis = viz.get("x_axis")
y_axis = viz.get("y_axis")
if chart_type == "table" or not x_axis or not y_axis:
return None
working = dataframe.copy()
top_n = viz.get("top_n", 12)
truncate_labels = viz.get("truncate_labels", 28)
orientation = viz.get("orientation", "vertical")
if chart_type == "bar" and y_axis in working.columns and len(working) > top_n:
working = working.sort_values(by=y_axis, ascending=False).head(top_n)
if working[x_axis].dtype == "object":
working[x_axis] = working[x_axis].map(lambda value: _truncate_label(value, truncate_labels))
label_lengths = working[x_axis].astype(str).map(len) if x_axis in working.columns else pd.Series(dtype=int)
auto_horizontal = chart_type == "bar" and (len(working) > 10 or (not label_lengths.empty and label_lengths.max() > 18))
horizontal = orientation == "horizontal" or auto_horizontal
fig_width = 12 if not horizontal else 14
fig_height = 5 if len(working) <= 12 else 7
fig, ax = plt.subplots(figsize=(fig_width, fig_height))
if chart_type == "line":
ax.plot(working[x_axis], working[y_axis], marker="o")
elif chart_type == "bar":
if horizontal:
ax.barh(working[x_axis], working[y_axis], color="#4C78A8")
else:
ax.bar(working[x_axis], working[y_axis], color="#4C78A8")
elif chart_type == "scatter":
ax.scatter(working[x_axis], working[y_axis], alpha=0.8, color="#4C78A8")
ax.set_title(viz.get("title") or f"{chart_type.title()} Chart")
if horizontal and chart_type == "bar":
ax.set_ylabel(x_axis)
ax.set_xlabel(y_axis)
else:
ax.set_xlabel(x_axis)
ax.set_ylabel(y_axis)
if viz.get("rotate_x_labels") and chart_type != "scatter" and not horizontal:
plt.setp(ax.get_xticklabels(), rotation=35, ha="right")
ax.grid(axis="y", linestyle="--", alpha=0.25)
fig.tight_layout()
return fig
def run_query(question: str):
if not question.strip():
return "Please enter a question.", pd.DataFrame(), "", "", None, "", ""
try:
result = service.ask(question.strip())
sql_payload = result.get("sql", {})
analysis = result.get("analysis", {})
visualization = result.get("visualization", {})
reflection = result.get("reflection", {})
result_payload = sql_payload.get("result", {})
dataframe = pd.DataFrame(result_payload.get("rows", []))
metadata_only = bool(sql_payload.get("metadata_only"))
summary = "" if metadata_only else analysis.get("summary", "")
sql_query = sql_payload.get("sql", "")
insights = "" if metadata_only else "\n".join(ensure_list(analysis.get("insights", [])))
followups = "" if metadata_only else "\n".join(ensure_list(analysis.get("follow_ups", [])))
risks = "\n".join(ensure_list(reflection.get("risks", [])))
figure = None if metadata_only else render_chart_gradio(dataframe, visualization)
return summary, dataframe, sql_query, insights, figure, followups, risks
except Exception as exc:
return f"Error: {exc}", pd.DataFrame(), "", "", None, "", ""
with gr.Blocks(title="BI Agent") as demo:
gr.Markdown("# Business Intelligence Agent")
gr.Markdown("Ask database questions, get SQL, results, checks, and charts when the data shape supports them.")
runtime = service.runtime_status()
gr.Markdown(
f"""
**Runtime Status**
- Groq: `{runtime.get('has_api_key', False)}`
- ClickHouse: `{runtime.get('has_database', False)}`
- Model: `{runtime.get('groq_model', 'unknown')}`
"""
)
with gr.Row():
with gr.Column(scale=4):
question_input = gr.Textbox(
label="Ask your question",
placeholder="Which popular but poorly rated products should we investigate?",
lines=3,
)
run_btn = gr.Button("Run Analysis")
summary_output = gr.Textbox(label="Summary")
table_output = gr.Dataframe(label="Result Table")
sql_output = gr.Code(label="Generated SQL", language="sql")
insights_output = gr.Textbox(label="Insights")
visualization_output = gr.Plot(label="Visualization")
followups_output = gr.Textbox(label="Follow-ups")
risks_output = gr.Textbox(label="Risks")
with gr.Column(scale=2, min_width=320):
schema_info = gr.Markdown(build_schema_info_markdown())
run_btn.click(
run_query,
inputs=[question_input],
outputs=[
summary_output,
table_output,
sql_output,
insights_output,
visualization_output,
followups_output,
risks_output,
],
)
if __name__ == "__main__":
demo.launch(share=True)