mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-05 01:37:46 +08:00
Fix: code supports matplotlib (#13724)
### What problem does this PR solve? Code as "final" node:  Code as "mid" node:  ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -24,7 +24,7 @@ from core.config import TIMEOUT
|
||||
from core.container import allocate_container_blocking, release_container
|
||||
from core.logger import logger
|
||||
from models.enums import ResourceLimitType, ResultStatus, RuntimeErrorType, SupportLanguage, UnauthorizedAccessType
|
||||
from models.schemas import CodeExecutionRequest, CodeExecutionResult
|
||||
from models.schemas import ArtifactItem, CodeExecutionRequest, CodeExecutionResult
|
||||
from utils.common import async_run_command
|
||||
|
||||
|
||||
@ -59,8 +59,12 @@ async def execute_code(req: CodeExecutionRequest):
|
||||
f.write("""import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.makedirs(os.path.join(os.getcwd(), "artifacts"), exist_ok=True)
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from main import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = json.loads(sys.argv[1])
|
||||
result = main(**args)
|
||||
@ -180,12 +184,14 @@ if (fs.existsSync(mainPath)) {
|
||||
logger.info(f"{args_json=}")
|
||||
|
||||
if returncode == 0:
|
||||
artifacts = await _collect_artifacts(container, task_id, workdir)
|
||||
return CodeExecutionResult(
|
||||
status=ResultStatus.SUCCESS,
|
||||
stdout=str(stdout),
|
||||
stderr=stderr,
|
||||
exit_code=0,
|
||||
time_used_ms=time_used_ms,
|
||||
artifacts=artifacts,
|
||||
)
|
||||
elif returncode == 124:
|
||||
return CodeExecutionResult(
|
||||
@ -229,6 +235,84 @@ if (fs.existsSync(mainPath)) {
|
||||
await release_container(container, language)
|
||||
|
||||
|
||||
ALLOWED_ARTIFACT_EXTENSIONS = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".svg": "image/svg+xml",
|
||||
".pdf": "application/pdf",
|
||||
".csv": "text/csv",
|
||||
".json": "application/json",
|
||||
".html": "text/html",
|
||||
}
|
||||
MAX_ARTIFACT_COUNT = 10
|
||||
MAX_ARTIFACT_SIZE = 10 * 1024 * 1024 # 10MB per file
|
||||
|
||||
|
||||
async def _collect_artifacts(container: str, task_id: str, host_workdir: str) -> list[ArtifactItem]:
|
||||
artifacts_path = f"/workspace/{task_id}/artifacts"
|
||||
|
||||
# List files in the artifacts directory inside the container
|
||||
returncode, stdout, _ = await async_run_command(
|
||||
"docker", "exec", container, "find", artifacts_path,
|
||||
"-maxdepth", "1", "-type", "f", timeout=5,
|
||||
)
|
||||
if returncode != 0 or not stdout.strip():
|
||||
return []
|
||||
|
||||
raw_names = [line.split("/")[-1] for line in stdout.strip().splitlines() if line.strip()]
|
||||
# Sanitize: reject names with path traversal or control characters
|
||||
filenames = [n for n in raw_names if n and "/" not in n and "\\" not in n and ".." not in n and not n.startswith(".")]
|
||||
if not filenames:
|
||||
return []
|
||||
|
||||
items: list[ArtifactItem] = []
|
||||
|
||||
for fname in filenames[:MAX_ARTIFACT_COUNT]:
|
||||
ext = os.path.splitext(fname)[1].lower()
|
||||
mime_type = ALLOWED_ARTIFACT_EXTENSIONS.get(ext)
|
||||
if not mime_type:
|
||||
logger.warning(f"Skipping artifact with disallowed extension: {fname}")
|
||||
continue
|
||||
|
||||
file_path = f"{artifacts_path}/{fname}"
|
||||
|
||||
# Check file size inside the container
|
||||
returncode, size_str, _ = await async_run_command(
|
||||
"docker", "exec", container, "stat", "-c", "%s", file_path, timeout=5,
|
||||
)
|
||||
if returncode != 0:
|
||||
logger.warning(f"Failed to stat artifact {fname}")
|
||||
continue
|
||||
|
||||
file_size = int(size_str.strip())
|
||||
if file_size > MAX_ARTIFACT_SIZE:
|
||||
logger.warning(f"Artifact {fname} too large ({file_size} bytes), skipping")
|
||||
continue
|
||||
if file_size == 0:
|
||||
continue
|
||||
|
||||
# Read file content via docker exec (docker cp doesn't work with gVisor tmpfs)
|
||||
returncode, content_b64, stderr = await async_run_command(
|
||||
"docker", "exec", container, "base64", file_path, timeout=30,
|
||||
)
|
||||
if returncode != 0:
|
||||
logger.warning(f"Failed to read artifact {fname}: {stderr}")
|
||||
continue
|
||||
|
||||
content_b64 = content_b64.replace("\n", "").strip()
|
||||
|
||||
items.append(ArtifactItem(
|
||||
name=fname,
|
||||
mime_type=mime_type,
|
||||
size=file_size,
|
||||
content_b64=content_b64,
|
||||
))
|
||||
logger.info(f"Collected artifact: {fname} ({file_size} bytes, {mime_type})")
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def analyze_error_result(stderr: str, exit_code: int) -> CodeExecutionResult:
|
||||
"""Analyze the error result and classify it"""
|
||||
if "Permission denied" in stderr:
|
||||
|
||||
Reference in New Issue
Block a user