Skip to content

Commit

Permalink
Model card creation (#181)
Browse files Browse the repository at this point in the history
* created popup component

* adding popup component

* added rendering code

* updating popup component

* Added close and download button

* Deleted cmf file

* updating model_card

* Wrote an rest api for model card and axios interface

* model card api update

* json filename update

* readded popup

* adding more changes required for model card creation

* added some json related changes to do some testing on popup side

* made some changes

* changes related to popup content

* updated code for json filename

* made some changes to display data properly in popup

* Align items inside popup component to left

* Made some changes in popup data fromat

* Added table structure for artifact data

* added style to table

* adding some code for popup

* made changes in table code

* keeping old_index.jsx file

* converted table to model card

* Added the code to refine popup model card

* Made changes regarding modelcard structure

* mistakenly commited wrong user name

* addressing review comments

* Addressed review comments and commented on get_model_data function

* Updating supported versions from <=3.10 to <3.11. So that, all the version of 3.10 are included

* Made proper alignment of close and download button

* Addressing review comments

---------

Co-authored-by: first second <first.second@corp.com>
Co-authored-by: Abhinav Chobey <chobey@abhinav-cmf-hpe.labs.hpecorp.net>
Co-authored-by: AyeshaSanadi <ayesha.sanadi@agiliad.com>
  • Loading branch information
4 people authored Jul 17, 2024
1 parent e6c8858 commit 172a7a1
Show file tree
Hide file tree
Showing 9 changed files with 489 additions and 12 deletions.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ models and performance metrics) recorded by the framework are versioned and iden
## Installation

#### 1. Pre-Requisites:
* 3.9>= Python <=3.10
* 3.9>= Python <3.11
* Git latest version

#### 2. Set up Python Virtual Environment:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ authors = [
]
description = "Track metadata for AI pipeline"
readme = "README.md"
requires-python = ">=3.9,<=3.10"
requires-python = ">=3.9,<3.11"
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: POSIX :: Linux",
Expand Down
72 changes: 72 additions & 0 deletions server/app/get_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,78 @@
from server.app.query_visualization_execution import query_visualization_execution
from fastapi.responses import FileResponse


async def get_model_data(mlmdfilepath, modelId):
'''
This function retrieves the necessary model data required for generating a model card.
Arguments:
mlmdfilepath (str): The file path to the metadata.
modelId (int): The ID of the model for which data is required.
Returns:
This function returns a tuple of DataFrames containing the following:
model_data_df (DataFrame): Metadata related to the model itself.
model_exe_df (DataFrame): Metadata of the executions in which the specified modelId was an input or output.
model_input_df (DataFrame): Metadata of input artifacts that led to the creation of the model.
model_output_df (DataFrame): Metadata of artifacts that used the model as an input.
The returned DataFrames provide comprehensive metadata for the specified model, aiding in the creation of detailed and accurate model cards.
'''
query = cmfquery.CmfQuery(mlmdfilepath)
pd.set_option('display.max_columns', None)
model_data_df = pd.DataFrame()
model_exe_df = pd.DataFrame()
model_input_df = pd.DataFrame()
model_output_df = pd.DataFrame()

# get name from id
modelName = ""
model_data_df = query.get_all_artifacts_by_ids_list([modelId])
# if above dataframe is not empty, we have the dataframe for given modelId with full model related details
if model_data_df.empty:
return model_data_df, model_exe_df, model_input_df, model_output_df
# However following check is done, in case, variable 'modelId' is not an ID for model artifact
modelType = model_data_df['type'].tolist()[0]
if not modelType == "Model":
# making model_data_df empty
model_data_df = pd.DataFrame()
return model_data_df, model_exe_df, model_input_df, model_output_df


# extracting modelName
modelName = model_data_df['name'].tolist()[0]

