Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion e2e/agents_deploy_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import pytest
from nemo_agents_plugin.entities import NAT_WORKFLOW_CONFIG_FORMAT, NEMO_AGENTS_SPEC_CONFIG_FORMAT
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.models.client import ModelsClient
from nmp.testing import MockProviderResponse, add_mock_provider

# The mocked completion the deployed agent must round-trip back to the caller.
Expand Down Expand Up @@ -274,7 +276,7 @@ def run_agent_deploy_and_invoke(
endpoints = deployment.get("endpoints") or []
assert endpoints and endpoints[0]["url"], deployment

sdk.models.wait_for_openai_model(model_name, workspace=workspace)
client_from_platform(sdk, ModelsClient).wait_for_openai_model(model_name, workspace=workspace)

response = sdk.agents.invoke(
workspace=workspace,
Expand Down
20 changes: 13 additions & 7 deletions e2e/test_evaluator_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@
from nemo_evaluator_sdk.metrics.tool_calling import ToolCallingMetric
from nemo_evaluator_sdk.values.results import EvaluationResult
from nemo_evaluator_sdk.values.scores import JSONScoreParser, RangeScore
from nemo_platform import APIConnectionError, APIStatusError, NeMoPlatform
from nemo_platform.types.inference import ModelProvider
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.client.errors import NemoHTTPError as APIStatusError
from nemo_platform_plugin.client.errors import NemoTransportError as APIConnectionError
from nemo_platform_plugin.models.client import ModelsClient
from nemo_platform_plugin.models.types import CreateModelEntityRequest, ModelProvider
from nmp.testing import add_mock_provider, short_unique_name, wait_for_model_entity
from nmp.testing.e2e import wait_for_platform_job
from nmp.testing.utils import ensure_passthrough_virtual_model
Expand Down Expand Up @@ -261,13 +265,15 @@ def _create_ready_mock_model(
mock_response_body=mock_response_body,
should_autoprovision_virtual_model=False,
)
sdk.models.create(
client_from_platform(sdk, ModelsClient).create_model(
workspace=workspace,
name=name,
backend_format="OPENAI_CHAT",
model_providers=[f"{workspace}/{provider.name}"],
body=CreateModelEntityRequest(
name=name,
backend_format="OPENAI_CHAT",
model_providers=[f"{workspace}/{provider.name}"],
),
exist_ok=True,
)
).data()
wait_for_model_entity(
sdk,
workspace,
Expand Down
4 changes: 2 additions & 2 deletions packages/nemo_nb/tests/test_myst_stripping.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def test_mixed_content():

```python
# Regular code
sdk.models.deploy()
sdk.models.create_deployment()
```

:::{warning}
Expand All @@ -267,4 +267,4 @@ def test_mixed_content():
assert "Use the CLI for faster deployment." in result
assert "Make sure GPU resources are configured." in result
assert "```python" in result
assert "sdk.models.deploy()" in result
assert "sdk.models.create_deployment()" in result
6 changes: 5 additions & 1 deletion packages/nemo_nb/tests/test_notebook_splitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,11 @@ def test_real_world_tab_set_example(self):
{"cell_type": "code", "metadata": {"language": "bash"}, "source": ["nemo models list\n"]},
{"cell_type": "markdown", "source": [":::\n"]},
{"cell_type": "markdown", "source": [":::{tab-item} Python SDK\n", ":sync: python-sdk\n"]},
{"cell_type": "code", "metadata": {"language": "python"}, "source": ["client.models.list()\n"]},
{
"cell_type": "code",
"metadata": {"language": "python"},
"source": ["client_from_platform(client, ModelsClient).list_models()\n"],
},
{"cell_type": "markdown", "source": [":::\n"]},
{"cell_type": "markdown", "source": ["::::\n"]},
]
Expand Down
6 changes: 3 additions & 3 deletions packages/nemo_nb/tests/test_strip_type_checker_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_strip_ty_ignore_comments():
"metadata": {"language": "python"},
"source": [
"# This is a regular comment\n",
"sdk.models.get_openai_route_base_url()\n",
"client_from_platform(sdk, ModelsClient).get_openai_route_base_url()\n",
"sdk.models.get_model_entity_route_openai_url(entity) # ty: ignore[unresolved-reference]\n",
"sdk.models.get_provider_route_openai_url(provider) # ty: ignore[unresolved-reference]\n",
],
Expand All @@ -35,8 +35,8 @@ def test_strip_ty_ignore_comments():
assert "# This is a regular comment" in result

# Verify that the code lines are still there (without the ty: comments)
assert "sdk.models.get_model_entity_route_openai_url(entity)" in result
assert "sdk.models.get_provider_route_openai_url(provider)" in result
assert "client_from_platform(sdk, ModelsClient).get_model_entity_route_openai_url(entity)" in result
assert "client_from_platform(sdk, ModelsClient).get_provider_route_openai_url(provider)" in result


def test_strip_type_ignore_comments():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_generate_python_code_simple_list():

assert "from nemo_platform import NeMoPlatform" in code
assert "client = NeMoPlatform()" in code
assert "response = client.models.list()" in code
assert "response = client_from_platform(client, ModelsClient).list_models()" in code
assert "print(response)" in code


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Typed HTTP clients for the Agents service.

Wraps the endpoint functions from ``agents.endpoints`` as direct methods
using the ``method()`` descriptor, following the files/models pattern.
"""

from nemo_platform_plugin.agents import endpoints
from nemo_platform_plugin.client.client import AsyncNemoClient, NemoClient
from nemo_platform_plugin.client.method import method


class _AgentsMethods:
get_agent = method(endpoints.get_agent)
list_agents = method(endpoints.list_agents)
create_agent = method(endpoints.create_agent)
delete_agent = method(endpoints.delete_agent)
get_deployment = method(endpoints.get_deployment)
list_deployments = method(endpoints.list_deployments)
create_deployment = method(endpoints.create_deployment)
delete_deployment = method(endpoints.delete_deployment)
invoke_agent = method(endpoints.invoke_agent)
invoke_deployment = method(endpoints.invoke_deployment)


class AgentsClient(_AgentsMethods, NemoClient):
"""Sync client for the Agents service API."""


class AsyncAgentsClient(_AgentsMethods, AsyncNemoClient):
"""Async client for the Agents service API."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Typed endpoint definitions for the Agents service.

Single source of truth for the HTTP contract. Replaces the Stainless-generated
agent resource from ``nemo_agents_plugin.sdk``.
"""

from __future__ import annotations

from abc import abstractmethod
from typing import Any

from nemo_platform_plugin.agents.types import (
Agent,
AgentDeployment,
CreateAgentDeploymentRequest,
CreateAgentRequest,
InvokeAgentRequest,
ListAgentsQueryParams,
ListDeploymentsQueryParams,
)
from nemo_platform_plugin.client.endpoint import delete, get, post
from nemo_platform_plugin.client.types import Paginated, PreparedRequest

_AGENTS = "/apis/agents/v2/workspaces/{workspace}/agents"
_DEPLOYMENTS = "/apis/agents/v2/workspaces/{workspace}/deployments"


# ---------------------------------------------------------------------------
# Agent CRUD
# ---------------------------------------------------------------------------


@get(f"{_AGENTS}/{{name}}")
@abstractmethod
def get_agent(*, workspace: str | None = None, name: str) -> Agent: ...


@get(_AGENTS)
@abstractmethod
def list_agents(
*, workspace: str | None = None, query_params: ListAgentsQueryParams | None = None
) -> Paginated[Agent]: ...


def _get_agent_on_conflict(body: CreateAgentRequest, workspace: str | None) -> PreparedRequest[Agent]:
"""Build the retrieve request replayed when ``create_agent(exist_ok=True)`` 409s."""
return get_agent(name=body.name, workspace=workspace)


@post(_AGENTS, get_on_conflict=_get_agent_on_conflict)
@abstractmethod
def create_agent(*, workspace: str | None = None, body: CreateAgentRequest, exist_ok: bool = False) -> Agent: ...


@delete(f"{_AGENTS}/{{name}}")
@abstractmethod
def delete_agent(*, workspace: str | None = None, name: str) -> None: ...


# ---------------------------------------------------------------------------
# Agent deployment CRUD
# ---------------------------------------------------------------------------


@get(f"{_DEPLOYMENTS}/{{name}}")
@abstractmethod
def get_deployment(*, workspace: str | None = None, name: str) -> AgentDeployment: ...


@get(_DEPLOYMENTS)
@abstractmethod
def list_deployments(
*, workspace: str | None = None, query_params: ListDeploymentsQueryParams | None = None
) -> Paginated[AgentDeployment]: ...


@post(_DEPLOYMENTS)
@abstractmethod
def create_deployment(*, workspace: str | None = None, body: CreateAgentDeploymentRequest) -> AgentDeployment: ...


@delete(f"{_DEPLOYMENTS}/{{name}}")
@abstractmethod
def delete_deployment(*, workspace: str | None = None, name: str) -> None: ...


# ---------------------------------------------------------------------------
# Agent invocation (gateway proxy to OpenAI-compatible endpoint)
# ---------------------------------------------------------------------------


@post(f"{_AGENTS}/{{name}}/-/v1/chat/completions")
@abstractmethod
def invoke_agent(*, workspace: str | None = None, name: str, body: InvokeAgentRequest) -> dict[str, Any]: ...


@post(f"{_DEPLOYMENTS}/{{name}}/-/v1/chat/completions")
@abstractmethod
def invoke_deployment(*, workspace: str | None = None, name: str, body: InvokeAgentRequest) -> dict[str, Any]: ...
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Shared request/response types for the Agents service.

Single source of truth for the HTTP contract. Replaces the Stainless-generated
agent resource from ``nemo_agents_plugin.sdk``.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, NotRequired, TypedDict

from nemo_platform_plugin.schema import Page
from pydantic import BaseModel, ConfigDict, Field

# ---------------------------------------------------------------------------
# Response types
# ---------------------------------------------------------------------------


class Agent(BaseModel):
"""An agent definition — stores agent config and metadata."""

model_config = ConfigDict(extra="allow")

name: str = ""
workspace: str = ""
project: str | None = None
description: str = ""
config: dict[str, Any] = Field(default_factory=dict)
config_format: str = "nat-workflow-v1"
id: str | None = None
created_at: datetime | None = None
created_by: str | None = None
updated_at: datetime | None = None
updated_by: str | None = None


AgentPage = Page[Agent]


class AgentDeployment(BaseModel):
"""A running (or pending) deployment of an Agent."""

model_config = ConfigDict(extra="allow")

name: str = ""
workspace: str = ""
project: str | None = None
agent: str = ""
config: dict[str, Any] = Field(default_factory=dict)
status: str = "pending"
deployment_mode: str = "subprocess"
endpoint: str = ""
id: str | None = None
created_at: datetime | None = None
updated_at: datetime | None = None


DeploymentPage = Page[AgentDeployment]


# ---------------------------------------------------------------------------
# Request types
# ---------------------------------------------------------------------------


class CreateAgentRequest(BaseModel):
name: str
description: str = ""
config: dict[str, Any] = Field(default_factory=dict)
config_format: str = "nat-workflow-v1"


class CreateAgentDeploymentRequest(BaseModel):
agent: str
config: dict[str, Any] = Field(default_factory=dict)


class InvokeAgentRequest(BaseModel):
"""OpenAI chat-completions request body for agent invocation."""

model_config = ConfigDict(extra="allow")

messages: list[dict[str, Any]] = Field(default_factory=list)
stream: bool = False


# ---------------------------------------------------------------------------
# Query parameter types
# ---------------------------------------------------------------------------


class ListAgentsQueryParams(TypedDict, total=False):
page: NotRequired[int]
page_size: NotRequired[int]
sort: NotRequired[str]
filter: NotRequired[str]


class ListDeploymentsQueryParams(TypedDict, total=False):
page: NotRequired[int]
page_size: NotRequired[int]
sort: NotRequired[str]
filter: NotRequired[str]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Typed HTTP clients for the Auditor service.

Wraps the endpoint functions from ``auditor.endpoints`` as direct methods
using the ``method()`` descriptor, following the files/models pattern.
"""

from nemo_platform_plugin.auditor import endpoints
from nemo_platform_plugin.client.client import AsyncNemoClient, NemoClient
from nemo_platform_plugin.client.method import method


class _AuditorMethods:
get_audit_config = method(endpoints.get_audit_config)
list_audit_configs = method(endpoints.list_audit_configs)
create_audit_config = method(endpoints.create_audit_config)
update_audit_config = method(endpoints.update_audit_config)
delete_audit_config = method(endpoints.delete_audit_config)
get_audit_target = method(endpoints.get_audit_target)
list_audit_targets = method(endpoints.list_audit_targets)
create_audit_target = method(endpoints.create_audit_target)
update_audit_target = method(endpoints.update_audit_target)
delete_audit_target = method(endpoints.delete_audit_target)
submit_audit = method(endpoints.submit_audit)
list_audit_jobs = method(endpoints.list_audit_jobs)
get_audit_job = method(endpoints.get_audit_job)


class AuditorClient(_AuditorMethods, NemoClient):
"""Sync client for the Auditor service API."""


class AsyncAuditorClient(_AuditorMethods, AsyncNemoClient):
"""Async client for the Auditor service API."""
Loading