feat: add specInsight func / language params in data visualization tool
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
SYSTEM_PROMPT = (
|
||||
"You are an AI agent designed to data analysis / visualization task. You have various tools at your disposal that you can call upon to efficiently complete complex requests."
|
||||
"The workspace directory is: {directory}"
|
||||
"The workspace directory is: {directory}; Read / write file in workspace"
|
||||
)
|
||||
|
||||
NEXT_STEP_PROMPT = """
|
||||
Based on user needs, break down the problem and use different tools step by step to solve it. Each step select the most appropriate tool proactively(ONLY ONE). After using each tool, clearly explain the execution results and suggest the next steps.
|
||||
"""
|
||||
NEXT_STEP_PROMPT = """Based on user needs, break down the problem and use different tools step by step to solve it. Each step select the most appropriate tool proactively (ONLY ONE). After using each tool, clearly explain the execution results and suggest the next steps."""
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
from app.tool.python_execute import PythonExecute
|
||||
from app.tool.chart_visualization.normal_python_execute import NormalPythonExecute
|
||||
|
||||
|
||||
class VisualizationPrepare(PythonExecute):
|
||||
class VisualizationPrepare(NormalPythonExecute):
|
||||
"""A tool for Chart Generation Preparation"""
|
||||
|
||||
name: str = "visualization_preparation"
|
||||
description: str = (
|
||||
"Using Python code to Generates metadata of data_visualization tool. Outputs: 1) Cleaned CSV data files 2) JSON info with csv path and visualization description."
|
||||
"Using Python code to generates metadata of data_visualization tool. Outputs: 1) JSON Information. 2) Cleaned CSV data files (Optional)."
|
||||
)
|
||||
parameters: dict = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code_type": {
|
||||
"description": "code type, visualization: csv -> chart; insight: choose insight into chart",
|
||||
"type": "string",
|
||||
"default": "visualization",
|
||||
"enum": ["visualization", "insight"],
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": """Python code template EXCLUSIVELY for visualization prepare. Must Contains:
|
||||
1. Data loading logic (handle dataframe/dict/file/url/json/web crawler)
|
||||
"description": """Python code for data_visualization prepare.
|
||||
## Visualization Type
|
||||
1. Data loading logic
|
||||
2. Csv Data and chart description generate
|
||||
2.1 Csv data (The data you want to visulazation, cleaning / transform from origin data, saved in .csv)
|
||||
2.2 Chart description of csv data (The chart title or description should be concise and clear. Examples: 'Product sales distribution', 'Monthly revenue trend'.)
|
||||
3. Save information in json file.( format: {"csvFilePath": string, "chartTitle": string}[])
|
||||
4. Json file saving with path print: print(json_path)
|
||||
## Insight Type
|
||||
1. Select the insights from the data_visualization results that you want to add to the chart.
|
||||
2. Save information in json file.( format: {"chartPath": string, "insights_id": number[]}[])
|
||||
# Note
|
||||
1. You can generate one or multiple csv data with different visualization needs.
|
||||
2. Make each chart data esay, clean and different.
|
||||
3. save/read in utf-8
|
||||
3. Json file saving in utf-8 with path print: print(json_path)
|
||||
""",
|
||||
},
|
||||
},
|
||||
"required": ["code"],
|
||||
"required": ["code", "code_type"],
|
||||
}
|
||||
|
||||
@@ -14,7 +14,13 @@ from app.config import config
|
||||
class ChartVisualization(BaseTool):
|
||||
name: str = "data_visualization"
|
||||
description: str = (
|
||||
"""Visualize statistical chart with JSON info from visualization_preparation tool. Outputs: 1) Charts (png/html) 2) Charts Insights (.md)(Optional)."""
|
||||
"""Visualize statistical chart or Add insights in chart with JSON info from visualization_preparation tool.
|
||||
For each chart, you can do steps as follows:
|
||||
1. Visualize statistical chart
|
||||
2. Choose insights into chart based on step 1
|
||||
Outputs:
|
||||
1. Charts (png/html)
|
||||
2. Charts Insights (.md)(Optional)."""
|
||||
)
|
||||
parameters: dict = {
|
||||
"type": "object",
|
||||
@@ -29,6 +35,18 @@ class ChartVisualization(BaseTool):
|
||||
"default": "html",
|
||||
"enum": ["png", "html"],
|
||||
},
|
||||
"tool_type": {
|
||||
"description": "visualize chart or add insights",
|
||||
"type": "string",
|
||||
"default": "visualization",
|
||||
"enum": ["visualization", "insight"],
|
||||
},
|
||||
"language": {
|
||||
"description": "english(en) / chinese(zh)",
|
||||
"type": "string",
|
||||
"default": "en",
|
||||
"enum": ["zh", "en"],
|
||||
},
|
||||
},
|
||||
"required": ["code"],
|
||||
}
|
||||
@@ -41,19 +59,26 @@ class ChartVisualization(BaseTool):
|
||||
self.llm = LLM(config_name=self.name.lower())
|
||||
return self
|
||||
|
||||
def get_csv_path(self, json_info: list[dict[str, str]]) -> list[str]:
|
||||
def get_file_path(
|
||||
self,
|
||||
json_info: list[dict[str, str]],
|
||||
path_str: str,
|
||||
directory: str = None,
|
||||
) -> list[str]:
|
||||
res = []
|
||||
for item in json_info:
|
||||
if os.path.exists(item["csvFilePath"]):
|
||||
res.append(item["csvFilePath"])
|
||||
if os.path.exists(item[path_str]):
|
||||
res.append(item[path_str])
|
||||
elif os.path.exists(
|
||||
os.path.join(f"{config.workspace_root}", item["csvFilePath"])
|
||||
os.path.join(f"{directory or config.workspace_root}", item[path_str])
|
||||
):
|
||||
res.append(
|
||||
os.path.join(f"{config.workspace_root}", item["csvFilePath"])
|
||||
os.path.join(
|
||||
f"{directory or config.workspace_root}", item[path_str]
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise Exception(f"No such file or directory: {item["csvFilePath"]}")
|
||||
raise Exception(f"No such file or directory: {item[path_str]}")
|
||||
return res
|
||||
|
||||
def success_output_template(self, result: list[dict[str, str]]) -> str:
|
||||
@@ -62,65 +87,125 @@ class ChartVisualization(BaseTool):
|
||||
return "Is EMPTY!"
|
||||
for item in result:
|
||||
content += f"""## {item["title"]}\nChart saved in: {item["chart_path"]}"""
|
||||
if "insight_path" in item and item["insight_path"]:
|
||||
content += f"""\nChart insights saved in {item["insight_path"]}\n"""
|
||||
if "insight_path" in item and item["insight_path"] and "insight_md" in item:
|
||||
content += "\n" + item["insight_md"]
|
||||
else:
|
||||
content += "\n"
|
||||
return f"Chart Generated Successful! Detail is below:\n{content}"
|
||||
return f"Chart Generated Successful!\n{content}"
|
||||
|
||||
async def execute(self, json_path: str, output_type: str) -> str:
|
||||
logger.info(f"📈 Chart Generation with json path: {json_path} ")
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as file:
|
||||
json_info = json.load(file)
|
||||
data_list = []
|
||||
csv_file_path = self.get_csv_path(json_info)
|
||||
for index, item in enumerate(json_info):
|
||||
df = pd.read_csv(csv_file_path[index], encoding="utf-8")
|
||||
df = df.astype(object)
|
||||
df = df.where(pd.notnull(df), None)
|
||||
data_dict_list = df.to_json(orient="records", force_ascii=False)
|
||||
async def data_visualization(
|
||||
self, json_info: list[dict[str, str]], output_type: str, language: str
|
||||
) -> str:
|
||||
data_list = []
|
||||
csv_file_path = self.get_file_path(json_info, "csvFilePath")
|
||||
for index, item in enumerate(json_info):
|
||||
df = pd.read_csv(csv_file_path[index], encoding="utf-8")
|
||||
df = df.astype(object)
|
||||
df = df.where(pd.notnull(df), None)
|
||||
data_dict_list = df.to_json(orient="records", force_ascii=False)
|
||||
|
||||
data_list.append(
|
||||
data_list.append(
|
||||
{
|
||||
"file_name": os.path.basename(csv_file_path[index]).replace(
|
||||
".csv", ""
|
||||
),
|
||||
"dict_data": data_dict_list,
|
||||
"chartTitle": item["chartTitle"],
|
||||
}
|
||||
)
|
||||
tasks = [
|
||||
self.invoke_vmind(
|
||||
dict_data=item["dict_data"],
|
||||
chart_description=item["chartTitle"],
|
||||
file_name=item["file_name"],
|
||||
output_type=output_type,
|
||||
task_type="visualization",
|
||||
language=language,
|
||||
)
|
||||
for item in data_list
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
error_list = []
|
||||
success_list = []
|
||||
for index, result in enumerate(results):
|
||||
csv_path = csv_file_path[index]
|
||||
if "error" in result and "chart_path" not in result:
|
||||
error_list.append(f"Error in {csv_path}: {result["error"]}")
|
||||
else:
|
||||
success_list.append(
|
||||
{
|
||||
"file_name": os.path.basename(csv_file_path[index]).replace(
|
||||
".csv", ""
|
||||
),
|
||||
"dict_data": data_dict_list,
|
||||
"chartTitle": item["chartTitle"],
|
||||
**result,
|
||||
"title": json_info[index]["chartTitle"],
|
||||
}
|
||||
)
|
||||
tasks = [
|
||||
self.invoke_vmind(
|
||||
item["dict_data"],
|
||||
item["chartTitle"],
|
||||
item["file_name"],
|
||||
output_type,
|
||||
)
|
||||
for item in data_list
|
||||
]
|
||||
if len(error_list) > 0:
|
||||
return {
|
||||
"observation": f"# Error chart generated{'\n'.join(error_list)}\n{self.success_output_template(success_list)}",
|
||||
"success": False,
|
||||
}
|
||||
else:
|
||||
return {"observation": f"{self.success_output_template(success_list)}"}
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
error_list = []
|
||||
success_list = []
|
||||
for index, result in enumerate(results):
|
||||
csv_path = csv_file_path[index]
|
||||
if "error" in result and "chart_path" not in result:
|
||||
error_list.append(f"Error in {csv_path}: {result["error"]}")
|
||||
else:
|
||||
success_list.append(
|
||||
{
|
||||
**result,
|
||||
"title": json_info[index]["chartTitle"],
|
||||
}
|
||||
)
|
||||
if len(error_list) > 0:
|
||||
return {
|
||||
"observation": f"# Error chart generated{'\n'.join(error_list)}\n{self.success_output_template(success_list)}",
|
||||
"success": False,
|
||||
}
|
||||
async def add_insighs(
|
||||
self, json_info: list[dict[str, str]], output_type: str
|
||||
) -> str:
|
||||
data_list = []
|
||||
chart_file_path = self.get_file_path(
|
||||
json_info, "chartPath", os.path.join(config.workspace_root, "visualization")
|
||||
)
|
||||
for index, item in enumerate(json_info):
|
||||
if "insights_id" in item:
|
||||
data_list.append(
|
||||
{
|
||||
"file_name": os.path.basename(chart_file_path[index]).replace(
|
||||
f".{output_type}", ""
|
||||
),
|
||||
"insights_id": item["insights_id"],
|
||||
}
|
||||
)
|
||||
tasks = [
|
||||
self.invoke_vmind(
|
||||
insights_id=item["insights_id"],
|
||||
file_name=item["file_name"],
|
||||
output_type=output_type,
|
||||
task_type="insight",
|
||||
)
|
||||
for item in data_list
|
||||
]
|
||||
results = await asyncio.gather(*tasks)
|
||||
error_list = []
|
||||
success_list = []
|
||||
for index, result in enumerate(results):
|
||||
chart_path = chart_file_path[index]
|
||||
if "error" in result and "chart_path" not in result:
|
||||
error_list.append(f"Error in {chart_path}: {result["error"]}")
|
||||
else:
|
||||
return {"observation": f"{self.success_output_template(success_list)}"}
|
||||
success_list.append(chart_path)
|
||||
success_template = (
|
||||
f"# Charts Update with Insights\n{",".join(success_list)}"
|
||||
if len(success_list) > 0
|
||||
else ""
|
||||
)
|
||||
if len(error_list) > 0:
|
||||
return {
|
||||
"observation": f"# Error in chart insights:{'\n'.join(error_list)}\n{success_template}",
|
||||
"success": False,
|
||||
}
|
||||
else:
|
||||
return {"observation": f"{success_template}"}
|
||||
|
||||
async def execute(
|
||||
self, json_path: str, output_type: str, tool_type: str, language: str
|
||||
) -> str:
|
||||
try:
|
||||
logger.info(f"📈 data_visualization with {json_path} in: {tool_type} ")
|
||||
with open(json_path, "r", encoding="utf-8") as file:
|
||||
json_info = json.load(file)
|
||||
if tool_type == "visualization":
|
||||
return await self.data_visualization(json_info, output_type, language)
|
||||
else:
|
||||
return await self.add_insighs(json_info, output_type)
|
||||
except Exception as e:
|
||||
return {
|
||||
"observation": f"Error: {e}",
|
||||
@@ -129,10 +214,13 @@ class ChartVisualization(BaseTool):
|
||||
|
||||
async def invoke_vmind(
|
||||
self,
|
||||
dict_data: list[dict[Hashable, Any]],
|
||||
chart_description: str,
|
||||
file_name: str,
|
||||
output_type: str,
|
||||
task_type: str,
|
||||
insights_id: list[str] = None,
|
||||
dict_data: list[dict[Hashable, Any]] = None,
|
||||
chart_description: str = None,
|
||||
language: str = "en",
|
||||
):
|
||||
llm_config = {
|
||||
"base_url": self.llm.base_url,
|
||||
@@ -143,9 +231,12 @@ class ChartVisualization(BaseTool):
|
||||
"llm_config": llm_config,
|
||||
"user_prompt": chart_description,
|
||||
"dataset": dict_data,
|
||||
"output_type": output_type,
|
||||
"file_name": file_name,
|
||||
"output_type": output_type,
|
||||
"insights_id": insights_id,
|
||||
"task_type": task_type,
|
||||
"directory": str(config.workspace_root),
|
||||
"language": language,
|
||||
}
|
||||
# build async sub process
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
|
||||
@@ -21,11 +21,12 @@ class NormalPythonExecute(PythonExecute):
|
||||
"code_type": {
|
||||
"description": "code type",
|
||||
"type": "string",
|
||||
"default": "process",
|
||||
"enum": ["process", "report", "others"],
|
||||
},
|
||||
},
|
||||
"required": ["code"],
|
||||
}
|
||||
|
||||
async def execute(self, code: str, code_type: str, timeout=5):
|
||||
async def execute(self, code: str, code_type: str | None = None, timeout=5):
|
||||
return await super().execute(code, timeout)
|
||||
|
||||
+12
-12
@@ -10,7 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@visactor/vchart": "^1.13.7",
|
||||
"@visactor/vmind": "^2.0.4",
|
||||
"@visactor/vmind": "2.0.5-alpha.1",
|
||||
"canvas": "^2.11.2",
|
||||
"get-stdin": "^9.0.0"
|
||||
},
|
||||
@@ -6235,9 +6235,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@visactor/calculator": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@visactor/calculator/-/calculator-2.0.4.tgz",
|
||||
"integrity": "sha512-E/EVf2VYVJVlSmHhhEL5yGkTpiDhv3YF3sZV1pojd+qitPtCwVVJXFtYMndT7YQba3EazTVxkXMcxgbsqq7DWw==",
|
||||
"version": "2.0.5-alpha.1",
|
||||
"resolved": "https://registry.npmjs.org/@visactor/calculator/-/calculator-2.0.5-alpha.1.tgz",
|
||||
"integrity": "sha512-kMh67wgOrtxatbI9siu86L9oruoXcuP9zUvQFEBtUOqBytHCgvqN7b/sKLgCrwi3P1rpx5GCb7ViHgxFEzZl/Q==",
|
||||
"dependencies": {
|
||||
"@visactor/vutils": "~0.19.3",
|
||||
"node-sql-parser": "~4.17.0",
|
||||
@@ -6245,9 +6245,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@visactor/chart-advisor": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@visactor/chart-advisor/-/chart-advisor-2.0.4.tgz",
|
||||
"integrity": "sha512-YD07atfObucrVXy0CD8CZJK3aL4bz2Nq3Q6/mRgQ2NLh2Cw7b1e2E9p/Q068nQrxGl72QEyvV9VDMKFEosA/Cg==",
|
||||
"version": "2.0.5-alpha.1",
|
||||
"resolved": "https://registry.npmjs.org/@visactor/chart-advisor/-/chart-advisor-2.0.5-alpha.1.tgz",
|
||||
"integrity": "sha512-XUGpHed2BAEL+0CeTdHize4C+ZkMCOtzOOzVnoDNjg6jsEB1c3jRcCSoRyT/voLuupXzbNNdLbpL2En/lVlm4Q==",
|
||||
"dependencies": {
|
||||
"@visactor/vutils": "~0.19.3"
|
||||
}
|
||||
@@ -6412,13 +6412,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@visactor/vmind": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@visactor/vmind/-/vmind-2.0.4.tgz",
|
||||
"integrity": "sha512-jSpg4i8w/rxurAIlyy4Hhe8IjiiU0jIqxhuWO6XVUOZRDcBUQhQVNqb1y8wM9tmR1h+kE6EpfmOumFTaTjj5Cg==",
|
||||
"version": "2.0.5-alpha.1",
|
||||
"resolved": "https://registry.npmjs.org/@visactor/vmind/-/vmind-2.0.5-alpha.1.tgz",
|
||||
"integrity": "sha512-uyB6tI2sYWruqX98fAgap+19RYnqsI+8Osx1pSxQXftRj9DA0BWEbphD6uM74nAPBS83OgmnsPseHOyHZfET5Q==",
|
||||
"dependencies": {
|
||||
"@stdlib/stats-base-dists-t-quantile": "0.2.1",
|
||||
"@visactor/calculator": "2.0.4",
|
||||
"@visactor/chart-advisor": "2.0.4",
|
||||
"@visactor/calculator": "2.0.5-alpha.1",
|
||||
"@visactor/chart-advisor": "2.0.5-alpha.1",
|
||||
"@visactor/vchart-theme": "^1.11.2",
|
||||
"@visactor/vdataset": "~0.19.3",
|
||||
"@visactor/vutils": "~0.19.3",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@visactor/vchart": "^1.13.7",
|
||||
"@visactor/vmind": "^2.0.4",
|
||||
"@visactor/vmind": "2.0.5-alpha.1",
|
||||
"canvas": "^2.11.2",
|
||||
"get-stdin": "^9.0.0"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Canvas from "canvas";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import VMind, { ChartType } from "@visactor/vmind";
|
||||
import VMind, { ChartType, DataTable } from "@visactor/vmind";
|
||||
import VChart from "@visactor/vchart";
|
||||
import { isString } from "@visactor/vutils";
|
||||
|
||||
@@ -94,13 +94,19 @@ async function getHtmlVChart(spec: any, width?: number, height?: number) {
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* get file path saved string
|
||||
* @param isUpdate {boolean} default: false, update existed file when is true
|
||||
*/
|
||||
function getSavedPathName(
|
||||
directory: string,
|
||||
fileName: string,
|
||||
outputType: "html" | "png" | "json" | "md"
|
||||
outputType: "html" | "png" | "json" | "md",
|
||||
isUpdate: boolean = false
|
||||
) {
|
||||
let newFileName = fileName;
|
||||
while (
|
||||
!isUpdate &&
|
||||
fs.existsSync(
|
||||
path.join(directory, "visualization", `${newFileName}.${outputType}`)
|
||||
)
|
||||
@@ -119,6 +125,7 @@ const readStdin = (): Promise<string> => {
|
||||
});
|
||||
};
|
||||
|
||||
/** Save insights markdown in local, and return content && path */
|
||||
const setInsightTemplate = (
|
||||
path: string,
|
||||
title: string,
|
||||
@@ -133,36 +140,66 @@ const setInsightTemplate = (
|
||||
}
|
||||
if (res) {
|
||||
fs.writeFileSync(path, res, "utf-8");
|
||||
return path;
|
||||
return { insight_path: path, insight_md: res };
|
||||
}
|
||||
return "";
|
||||
return {};
|
||||
};
|
||||
|
||||
async function generateChart() {
|
||||
const input = await readStdin();
|
||||
const inputData = JSON.parse(input);
|
||||
const res: { chart_path?: string; error?: string; insight_path?: string } =
|
||||
{};
|
||||
/** Save vmind result into local file, Return chart file path */
|
||||
async function saveChartRes(options: {
|
||||
spec: any;
|
||||
directory: string;
|
||||
outputType: "png" | "html";
|
||||
fileName: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
isUpdate?: boolean;
|
||||
}) {
|
||||
const { directory, fileName, spec, outputType, width, height, isUpdate } =
|
||||
options;
|
||||
const specPath = getSavedPathName(directory, fileName, "json", isUpdate);
|
||||
fs.writeFileSync(specPath, JSON.stringify(spec, null, 2));
|
||||
const savedPath = getSavedPathName(directory, fileName, outputType, isUpdate);
|
||||
if (outputType === "png") {
|
||||
const base64 = await getBase64(spec, width, height);
|
||||
fs.writeFileSync(savedPath, base64);
|
||||
} else {
|
||||
const html = await getHtmlVChart(spec, width, height);
|
||||
fs.writeFileSync(savedPath, html, "utf-8");
|
||||
}
|
||||
return savedPath;
|
||||
}
|
||||
|
||||
async function generateChart(
|
||||
vmind: VMind,
|
||||
options: {
|
||||
dataset: string | DataTable;
|
||||
userPrompt: string;
|
||||
directory: string;
|
||||
outputType: "png" | "html";
|
||||
fileName: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
language?: "en" | "zh";
|
||||
}
|
||||
) {
|
||||
let res: {
|
||||
chart_path?: string;
|
||||
error?: string;
|
||||
insight_path?: string;
|
||||
insight_md?: string;
|
||||
} = {};
|
||||
const {
|
||||
dataset,
|
||||
userPrompt,
|
||||
directory,
|
||||
width,
|
||||
height,
|
||||
outputType,
|
||||
fileName,
|
||||
language,
|
||||
} = options;
|
||||
try {
|
||||
const {
|
||||
llm_config,
|
||||
user_prompt: userPrompt,
|
||||
dataset,
|
||||
output_type: outputType = "png",
|
||||
width,
|
||||
height,
|
||||
file_name: fileName,
|
||||
directory,
|
||||
} = inputData;
|
||||
const { base_url: baseUrl, model, api_key: apiKey } = llm_config;
|
||||
const vmind = new VMind({
|
||||
url: `${baseUrl}/chat/completions`,
|
||||
model,
|
||||
headers: {
|
||||
"api-key": apiKey,
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
// Get chart spec and save in local file
|
||||
const jsonDataset = isString(dataset) ? JSON.parse(dataset) : dataset;
|
||||
const { spec, error, chartType } = await vmind.generateChart(
|
||||
@@ -175,12 +212,9 @@ async function generateChart() {
|
||||
}
|
||||
);
|
||||
if (error || !spec) {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
error: error || "Spec of Chart was Empty!",
|
||||
})
|
||||
);
|
||||
return;
|
||||
return {
|
||||
error: error || "Spec of Chart was Empty!",
|
||||
};
|
||||
}
|
||||
|
||||
spec.title = {
|
||||
@@ -190,16 +224,14 @@ async function generateChart() {
|
||||
fs.mkdirSync(path.join(directory, "visualization"));
|
||||
}
|
||||
const specPath = getSavedPathName(directory, fileName, "json");
|
||||
fs.writeFileSync(specPath, JSON.stringify(spec, null, 2));
|
||||
const savedPath = getSavedPathName(directory, fileName, outputType);
|
||||
if (outputType === "png") {
|
||||
const base64 = await getBase64(spec, width, height);
|
||||
fs.writeFileSync(savedPath, base64);
|
||||
} else {
|
||||
const html = await getHtmlVChart(spec, width, height);
|
||||
fs.writeFileSync(savedPath, html, "utf-8");
|
||||
}
|
||||
res.chart_path = savedPath;
|
||||
res.chart_path = await saveChartRes({
|
||||
directory,
|
||||
spec,
|
||||
width,
|
||||
height,
|
||||
fileName,
|
||||
outputType,
|
||||
});
|
||||
|
||||
// get chart insights and save in local
|
||||
const insights = [];
|
||||
@@ -230,6 +262,7 @@ async function generateChart() {
|
||||
AlgorithmType.Volatility,
|
||||
],
|
||||
usePolish: false,
|
||||
language: language === "en" ? "english" : "chinese",
|
||||
});
|
||||
insights.push(...vmindInsights);
|
||||
}
|
||||
@@ -238,17 +271,103 @@ async function generateChart() {
|
||||
.filter((insight) => !!insight) as string[];
|
||||
spec.insights = insights;
|
||||
fs.writeFileSync(specPath, JSON.stringify(spec, null, 2));
|
||||
const insightRes = setInsightTemplate(
|
||||
getSavedPathName(directory, fileName, "md"),
|
||||
userPrompt,
|
||||
insightsText
|
||||
);
|
||||
res.insight_path = insightRes;
|
||||
res = {
|
||||
...res,
|
||||
...setInsightTemplate(
|
||||
getSavedPathName(directory, fileName, "md"),
|
||||
userPrompt,
|
||||
insightsText
|
||||
),
|
||||
};
|
||||
} catch (error: any) {
|
||||
res.error = error.toString();
|
||||
} finally {
|
||||
console.log(JSON.stringify(res));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
generateChart();
|
||||
async function updateChartWithInsight(
|
||||
vmind: VMind,
|
||||
options: {
|
||||
directory: string;
|
||||
outputType: "png" | "html";
|
||||
fileName: string;
|
||||
insightsId: number[];
|
||||
}
|
||||
) {
|
||||
const { directory, outputType, fileName, insightsId } = options;
|
||||
let res: { error?: string; chart_path?: string } = {};
|
||||
try {
|
||||
const specPath = getSavedPathName(directory, fileName, "json", true);
|
||||
const spec = JSON.parse(fs.readFileSync(specPath, "utf8"));
|
||||
// llm select index from 1
|
||||
const insights = (spec.insights || []).filter(
|
||||
(_insight: any, index: number) => insightsId.includes(index + 1)
|
||||
);
|
||||
const { newSpec, error } = await vmind.updateSpecByInsights(spec, insights);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
res.chart_path = await saveChartRes({
|
||||
spec: newSpec,
|
||||
directory,
|
||||
outputType,
|
||||
fileName,
|
||||
isUpdate: true,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.error = error.toString();
|
||||
} finally {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
async function executeVMind() {
|
||||
const input = await readStdin();
|
||||
const inputData = JSON.parse(input);
|
||||
let res;
|
||||
const {
|
||||
llm_config,
|
||||
width,
|
||||
dataset = [],
|
||||
height,
|
||||
directory,
|
||||
user_prompt: userPrompt,
|
||||
output_type: outputType = "png",
|
||||
file_name: fileName,
|
||||
task_type: taskType = "visualization",
|
||||
insights_id: insightsId = [],
|
||||
language = "en",
|
||||
} = inputData;
|
||||
const { base_url: baseUrl, model, api_key: apiKey } = llm_config;
|
||||
const vmind = new VMind({
|
||||
url: `${baseUrl}/chat/completions`,
|
||||
model,
|
||||
headers: {
|
||||
"api-key": apiKey,
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
if (taskType === "visualization") {
|
||||
res = await generateChart(vmind, {
|
||||
dataset,
|
||||
userPrompt,
|
||||
directory,
|
||||
outputType,
|
||||
fileName,
|
||||
width,
|
||||
height,
|
||||
language,
|
||||
});
|
||||
} else if (taskType === "insight" && insightsId.length) {
|
||||
res = await updateChartWithInsight(vmind, {
|
||||
directory,
|
||||
fileName,
|
||||
outputType,
|
||||
insightsId,
|
||||
});
|
||||
}
|
||||
console.log(JSON.stringify(res));
|
||||
}
|
||||
|
||||
executeVMind();
|
||||
|
||||
@@ -9,7 +9,9 @@ async def main():
|
||||
agent = DataAnalysis()
|
||||
# agent = Manus()
|
||||
await agent.run(
|
||||
"""分析以下数据并生成一个图文数据报告在本地./data文件夹下,格式为html.Requriment:展示3个团队半年内的人效变化,并且将相邻两个月各团队的环比上升或者下降的比例体现出来
|
||||
"""Requriment:
|
||||
1. 分析以下数据并生成一个图文数据报告,格式为html.最终生成的产物是一个data report
|
||||
2. 图表中需要有insights
|
||||
Data:月份 团队A 团队B 团队C
|
||||
1月 1200小时 1350小时 1100小时
|
||||
2月 1250小时 1400小时 1150小时
|
||||
|
||||
@@ -3,7 +3,6 @@ from app.tool.chart_visualization import ChartVisualization
|
||||
|
||||
|
||||
async def mock_request(delay, value):
|
||||
print("!!!!")
|
||||
await asyncio.sleep(delay) # 模拟异步IO操作(如网络请求)
|
||||
return value
|
||||
|
||||
@@ -23,7 +22,11 @@ async def main():
|
||||
|
||||
async def test_chart():
|
||||
chartTool = ChartVisualization()
|
||||
print(await chartTool.execute("./data/visualization_info.json", "html"))
|
||||
print(
|
||||
await chartTool.execute(
|
||||
"workspace/insight_metadata.json", "html", "insight", "en"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user