Skip to main content

plugin-platform-services

Calls NeMo Platform services (entity store, jobs, files, secrets, models, inference gateway, auth) from a plugin. Use when a plugin needs to submit jobs, access files, read secrets, look up models, call the inference gateway, check permissions, or route calls between services. Trigger keywords: jobs service, files service, secrets service, models service, inference gateway, auth client, NeMo SDK, platform SDK, service-to-service, inter-service call, add_job_routes, job_route_factory, NMP_BASE_URL.

Source facts

Repository
NVIDIA-NeMo/nemo-platform
Last source activity
August 14, 2026 at 17:32
Detected SKILL.md language
English
Stars
79
Forks
24

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
plugin-platform-services
description
Calls NeMo Platform services (entity store, jobs, files, secrets, models, inference gateway, auth) from a plugin. Use when a plugin needs to submit jobs, access files, read secrets, look up models, call the inference gateway, check permissions, or route calls between services. Trigger keywords: jobs service, files service, secrets service, models service, inference gateway, auth client, NeMo SDK, platform SDK, service-to-service, inter-service call, add_job_routes, job_route_factory, NMP_BASE_URL.
# Platform Services for Plugins ## SDK Access Patterns **In request scope (FastAPI endpoint):** ```python from nmp.common.service.dependencies import get_sdk_client from nemo_platform import AsyncNeMoPlatform from fastapi import Depends @router.get("/items") async def list_items( workspace: str, sdk: AsyncNeMoPlatform = Depends(get_sdk_client), ) -> ...: models = await sdk.models.list(workspace=workspace) filesets = await sdk.files.list(workspace=workspace) ``` `get_sdk_client` propagates the current user's auth headers automatically. Never set `X-NMP-Principal-Id` manually in request-scope code. **In background/controller (no request context):** ```python from nmp.common.sdk_factory import get_async_platform_sdk sdk = get_async_platform_sdk(as_service="my-plugin", internal=True) ``` `internal=True` adds headers that suppress the access log flood from controller polling. ## Key Env Vars `NMP_BASE_URL` is the single most important env var — it points the SDK at the running platform: ```bash NMP_BASE_URL=http://localhost:8080 # all services at /apis/* on this base # Per-service URL overrides (production Kubernetes deployments): NMP_ENTITIES_URL=http://entities:8080 NMP_JOBS_URL=http://jobs:8080 NMP_FILES_URL=http://files:8080 NMP_SECRETS_URL=http://secrets:8080 ``` When a `NMP_<SERVICE>_URL` is set, the SDK factory routes that service's calls through that URL instead of the base URL. ## Entity Store (quick reference) See `../plugin-entities/SKILL.md` for full CRUD patterns. ```python # Request scope: use get_entity_client dependency from nemo_platform_plugin.entity_client import NemoEntitiesClient, get_entity_client # Background/controller scope: build manually from nmp.common.sdk_factory import get_async_platform_sdk from nemo_platform_plugin.client.adapter import client_from_platform from nemo_platform_plugin.entities.client import AsyncEntitiesClient from nmp.common.entities.client import EntityClient sdk = get_async_platform_sdk(as_service="my-plugin", internal=True) entity_client = EntityClient(client_from_platform(sdk, AsyncEntitiesClient)) ``` ## Jobs Service **Preferred: `add_job_routes(JobClass)`** — the plugin service mounts its job in one line. The wrapper derives everything from the `NemoJob` subclass and generates all 10 standard routes under the job collection path (POST/GET /jobs/{job-name}, GET/DELETE /jobs/{job-name}/{name}, POST /jobs/{job-name}/{name}/cancel, GET /jobs/{job-name}/{name}/logs, GET /jobs/{job-name}/{name}/results, …). ```python from nemo_platform_plugin.jobs.routes import add_job_routes from nemo_my_plugin.jobs.process import ProcessJob # your NemoJob subclass router = add_job_routes(ProcessJob) app.include_router( router, prefix="/v2/workspaces/{workspace}", ) ``` The `NemoJob` subclass declares `spec_schema` (Pydantic) and overrides `compile()` to produce a `PlatformJobSpec`. See the `plugin-job` skill for the full pattern. **Manual SDK call** (when not using `add_job_routes`): ```python # ALWAYS pass source=service_name — without it, your jobs are invisible in list_jobs job = await sdk.jobs.create( source="my-plugin", # ← REQUIRED spec=job_spec, platform_spec=platform_spec, workspace=workspace, ) status = await sdk.jobs.get_status(name=job.name, workspace=workspace) await sdk.jobs.cancel(name=job.name, workspace=workspace) ``` ## Files Service ```python sdk: AsyncNeMoPlatform = ... # from get_sdk_client or get_async_platform_sdk # Create a fileset fileset = await sdk.files.create(workspace=workspace, name="my-outputs") # List filesets filesets = await sdk.files.list(workspace=workspace) # Upload a file (raw HTTP — SDK doesn't wrap this) import httpx with open("result.json", "rb") as f: httpx.put( f"{base_url}/apis/files/v2/workspaces/{workspace}/filesets/my-outputs/-/result.json", content=f.read(), headers={"X-NMP-Principal-Id": "service:my-plugin"}, # background/controller scope only — use get_sdk_client() in request-scope FastAPI routes ) ``` Storage backend types: `local`, `s3`, `ngc`, `huggingface` — configured via `StorageConfig` from `nmp.common.files.storage_config`. ## Secrets Service ```python sdk: AsyncNeMoPlatform = ... # Create a secret await sdk.secrets.create("my-api-key", workspace=workspace, value="sk-...") # Access a secret value — POST to /access, NOT a simple GET # Returns PlatformSecretAccessResponse — the value is in .data response = await sdk.secrets.access("my-api-key", workspace=workspace) secret_value = response.data # SecretRef format for storage configs from nmp.common.api.common import SecretRef ref = SecretRef("workspace-name/my-secret") # workspace/name ref = SecretRef("my-secret") # name only (uses request workspace) ``` `sdk.secrets.access()` calls `POST /secrets/{name}/access` internally. This is intentional — access is audited. Do NOT try to read the value via a GET. ## Models Service ```python sdk: AsyncNeMoPlatform = ... # List available models models = await sdk.models.list(workspace=workspace) # Get a specific model model = await sdk.models.retrieve(name="llama-3-8b", workspace=workspace) # model.files_url — fileset URL for model weights ``` ## Inference Gateway OpenAI-compatible URL pattern: ```python import httpx # Call via OpenAI-compatible interface resp = httpx.post( f"{base_url}/apis/inference-gateway/v2/workspaces/{workspace}/openai/-/v1/chat/completions", json={ "model": "llama-3-8b", "messages": [{"role": "user", "content": "Hello!"}], "stream": False, }, headers={"X-NMP-Principal-Id": "service:my-plugin"}, ) resp.raise_for_status() result = resp.json() # Route to specific deployed model resp = httpx.post( f"{base_url}/apis/inference-gateway/v2/workspaces/{workspace}/model/my-model/-/v1/completions", json={...}, ) ``` Streaming (SSE) is supported — use `stream=True` on the httpx request and iterate `resp.iter_lines()`. ## Auth ```python from nmp.common.auth.dependencies import get_auth_client from nmp.common.auth.client import AuthClient from fastapi import Depends @router.get("/items") async def list_items( workspace: str, auth_client: AuthClient = Depends(get_auth_client), ) -> ...: principal_id = auth_client.principal.id await auth_client.authorize_request("GET", f"/apis/my-plugin/v2/workspaces/{workspace}/items") ``` The `get_auth_client` dependency is injected automatically by the platform's middleware — no setup required in plugins. ## Auth in Endpoints ```python from nmp.common.auth.dependencies import get_auth_client from nmp.common.auth.client import AuthClient from fastapi import Depends @router.get("/items") async def list_items( workspace: str, auth_client: AuthClient = Depends(get_auth_client), ) -> ...: principal_id = auth_client.principal.id # authorize_request raises HTTPException(403) if the principal lacks permission await auth_client.authorize_request("GET", f"/apis/my-plugin/v2/workspaces/{workspace}/items") ``` ## Job Routes See the **Jobs Service** section above — `add_job_routes(JobClass)` from `nemo_platform_plugin.jobs.routes` is the canonical wrapper. It derives every argument from the `NemoJob` subclass and generates all 10 standard routes (create, list, get, status, delete, cancel, logs, results, get-result, download-result). > **Always pass `source=service_name`** when creating jobs manually (jobs become invisible in the UI without it). ## See Also - [`services-reference.md`](services-reference.md) — base URLs and key endpoints table for all services ## Gotchas - **`source=service_name` required when creating jobs manually**: Without it, `list_jobs` for your service returns jobs from ALL services. Jobs become effectively invisible. - **`sdk.secrets.access()` not `.get()`**: The value endpoint is `POST /access`, not `GET /{name}`. `.get()` only returns metadata (no value). - **`internal=True` required for background/controller SDK calls**: Without it, every controller poll floods the entity store access log. - **Never set `X-NMP-Principal-Id` manually in request-scope code**: `get_sdk_client` propagates the current user's headers automatically. Manual headers will either be ignored or cause auth failures. - **`NMP_BASE_URL` defaults to `http://localhost:8080`**: In production this must be set to the actual cluster URL. Missing this env var is the most common cause of "connection refused" errors.
View on GitHub