From 8991ca707a0eb75fa66e59000df4d6a360ca3e84 Mon Sep 17 00:00:00 2001 From: Anai-Guo Date: Thu, 16 Jul 2026 15:13:54 -0700 Subject: [PATCH 1/2] fix(dataset): resolve relative sample files on Windows and filesystem URIs (#4502) `json_dataset()` / `csv_dataset()` stored `Dataset.location` with `os.path.abspath()`, while `resolve_sample_files()` derived the parent directory by splitting that value on the fsspec filesystem separator. Two problems follow: * On Windows `abspath()` produces backslashes but `LocalFileSystem.sep` is `/`, so the split yields no parent and relative sample files were left unresolved. * `os.path.abspath()` treats `file://`, `s3://` and other filesystem URIs as local path strings, corrupting the stored location. Use the existing filesystem-aware helpers instead: `absolute_file_path()` to build the location (it preserves URI schemes and only resolves genuine relative paths) and `dirname()` to derive the parent directory. Adds a regression test covering a `file://` dataset location. --- src/inspect_ai/dataset/_sources/csv.py | 5 ++--- src/inspect_ai/dataset/_sources/json.py | 5 ++--- src/inspect_ai/dataset/_sources/util.py | 4 ++-- tests/dataset/test_dataset.py | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/inspect_ai/dataset/_sources/csv.py b/src/inspect_ai/dataset/_sources/csv.py index b5d2308c11..9ad2b4c23d 100644 --- a/src/inspect_ai/dataset/_sources/csv.py +++ b/src/inspect_ai/dataset/_sources/csv.py @@ -1,11 +1,10 @@ import csv -import os from io import TextIOWrapper from pathlib import Path from typing import Any from inspect_ai._util.asyncfiles import is_s3_filename -from inspect_ai._util.file import file +from inspect_ai._util.file import absolute_file_path, file from inspect_ai.dataset._sources.util import resolve_sample_files from .._dataset import ( @@ -83,7 +82,7 @@ def csv_dataset( dataset = MemoryDataset( samples=data_to_samples(valid_data, data_to_sample, auto_id), name=name, - location=os.path.abspath(csv_file), + location=absolute_file_path(csv_file), ) # resolve relative file paths diff --git a/src/inspect_ai/dataset/_sources/json.py b/src/inspect_ai/dataset/_sources/json.py index 24fa0920a9..1c0b783ddb 100644 --- a/src/inspect_ai/dataset/_sources/json.py +++ b/src/inspect_ai/dataset/_sources/json.py @@ -1,5 +1,4 @@ import json -import os from io import TextIOWrapper from pathlib import Path from typing import Any @@ -7,7 +6,7 @@ import jsonlines from inspect_ai._util.asyncfiles import is_s3_filename -from inspect_ai._util.file import file +from inspect_ai._util.file import absolute_file_path, file from .._dataset import ( Dataset, @@ -87,7 +86,7 @@ def json_dataset( dataset_reader(f, **reader_kwargs), data_to_sample, auto_id ), name=name, - location=os.path.abspath(json_file), + location=absolute_file_path(json_file), ) # resolve relative file paths diff --git a/src/inspect_ai/dataset/_sources/util.py b/src/inspect_ai/dataset/_sources/util.py index f325366a9c..d259c7926e 100644 --- a/src/inspect_ai/dataset/_sources/util.py +++ b/src/inspect_ai/dataset/_sources/util.py @@ -7,7 +7,7 @@ ContentImage, ContentVideo, ) -from inspect_ai._util.file import filesystem +from inspect_ai._util.file import dirname, filesystem from inspect_ai.model._chat_message import ChatMessage, ChatMessageUser from inspect_ai.util._sandbox.environment import SandboxEnvironmentSpec @@ -22,7 +22,7 @@ def resolve_sample_files(dataset: Dataset) -> None: # filesystem and parent for resolving paths fs = filesystem(dataset.location) - parent_dir = fs.sep.join(dataset.location.split(fs.sep)[:-1]) + parent_dir = dirname(dataset.location) # resolve file locations def resolve_file(file: str) -> str: diff --git a/tests/dataset/test_dataset.py b/tests/dataset/test_dataset.py index 4ab0bc980f..21d2c22c56 100644 --- a/tests/dataset/test_dataset.py +++ b/tests/dataset/test_dataset.py @@ -8,6 +8,7 @@ from test_helpers.utils import skip_if_github_action from inspect_ai._util.content import ContentImage +from inspect_ai._util.file import exists from inspect_ai.dataset import ( Dataset, FieldSpec, @@ -174,6 +175,21 @@ def test_dataset_image_paths() -> None: assert image.exists() +def test_dataset_image_paths_file_uri() -> None: + # dataset locations that are filesystem URIs should keep their scheme and + # still resolve relative sample files against the dataset's parent dir + dataset = json_dataset(Path(dataset_path("images.jsonl")).resolve().as_uri()) + assert dataset.location is not None + assert dataset.location.startswith("file://") + sample = dataset[0] + assert not isinstance(sample.input, str) + assert isinstance(sample.input[0], ChatMessageUser) + content = sample.input[0].content[1] + assert isinstance(content, ContentImage) + assert content.image.startswith("file://") + assert exists(content.image) + + def test_dataset_auto_id() -> None: dataset = json_dataset(dataset_path("dataset.jsonl")) assert all(sample.id is None for sample in dataset) From 8c120e5c2a0b70d050c8b5b87be9cb5e523071bf Mon Sep 17 00:00:00 2001 From: dragonstyle Date: Fri, 17 Jul 2026 18:02:53 -0400 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d392b122b..d72fbe8013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - Bugfix: Native compaction now truncates an oversized tool output before summarizing instead of crashing or silently summarizing an error when it would exceed the model's context window. (#3600) - Bugfix: React agent compaction now works after a checkpoint resume — the restored conversation is no longer treated as an always-preserved prefix, so compaction shrinks the context again. - Bugfix: Nested `score_reducer` and `task_source` values in serialized task args now restore as instances rather than their registered factories. (#4374) +- Bugfix: Properly resolve relative sample file paths and file URIs on windows (#4502) - Viewer: log listing responses include the log dir's canonical URI (`log_dir_uri`) so the viewer can reliably scope its local cache to the directory. - Inspect View: Reuse one warm async S3 client and connection pool across requests — for both log reads and directory listings — instead of creating one per operation, eliminating the per-request credential/connection cold-start (e.g. `/log-headers` ~3s -> ~0.3s, `/logs` ~1.5s -> ~0.06s). - Model Roles: `resolve_model_roles` now copies a `Model` passed by object (e.g. via `eval(model_roles=...)`) before stamping its role, so roles supplied through the Python API — not just `--model-role` — get a distinct instance and are not misattributed. (#4464)