# model's executions data with props and custom props
exe_df = query.get_all_executions_for_artifact(modelName)
exe_ids = []
if not exe_df.empty:
exe_df.drop(columns=['execution_type_name', 'execution_name'], inplace=True)
exe_ids = exe_df['execution_id'].tolist()


if not exe_ids:
return model_data_df, model_exe_df, model_input_df, model_output_df
model_exe_df = query.get_all_executions_by_ids_list(exe_ids)
model_exe_df.drop(columns=['Python_Env', 'Git_Start_Commit', 'Git_End_Commit'], inplace=True)

in_art_ids = []
# input artifacts
# it is usually not a good practice to use functions starting with _ outside of the file they are defined .. should i change??
in_art_ids.extend(query._get_input_artifacts(exe_ids))
if modelId in in_art_ids:
in_art_ids.remove(modelId)
model_input_df = query.get_all_artifacts_by_ids_list(in_art_ids)

out_art_ids = []
# output artifacts
out_art_ids.extend(query._get_output_artifacts(exe_ids))
if modelId in out_art_ids:
out_art_ids.remove(modelId)
model_output_df = query.get_all_artifacts_by_ids_list(out_art_ids)

return model_data_df, model_exe_df, model_input_df, model_output_df

async def get_executions_by_ids(mlmdfilepath, pipeline_name, exe_ids):
'''
Args:
Expand Down
40 changes: 35 additions & 5 deletions server/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
import pandas as pd
from typing import List, Dict, Any

from cmflib import cmfquery, cmf_merger
from server.app.get_data import (
Expand All @@ -15,7 +16,8 @@
get_artifact_types,
get_all_artifact_ids,
get_all_exe_ids,
get_executions_by_ids
get_executions_by_ids,
get_model_data
)
from server.app.query_visualization import query_visualization
from server.app.query_exec_lineage import query_exec_lineage
Expand Down Expand Up @@ -146,8 +148,7 @@ async def display_exec(
@app.get("/display_artifact_lineage/{pipeline_name}")
async def display_artifact_lineage(request: Request, pipeline_name: str):
'''
This api's returns dictionary of nodes and links for given
pipeline.
This api returns dictionary of nodes and links for given pipeline.
response = {
nodes: [{id:"",name:""}],
links: [{source:1,target:4},{}],
Expand All @@ -170,8 +171,7 @@ async def display_artifact_lineage(request: Request, pipeline_name: str):
@app.get("/get_execution_types/{pipeline_name}")
async def get_execution_types(request: Request, pipeline_name: str):
'''
This api's returns
list of execution types.
This api's returns list of execution types.
'''
# checks if mlmd file exists on server
Expand Down Expand Up @@ -304,6 +304,34 @@ async def upload_file(request:Request, pipeline_name: str = Query(..., descripti
except Exception as e:
return {"error": f"Failed to up load file: {e}"}

@app.get("/model-card")
async def model_card(request:Request, modelId: int, response_model=List[Dict[str, Any]]):
json_payload_1 = ""
json_payload_2 = ""
json_payload_3 = ""
json_payload_4 = ""
model_data_df = pd.DataFrame()
model_exe_df = pd.DataFrame()
model_input_art_df = pd.DataFrame()
model_output_art_df = pd.DataFrame()
df = pd.DataFrame()
# checks if mlmd file exists on server
if os.path.exists(server_store_path):
model_data_df, model_exe_df, model_input_art_df, model_output_art_df = await get_model_data(server_store_path, modelId)
if not model_data_df.empty:
result_1 = model_data_df.to_json(orient="records")
json_payload_1 = json.loads(result_1)
if not model_exe_df.empty:
result_2 = model_exe_df.to_json(orient="records")
json_payload_2 = json.loads(result_2)
if not model_input_art_df.empty:
result_3 = model_input_art_df.to_json(orient="records")
json_payload_3 = json.loads(result_3)
if not model_output_art_df.empty:
result_4 = model_output_art_df.to_json(orient="records")
json_payload_4 = json.loads(result_4)
return [json_payload_1, json_payload_2, json_payload_3, json_payload_4]

async def update_global_art_dict():
global dict_of_art_ids
output_dict = await get_all_artifact_ids(server_store_path)
Expand All @@ -316,3 +344,5 @@ async def update_global_exe_dict():
output_dict = await get_all_exe_ids(server_store_path)
dict_of_exe_ids = output_dict
return


13 changes: 13 additions & 0 deletions ui/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ class FastAPIClient {
console.error(error);
}
}

async getModelCard(modelId) {
return this.apiClient.get(`/model-card`, {
params: {
modelId: modelId,
},
})
.then(({data}) => {
return data;
});
}

}


export default FastAPIClient;
41 changes: 37 additions & 4 deletions ui/src/components/ArtifactTable/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
// ArtifactTable.jsx
import React, { useState, useEffect } from "react";
import "./index.css";
const ArtifactTable = ({ artifacts, onSort, onFilter }) => {
import Popup from "../../components/Popup";
import FastAPIClient from "../../client";
import config from "../../config";

const client = new FastAPIClient(config);

const ArtifactTable = ({ artifacts, ArtifactType, onSort, onFilter }) => {

// Default sorting order
const [sortOrder, setSortOrder] = useState("Context_Type");
Expand All @@ -27,9 +33,12 @@ const ArtifactTable = ({ artifacts, onSort, onFilter }) => {
const [filterValue, setFilterValue] = useState("");

const [expandedRow, setExpandedRow] = useState(null);
const [showPopup, setShowPopup] = useState(false);
const [popupData, setPopupData] = useState('');

const consistentColumns = [];


useEffect(() => {
// Set initial sorting order when component mounts
setSortOrder("asc");
Expand All @@ -55,6 +64,19 @@ const ArtifactTable = ({ artifacts, onSort, onFilter }) => {
}
};

const handleLinkClick = (model_id) => {
client.getModelCard(model_id).then((data) => {
console.log(data);
setPopupData(data);
setShowPopup(true);
});
};

const handleClosePopup = () => {
setShowPopup(false);
};


return (
<div className="container flex flex-col mx-auto p-6 mr-4">
<div className="flex flex-col items-end m-1">
Expand Down Expand Up @@ -83,7 +105,13 @@ const ArtifactTable = ({ artifacts, onSort, onFilter }) => {
name {sortOrder === "asc" && <span className="arrow">&#8593;</span>}
{sortOrder === "desc" && <span className="arrow">&#8595;</span>}
</th>
<th scope="col" className="exe_uuid px-6 py-3">
{ArtifactType === "Model" && (
<th scope="col" className="model_card px-6 py-3">
Model_Card
</th>
)}

<th scope="col" className="exe_uuid px-6 py-3">
execution_type_name
</th>
<th scope="col" className="url px-6 py-3">
Expand All @@ -105,14 +133,19 @@ const ArtifactTable = ({ artifacts, onSort, onFilter }) => {
<React.Fragment key={index}>
<tr
key={index}
onClick={() => toggleRow(index)}
className="text-sm font-medium text-gray-800"
>
<td className="px-6 py-4">
<td className="px-6 py-4" onClick={() => toggleRow(index)}>
{expandedRow === index ? "-" : "+"}
</td>
<td className="px-6 py-4">{data.id}</td>
<td className="px-6 py-4">{data.name}</td>
{ArtifactType === "Model" && (
<td className="px-6 py-4">
<a href="#" onClick={(e) => { e.preventDefault(); handleLinkClick(data.id); }}>Open Model Card</a>
<Popup show={showPopup} model_data={popupData} onClose={handleClosePopup} />
</td>
)}
<td className="px-6 py-4">{data.execution_type_name}</td>
<td className="px-6 py-4">{data.url}</td>
<td className="px-6 py-4">{data.uri}</td>
Expand Down
Loading

0 comments on commit 172a7a1

Please sign in to comment.