forked from Marker-Inc-Korea/AutoRAG
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/dashboard (Marker-Inc-Korea#432)
* 🔧 chore: Add autorag dashboard command and dependencies for panel, holoviews, hvplot * ✨ feat: Add initial dashboard setup with DuckDB data retrieval and plotting. * add duckdb to requirements.txt * add find_trial_dir at util.py * add find_node_summary_files util function * ✨ feat: Update dashboard layout and styling, add new charts and widgets * add dict_to_markdown util function * show summary of trial & work dynamic trial directory on cli.py * add dict_to_markdown_table feature for metric display * make dashboard with each nodes boxplot and stripplot * add config YAML file tab * delete unused annotation and requirements * remove unused value and test it on the cli command * edit documentation and README.md for dashboard * try-except when the result summary file is something wrong in the node_view --------- Co-authored-by: Bwook (Byoungwook) Kim <bwook00@naver.com> Co-authored-by: jeffrey <vkefhdl1@gmail.com> Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
- Loading branch information
1 parent
cbae901
commit 46b6031
Showing
8 changed files
with
329 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import ast | ||
import logging | ||
import os | ||
from typing import Dict, List | ||
|
||
import matplotlib.pyplot as plt | ||
import pandas as pd | ||
import panel as pn | ||
import seaborn as sns | ||
import yaml | ||
|
||
from autorag.utils.util import dict_to_markdown, dict_to_markdown_table | ||
|
||
pn.extension('terminal', 'tabulator', 'mathjax', 'ipywidgets', | ||
console_output='disable', sizing_mode="stretch_width") | ||
logger = logging.getLogger("AutoRAG") | ||
|
||
|
||
def find_node_dir(trial_dir: str) -> List[str]: | ||
trial_summary_df = pd.read_csv(os.path.join(trial_dir, 'summary.csv')) | ||
result_paths = [] | ||
for idx, row in trial_summary_df.iterrows(): | ||
node_line_name = row['node_line_name'] | ||
node_type = row['node_type'] | ||
result_paths.append(os.path.join(trial_dir, node_line_name, node_type)) | ||
return result_paths | ||
|
||
|
||
def get_metric_values(node_summary_df: pd.DataFrame) -> Dict: | ||
non_metric_column_names = ['filename', 'module_name', 'module_params', 'execution_time', 'average_output_token', | ||
'is_best'] | ||
best_row = node_summary_df.loc[node_summary_df['is_best']].drop(columns=non_metric_column_names, errors='ignore') | ||
assert len(best_row) == 1, "The best module must be only one." | ||
return best_row.iloc[0].to_dict() | ||
|
||
|
||
def make_trial_summary_md(trial_dir): | ||
markdown_text = f"""# Trial Result Summary | ||
- Trial Directory : {trial_dir} | ||
""" | ||
node_dirs = find_node_dir(trial_dir) | ||
for node_dir in node_dirs: | ||
node_summary_filepath = os.path.join(node_dir, 'summary.csv') | ||
node_type = os.path.basename(node_dir) | ||
node_summary_df = pd.read_csv(node_summary_filepath) | ||
best_row = node_summary_df.loc[node_summary_df['is_best']].iloc[0] | ||
metric_dict = get_metric_values(node_summary_df) | ||
markdown_text += f"""--- | ||
## {node_type} best module | ||
### Module Name | ||
{best_row['module_name']} | ||
### Module Params | ||
{dict_to_markdown(ast.literal_eval(best_row['module_params']), level=3)} | ||
### Metric Values | ||
{dict_to_markdown_table(metric_dict, key_column_name='metric_name', value_column_name='metric_value')} | ||
""" | ||
|
||
return markdown_text | ||
|
||
|
||
def node_view(node_dir: str): | ||
non_metric_column_names = ['filename', 'module_name', 'module_params', 'execution_time', 'average_output_token', | ||
'is_best'] | ||
summary_df = pd.read_csv(os.path.join(node_dir, 'summary.csv')) | ||
df_widget = pn.widgets.Tabulator(summary_df, name='Summary DataFrame') | ||
# TODO: add on click listener for pop-up of each file detail. | ||
# https://panel.holoviz.org/reference/widgets/Tabulator.html | ||
|
||
try: | ||
fig, ax = plt.subplots(figsize=(10, 5)) | ||
metric_df = summary_df.drop(columns=non_metric_column_names, errors='ignore') | ||
sns.stripplot(data=metric_df, ax=ax) | ||
strip_plot_pane = pn.pane.Matplotlib(fig, tight=True) | ||
|
||
fig2, ax2 = plt.subplots(figsize=(10, 5)) | ||
sns.boxplot(data=metric_df, ax=ax2) | ||
box_plot_pane = pn.pane.Matplotlib(fig2, tight=True) | ||
plot_pane = pn.Row(strip_plot_pane, box_plot_pane) | ||
|
||
layout = pn.Column("## Summary distribution plot", plot_pane, "## Summary DataFrame", df_widget) | ||
except Exception as e: | ||
logger.error(f'Skipping make boxplot and stripplot with error {e}') | ||
layout = pn.Column("## Summary DataFrame", df_widget) | ||
layout.servable() | ||
return layout | ||
|
||
|
||
CSS = """ | ||
div.card-margin:nth-child(1) { | ||
max-height: 300px; | ||
} | ||
div.card-margin:nth-child(2) { | ||
max-height: 400px; | ||
} | ||
""" | ||
|
||
|
||
def yaml_to_markdown(yaml_filepath): | ||
markdown_content = "" | ||
with open(yaml_filepath, 'r', encoding='utf-8') as file: | ||
try: | ||
content = yaml.safe_load(file) | ||
markdown_content += f"## {os.path.basename(yaml_filepath)}\n```yaml\n{yaml.dump(content, allow_unicode=True)}\n```\n\n" | ||
except yaml.YAMLError as exc: | ||
print(f"Error in {yaml_filepath}: {exc}") | ||
return markdown_content | ||
|
||
|
||
def run(trial_dir: str): | ||
trial_summary_md = make_trial_summary_md(trial_dir=trial_dir) | ||
trial_summary_tab = pn.pane.Markdown(trial_summary_md, sizing_mode='stretch_width') | ||
|
||
node_views = [(str(os.path.basename(node_dir)), node_view(node_dir)) for node_dir in find_node_dir(trial_dir)] | ||
|
||
yaml_file_markdown = yaml_to_markdown(os.path.join(trial_dir, "config.yaml")) | ||
|
||
yaml_file = pn.pane.Markdown(yaml_file_markdown, sizing_mode='stretch_width') | ||
|
||
tabs = pn.Tabs(('Summary', trial_summary_tab), *node_views, ('Used YAML file', yaml_file), dynamic=True) | ||
|
||
template = pn.template.FastListTemplate(site="AutoRAG", title="Dashboard", | ||
main=[tabs], raw_css=[CSS]).servable() | ||
template.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import os | ||
import pathlib | ||
|
||
import pandas as pd | ||
import pytest | ||
|
||
from autorag.dashboard import get_metric_values, make_trial_summary_md | ||
|
||
root_dir = pathlib.PurePath(os.path.dirname(os.path.realpath(__file__))).parent | ||
sample_project_dir = os.path.join(root_dir, 'resources', 'result_project') | ||
sample_trial_dir = os.path.join(sample_project_dir, '0') | ||
|
||
|
||
@pytest.fixture | ||
def retrieval_summary_df(): | ||
return pd.read_csv(os.path.join(sample_trial_dir, 'retrieve_node_line', 'retrieval', 'summary.csv')) | ||
|
||
|
||
def test_get_metric_values(retrieval_summary_df): | ||
result_dict = get_metric_values(retrieval_summary_df) | ||
assert len(result_dict.keys()) == 3 | ||
assert set(list(result_dict.keys())) == {'retrieval_f1', 'retrieval_recall', 'retrieval_precision'} | ||
assert result_dict['retrieval_recall'] == 1.0 | ||
assert result_dict['retrieval_precision'] == 0.1 | ||
|
||
|
||
def test_make_trial_summary_md(): | ||
md_text = make_trial_summary_md(sample_trial_dir) | ||
assert bool(md_text) | ||
|
||
# def test_dashboard_run(): | ||
# dashboard.run(sample_trial_dir) |
Oops, something went wrong.