Skip to content

Commit 4688935

Browse files
dragonstyleclaude
andcommitted
persist Task.viewer through EvalSpec
Adds a typed `viewer: ViewerConfig | None` parameter on `Task` that flows through `TaskLogger` into `EvalSpec.viewer`, so the authored descriptor round-trips through the eval log. Backward compatible: tasks without a viewer leave the field as None, and pre-existing eval logs (which have no viewer key) parse fine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dbab8dd commit 4688935

5 files changed

Lines changed: 59 additions & 0 deletions

File tree

‎src/inspect_ai/_eval/run.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ async def eval_run(
240240
model_args=resolved_task.model.model_args,
241241
eval_config=task_eval_config,
242242
metadata=((metadata or {}) | (task.metadata or {})) or None,
243+
viewer=task.viewer,
243244
recorder=recorder,
244245
header_only=header_only,
245246
)

‎src/inspect_ai/_eval/task/log.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from inspect_ai.solver._plan import Plan
5959
from inspect_ai.solver._solver import Solver, SolverSpec
6060
from inspect_ai.util._sandbox.environment import SandboxEnvironmentSpec
61+
from inspect_ai.viewer import ViewerConfig
6162

6263
logger = logging.getLogger(__name__)
6364

@@ -124,6 +125,7 @@ def __init__(
124125
model_args: dict[str, Any],
125126
eval_config: EvalConfig,
126127
metadata: dict[str, Any] | None,
128+
viewer: ViewerConfig | None,
127129
recorder: Recorder,
128130
header_only: bool,
129131
) -> None:
@@ -218,6 +220,7 @@ def __init__(
218220
revision=revision,
219221
packages=packages,
220222
metadata=metadata,
223+
viewer=viewer,
221224
)
222225

223226
# stack recorder and location

‎src/inspect_ai/_eval/task/task.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
SandboxEnvironmentType,
4444
resolve_sandbox_environment,
4545
)
46+
from inspect_ai.viewer import ViewerConfig
4647

4748
from .epochs import Epochs
4849

@@ -91,6 +92,7 @@ def __init__(
9192
version: int | str = 0,
9293
metadata: dict[str, Any] | None = None,
9394
tags: list[str] | None = None,
95+
viewer: ViewerConfig | None = None,
9496
**kwargs: Unpack[TaskDeprecatedArgs],
9597
) -> None:
9698
"""Create a task.
@@ -134,6 +136,9 @@ def __init__(
134136
of the task spec or breaking changes to it)
135137
metadata: Additional metadata to associate with the task.
136138
tags: Tags to associate with the task.
139+
viewer: Log viewer configuration for this task (controls how
140+
scanner results are rendered in the sidebar). Persisted in
141+
the eval log via `EvalSpec.viewer`.
137142
**kwargs: Deprecated arguments.
138143
"""
139144
# handle deprecated args
@@ -187,6 +192,7 @@ def __init__(
187192
self._name = name
188193
self.metadata = metadata
189194
self.tags = tags
195+
self.viewer = viewer
190196

191197
@property
192198
def name(self) -> str:

‎src/inspect_ai/log/_log.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from inspect_ai.util._sandbox.environment import SandboxEnvironmentSpec
3939
from inspect_ai.util._store import Store
4040
from inspect_ai.util._store_model import SMT
41+
from inspect_ai.viewer import ViewerConfig
4142

4243
from ..event._event import Event
4344
from ._util import thin_input, thin_metadata, thin_target, thin_text
@@ -913,6 +914,10 @@ class EvalSpec(BaseModel):
913914
metadata: dict[str, Any] | None = Field(default=None)
914915
"""Additional eval metadata."""
915916

917+
viewer: ViewerConfig | None = Field(default=None)
918+
"""Log viewer configuration — controls how scanner results are rendered
919+
in the sidebar. Authored via `Task(viewer=...)`."""
920+
916921
scorers: list[EvalScorer] | None = Field(default=None)
917922
"""Scorers and args for this eval"""
918923

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Round-trip `Task.viewer` through `EvalSpec.viewer` via the eval log."""
2+
3+
from inspect_ai import Task, eval
4+
from inspect_ai.viewer import (
5+
MetadataField,
6+
ScannerResultField,
7+
ScannerResultView,
8+
ViewerConfig,
9+
)
10+
11+
12+
def test_task_viewer_roundtrips_through_eval_log() -> None:
13+
"""`Task(viewer=cfg)` is persisted to `EvalSpec.viewer` and re-read equal."""
14+
cfg = ViewerConfig(
15+
scanner_result_view={
16+
"*": ScannerResultView(
17+
fields=[
18+
ScannerResultField(name="explanation", label="Rationale"),
19+
MetadataField(key="summary", label="Summary", collapsed=True),
20+
ScannerResultField(name="value"),
21+
"metadata",
22+
],
23+
exclude_fields=["answer", MetadataField(key="_internal_state")],
24+
),
25+
},
26+
)
27+
log = eval(Task(viewer=cfg), model="mockllm/model")[0]
28+
assert log.eval.viewer == cfg
29+
30+
31+
def test_task_without_viewer_persists_as_none() -> None:
32+
"""Tasks without a viewer argument leave `EvalSpec.viewer` as `None`."""
33+
log = eval(Task(), model="mockllm/model")[0]
34+
assert log.eval.viewer is None
35+
36+
37+
def test_bare_scanner_result_view_shorthand_roundtrips() -> None:
38+
"""Passing a bare `ScannerResultView` as `viewer=` is stored as-is."""
39+
cfg = ViewerConfig(
40+
scanner_result_view=ScannerResultView(fields=["value", "explanation"]),
41+
)
42+
log = eval(Task(viewer=cfg), model="mockllm/model")[0]
43+
assert log.eval.viewer == cfg
44+
assert isinstance(log.eval.viewer.scanner_result_view, ScannerResultView)

0 commit comments

Comments
 (0)