diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx index 676ed9e927..491a2f6ba0 100644 --- a/docs/agents/index.mdx +++ b/docs/agents/index.mdx @@ -81,10 +81,10 @@ An agent's behavior is described by the platform-managed `agent.yaml`: |---------|---------|---------| | `config_format` | Selects the versioned Platform agent contract. Use `nemo-agents-spec-v1`. | `nemo-agents-spec-v1` | | `name` | Provides the agent name used in runtime metadata. | `calculator-agent` | -| `description` | Summarizes the agent's purpose for users and image metadata. | `Calculator agent executed with Codex` | +| `description` | Summarizes the agent's purpose for users and image metadata. | `Calculator agent executed with DeepAgents` | | `instructions` | Defines shared instructions for every harness. `instructions.system.content` contains the system prompt. | `system.content: You are a concise calculator agent.` | -| `default_harness` | Selects the entry under `harnesses` that runs by default. | `codex` | -| `harnesses` | Declares the supported harness adapters and their harness-specific model or runtime settings. | `codex.kind: codex`, `codex.settings.sandbox: read-only` | +| `default_harness` | Selects the entry under `harnesses` that runs by default. | `deepagents` | +| `harnesses` | Declares the supported harness adapters and their harness-specific model or runtime settings. | `deepagents.kind: deepagents` | | `models` | Declares shared named model bindings. A harness uses `models.default` unless it defines its own model. | `default.provider: nvidia`, `default.model: nvidia-nemotron-3-nano-30b-a3b` | | `skills` | Lists skill directories, relative to `agent.yaml`, that are made available to the harness. | `paths: [./skills/math]` | | `mcp` | Configures shared MCP servers that expose tools and resources to the harness. | `servers.github.transport: streamable-http`, `servers.github.url: ${GITHUB_MCP_URL}` | @@ -92,14 +92,14 @@ An agent's behavior is described by the platform-managed `agent.yaml`: | `environment` | Configures the agent's runtime workspace, artifacts directory, provider, and provider-specific settings. | `workspace: ./workspace`, `artifacts: ./artifacts` | | `telemetry` | Configures telemetry collection and output, including Relay, ATIF, and ATOF settings. | `provider: relay`, `atif.enabled: true`, `atof.enabled: true` | -The calculator agent uses Codex as its harness and the Platform Inference +The calculator agent uses DeepAgents as its harness and the Platform Inference Gateway for model access. It answers arithmetic and numeric comparison requests and records ATIF and ATOF telemetry with NeMo Relay: ```yaml config_format: nemo-agents-spec-v1 name: calculator-agent -description: Calculator agent executed with Codex +description: Calculator agent executed with DeepAgents instructions: system: @@ -107,14 +107,13 @@ instructions: You are a concise calculator agent. Solve arithmetic and numeric comparison requests yourself and return only the answer. -default_harness: codex +default_harness: deepagents harnesses: - codex: - kind: codex + deepagents: + kind: deepagents settings: - sandbox: read-only - reasoning_effort: high + deepagents: {} models: default: @@ -145,7 +144,7 @@ telemetry: filename_template: trajectory-{session_id}.atif.json storage: - type: http - endpoint: http://127.0.0.1:8080/apis/intake/v2/workspaces/default/ingest/atif + endpoint: http://localhost:8080/apis/intake/v2/workspaces/default/ingest/atif timeout_millis: 3000 atof: enabled: true diff --git a/plugins/nemo-agents/README.md b/plugins/nemo-agents/README.md index 2b723ec6b7..2533b4efdc 100644 --- a/plugins/nemo-agents/README.md +++ b/plugins/nemo-agents/README.md @@ -79,31 +79,34 @@ nemo-relay --version --- -### Calculator agent demo — Codex + Relay +### Calculator agent demo — DeepAgents + Relay [`examples/nemo-agent-config/calculator-agent/agent.yaml`](examples/nemo-agent-config/calculator-agent/agent.yaml) -uses Codex as its harness and routes `nvidia-nemotron-3-nano-30b-a3b` +uses DeepAgents as its harness and routes `nvidia-nemotron-3-nano-30b-a3b` through the Platform Inference Gateway. The agent answers arithmetic and numeric comparison requests and records ATIF and ATOF telemetry with NeMo Relay. -#### Step 1 — Start the platform +#### Step 1 — Configure and start the platform -Run this in a **dedicated terminal** from the repository root. Use a separate -terminal for the remaining steps. +Set the NVIDIA API key and local Platform URL from the repository root: ```bash -nemo services run +export NVIDIA_API_KEY="" +export NMP_BASE_URL=http://localhost:8080 ``` -#### Preflight — Verify Platform readiness - -In the second terminal, set the local Platform URL and confirm that an existing -instance is running and ready before continuing: +Start ClickHouse for Intake, then set up NeMo Platform without deploying the +default demo agent: ```bash -export NMP_BASE_URL=http://localhost:8080 +services/intake/scripts/spans/run_clickhouse.sh +nemo setup --auto --start-services --install-skills --no-deploy-agent +``` + +Confirm that the Platform is ready before continuing: +```bash curl -fsS --connect-timeout 2 --max-time 5 \ "$NMP_BASE_URL/health/ready" >/dev/null || { echo "NeMo Platform is not ready at $NMP_BASE_URL" @@ -111,35 +114,7 @@ curl -fsS --connect-timeout 2 --max-time 5 \ } ``` -#### Step 2 — Configure the model provider and harness - -Set the NVIDIA API key, then verify that Codex and NeMo Relay are ready: - -```bash -export NVIDIA_API_KEY="" - -codex login -nemo-relay --version -``` - -Create the local NVIDIA Build provider and wait for its models to be -registered: - -```bash -nemo secrets create ngc-api-key \ - --value "$NVIDIA_API_KEY" - -nemo inference providers create nvidia-build \ - --host-url https://integrate.api.nvidia.com \ - --api-key-secret-name ngc-api-key - -nemo wait inference provider nvidia-build -``` - -In production, the `system/nvidia-build` provider is normally created by the -Platform seed job, so this local provider setup is not required. - -#### Step 3 — Create and deploy the agent +#### Step 2 — Create and deploy the agent ```bash nemo agents create \ @@ -155,7 +130,7 @@ nemo agents deploy \ `create` validates the config and registers the agent. `deploy` waits for the deployment to reach `running` by default. -#### Step 4 — Invoke through the gateway +#### Step 3 — Invoke through the gateway ```bash nemo agents invoke \ @@ -165,22 +140,17 @@ nemo agents invoke \ The response content should be `12`. -#### Step 5 — Verify Relay telemetry +#### Step 4 — Verify Relay telemetry -The config writes ATIF and ATOF files beneath the deployment's artifacts -directory: +The config writes ATOF events beneath the deployment's artifacts directory: ```bash find ~/.local/share/nemo/agents/system/default \ -path "*calculator-agent-deployment*/artifacts/*" \ - \( -name "*atif*" -o -name "*atof*" \) \ + -name "*.atof.jsonl" \ -exec ls -lh {} \; ``` -Its ATIF configuration also sends trajectories to the local Platform Intake -API at `http://127.0.0.1:8080`. Ensure Intake is running if you want to use that -HTTP sink. - ### Packaging agents as container images `nemo agents package` automatically detects `nemo-agents-spec-v1` and selects diff --git a/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/README.md b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/README.md new file mode 100644 index 0000000000..30d90866cf --- /dev/null +++ b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/README.md @@ -0,0 +1,123 @@ +# Calculator Agent + +This example provides two Platform-managed `nemo-agents-spec-v1` configurations +for comparing a DeepAgents calculator agent with and without tools: + +| Config | Calculator server | +| --- | --- | +| `agent.yaml` | Disabled | +| `agent-with-mcp.yaml` | Enabled through MCP | + +Both variants use the Platform Inference Gateway and record ATOF telemetry +through NeMo Relay. + +## Prerequisites + +Run these commands from the repository root: + +```bash +export NVIDIA_API_KEY="" +export NMP_BASE_URL=http://localhost:8080 + +make bootstrap-python +source .venv/bin/activate + +command -v calculator-server +``` + +Start ClickHouse for Intake: + +```bash +services/intake/scripts/spans/run_clickhouse.sh +``` + +Set up NeMo Platform without deploying the default demo agent: + +```bash +nemo setup --auto --start-services --install-skills --no-deploy-agent +``` + +Use `nemo setup` without `--auto` for interactive provider and model selection. +Confirm the Platform is ready before continuing: + +```bash +curl -fsS --connect-timeout 2 --max-time 5 \ + "$NMP_BASE_URL/health/ready" >/dev/null +``` + +## Run without MCP + +Create and deploy the basic calculator agent: + +```bash +nemo agents create \ + --name calculator-agent \ + --agent-config plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent.yaml + +nemo agents deploy \ + --agent calculator-agent \ + --name calculator-agent-deployment \ + --mode subprocess +``` + +Invoke it: + +```bash +nemo agents invoke \ + --agent-deployment calculator-agent-deployment \ + --input "What is 12 multiplied by 8?" +``` + +The response should report `96`. This configuration has no calculator server, +so its ATOF events should contain no calculator tool call. + +## Run with MCP + +Create and deploy the tool-enabled variant: + +```bash +nemo agents create \ + --name calculator-agent-with-mcp \ + --agent-config plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent-with-mcp.yaml + +nemo agents deploy \ + --agent calculator-agent-with-mcp \ + --name calculator-agent-with-mcp-deployment \ + --mode subprocess +``` + +Invoke it: + +```bash +nemo agents invoke \ + --agent-deployment calculator-agent-with-mcp-deployment \ + --input "Use the calculator multiply tool to calculate 12 multiplied by 8. Do not calculate it yourself." +``` + +The response should report `96` after calling the calculator's `multiply` +tool. + +## Verify telemetry + +List the local ATOF artifacts for both deployments: + +```bash +find ~/.local/share/nemo/agents/system/default \ + -path "*calculator-agent*-deployment*/artifacts/*" \ + -name "*.atof.jsonl" \ + -exec ls -lh {} \; +``` + +Inspect calculator tool calls in the MCP deployment's ATOF events: + +```bash +find ~/.local/share/nemo/agents/system/default \ + -path "*calculator-agent-with-mcp-deployment*/artifacts/*" \ + -name "*.atof.jsonl" \ + -exec jq -c \ + 'select(.category == "tool" and .scope_category == "start") + | {name, arguments: .data}' {} + +``` + +The MCP deployment should include a `multiply` call. Running the same command +against the basic deployment should print no calculator tool calls. diff --git a/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent-with-mcp.yaml b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent-with-mcp.yaml new file mode 100644 index 0000000000..1ede4d1a59 --- /dev/null +++ b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent-with-mcp.yaml @@ -0,0 +1,50 @@ +config_format: nemo-agents-spec-v1 +name: calculator-agent-with-mcp +description: Calculator agent executed with DeepAgents and a calculator MCP server + +instructions: + system: + content: | + You are a concise calculator agent. You must use the appropriate + calculator MCP tool for every arithmetic or numeric comparison request + and base your answer on its result. + +default_harness: deepagents + +harnesses: + deepagents: + kind: deepagents + settings: + deepagents: {} + +models: + default: + provider: nvidia + model: nvidia-nemotron-3-nano-30b-a3b + api_key_env: NVIDIA_API_KEY + +skills: + paths: [] + +mcp: + servers: + calculator: + transport: stdio + url: calculator-server + +tools: + blocked: [] + +environment: + workspace: ./workspace + artifacts: ./artifacts + +telemetry: + enabled: true + provider: relay + output_dir: ./artifacts/relay + project: calculator-agent-with-mcp + atof: + enabled: true + filename: events.atof.jsonl + mode: append diff --git a/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent.yaml b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent.yaml index dfb6ef5c68..329af8ccc6 100644 --- a/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent.yaml +++ b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/agent.yaml @@ -1,6 +1,6 @@ config_format: nemo-agents-spec-v1 name: calculator-agent -description: Calculator agent executed with Codex +description: Calculator agent executed with DeepAgents instructions: system: @@ -8,14 +8,13 @@ instructions: You are a concise calculator agent. Solve arithmetic and numeric comparison requests yourself and return only the answer. -default_harness: codex +default_harness: deepagents harnesses: - codex: - kind: codex + deepagents: + kind: deepagents settings: - sandbox: read-only - reasoning_effort: high + deepagents: {} models: default: diff --git a/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/mcps/calculator.py b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/mcps/calculator.py new file mode 100644 index 0000000000..52c35a331b --- /dev/null +++ b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/mcps/calculator.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""A toy calculator exposed as an MCP stdio server.""" + +from __future__ import annotations + +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("calculator") + + +@mcp.tool() +def add(numbers: list[float]) -> float: + """Add two or more numbers.""" + if len(numbers) < 2: + raise ValueError("add requires at least two numbers") + return sum(numbers) + + +@mcp.tool() +def subtract(left: float, right: float) -> float: + """Subtract right from left.""" + return left - right + + +@mcp.tool() +def multiply(numbers: list[float]) -> float: + """Multiply two or more numbers.""" + if len(numbers) < 2: + raise ValueError("multiply requires at least two numbers") + result = 1.0 + for number in numbers: + result *= number + return result + + +@mcp.tool() +def divide(dividend: float, divisor: float) -> float: + """Divide dividend by divisor.""" + if divisor == 0: + raise ValueError("cannot divide by zero") + return dividend / divisor + + +@mcp.tool() +def compare(left: float, right: float) -> str: + """Compare two numbers.""" + if left > right: + relation = "greater than" + elif left < right: + relation = "less than" + else: + relation = "equal to" + return f"{left:g} is {relation} {right:g}" + + +def main() -> None: + """Run the calculator MCP server over stdio.""" + mcp.run(transport="stdio") diff --git a/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/pyproject.toml b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/pyproject.toml new file mode 100644 index 0000000000..237432d017 --- /dev/null +++ b/plugins/nemo-agents/examples/nemo-agent-config/calculator-agent/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "nemo-agent-config-example-calculator" +version = "0.1.0" +description = "A calculator tool server for the nemo-agents-spec-v1 example." +requires-python = ">=3.11,<3.15" +dependencies = [ + "mcp>=1.28.1,<2", +] + +[project.scripts] +calculator-server = "mcps.calculator:main" + +[tool.hatch.build.targets.wheel] +packages = ["mcps"] diff --git a/plugins/nemo-agents/pyproject.toml b/plugins/nemo-agents/pyproject.toml index 6a0a9b238b..25e0adc39c 100644 --- a/plugins/nemo-agents/pyproject.toml +++ b/plugins/nemo-agents/pyproject.toml @@ -99,6 +99,7 @@ nemo-deployments-plugin = { workspace = true } nemo-agents-example-calculator = { workspace = true } nemo-agents-example-email-phishing = { workspace = true } nemo-agents-example-email-security = { workspace = true } +nemo-agent-config-example-calculator = {workspace = true} [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/plugins/nemo-agents/tests/unit/test_fabric_calculator_example.py b/plugins/nemo-agents/tests/unit/test_fabric_calculator_example.py new file mode 100644 index 0000000000..01f76fb0a1 --- /dev/null +++ b/plugins/nemo-agents/tests/unit/test_fabric_calculator_example.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for the Fabric calculator example.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from nemo_agents_plugin.agent_config import load_agent_config +from nemo_agents_plugin.fabric.translator import translate_agent_config +from nemo_fabric import Fabric # ty: ignore[unresolved-import] + +EXAMPLE_DIR = Path(__file__).parents[2] / "examples/nemo-agent-config/calculator-agent" +BASE_CONFIG_PATH = EXAMPLE_DIR / "agent.yaml" +MCP_CONFIG_PATH = EXAMPLE_DIR / "agent-with-mcp.yaml" + + +def test_base_example_has_no_mcp_servers() -> None: + config = load_agent_config(BASE_CONFIG_PATH) + fabric_config = translate_agent_config(config) + + assert config.name == "calculator-agent" + assert fabric_config.harness.adapter_id == "nvidia.fabric.langchain.deepagents" + assert fabric_config.mcp is not None + assert fabric_config.mcp.servers == {} + assert fabric_config.relay is not None + assert fabric_config.environment is not None + assert fabric_config.environment.workspace == "./workspace" + assert fabric_config.environment.artifacts == "./artifacts" + assert fabric_config.relay.output_dir == "./artifacts/relay" + + plan = Fabric().plan(fabric_config, base_dir=EXAMPLE_DIR) + assert plan.adapter.adapter_id == "nvidia.fabric.langchain.deepagents" + assert "mcp_servers" not in plan.capability_plan + + +def test_mcp_example_adds_harness_native_stdio_server() -> None: + config = load_agent_config(MCP_CONFIG_PATH) + fabric_config = translate_agent_config(config) + + assert config.name == "calculator-agent-with-mcp" + assert fabric_config.mcp is not None + calculator = fabric_config.mcp.servers["calculator"] + assert calculator.transport == "stdio" + assert calculator.url == "calculator-server" + assert calculator.exposure == "harness_native" + assert fabric_config.relay is not None + assert fabric_config.relay.observability.atif is None + assert fabric_config.relay.observability.atof is not None + + plan = Fabric().plan(fabric_config, base_dir=EXAMPLE_DIR) + assert plan.adapter.adapter_id == "nvidia.fabric.langchain.deepagents" + assert list(plan.capability_plan["native"]["mcp_servers"]) == ["calculator"] + + +@pytest.mark.asyncio +async def test_calculator_server_over_stdio() -> None: + server = StdioServerParameters( + command=sys.executable, + args=["-c", "from mcps.calculator import main; main()"], + cwd=str(EXAMPLE_DIR), + ) + + async with stdio_client(server) as streams: + async with ClientSession(*streams) as session: + await session.initialize() + tools = await session.list_tools() + result = await session.call_tool("multiply", {"numbers": [12, 8]}) + + assert {tool.name for tool in tools.tools} == {"add", "subtract", "multiply", "divide", "compare"} + assert result.isError is False + assert result.structuredContent == {"result": 96.0} diff --git a/pyproject.toml b/pyproject.toml index 80a5caff8d..e15ddde813 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -452,6 +452,7 @@ members = [ "plugins/nemo-agents/examples/calculator-agent", "plugins/nemo-agents/examples/email-phishing-analyzer", "plugins/nemo-agents/examples/email-security-analyst", + "plugins/nemo-agents/examples/nemo-agent-config/calculator-agent", "plugins/nemo-customizer", "plugins/nemo-automodel", "plugins/nemo-unsloth", diff --git a/uv.lock b/uv.lock index 3f40ddca0a..9f019d5b97 100644 --- a/uv.lock +++ b/uv.lock @@ -21,6 +21,7 @@ members = [ "filesets", "garak-api", "models", + "nemo-agent-config-example-calculator", "nemo-agents-example-calculator", "nemo-agents-example-email-phishing", "nemo-agents-example-email-security", @@ -3790,6 +3791,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, ] +[[package]] +name = "nemo-agent-config-example-calculator" +version = "0.1.0" +source = { editable = "plugins/nemo-agents/examples/nemo-agent-config/calculator-agent" } +dependencies = [ + { name = "mcp", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] + +[package.metadata] +requires-dist = [{ name = "mcp", specifier = ">=1.28.1,<2" }] + [[package]] name = "nemo-agents-example-calculator" version = "0.0.0"