From f8d817a61389880890fe5a4a291ca46fc3123bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pstr=C4=85g?= Date: Fri, 6 Sep 2024 03:14:50 +0200 Subject: [PATCH 1/2] add example questions + some minor improvements --- src/dbally/gradio/interface.py | 104 +++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/src/dbally/gradio/interface.py b/src/dbally/gradio/interface.py index 6898fd33..90f77414 100644 --- a/src/dbally/gradio/interface.py +++ b/src/dbally/gradio/interface.py @@ -13,18 +13,37 @@ from dbally.views.exceptions import ViewExecutionError -def create_gradio_interface(collection: Collection, *, preview_limit: Optional[int] = None) -> gr.Interface: +def create_gradio_interface( + collection: Collection, + *, + title: str = "db-ally lab", + header: str = "🔍 db-ally lab", + examples: Optional[List[str]] = None, + examples_per_page: int = 4, + preview_limit: Optional[int] = None, +) -> gr.Interface: """ Creates a Gradio interface for interacting with the user collection and similarity stores. Args: collection: The collection to interact with. + title: The title of the gradio interface. + header: The header of the gradio interface. + examples: The example questions to display. + examples_per_page: The number of examples to display per page. preview_limit: The maximum number of preview data records to display. Default is None. Returns: The created Gradio interface. """ - adapter = GradioAdapter(collection=collection, preview_limit=preview_limit) + adapter = GradioAdapter( + collection=collection, + title=title, + header=header, + examples=examples, + examples_per_page=examples_per_page, + preview_limit=preview_limit, + ) return adapter.create_interface() @@ -33,16 +52,33 @@ class GradioAdapter: Gradio adapter for the db-ally lab. """ - def __init__(self, collection: Collection, *, preview_limit: Optional[int] = None) -> None: + def __init__( + self, + collection: Collection, + *, + title: str = "db-ally lab", + header: str = "🔍 db-ally lab", + examples: Optional[List[str]] = None, + examples_per_page: int = 4, + preview_limit: Optional[int] = None, + ) -> None: """ Creates the gradio adapter. Args: collection: The collection to interact with. + title: The title of the gradio interface. + header: The header of the gradio interface. + examples: The example questions to display. + examples_per_page: The number of examples to display per page. preview_limit: The maximum number of preview data records to display. """ self.collection = collection self.preview_limit = preview_limit + self.title = title + self.header = header + self.examples = examples or [] + self.examples_per_page = examples_per_page self.log = self._setup_event_buffer() def _setup_event_buffer(self) -> StringIO: @@ -93,8 +129,8 @@ def _render_view_preview(self, view_name: str) -> Tuple[gr.Dataframe, gr.Label]: view = self.collection.get(view_name) if isinstance(view, BaseStructuredView): - results = view.execute().results - data = self._load_results_into_dataframe(results) + result = view.execute() + data = self._load_results_into_dataframe(result.results) if self.preview_limit is not None: data = data.head(self.preview_limit) @@ -198,8 +234,8 @@ def create_interface(self) -> gr.Interface: views = list(self.collection.list()) selected_view = views[0] if views else None - with gr.Blocks(title="db-ally lab") as demo: - gr.Markdown("# 🔍 db-ally lab") + with gr.Blocks(title=self.title) as demo: + gr.Markdown(f"# {self.header}") with gr.Tab("Collection"): with gr.Row(): @@ -208,34 +244,52 @@ def create_interface(self) -> gr.Interface: label="API Key", placeholder="Enter your API Key", type="password", - interactive=bool(views), + interactive=bool(selected_view), ) model_name = gr.Textbox( label="Model Name", placeholder="Enter your model name", value=self.collection._llm.model_name, # pylint: disable=protected-access - interactive=bool(views), + interactive=bool(selected_view), max_lines=1, ) question = gr.Textbox( label="Question", placeholder="Enter your question", - interactive=bool(views), + interactive=bool(selected_view), max_lines=1, ) natural_language_response_checkbox = gr.Checkbox( label="Use Natural Language Responder", - interactive=bool(views), - ) - ask_button = gr.Button( - value="Ask", - variant="primary", - interactive=bool(views), + interactive=bool(selected_view), ) - clear_button = gr.ClearButton( - value="Reset", - components=[question], - interactive=bool(views), + + if self.examples and selected_view: + gr.Examples( + label="Example questions", + examples=self.examples, + inputs=question, + examples_per_page=self.examples_per_page, + ) + + with gr.Row(): + clear_button = gr.ClearButton( + value="Reset", + components=[question], + interactive=bool(selected_view), + ) + ask_button = gr.Button( + value="Ask", + variant="primary", + interactive=bool(selected_view), + ) + + gr.HTML( + """ +
+ POWERED BY DB-ALLY +
+ """ ) with gr.Column(): @@ -243,7 +297,7 @@ def create_interface(self) -> gr.Interface: label="View Preview", choices=views, value=selected_view, - interactive=bool(views), + interactive=bool(selected_view), ) if selected_view: view_preview, view_preview_label = self._render_view_preview(selected_view) @@ -266,7 +320,7 @@ def create_interface(self) -> gr.Interface: visible=False, ) iql_aggregation_result = gr.Code( - label="IQL Aggreagation Query", + label="IQL Aggregation Query", lines=1, language="python", visible=False, @@ -290,18 +344,18 @@ def create_interface(self) -> gr.Interface: ) with gr.Tab("Logs"): - log_console = gr.Code(label="Logs", language="shell") + log_console = gr.Code(language="shell", show_label=False) with gr.Tab("Help"): gr.Markdown( """ - ## How to use this app: + ## How to use this app 1. Enter your API Key for the LLM you want to use in the provided field. 2. Choose the [model](https://docs.litellm.ai/docs/providers) you want to use. 3. Type your question in the textbox. 4. Click on `Ask`. The retrieval results will appear in the `Results` tab. - ## Learn more: + ## Learn more Want to learn more about db-ally? Check out our resources: - [Website](https://deepsense.ai/db-ally) - [GitHub](https://github.com/deepsense-ai/db-ally) From da3238f8c3b923bd02775ba0e2d178d31956219a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pstr=C4=85g?= Date: Tue, 10 Sep 2024 10:46:24 +0200 Subject: [PATCH 2/2] add iql preview --- setup.cfg | 4 +- src/dbally/gradio/interface.py | 107 +++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/setup.cfg b/setup.cfg index b6552e0e..11d8dddc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,8 +54,8 @@ langsmith= elasticsearch = elasticsearch~=8.13.1 gradio = - gradio~=4.31.5 - gradio_client~=0.16.4 + gradio~=4.42.0 + gradio_client~=1.3.0 local = accelerate~=0.31.0 torch~=2.2.1 diff --git a/src/dbally/gradio/interface.py b/src/dbally/gradio/interface.py index 90f77414..0ea2907b 100644 --- a/src/dbally/gradio/interface.py +++ b/src/dbally/gradio/interface.py @@ -11,6 +11,7 @@ from dbally.collection import Collection from dbally.collection.exceptions import NoViewFoundError from dbally.views.exceptions import ViewExecutionError +from dbally.views.exposed_functions import ExposedFunction, MethodParamWithTyping def create_gradio_interface( @@ -115,27 +116,6 @@ def _render_dataframe(self, df: pd.DataFrame, message: Optional[str] = None) -> gr.Label(value=message, visible=df.empty, show_label=False), ) - def _render_view_preview(self, view_name: str) -> Tuple[gr.Dataframe, gr.Label]: - """ - Loads preview data for a selected view name. - - Args: - view_name: The name of the selected view to load preview data for. - - Returns: - A tuple containing the preview dataframe, load status text, and four None values to clean gradio fields. - """ - data = pd.DataFrame() - view = self.collection.get(view_name) - - if isinstance(view, BaseStructuredView): - result = view.execute() - data = self._load_results_into_dataframe(result.results) - if self.preview_limit is not None: - data = data.head(self.preview_limit) - - return self._render_dataframe(data, "Preview not available") - async def _ask_collection( self, question: str, @@ -166,13 +146,15 @@ async def _ask_collection( question=question, return_natural_response=return_natural_response, ) - except (NoViewFoundError, ViewExecutionError): + except (NoViewFoundError, ViewExecutionError) as e: + view_name = e.view_name sql = "" iql_filters = "" iql_aggregation = "" retrieved_rows = pd.DataFrame() textual_response = "" else: + view_name = result.view_name sql = result.context.get("sql", "") iql_filters = result.context.get("iql", {}).get("filters", "") iql_aggregation = result.context.get("iql", {}).get("aggregation", "") @@ -185,10 +167,11 @@ async def _ask_collection( log_content = self.log.read() return ( + gr.Textbox(value=textual_response, visible=return_natural_response), + gr.Textbox(value=view_name, visible=True), gr.Code(value=iql_filters, visible=bool(iql_filters)), gr.Code(value=iql_aggregation, visible=bool(iql_aggregation)), gr.Code(value=sql, visible=bool(sql)), - gr.Textbox(value=textual_response, visible=return_natural_response), retrieved_rows, empty_retrieved_rows_warning, log_content, @@ -224,6 +207,36 @@ def _load_results_into_dataframe(results: List[Dict[str, Any]]) -> pd.DataFrame: """ return pd.DataFrame(json.loads(json.dumps(results, default=str))) + def _render_param(self, param: MethodParamWithTyping) -> str: + if param.similarity_index: + return f"{param.name}: {str(param.type).replace('typing.', '')}" + return str(param) + + def _render_tab_data(self, data: pd.DataFrame) -> None: + with gr.Tab("Data"): + if data.empty: + gr.Label("No data available", show_label=False) + else: + gr.Dataframe(value=data, height=320) + + def _render_tab_iql(self, methods: List[ExposedFunction], label: str) -> None: + with gr.Tab(f"IQL {label}"): + if methods: + gr.Dataframe( + value=[ + [ + f"{method.name}({', '.join(self._render_param(param) for param in method.parameters)})", + method.description, + ] + for method in methods + ], + headers=["signature", "description"], + interactive=False, + height=325, + ) + else: + gr.Label(f"No {label.lower()} available", show_label=False) + def create_interface(self) -> gr.Interface: """ Creates a Gradio interface for interacting with the collection. @@ -299,18 +312,41 @@ def create_interface(self) -> gr.Interface: value=selected_view, interactive=bool(selected_view), ) - if selected_view: - view_preview, view_preview_label = self._render_view_preview(selected_view) - else: - view_preview, view_preview_label = self._render_dataframe( - pd.DataFrame(), "No view selected" - ) + + @gr.render(inputs=view_dropdown, triggers=[demo.load, view_dropdown.change]) + def render_view_preview(view_name: Optional[str]) -> None: + if view_name is None: + gr.Label("No views", show_label=False) + return + + view = self.collection.get(view_name) + + if not isinstance(view, BaseStructuredView): + gr.Label(value="Preview not available", show_label=False) + return + + result = view.execute() + data = self._load_results_into_dataframe(result.results) + if self.preview_limit is not None: + data = data.head(self.preview_limit) + + filters = view.list_filters() + aggregations = view.list_aggregations() + + self._render_tab_data(data) + self._render_tab_iql(filters, "Filters") + self._render_tab_iql(aggregations, "Aggregations") with gr.Tab("Results"): natural_language_response = gr.Textbox( label="Natural Language Response", visible=False, ) + selected_view_name = gr.Textbox( + label="Selected View", + visible=False, + max_lines=1, + ) with gr.Row(): iql_fitlers_result = gr.Code( @@ -367,6 +403,7 @@ def create_interface(self) -> gr.Interface: [ natural_language_response_checkbox, natural_language_response, + selected_view_name, iql_fitlers_result, iql_aggregation_result, sql_result, @@ -379,6 +416,7 @@ def create_interface(self) -> gr.Interface: fn=self._clear_results, outputs=[ natural_language_response, + selected_view_name, iql_fitlers_result, iql_aggregation_result, sql_result, @@ -386,14 +424,6 @@ def create_interface(self) -> gr.Interface: retrieved_rows_label, ], ) - view_dropdown.change( - fn=self._render_view_preview, - inputs=view_dropdown, - outputs=[ - view_preview, - view_preview_label, - ], - ) ask_button.click( fn=self._ask_collection, inputs=[ @@ -403,10 +433,11 @@ def create_interface(self) -> gr.Interface: natural_language_response_checkbox, ], outputs=[ + natural_language_response, + selected_view_name, iql_fitlers_result, iql_aggregation_result, sql_result, - natural_language_response, retrieved_rows, retrieved_rows_label, log_console,