DAO / src /data_agent_baseline /tools /filesystem.py
sipe5001's picture
Add Hugging Face Docker Space configuration
d3d0e0e
Raw History Blame Contribute Delete
4.39 kB
from __future__ import annotations
import csv
import json
from pathlib import Path
from data_agent_baseline.benchmark.schema import PublicTask
def resolve_context_path(task: PublicTask, relative_path: str) -> Path:
candidate = (task.context_dir / relative_path).resolve()
context_root = task.context_dir.resolve()
if context_root not in candidate.parents and candidate != context_root:
raise ValueError(f"Path escapes context dir: {relative_path}")
if not candidate.exists():
raise FileNotFoundError(f"Missing context asset: {relative_path}")
return candidate
def list_context_tree(task: PublicTask, *, max_depth: int = 4) -> dict[str, object]:
entries: list[dict[str, object]] = []
def walk(path: Path, depth: int) -> None:
if depth > max_depth:
return
for child in sorted(path.iterdir(), key=lambda item: (item.is_file(), item.name)):
if child.name == "_auto.db":
continue
rel_path = child.relative_to(task.context_dir).as_posix()
entries.append(
{
"path": rel_path,
"kind": "dir" if child.is_dir() else "file",
"size": child.stat().st_size if child.is_file() else None,
}
)
if child.is_dir():
walk(child, depth + 1)
walk(task.context_dir, 1)
return {
"root": str(task.context_dir),
"entries": entries,
}
def read_csv_preview(task: PublicTask, relative_path: str, *, max_rows: int = 20) -> dict[str, object]:
path = resolve_context_path(task, relative_path)
with path.open(newline="") as handle:
reader = csv.reader(handle)
rows = list(reader)
if not rows:
return {
"path": relative_path,
"columns": [],
"rows": [],
"row_count": 0,
}
header = rows[0]
data_rows = rows[1:]
return {
"path": relative_path,
"columns": header,
"rows": data_rows[:max_rows],
"row_count": len(data_rows),
}
def read_json_preview(task: PublicTask, relative_path: str, *, max_chars: int = 10000) -> dict[str, object]:
path = resolve_context_path(task, relative_path)
payload = json.loads(path.read_text())
# For {table, records} format (common in this benchmark), provide structured preview
# showing ALL column names from the first record + sample records (minimum 2)
if isinstance(payload, dict) and "records" in payload and isinstance(payload["records"], list):
records = payload["records"]
columns = list(records[0].keys()) if records else []
# Always show at least 2 sample records, even if it exceeds max_chars
sample_count = max(2, min(len(records), 5))
sample_records = records[:sample_count]
structured_preview = json.dumps(
{"table": payload.get("table", ""), "columns": columns,
"record_count": len(records), "sample_records": sample_records},
ensure_ascii=False, indent=2,
)
# If more than 2 records, trim to min(10000, max_chars)
if sample_count > 2 and len(structured_preview) > min(10000, max_chars):
# Reduce sample_records to fit within max_chars
for n in range(sample_count, 1, -1):
sample_records = records[:n]
structured_preview = json.dumps(
{"table": payload.get("table", ""), "columns": columns,
"record_count": len(records), "sample_records": sample_records},
ensure_ascii=False, indent=2,
)
if len(structured_preview) <= min(10000, max_chars):
break
preview = structured_preview
else:
preview = json.dumps(payload, ensure_ascii=False, indent=2)[:min(10000, max_chars)]
return {
"path": relative_path,
"preview": preview,
"truncated": len(preview) >= max_chars,
}
def read_doc_preview(task: PublicTask, relative_path: str, *, max_chars: int = 5000) -> dict[str, object]:
path = resolve_context_path(task, relative_path)
text = path.read_text(errors="replace")
return {
"path": relative_path,
"preview": text[:max_chars],
"truncated": len(text) > max_chars,
}