From 51551b1d480684d75236fb4d6707c9b7f9996736 Mon Sep 17 00:00:00 2001 From: aisi-inspect <166920645+aisi-inspect@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:37:52 +0000 Subject: [PATCH] release v0.3.15 --- src/inspect_ai/_view/www/App.css | 22 ++++ src/inspect_ai/_view/www/App.mjs | 17 +-- .../_view/www/libs/prism/prism-dark.css | 108 +++++++++++++++++- .../_view/www/src/components/ChatView.mjs | 2 +- .../_view/www/src/samples/SampleDisplay.mjs | 16 +-- .../_view/www/src/samples/SampleList.mjs | 3 +- .../_tool/environment/docker/cleanup.py | 7 +- .../_tool/environment/docker/compose.py | 8 +- .../solver/_tool/environment/docker/config.py | 60 +++++----- .../solver/_tool/environment/docker/docker.py | 76 ++++++------ .../solver/_tool/environment/docker/util.py | 4 - tools/vscode/tools/ts-to-mjs/yarn.lock | 16 +-- 12 files changed, 228 insertions(+), 111 deletions(-) diff --git a/src/inspect_ai/_view/www/App.css b/src/inspect_ai/_view/www/App.css index 6d5a2dd12..701347fc7 100644 --- a/src/inspect_ai/_view/www/App.css +++ b/src/inspect_ai/_view/www/App.css @@ -441,6 +441,20 @@ pre[class*=language-] { margin-bottom: 0.75em; } + +.sample-answer .markdown-content h1, +.sample-answer .markdown-content h2, +.sample-answer .markdown-content h3, +.sample-answer .markdown-content h4, +.sample-answer .markdown-content h5, +.sample-answer .markdown-content h6 { + font-size: 1em; + font-weight: 400; + margin-top: 0em; + margin-bottom: 0em; +} + + .accordion-item:not(.no-highlight) .collapse.show.highlight-when-expanded, .accordion-item:not(.no-highlight) .collapsing.highlight-when-expanded, .accordion-item:not(.no-highlight) .accordion-button[aria-expanded="true"].highlight-when-expanded { @@ -604,4 +618,12 @@ table.table.table-sm td { .expandable-panel pre { overflow: unset; +} + +.tool-output { + background-color: #f8f8f8; +} + +.vscode-dark .tool-output { + background-color: #333333; } \ No newline at end of file diff --git a/src/inspect_ai/_view/www/App.mjs b/src/inspect_ai/_view/www/App.mjs index 52282f44a..a7e2ddc0e 100644 --- a/src/inspect_ai/_view/www/App.mjs +++ b/src/inspect_ai/_view/www/App.mjs @@ -1,5 +1,5 @@ import { html } from "htm/preact"; -import { useCallback, useState, useEffect, useRef } from "preact/hooks"; +import { useCallback, useState, useEffect, useMemo, useRef } from "preact/hooks"; // Registration component import "./src/Register.mjs"; @@ -267,13 +267,15 @@ export function App() { /> `; - const progress = () => { + const progress = useMemo(() => { if (status.loading) { return html`<${ProgressBar}/>`; + } else { + return undefined; } - } + }, [status]); - const workspace = () => { + const workspace = useMemo(() => { if (status.error) { return html`<${ErrorPanel} title="An error occurred while loading this task." @@ -289,13 +291,14 @@ export function App() { offcanvas=${offcanvas} />` } - } + }, [logs, currentLog, selected, fullScreen, offcanvas, status]); + return html` <${AppErrorBoundary}>
{ return sampleMetadatas; }; -const inputString = (input) => { - if (typeof input === "string") { - return input; - } else { - return input.map((inp) => { - if (typeof inp === "string") { - return inp; - } else { - return inp.content; - } - }); - } -}; - const SampleSummary = ({ id, sample, sampleDescriptor }) => { const input = sampleDescriptor?.messageShape.input > 0 diff --git a/src/inspect_ai/_view/www/src/samples/SampleList.mjs b/src/inspect_ai/_view/www/src/samples/SampleList.mjs index 739b72b99..53724f280 100644 --- a/src/inspect_ai/_view/www/src/samples/SampleList.mjs +++ b/src/inspect_ai/_view/www/src/samples/SampleList.mjs @@ -11,6 +11,7 @@ import { } from "../utils/Format.mjs"; import { EmptyPanel } from "../components/EmptyPanel.mjs"; import { VirtualList } from "../components/VirtualList.mjs"; +import { inputString } from "../utils/Format.mjs" const kSampleHeight = 82; const kSeparatorHeight = 20; @@ -240,7 +241,7 @@ const SampleRow = ({ ...cellStyle, }} > - ${sample.input} + ${inputString(sample.input)}None: # bring down services await compose_down(project=project, quiet=quiet) - # remove temp dir - project.temp_dir.cleanup() - # remove the project from the list of running projects running_projects().remove(project) @@ -53,6 +51,9 @@ async def project_cleanup_shutdown() -> None: f"Error cleaning up compose containers: {exception_message(result)}" ) + # cleanup auto config + auto_config_cleanup() + def running_projects() -> list[ComposeProject]: return _running_projects.get() diff --git a/src/inspect_ai/solver/_tool/environment/docker/compose.py b/src/inspect_ai/solver/_tool/environment/docker/compose.py index 23ccd0131..0744616d4 100644 --- a/src/inspect_ai/solver/_tool/environment/docker/compose.py +++ b/src/inspect_ai/solver/_tool/environment/docker/compose.py @@ -6,6 +6,7 @@ from inspect_ai.util._context.subprocess import ExecResult, subprocess +from .config import auto_config from .util import ComposeProject, tools_log logger = getLogger(__name__) @@ -129,7 +130,7 @@ async def compose_exec( async def compose_services(project: ComposeProject) -> dict[str, ComposeService]: result = await compose_command(["config"], project=project) if not result.success: - raise RuntimeError("Error reading docker config: {result.stderr}") + raise RuntimeError(f"Error reading docker config: {result.stderr}") return cast(dict[str, ComposeService], yaml.safe_load(result.stdout)["services"]) @@ -193,8 +194,9 @@ async def compose_command( compose_command = compose_command + ["--project-name", project.name] # add config file if specified - if project.config: - compose_command = compose_command + ["-f", project.config] + config = project.config if project.config else await auto_config() + if config: + compose_command = compose_command + ["-f", config] # build final command compose_command = compose_command + command diff --git a/src/inspect_ai/solver/_tool/environment/docker/config.py b/src/inspect_ai/solver/_tool/environment/docker/config.py index d1ca8958a..fe0c28e60 100644 --- a/src/inspect_ai/solver/_tool/environment/docker/config.py +++ b/src/inspect_ai/solver/_tool/environment/docker/config.py @@ -1,5 +1,4 @@ import os -import tempfile from logging import getLogger from pathlib import Path @@ -8,18 +7,28 @@ logger = getLogger(__name__) -async def auto_config(temp_dir: str) -> str | None: +async def auto_config() -> str | None: # compose file provides all the config we need if has_compose_file(): return None + # temporary auto-compose + if has_auto_compose_file(): + return AUTO_COMPOSE_YAML + # dockerfile just needs a compose.yaml synthesized elif has_dockerfile(): - return await dockerfile_compose(Path(), temp_dir) + return await auto_compose_file(COMPOSE_DOCKERFILE_YAML) # otherwise provide a generic python container else: - return await generic_container_compose(temp_dir) + return await auto_compose_file(COMPOSE_GENERIC_YAML) + + +def auto_config_cleanup() -> None: + # if we have an auto-generated .compose.yaml then clean it up + if has_auto_compose_file(): + Path(AUTO_COMPOSE_YAML).unlink(True) def has_compose_file() -> bool: @@ -39,37 +48,32 @@ def has_dockerfile() -> bool: return os.path.isfile("Dockerfile") -# Our default compose.yaml -COMPOSE_GENERIC_YAML = """ +def has_auto_compose_file() -> bool: + return os.path.isfile(AUTO_COMPOSE_YAML) + + +AUTO_COMPOSE_YAML = ".compose.yaml" + +COMPOSE_COMMENT = """# inspect auto-generated docker compose file +# (will be removed when task is complete)""" + +COMPOSE_GENERIC_YAML = f"""{COMPOSE_COMMENT} services: default: image: "python:3.12-bookworm" - command: tail -f /dev/null + command: "tail -f /dev/null" """ - -async def generic_container_compose(directory: str) -> str: - return await default_compose_file(directory, COMPOSE_GENERIC_YAML) - - -async def dockerfile_compose(context: Path, directory: str) -> str: - # Template for a DockerFile - compose_dockerfile_yaml = f""" +COMPOSE_DOCKERFILE_YAML = f"""{COMPOSE_COMMENT} services: default: build: - context: {context.resolve().as_posix()} - command: tail -f /dev/null - """ - - return await default_compose_file(directory, compose_dockerfile_yaml) + context: "." + command: "tail -f /dev/null" +""" -# Provide the path to a default compose file -async def default_compose_file(directory: str, contents: str) -> str: - with tempfile.NamedTemporaryFile( - dir=directory, suffix=".yaml", delete=False - ) as compose_file: - async with aiofiles.open(compose_file.name, "w", encoding="utf-8") as f: - await f.write(contents) - return compose_file.name +async def auto_compose_file(contents: str) -> str: + async with aiofiles.open(AUTO_COMPOSE_YAML, "w", encoding="utf-8") as f: + await f.write(contents) + return AUTO_COMPOSE_YAML diff --git a/src/inspect_ai/solver/_tool/environment/docker/docker.py b/src/inspect_ai/solver/_tool/environment/docker/docker.py index 820653746..14c97e462 100644 --- a/src/inspect_ai/solver/_tool/environment/docker/docker.py +++ b/src/inspect_ai/solver/_tool/environment/docker/docker.py @@ -26,7 +26,6 @@ compose_services, compose_up, ) -from .config import auto_config from .util import ComposeProject, task_project_name, tools_log logger = getLogger(__name__) @@ -39,41 +38,41 @@ async def task_init(cls, task_name: str, config: str | None) -> None: # intialize project cleanup project_cleanup_startup() - # create project - temp_dir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True) - project = ComposeProject( - name=task_project_name(task_name), - config=config if config else await auto_config(temp_dir.name), - env=dict(), - working_dir="/", - temp_dir=temp_dir, - ) - - # build containers which are out of date - await compose_build(project) - - # cleanup images created during build - await compose_cleanup_images(project) + try: + # create project + project = ComposeProject( + name=task_project_name(task_name), + config=config, + env=dict(), + working_dir="/", + ) - # pull any remote images - services = await compose_services(project) - for name, service in services.items(): - if ( - service.get("build", None) is None - and service.get("x-local", None) is None - ): - pull_result = await compose_pull(name, project) - if not pull_result.success: - image = service.get("image", "(unknown)") - logger.error( - f"Failed to pull docker image '{image}' from remote registry. If this is a locally built image add 'x-local: true' to the the service definition to prevent this error." - ) - - # cleanup temp_dir - temp_dir.cleanup() - - # provide some space above task display - print("") + # build containers which are out of date + await compose_build(project) + + # cleanup images created during build + await compose_cleanup_images(project) + + # pull any remote images + services = await compose_services(project) + for name, service in services.items(): + if ( + service.get("build", None) is None + and service.get("x-local", None) is None + ): + pull_result = await compose_pull(name, project) + if not pull_result.success: + image = service.get("image", "(unknown)") + logger.error( + f"Failed to pull docker image '{image}' from remote registry. If this is a locally built image add 'x-local: true' to the the service definition to prevent this error." + ) + + # provide some space above task display + print("") + + except BaseException as ex: + await project_cleanup_shutdown() + raise ex @classmethod async def task_cleanup(cls, task_name: str, config: str | None) -> None: @@ -92,13 +91,8 @@ async def sample_init( env[f"SAMPLE_METADATA_{key.replace(' ', '_').upper()}"] = str(value) # create project - temp_dir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True) project = ComposeProject( - name=task_project_name(task_name), - config=config if config else await auto_config(temp_dir.name), - env=env, - working_dir="/", - temp_dir=temp_dir, + name=task_project_name(task_name), config=config, env=env, working_dir="/" ) # enumerate the services that will be created diff --git a/src/inspect_ai/solver/_tool/environment/docker/util.py b/src/inspect_ai/solver/_tool/environment/docker/util.py index d45e4bab1..46fe48b85 100644 --- a/src/inspect_ai/solver/_tool/environment/docker/util.py +++ b/src/inspect_ai/solver/_tool/environment/docker/util.py @@ -1,4 +1,3 @@ -import tempfile from dataclasses import dataclass from logging import getLogger @@ -15,7 +14,6 @@ class ComposeProject: config: str | None env: dict[str, str] working_dir: str - temp_dir: tempfile.TemporaryDirectory[str] def __init__( self, @@ -23,13 +21,11 @@ def __init__( config: str | None, env: dict[str, str], working_dir: str, - temp_dir: tempfile.TemporaryDirectory[str], ) -> None: self.name = name self.config = config self.env = env self.working_dir = working_dir - self.temp_dir = temp_dir def __eq__(self, other: object) -> bool: if not isinstance(other, ComposeProject): diff --git a/tools/vscode/tools/ts-to-mjs/yarn.lock b/tools/vscode/tools/ts-to-mjs/yarn.lock index c28ca1399..505aa198c 100644 --- a/tools/vscode/tools/ts-to-mjs/yarn.lock +++ b/tools/vscode/tools/ts-to-mjs/yarn.lock @@ -1166,11 +1166,11 @@ __metadata: linkType: hard "braces@npm:^3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: ^7.0.1 - checksum: e2a8e769a863f3d4ee887b5fe21f63193a891c68b612ddb4b68d82d1b5f3ff9073af066c343e9867a393fe4c2555dcb33e89b937195feb9c1613d259edfcd459 + fill-range: ^7.1.1 + checksum: b95aa0b3bd909f6cd1720ffcf031aeaf46154dd88b4da01f9a1d3f7ea866a79eba76a6d01cbc3c422b2ee5cdc39a4f02491058d5df0d7bf6e6a162a832df1f69 languageName: node linkType: hard @@ -1633,12 +1633,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: ^5.0.1 - checksum: cc283f4e65b504259e64fd969bcf4def4eb08d85565e906b7d36516e87819db52029a76b6363d0f02d0d532f0033c9603b9e2d943d56ee3b0d4f7ad3328ff917 + checksum: b4abfbca3839a3d55e4ae5ec62e131e2e356bf4859ce8480c64c4876100f4df292a63e5bb1618e1d7460282ca2b305653064f01654474aa35c68000980f17798 languageName: node linkType: hard