Merge pull request #1 from 666haiwen/feat/data_visualization_hack_czx

Feat: Update chart generation tools in data analysis
This commit is contained in:
ZJU_czx
2025-03-31 20:30:02 +08:00
committed by GitHub
13 changed files with 413 additions and 180 deletions
+6 -32
View File
@@ -1,17 +1,17 @@
from pydantic import Field
from app.agent.browser import BrowserAgent
from app.agent.toolcall import ToolCallAgent
from app.config import config
from app.prompt.browser import NEXT_STEP_PROMPT as BROWSER_NEXT_STEP_PROMPT
from app.prompt.visualization import NEXT_STEP_PROMPT, SYSTEM_PROMPT
from app.tool import Terminate, ToolCollection
from app.tool.browser_use_tool import BrowserUseTool
from app.tool.chart_visualization.chart_visualization import ChartVisualization
from app.tool.chart_visualization.normal_python_execute import NormalPythonExecute
from app.tool.chart_visualization.data_analysis_python import DataAnalysisPythonExecute
from app.tool.chart_visualization.chart_prepare import (
VisualizationPrepare,
)
class DataAnalysis(BrowserAgent):
class DataAnalysis(ToolCallAgent):
"""
A data analysis agent that uses planning to solve various data analysis tasks.
@@ -34,34 +34,8 @@ class DataAnalysis(BrowserAgent):
available_tools: ToolCollection = Field(
default_factory=lambda: ToolCollection(
NormalPythonExecute(),
DataAnalysisPythonExecute(),
VisualizationPrepare(),
ChartVisualization(),
BrowserUseTool(),
Terminate(),
)
)
async def think(self) -> bool:
"""Process current state and decide next actions with appropriate context."""
# Store original prompt
original_prompt = self.next_step_prompt
# Only check recent messages (last 3) for browser activity
recent_messages = self.memory.messages[-3:] if self.memory.messages else []
browser_in_use = any(
"browser_use" in msg.content.lower()
for msg in recent_messages
if hasattr(msg, "content") and isinstance(msg.content, str)
)
if browser_in_use:
# Override with browser-specific prompt temporarily to get browser context
self.next_step_prompt = BROWSER_NEXT_STEP_PROMPT
# Call parent's think method
result = await super().think()
# Restore original prompt
self.next_step_prompt = original_prompt
return result
+3 -3
View File
@@ -1,8 +1,8 @@
SYSTEM_PROMPT = (
"You are an AI agent designed to data analysis and data visualization task. You have various tools at your disposal that you can call upon to efficiently complete complex requests."
"The initial directory is: {directory}"
"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}"
)
NEXT_STEP_PROMPT = """
Based on user needs, proactively select the most appropriate tool or combination of tools. For complex tasks, you can break down the problem and use different tools step by step to solve it. After using each tool, clearly explain the execution results and suggest the next steps.
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.
"""
+2 -2
View File
@@ -1,5 +1,5 @@
from app.tool.chart_visualization.chart_visualization import ChartVisualization
from app.tool.chart_visualization.data_analysis_python import DataAnalysisPythonExecute
from app.tool.chart_visualization.chart_prepare import VisualizationPrepare
from app.tool.chart_visualization.normal_python_execute import NormalPythonExecute
__all__ = ["ChartVisualization", "DataAnalysisPythonExecute", "NormalPythonExecute"]
__all__ = ["ChartVisualization", "VisualizationPrepare", "NormalPythonExecute"]
@@ -0,0 +1,31 @@
from app.tool.python_execute import PythonExecute
class VisualizationPrepare(PythonExecute):
"""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."
)
parameters: dict = {
"type": "object",
"properties": {
"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)
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)
# 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
""",
},
},
"required": ["code"],
}
@@ -1,8 +1,6 @@
import subprocess
import json
import base64
import asyncio
import pandas as pd
import aiofiles
import os
from typing import Any, Hashable
from pydantic import Field, model_validator
@@ -10,23 +8,20 @@ from pydantic import Field, model_validator
from app.llm import LLM
from app.tool.base import BaseTool
from app.logger import logger
from app.config import config
class ChartVisualization(BaseTool):
name: str = "generate_data_visualization"
description: str = """Visualize a statistical chart using csv data and chart description. The tool accepts local csv data file path and description of the chart, and output a chart in png or html.
Note: Each tool call generates only one single chart.
"""
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)."""
)
parameters: dict = {
"type": "object",
"properties": {
"csv_path": {
"json_path": {
"type": "string",
"description": """file path of csv data with ".csv" in the end""",
},
"chart_description": {
"type": "string",
"description": "The chart title or description should be concise and clear. Examples: 'Product sales distribution', 'Monthly revenue trend'.",
"description": """file path of json info with ".json" in the end""",
},
"output_type": {
"description": "Rendering format (html=interactive)",
@@ -35,7 +30,7 @@ Note: Each tool call generates only one single chart.
"enum": ["png", "html"],
},
},
"required": ["code", "chart_description"],
"required": ["code"],
}
llm: LLM = Field(default_factory=LLM, description="Language model instance")
@@ -46,40 +41,86 @@ Note: Each tool call generates only one single chart.
self.llm = LLM(config_name=self.name.lower())
return self
async def execute(
self, csv_path: str, chart_description: str, output_type: str
) -> str:
logger.info(
f"📈 Chart Generation with data and description: {chart_description} with {csv_path} "
)
def get_csv_path(self, json_info: list[dict[str, str]]) -> list[str]:
res = []
for item in json_info:
if os.path.exists(item["csvFilePath"]):
res.append(item["csvFilePath"])
elif os.path.exists(
os.path.join(f"{config.workspace_root}", item["csvFilePath"])
):
res.append(
os.path.join(f"{config.workspace_root}", item["csvFilePath"])
)
else:
raise Exception(f"No such file or directory: {item["csvFilePath"]}")
return res
def success_output_template(self, result: list[dict[str, str]]) -> str:
content = ""
if len(result) == 0:
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"""
else:
content += "\n"
return f"Chart Generated Successful! Detail is below:\n{content}"
async def execute(self, json_path: str, output_type: str) -> str:
logger.info(f"📈 Chart Generation with json path: {json_path} ")
try:
df = pd.read_csv(csv_path)
df = df.astype(object)
df = df.where(pd.notnull(df), None)
data_dict_list = df.to_json(orient="records", force_ascii=False)
result = await self.invoke_vmind(
data_dict_list, chart_description, output_type
)
if "error" in result:
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)
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(
item["dict_data"],
item["chartTitle"],
item["file_name"],
output_type,
)
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(
{
**result,
"title": json_info[index]["chartTitle"],
}
)
if len(error_list) > 0:
return {
"observation": f"Error: {result["error"]}",
"observation": f"# Error chart generated{'\n'.join(error_list)}\n{self.success_output_template(success_list)}",
"success": False,
}
chart_file_path = csv_path.replace(".csv", f".{output_type}")
while os.path.exists(chart_file_path):
chart_file_path = chart_file_path.replace(
f".{output_type}", f"_new.{output_type}"
)
if output_type == "png":
byte_data = base64.b64decode(result["res"])
async with aiofiles.open(chart_file_path, "wb") as file:
await file.write(byte_data)
else:
async with aiofiles.open(
chart_file_path, "w", encoding="utf-8"
) as file:
await file.write(result["res"])
return {"observation": f"chart successfully saved to {chart_file_path}"}
return {"observation": f"{self.success_output_template(success_list)}"}
except Exception as e:
return {
"observation": f"Error: {e}",
@@ -90,6 +131,7 @@ Note: Each tool call generates only one single chart.
self,
dict_data: list[dict[Hashable, Any]],
chart_description: str,
file_name: str,
output_type: str,
):
llm_config = {
@@ -102,16 +144,27 @@ Note: Each tool call generates only one single chart.
"user_prompt": chart_description,
"dataset": dict_data,
"output_type": output_type,
"file_name": file_name,
"directory": str(config.workspace_root),
}
process = subprocess.run(
["npx", "ts-node", "src/chartVisualize.ts"],
input=json.dumps(vmind_params),
capture_output=True,
text=True,
encoding="utf-8",
# build async sub process
process = await asyncio.create_subprocess_exec(
"npx",
"ts-node",
"src/chartVisualize.ts",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=os.path.dirname(__file__),
)
if process.returncode == 0:
return json.loads(process.stdout)
else:
return {"error": f"Node.js Error: {process.stderr}"}
input_json = json.dumps(vmind_params, ensure_ascii=False).encode("utf-8")
try:
stdout, stderr = await process.communicate(input_json)
stdout_str = stdout.decode("utf-8")
stderr_str = stderr.decode("utf-8")
if process.returncode == 0:
return json.loads(stdout_str)
else:
return {"error": f"Node.js Error: {stderr_str}"}
except Exception as e:
return {"error": f"Subprocess Error: {str(e)}"}
@@ -1,42 +0,0 @@
from app.tool.chart_visualization.normal_python_execute import NormalPythonExecute
class DataAnalysisPythonExecute(NormalPythonExecute):
"""A tool for executing Python code in data analysis task with timeout and safety restrictions."""
name: str = "data_analysis_python_execute"
description: str = (
"Executes Python code string in data analysis task, save data table in csv file. Note: Only print outputs are visible, function return values are not captured. Use print statements to see results."
)
parameters: dict = {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": """Python code template EXCLUSIVELY for data analysis. Must Contains:
1. Data loading logic (handle dataframe/dict/file/url/json/web crawler)
2. Data analysis (cleaning/transformation)
3. CSV saving with path print: print(csv_path)
""",
},
"analysis_content": {
"type": "string",
"description": "Your analysis of current task, ensure your analysis is concise, clear, and easy to understand.",
},
},
"required": ["code"],
}
async def execute(self, code: str, analysis_content: str, timeout=5):
"""
Executes the provided Python code with a timeout.
Args:
code (str): The Python code to execute.
analysis_content (str): The analysis content of current task.
timeout (int): Execution timeout in seconds.
Returns:
Dict: Contains 'output' with execution output or error message and 'success' status.
"""
return await super().execute(code, timeout)
@@ -1,10 +1,4 @@
import sys
from io import StringIO
from app.tool.python_execute import PythonExecute
from app.tool.chart_visualization.utils import (
extract_executable_code,
)
class NormalPythonExecute(PythonExecute):
@@ -12,34 +6,26 @@ class NormalPythonExecute(PythonExecute):
name: str = "common_python_execute"
description: str = (
"""Executes Python code strings. Note:
1. Only outputs from print() are visible; function return values are not captured. Use print() statements to display results
2. Applicable to scenarios **excluding data analysis and chart generation**"""
"""Executes Python code strings to tasks such as data process and data report"""
)
parameters: dict = {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The Python code to execute.",
"description": """The Python code to execute. Note:
1. Only outputs from print() are visible; function return values are not captured. Use print() statements to display results
2. Do data process (cleaning / transform) saved in *.csv
3. Generate a data analysis report in html""",
},
"code_type": {
"description": "code type",
"type": "string",
"enum": ["process", "report", "others"],
},
},
"required": ["code"],
}
def _run_code(self, code: str, result_dict: dict, safe_globals: dict) -> None:
original_stdout = sys.stdout
be_extracted_code = extract_executable_code(code) # ignore_security_alert RCE
try:
output_buffer = StringIO()
sys.stdout = output_buffer
exec( # ignore_security_alert RCE
be_extracted_code, safe_globals, safe_globals
) # ignore_security_alert RCE
result_dict["observation"] = output_buffer.getvalue()
result_dict["success"] = True
except Exception as e:
result_dict["observation"] = str(e)
result_dict["success"] = False
finally:
sys.stdout = original_stdout
async def execute(self, code: str, code_type: str, timeout=5):
return await super().execute(code, timeout)
+28 -5
View File
@@ -11,9 +11,11 @@
"dependencies": {
"@visactor/vchart": "^1.13.7",
"@visactor/vmind": "^2.0.4",
"canvas": "^2.11.2"
"canvas": "^2.11.2",
"get-stdin": "^9.0.0"
},
"devDependencies": {
"@types/get-stdin": "^7.0.0",
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
@@ -6213,6 +6215,16 @@
"url": "https://opencollective.com/turf"
}
},
"node_modules/@types/get-stdin": {
"version": "7.0.0",
"resolved": "https://bnpm.byted.org/@types/get-stdin/-/get-stdin-7.0.0.tgz",
"integrity": "sha512-kiDwIsKQvsLRvtBOnasij+6eChbCzcUT7OyVvrC5BEOE4QSKbpnwejEp0xND/9sIdOTfiu+BBl3zsB16MJ3Fww==",
"deprecated": "This is a stub types definition. get-stdin provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"get-stdin": "*"
}
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
@@ -7131,6 +7143,14 @@
"geojson-flatten": "geojson-flatten"
}
},
"node_modules/geojson-flatten/node_modules/get-stdin": {
"version": "6.0.0",
"resolved": "https://bnpm.byted.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"engines": {
"node": ">=4"
}
},
"node_modules/geojson-linestring-dissolve": {
"version": "0.0.1",
"resolved": "https://bnpm.byted.org/geojson-linestring-dissolve/-/geojson-linestring-dissolve-0.0.1.tgz",
@@ -7180,11 +7200,14 @@
}
},
"node_modules/get-stdin": {
"version": "6.0.0",
"resolved": "https://bnpm.byted.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"version": "9.0.0",
"resolved": "https://bnpm.byted.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"engines": {
"node": ">=4"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gifuct-js": {
+3 -1
View File
@@ -3,6 +3,7 @@
"version": "1.0.0",
"main": "src/index.ts",
"devDependencies": {
"@types/get-stdin": "^7.0.0",
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
@@ -10,7 +11,8 @@
"dependencies": {
"@visactor/vchart": "^1.13.7",
"@visactor/vmind": "^2.0.4",
"canvas": "^2.11.2"
"canvas": "^2.11.2",
"get-stdin": "^9.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
@@ -1,10 +1,27 @@
import Canvas from "canvas";
import path from "path";
import { readFileSync } from "fs";
import VMind from "@visactor/vmind";
import fs from "fs";
import VMind, { ChartType } from "@visactor/vmind";
import VChart from "@visactor/vchart";
import { isString } from "@visactor/vutils";
enum AlgorithmType {
OverallTrending = "overallTrend",
AbnormalTrend = "abnormalTrend",
PearsonCorrelation = "pearsonCorrelation",
SpearmanCorrelation = "spearmanCorrelation",
ExtremeValue = "extremeValue",
MajorityValue = "majorityValue",
StatisticsAbnormal = "statisticsAbnormal",
StatisticsBase = "statisticsBase",
DbscanOutlier = "dbscanOutlier",
LOFOutlier = "lofOutlier",
TurningPoint = "turningPoint",
PageHinkley = "pageHinkley",
DifferenceOutlier = "differenceOutlier",
Volatility = "volatility",
}
const getBase64 = async (spec: any, width?: number, height?: number) => {
spec.animation = false;
width && (spec.width = width);
@@ -36,7 +53,7 @@ const serializeSpec = (spec: any) => {
});
};
async function getHtmlVChart(spec: any, width: number, height: number) {
async function getHtmlVChart(spec: any, width?: number, height?: number) {
return `<!DOCTYPE html>
<html>
<head>
@@ -77,8 +94,55 @@ async function getHtmlVChart(spec: any, width: number, height: number) {
`;
}
function getSavedPathName(
directory: string,
fileName: string,
outputType: "html" | "png" | "json" | "md"
) {
let newFileName = fileName;
while (
fs.existsSync(
path.join(directory, "visualization", `${newFileName}.${outputType}`)
)
) {
newFileName += "_new";
}
return path.join(directory, "visualization", `${newFileName}.${outputType}`);
}
const readStdin = (): Promise<string> => {
return new Promise((resolve) => {
let input = "";
process.stdin.setEncoding("utf-8"); // 确保编码与 Python 端一致
process.stdin.on("data", (chunk) => (input += chunk));
process.stdin.on("end", () => resolve(input));
});
};
const setInsightTemplate = (
path: string,
title: string,
insights: string[]
) => {
let res = "";
if (insights.length) {
res += `## ${title} Insights`;
insights.forEach((insight, index) => {
res += `\n${index + 1}. ${insight}`;
});
}
if (res) {
fs.writeFileSync(path, res, "utf-8");
return path;
}
return "";
};
async function generateChart() {
const inputData = JSON.parse(readFileSync(process.stdin.fd, "utf-8"));
const input = await readStdin();
const inputData = JSON.parse(input);
const res: { chart_path?: string; error?: string; insight_path?: string } =
{};
try {
const {
llm_config,
@@ -87,6 +151,8 @@ async function generateChart() {
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({
@@ -97,8 +163,9 @@ async function generateChart() {
Authorization: `Bearer ${apiKey}`,
},
});
// Get chart spec and save in local file
const jsonDataset = isString(dataset) ? JSON.parse(dataset) : dataset;
const { spec, error } = await vmind.generateChart(
const { spec, error, chartType } = await vmind.generateChart(
userPrompt,
undefined,
jsonDataset,
@@ -115,17 +182,72 @@ async function generateChart() {
);
return;
}
if (outputType === "png") {
console.log(
JSON.stringify({ res: await getBase64(spec, width, height) })
);
} else {
console.log(
JSON.stringify({ res: await getHtmlVChart(spec, width, height) })
);
spec.title = {
text: userPrompt,
};
if (!fs.existsSync(path.join(directory, "visualization"))) {
fs.mkdirSync(path.join(directory, "visualization"));
}
} catch (error) {
console.log(JSON.stringify({ error }));
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;
// get chart insights and save in local
const insights = [];
if (
chartType &&
[
ChartType.BarChart,
ChartType.LineChart,
ChartType.AreaChart,
ChartType.ScatterPlot,
ChartType.DualAxisChart,
].includes(chartType)
) {
const { insights: vmindInsights } = await vmind.getInsights(spec, {
maxNum: 6,
algorithms: [
AlgorithmType.OverallTrending,
AlgorithmType.AbnormalTrend,
AlgorithmType.PearsonCorrelation,
AlgorithmType.SpearmanCorrelation,
AlgorithmType.StatisticsAbnormal,
AlgorithmType.LOFOutlier,
AlgorithmType.DbscanOutlier,
AlgorithmType.MajorityValue,
AlgorithmType.PageHinkley,
AlgorithmType.TurningPoint,
AlgorithmType.StatisticsBase,
AlgorithmType.Volatility,
],
usePolish: false,
});
insights.push(...vmindInsights);
}
const insightsText = insights
.map((insight) => insight.textContent?.plainText)
.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;
} catch (error: any) {
res.error = error.toString();
} finally {
console.log(JSON.stringify(res));
}
}
@@ -0,0 +1,50 @@
import asyncio
import time
from app.agent.manus import Manus
from app.agent.data_analysis import DataAnalysis
from app.flow.base import FlowType
from app.flow.flow_factory import FlowFactory
from app.logger import logger
async def run_flow():
agents = {
# "manus": Manus(),
"visactor": DataAnalysis(),
}
try:
prompt = """Here's last month's sales data from my Amazon store. Could you analyze it thoroughly with visualizations and recommend specific, data-driven strategies to boost next month's sales by 10%?
File Path: workspace/amazon_sales_jan2025.csv
"""
flow = FlowFactory.create_flow(
flow_type=FlowType.PLANNING,
agents=agents,
)
logger.warning("Processing your request...")
try:
start_time = time.time()
result = await asyncio.wait_for(
flow.execute(prompt),
timeout=3600, # 60 minute timeout for the entire execution
)
elapsed_time = time.time() - start_time
logger.info(f"Request processed in {elapsed_time:.2f} seconds")
logger.info(result)
except asyncio.TimeoutError:
logger.error("Request processing timed out after 1 hour")
logger.info(
"Operation terminated due to timeout. Please try a simpler request."
)
except KeyboardInterrupt:
logger.info("Operation cancelled by user.")
except Exception as e:
logger.error(f"Error: {str(e)}")
if __name__ == "__main__":
asyncio.run(run_flow())
@@ -0,0 +1,32 @@
import asyncio
from app.tool.chart_visualization import ChartVisualization
async def mock_request(delay, value):
print("!!!!")
await asyncio.sleep(delay) # 模拟异步IO操作(如网络请求)
return value
async def main():
# 创建多个异步任务
tasks = [
mock_request(1, "结果1"),
mock_request(2, "结果2"),
mock_request(3, "结果3"),
]
# 并发执行所有任务,等待全部完成
results = await asyncio.gather(*tasks)
print(results) # 输出: ['结果1', '结果2', '结果3']
async def test_chart():
chartTool = ChartVisualization()
print(await chartTool.execute("./data/visualization_info.json", "html"))
if __name__ == "__main__":
asyncio.run(test_chart())
# 运行主协程
# asyncio.run(main())
+3 -1
View File
@@ -10,7 +10,9 @@ class PythonExecute(BaseTool):
"""A tool for executing Python code with timeout and safety restrictions."""
name: str = "python_execute"
description: str = "Executes Python code string. Note: Only print outputs are visible, function return values are not captured. Use print statements to see results."
description: str = (
"Executes Python code string. Note: Only print outputs are visible, function return values are not captured. Use print statements to see results."
)
parameters: dict = {
"type": "object",
"properties": {