From 80db574ceec209a5b25651349b1c7667ae0f7c0d Mon Sep 17 00:00:00 2001 From: "Arpit Singh (SW-CLOUD)" Date: Mon, 10 Aug 2026 11:59:37 -0700 Subject: [PATCH 1/5] fix(evaluator): prefer sync SDK for FilesetRef dataset download Avoid binding the shared async platform client to a short-lived asyncio.run() loop before result-entity persistence. Signed-off-by: Arpit Singh (SW-CLOUD) --- .../src/nemo_evaluator/jobs/evaluate.py | 16 +++++---- .../nemo-evaluator/tests/test_evaluate_job.py | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py index 671d7fce70c..5b077ec047c 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py @@ -89,6 +89,16 @@ def _resolve_run_dataset( return dataset destination = str(ctx.storage.persistent / "dataset") + # Prefer the sync SDK so Files download does not bind the shared async + # httpx client to a short-lived asyncio.run() loop. Result persistence later + # reuses async_sdk via a second run_sync; reusing the same client across two + # closed loops raises "Event loop is closed" and silently drops the entity. + if sdk is not None: + return download_dataset_sync( + sdk=sdk, + dataset=dataset, + destination=destination, + ) if async_sdk is not None: return run_sync( lambda: download_dataset( @@ -97,12 +107,6 @@ def _resolve_run_dataset( destination=destination, ) ) - if sdk is not None: - return download_dataset_sync( - sdk=sdk, - dataset=dataset, - destination=destination, - ) raise ValueError("FilesetRef datasets require an SDK client for local evaluator job execution.") diff --git a/plugins/nemo-evaluator/tests/test_evaluate_job.py b/plugins/nemo-evaluator/tests/test_evaluate_job.py index b37a5bfde80..5440a92b291 100644 --- a/plugins/nemo-evaluator/tests/test_evaluate_job.py +++ b/plugins/nemo-evaluator/tests/test_evaluate_job.py @@ -1259,6 +1259,42 @@ def test_downloads_fileset_ref_dataset_with_sync_sdk_and_passes_path_to_sdk_eval assert call_kwargs["target"] is None assert call_kwargs["prompt_template"] is None + def test_prefers_sync_sdk_for_fileset_ref_when_both_sdks_injected( + self, tmp_path: Path, mocker: MockerFixture + ) -> None: + """Avoid binding async_sdk's httpx client before result-entity persistence.""" + result = _empty_evaluation_result() + result_payload = result.model_dump(mode="json") + evaluator = mocker.Mock() + evaluator.run_sync.return_value = result + mocker.patch("nemo_evaluator.jobs.evaluate.Evaluator", return_value=evaluator) + downloaded_path = tmp_path / "persistent" / "dataset" / "default" / "helpsteer2" / "validation.jsonl" + download_dataset = mocker.patch("nemo_evaluator.jobs.evaluate.download_dataset", create=True) + download_dataset_sync = mocker.patch( + "nemo_evaluator.jobs.evaluate.download_dataset_sync", + return_value=downloaded_path, + create=True, + ) + persist = mocker.patch("nemo_evaluator.jobs.evaluate.persist_evaluate_result") + ctx = _make_job_context(tmp_path) + sync_sdk = mocker.Mock() + async_sdk = mocker.Mock() + dataset = FilesetRef(root="default/helpsteer2#validation.jsonl") + config = {**_exact_match_spec(), "dataset": dataset} + + run_result = EvaluateJob().run(config, ctx=ctx, sdk=sync_sdk, async_sdk=async_sdk) + + _assert_saved_result_artifact(run_result, ctx, result_payload) + download_dataset.assert_not_called() + download_dataset_sync.assert_called_once_with( + sdk=sync_sdk, + dataset=dataset, + destination=str(ctx.storage.persistent / "dataset"), + ) + persist.assert_called_once() + assert persist.call_args.kwargs["async_sdk"] is async_sdk + assert persist.call_args.kwargs["dataset_ref"] == dataset.root + class TestEvaluateTask: """Coverage for the compiled container task entrypoint.""" From 75321d2d329898a24da27f6ff8c935e6f32d1fb2 Mon Sep 17 00:00:00 2001 From: "Arpit Singh (SW-CLOUD)" Date: Mon, 10 Aug 2026 12:24:51 -0700 Subject: [PATCH 2/5] fix(evaluator): clone async SDK client for FilesetRef download fallback When only async_sdk is available, download through a fresh httpx client so the injected shared client is not bound to a short-lived asyncio.run loop. Signed-off-by: Arpit Singh (SW-CLOUD) --- .../src/nemo_evaluator/jobs/evaluate.py | 29 +++++++++++++++---- .../nemo-evaluator/tests/test_evaluate_job.py | 11 +++++-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py index 5b077ec047c..9cf136513d9 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py @@ -39,7 +39,7 @@ ) from nemo_evaluator_sdk.values.multi_metric_results import BenchmarkEvaluationResult from nemo_evaluator_sdk.values.results import EvaluationResult -from nemo_platform import AsyncNeMoPlatform, NeMoPlatform +from nemo_platform import AsyncNeMoPlatform, DefaultAsyncHttpxClient, NeMoPlatform from nemo_platform_plugin.entities import EntityClient from nemo_platform_plugin.job import NemoJob from nemo_platform_plugin.job_context import JobContext @@ -77,6 +77,25 @@ class EvaluationResultFiles: artifacts_dir: Path +async def _download_dataset_with_cloned_async_sdk( + async_sdk: AsyncNeMoPlatform, + dataset: FilesetRef, + destination: str, +) -> Path: + """Download via a throwaway httpx client so the injected async SDK stays reusable. + + ``run_sync`` uses ``asyncio.run``, which closes its loop. Binding the shared + ``async_sdk`` httpx client to that loop would leave it unusable for later + ``run_sync`` calls (e.g. result-entity persistence). + """ + async with DefaultAsyncHttpxClient() as http_client: + return await download_dataset( + sdk=async_sdk.copy(http_client=http_client), + dataset=dataset, + destination=destination, + ) + + def _resolve_run_dataset( dataset: DatasetSpec, *, @@ -101,10 +120,10 @@ def _resolve_run_dataset( ) if async_sdk is not None: return run_sync( - lambda: download_dataset( - sdk=async_sdk, - dataset=dataset, - destination=destination, + lambda: _download_dataset_with_cloned_async_sdk( + async_sdk, + dataset, + destination, ) ) raise ValueError("FilesetRef datasets require an SDK client for local evaluator job execution.") diff --git a/plugins/nemo-evaluator/tests/test_evaluate_job.py b/plugins/nemo-evaluator/tests/test_evaluate_job.py index 5440a92b291..5a76398bd46 100644 --- a/plugins/nemo-evaluator/tests/test_evaluate_job.py +++ b/plugins/nemo-evaluator/tests/test_evaluate_job.py @@ -1202,16 +1202,23 @@ def test_downloads_fileset_ref_dataset_and_passes_path_to_sdk_evaluator( create=True, ) download_dataset_sync = mocker.patch("nemo_evaluator.jobs.evaluate.download_dataset_sync", create=True) + cloned_sdk = mocker.Mock(name="cloned_async_sdk") + async_sdk = mocker.Mock(name="async_sdk") + async_sdk.copy.return_value = cloned_sdk + http_client = mocker.AsyncMock(name="http_client") + http_client.__aenter__.return_value = http_client + http_client.__aexit__.return_value = None + mocker.patch("nemo_evaluator.jobs.evaluate.DefaultAsyncHttpxClient", return_value=http_client) ctx = _make_job_context(tmp_path) - async_sdk = mocker.Mock() dataset = FilesetRef(root="default/helpsteer2#validation.jsonl") config = {**_exact_match_spec(), "dataset": dataset} run_result = EvaluateJob().run(config, ctx=ctx, async_sdk=async_sdk) _assert_saved_result_artifact(run_result, ctx, result_payload) + async_sdk.copy.assert_called_once_with(http_client=http_client) download_dataset.assert_awaited_once_with( - sdk=async_sdk, + sdk=cloned_sdk, dataset=dataset, destination=str(ctx.storage.persistent / "dataset"), ) From 3d5c8dbbb2c283dce26aea3da29ac5ec5102b242 Mon Sep 17 00:00:00 2001 From: "Arpit Singh (SW-CLOUD)" Date: Mon, 10 Aug 2026 14:06:52 -0700 Subject: [PATCH 3/5] fix(evaluator): isolate async SDK for job run_sync call sites Share run_with_isolated_async_sdk across FilesetRef download, result persistence, and Intake publication so shared httpx clients are not bound to short-lived asyncio.run loops. Signed-off-by: Arpit Singh (SW-CLOUD) --- .../src/nemo_evaluator/jobs/evaluate.py | 38 ++++--------------- .../src/nemo_evaluator/jobs/publication.py | 11 +++--- .../nemo_evaluator/jobs/result_persistence.py | 13 +++++-- .../src/nemo_evaluator/jobs/utils.py | 27 +++++++++++++ .../tests/jobs/test_publication.py | 7 ++++ .../nemo-evaluator/tests/jobs/test_utils.py | 29 ++++++++++++++ .../nemo-evaluator/tests/test_evaluate_job.py | 2 +- .../tests/test_result_persistence.py | 17 ++++++++- 8 files changed, 102 insertions(+), 42 deletions(-) create mode 100644 plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py create mode 100644 plugins/nemo-evaluator/tests/jobs/test_utils.py diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py index 9cf136513d9..8796b6037d0 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py @@ -23,11 +23,11 @@ unresolved_model_refs, ) from nemo_evaluator.jobs.result_persistence import persist_evaluate_result +from nemo_evaluator.jobs.utils import run_with_isolated_async_sdk from nemo_evaluator.metric_refs import MetricRefOrInline from nemo_evaluator.shared.metric_bundles.bundles import unbundle_metric from nemo_evaluator_sdk import Evaluator from nemo_evaluator_sdk.execution.config import resolve_params -from nemo_evaluator_sdk.execution.metric_execution import run_sync from nemo_evaluator_sdk.values import ( Agent, AgentBase, @@ -39,7 +39,7 @@ ) from nemo_evaluator_sdk.values.multi_metric_results import BenchmarkEvaluationResult from nemo_evaluator_sdk.values.results import EvaluationResult -from nemo_platform import AsyncNeMoPlatform, DefaultAsyncHttpxClient, NeMoPlatform +from nemo_platform import AsyncNeMoPlatform, NeMoPlatform from nemo_platform_plugin.entities import EntityClient from nemo_platform_plugin.job import NemoJob from nemo_platform_plugin.job_context import JobContext @@ -77,25 +77,6 @@ class EvaluationResultFiles: artifacts_dir: Path -async def _download_dataset_with_cloned_async_sdk( - async_sdk: AsyncNeMoPlatform, - dataset: FilesetRef, - destination: str, -) -> Path: - """Download via a throwaway httpx client so the injected async SDK stays reusable. - - ``run_sync`` uses ``asyncio.run``, which closes its loop. Binding the shared - ``async_sdk`` httpx client to that loop would leave it unusable for later - ``run_sync`` calls (e.g. result-entity persistence). - """ - async with DefaultAsyncHttpxClient() as http_client: - return await download_dataset( - sdk=async_sdk.copy(http_client=http_client), - dataset=dataset, - destination=destination, - ) - - def _resolve_run_dataset( dataset: DatasetSpec, *, @@ -108,10 +89,8 @@ def _resolve_run_dataset( return dataset destination = str(ctx.storage.persistent / "dataset") - # Prefer the sync SDK so Files download does not bind the shared async - # httpx client to a short-lived asyncio.run() loop. Result persistence later - # reuses async_sdk via a second run_sync; reusing the same client across two - # closed loops raises "Event loop is closed" and silently drops the entity. + # Prefer sync when available; async path isolates httpx so later run_sync calls + # (result persistence) can reuse the injected async_sdk. if sdk is not None: return download_dataset_sync( sdk=sdk, @@ -119,12 +98,9 @@ def _resolve_run_dataset( destination=destination, ) if async_sdk is not None: - return run_sync( - lambda: _download_dataset_with_cloned_async_sdk( - async_sdk, - dataset, - destination, - ) + return run_with_isolated_async_sdk( + async_sdk, + lambda sdk: download_dataset(sdk=sdk, dataset=dataset, destination=destination), ) raise ValueError("FilesetRef datasets require an SDK client for local evaluator job execution.") diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/publication.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/publication.py index a6bc485fa98..5e7fff3cea5 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/publication.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/publication.py @@ -18,8 +18,8 @@ from nemo_evaluator.intake.publish import PublishError, PublishReport, publish_to_intake from nemo_evaluator.jobs.agent_spec import IntakePublicationSpec, Target, target_agent_identity +from nemo_evaluator.jobs.utils import run_with_isolated_async_sdk from nemo_evaluator_sdk.agent_eval.results import AgentEvalResult -from nemo_evaluator_sdk.execution.metric_execution import run_sync from nemo_platform import AsyncNeMoPlatform from nemo_platform._exceptions import NeMoPlatformError, NotFoundError from nemo_platform_plugin.jobs.schemas import PlatformJobStatus @@ -153,15 +153,16 @@ def fail(error: str, report: PublishReport | None = None) -> PublicationOutcome: workspace, ) try: - report = run_sync( - lambda: _publish( + report = run_with_isolated_async_sdk( + async_sdk, + lambda sdk: _publish( result, - platform=async_sdk, + platform=sdk, spec=spec, workspace=workspace, agent_name=agent_name, model_name=model_name, - ) + ), ) except PublishError as error: return fail(str(error), error.report) diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py index 5f71ac3eccb..b3a32b24c0e 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py @@ -28,8 +28,8 @@ ModelTarget, Target, ) +from nemo_evaluator.jobs.utils import run_with_isolated_async_sdk from nemo_evaluator_sdk.agent_eval.results import AgentEvalResult -from nemo_evaluator_sdk.execution.metric_execution import run_sync from nemo_evaluator_sdk.values import Agent, AgentBase, Model from nemo_evaluator_sdk.values.multi_metric_results import BenchmarkEvaluationResult from nemo_evaluator_sdk.values.results import EvaluationResult @@ -107,15 +107,20 @@ def _row_target_fields(target: Model | Agent | None) -> tuple[str | None, str | def _persist(entity: EntityBase, *, async_sdk: AsyncNeMoPlatform | None) -> None: - client = _entity_client(async_sdk) - if client is None: + if async_sdk is None: logger.info("No async task SDK injected; skipping result-entity persistence (platformless local run).") return # Best-effort: the eval has already succeeded and the full bundle is saved, so a transient # entity-store error must not fail the job — the record is re-derivable from the bundle. Log # loudly and move on. (`save` is create-or-update, so a re-run of the same job id is idempotent.) try: - run_sync(lambda: client.save(entity)) + + async def _save(sdk: AsyncNeMoPlatform) -> EntityBase: + client = _entity_client(sdk) + assert client is not None + return await client.save(entity) + + run_with_isolated_async_sdk(async_sdk, _save) except Exception: logger.exception( "Failed to persist result entity %r in workspace %r; the result bundle is still saved", diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py new file mode 100644 index 00000000000..f081f7efe17 --- /dev/null +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Shared helpers for evaluator plugin jobs.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from typing import TypeVar + +from nemo_evaluator_sdk.execution.metric_execution import run_sync +from nemo_platform import AsyncNeMoPlatform, DefaultAsyncHttpxClient + +T = TypeVar("T") + + +def run_with_isolated_async_sdk( + async_sdk: AsyncNeMoPlatform, + fn: Callable[[AsyncNeMoPlatform], Awaitable[T]], +) -> T: + """Run ``fn(cloned_sdk)`` via ``run_sync`` without binding ``async_sdk``'s httpx client.""" + + async def _run() -> T: + async with DefaultAsyncHttpxClient() as http_client: + return await fn(async_sdk.copy(http_client=http_client)) + + return run_sync(_run) diff --git a/plugins/nemo-evaluator/tests/jobs/test_publication.py b/plugins/nemo-evaluator/tests/jobs/test_publication.py index 310aa20eda1..2af1160a362 100644 --- a/plugins/nemo-evaluator/tests/jobs/test_publication.py +++ b/plugins/nemo-evaluator/tests/jobs/test_publication.py @@ -117,6 +117,7 @@ def __init__( self.atif_calls: list[dict[str, Any]] = [] self.eval_result_calls: list[dict[str, Any]] = [] self.trace_calls: _SessionIds = [] + self.copy_calls = 0 self.evaluations = _FakeEvaluations(missing=missing_evaluation, error=preflight_error) self.intake = SimpleNamespace( ingest=SimpleNamespace(atif=_FakeIngest(self.atif_calls, error=ingest_error)), @@ -124,6 +125,11 @@ def __init__( traces=_FakeTraces(self.trace_calls), ) + def copy(self, **kwargs: Any) -> _FakeClient: + del kwargs + self.copy_calls += 1 + return self + def _client(**kwargs: Any) -> AsyncNeMoPlatform: return cast(AsyncNeMoPlatform, _FakeClient(**kwargs)) @@ -267,6 +273,7 @@ def test_publishes_and_reports_what_landed() -> None: client = _FakeClient() outcome = _publish(cast(AsyncNeMoPlatform, client)) + assert client.copy_calls == 1 assert outcome.status == PlatformJobStatus.COMPLETED assert outcome.evaluation_id == "eval-1" assert outcome.trial_count == 1 diff --git a/plugins/nemo-evaluator/tests/jobs/test_utils.py b/plugins/nemo-evaluator/tests/jobs/test_utils.py new file mode 100644 index 00000000000..54dce48972b --- /dev/null +++ b/plugins/nemo-evaluator/tests/jobs/test_utils.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Unit tests for evaluator job helpers.""" + +from __future__ import annotations + +from nemo_evaluator.jobs.utils import run_with_isolated_async_sdk +from pytest_mock import MockerFixture + + +def test_run_with_isolated_async_sdk_passes_cloned_client(mocker: MockerFixture) -> None: + cloned = mocker.Mock(name="cloned_sdk") + async_sdk = mocker.Mock(name="async_sdk") + async_sdk.copy.return_value = cloned + http_client = mocker.AsyncMock(name="http_client") + http_client.__aenter__.return_value = http_client + http_client.__aexit__.return_value = None + mocker.patch("nemo_evaluator.jobs.utils.DefaultAsyncHttpxClient", return_value=http_client) + + seen: list[object] = [] + + async def fn(sdk: object) -> int: + seen.append(sdk) + return 42 + + assert run_with_isolated_async_sdk(async_sdk, fn) == 42 + async_sdk.copy.assert_called_once_with(http_client=http_client) + assert seen == [cloned] diff --git a/plugins/nemo-evaluator/tests/test_evaluate_job.py b/plugins/nemo-evaluator/tests/test_evaluate_job.py index 5a76398bd46..069d85ccb35 100644 --- a/plugins/nemo-evaluator/tests/test_evaluate_job.py +++ b/plugins/nemo-evaluator/tests/test_evaluate_job.py @@ -1208,7 +1208,7 @@ def test_downloads_fileset_ref_dataset_and_passes_path_to_sdk_evaluator( http_client = mocker.AsyncMock(name="http_client") http_client.__aenter__.return_value = http_client http_client.__aexit__.return_value = None - mocker.patch("nemo_evaluator.jobs.evaluate.DefaultAsyncHttpxClient", return_value=http_client) + mocker.patch("nemo_evaluator.jobs.utils.DefaultAsyncHttpxClient", return_value=http_client) ctx = _make_job_context(tmp_path) dataset = FilesetRef(root="default/helpsteer2#validation.jsonl") config = {**_exact_match_spec(), "dataset": dataset} diff --git a/plugins/nemo-evaluator/tests/test_result_persistence.py b/plugins/nemo-evaluator/tests/test_result_persistence.py index 54a2e6839cf..d73ac71886f 100644 --- a/plugins/nemo-evaluator/tests/test_result_persistence.py +++ b/plugins/nemo-evaluator/tests/test_result_persistence.py @@ -40,9 +40,22 @@ from nemo_platform_plugin.job_results import LocalJobResults from pytest_mock import MockerFixture + # An opaque stand-in for the async task SDK: every test that reaches the save path patches # `_entity_client`, so the value is never used as a real client — only its presence matters. -_ASYNC_SDK = cast(AsyncNeMoPlatform, object()) +# ``copy`` returns self so ``run_with_isolated_async_sdk`` can isolate a throwaway httpx client. +class _FakeAsyncSdk: + def __init__(self) -> None: + self.copy_calls = 0 + + def copy(self, **kwargs: object) -> _FakeAsyncSdk: + del kwargs + self.copy_calls += 1 + return self + + +_ASYNC_SDK_FAKE = _FakeAsyncSdk() +_ASYNC_SDK = cast(AsyncNeMoPlatform, _ASYNC_SDK_FAKE) def test_entity_client_adapts_async_sdk(mocker: MockerFixture) -> None: @@ -165,6 +178,7 @@ def _eval_result() -> EvaluationResult: def test_persist_agent_eval_result_builds_entity_and_saves(tmp_path: Path, mocker: MockerFixture) -> None: client = _FakeClient() mocker.patch.object(result_persistence, "_entity_client", return_value=client) + _ASYNC_SDK_FAKE.copy_calls = 0 persist_agent_eval_result( _agent_result(), @@ -174,6 +188,7 @@ def test_persist_agent_eval_result_builds_entity_and_saves(tmp_path: Path, mocke async_sdk=_ASYNC_SDK, ) + assert _ASYNC_SDK_FAKE.copy_calls == 1 (entity,) = client.saved assert isinstance(entity, AgentEvalResultEntity) assert entity.name == "job-1" From 042620f023ad0e5532c74597b56af0bfb92d7cc0 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Mon, 10 Aug 2026 15:06:49 -0700 Subject: [PATCH 4/5] Update plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py Co-authored-by: Nick Goncharenko <8766167+ngoncharenko@users.noreply.github.com> Signed-off-by: Arpit Singh (SW-CLOUD) --- plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py index f081f7efe17..c9e674b8c41 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py @@ -18,7 +18,12 @@ def run_with_isolated_async_sdk( async_sdk: AsyncNeMoPlatform, fn: Callable[[AsyncNeMoPlatform], Awaitable[T]], ) -> T: - """Run ``fn(cloned_sdk)`` via ``run_sync`` without binding ``async_sdk``'s httpx client.""" + """Run ``fn(cloned_sdk)`` via ``run_sync`` without binding ``async_sdk``'s httpx client. + + Clones ``async_sdk`` onto a throwaway httpx client for the duration of ``fn`` + so the injected client's transport is not bound to this temporary event loop + (and later ``run_sync`` calls can still reuse ``async_sdk``). + """ async def _run() -> T: async with DefaultAsyncHttpxClient() as http_client: From 9f86a902df785afb5068d97650d129ebc501b4e1 Mon Sep 17 00:00:00 2001 From: "Arpit Singh (SW-CLOUD)" Date: Mon, 10 Aug 2026 15:26:05 -0700 Subject: [PATCH 5/5] style(evaluator): fix docstring indentation in job utils Signed-off-by: Arpit Singh (SW-CLOUD) --- plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py index c9e674b8c41..43145757869 100644 --- a/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py +++ b/plugins/nemo-evaluator/src/nemo_evaluator/jobs/utils.py @@ -19,10 +19,10 @@ def run_with_isolated_async_sdk( fn: Callable[[AsyncNeMoPlatform], Awaitable[T]], ) -> T: """Run ``fn(cloned_sdk)`` via ``run_sync`` without binding ``async_sdk``'s httpx client. - - Clones ``async_sdk`` onto a throwaway httpx client for the duration of ``fn`` - so the injected client's transport is not bound to this temporary event loop - (and later ``run_sync`` calls can still reuse ``async_sdk``). + + Clones ``async_sdk`` onto a throwaway httpx client for the duration of ``fn`` so the injected + client's transport is not bound to this temporary event loop (and later ``run_sync`` calls can + still reuse ``async_sdk``). """ async def _run() -> T: