Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class TelemetryConfig(BaseModel):
provider: str | None = None
output_dir: str | None = None
project: str | None = None
agent_name: str | None = None
atif: dict[str, Any] | None = None
atof: dict[str, Any] | None = None
opentelemetry: dict[str, Any] | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from nemo_agents_plugin.utils import (
inject_default_model,
inject_fabric_gateway_url,
inject_fabric_trace_agent_name,
inject_gateway_url,
inject_nemo_trace_fields,
)
Expand Down Expand Up @@ -72,8 +73,8 @@ def resolve_for_deployment(
workspace: str,
agent_name: str,
) -> dict[str, Any]:
del agent_name
return self._normalize(inject_fabric_gateway_url(config, workspace))
resolved = self._normalize(inject_fabric_gateway_url(config, workspace))
return inject_fabric_trace_agent_name(resolved, agent_name)

@staticmethod
def _normalize(config: dict[str, Any]) -> dict[str, Any]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,21 @@ def _apply_telemetry(fabric_config: Any, config: AgentConfig, model: ModelConfig
)


def _telemetry_agent_name(config: AgentConfig) -> str:
"""Return the name traces should be tagged with for this agent."""
return config.telemetry.agent_name or config.name


def _relay_observability_config(config: AgentConfig, model: ModelConfig) -> dict[str, Any]:
telemetry = config.telemetry
agent_name = _telemetry_agent_name(config)
observability: dict[str, Any] = {"version": 3}

if telemetry.atif is not None:
atif = dict(telemetry.atif)
if telemetry.output_dir is not None:
atif.setdefault("output_directory", telemetry.output_dir)
atif.setdefault("agent_name", config.name)
atif.setdefault("agent_name", agent_name)
atif.setdefault("model_name", model.model)
observability["atif"] = atif

Expand All @@ -211,7 +217,7 @@ def _relay_observability_config(config: AgentConfig, model: ModelConfig) -> dict
if telemetry.opentelemetry is not None:
observability["opentelemetry"] = _relay_opentelemetry_config(
telemetry.opentelemetry,
agent_name=config.name,
agent_name=agent_name,
)

return observability
Expand Down
12 changes: 12 additions & 0 deletions plugins/nemo-agents/src/nemo_agents_plugin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,18 @@ def rebind_intake_ingest_workspace(config: dict[str, Any], workspace: str) -> di
return config


def inject_fabric_trace_agent_name(
config: dict[str, Any],
agent_name: str,
) -> dict[str, Any]:
"""Stamp the Platform-registered *agent_name* onto a Fabric agent spec's telemetry."""
telemetry = config.get("telemetry")
if not agent_name or not isinstance(telemetry, dict):
return config
telemetry.setdefault("agent_name", agent_name)
return config


def inject_nemo_trace_fields(
config: dict[str, Any],
workspace: str,
Expand Down
43 changes: 43 additions & 0 deletions plugins/nemo-agents/tests/unit/test_agent_config_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,46 @@ def inject_gateway(config: dict[str, Any], workspace: str) -> dict[str, Any]:
assert resolved["environment"]["provider"] == "local"
assert "workflow" not in resolved
assert calls == ["test-workspace"]


def test_nemo_agents_deployment_resolution_stamps_registered_agent_name(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
agent_config_formats,
"inject_fabric_gateway_url",
lambda config, workspace: config,
)

resolved = resolve_agent_config_for_deployment(
NEMO_AGENTS_SPEC_CONFIG_FORMAT,
_nemo_agents_config(),
workspace="test-workspace",
agent_name="test-agent-hzwy9s",
)

# The spec's own ``name`` stays untouched; only telemetry is retagged so
# per-agent trace queries match the Platform-registered name.
assert resolved["name"] == "test-agent"
assert resolved["telemetry"]["agent_name"] == "test-agent-hzwy9s"


def test_nemo_agents_deployment_resolution_keeps_explicit_telemetry_agent_name(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
agent_config_formats,
"inject_fabric_gateway_url",
lambda config, workspace: config,
)
config = _nemo_agents_config()
config["telemetry"] = {"enabled": True, "agent_name": "author-chosen-name"}

resolved = resolve_agent_config_for_deployment(
NEMO_AGENTS_SPEC_CONFIG_FORMAT,
config,
workspace="test-workspace",
agent_name="test-agent-hzwy9s",
)

assert resolved["telemetry"]["agent_name"] == "author-chosen-name"
52 changes: 46 additions & 6 deletions plugins/nemo-agents/tests/unit/test_fabric_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,10 @@ def test_environment_spec_env_forwarded_platform_values_win(self, monkeypatch: p

fabric_config = translate_agent_config(config, harness_name="codex")

assert fabric_config.environment.env["CUSTOM"] == "from-spec"
assert fabric_config.environment.env["NMP_WORKSPACE"] == "runtime-ws"
environment = fabric_config.environment
assert environment is not None
assert environment.env["CUSTOM"] == "from-spec"
assert environment.env["NMP_WORKSPACE"] == "runtime-ws"

def test_environment_mirror_fields_forwarded(self) -> None:
payload = copy.deepcopy(_example_yaml_config())
Expand All @@ -266,10 +268,12 @@ def test_environment_mirror_fields_forwarded(self) -> None:

fabric_config = translate_agent_config(config, harness_name="codex")

assert fabric_config.environment.control_location == "in_env_control"
assert fabric_config.environment.ownership == "fabric_owned"
assert fabric_config.environment.connection == {"url": "http://sandbox"}
assert fabric_config.environment.metadata == {"team": "platform"}
environment = fabric_config.environment
assert environment is not None
assert environment.control_location == "in_env_control"
assert environment.ownership == "fabric_owned"
assert environment.connection == {"url": "http://sandbox"}
assert environment.metadata == {"team": "platform"}

def test_runtime_constraints_forwarded(self) -> None:
payload = copy.deepcopy(_example_yaml_config())
Expand Down Expand Up @@ -371,6 +375,42 @@ def test_relay_telemetry_uses_latest_fabric_shape(self) -> None:
},
}

def test_relay_telemetry_prefers_platform_registered_agent_name(self) -> None:
payload = copy.deepcopy(_example_yaml_config())
payload["telemetry"]["enabled"] = True
payload["telemetry"]["agent_name"] = "example-agent-hzwy9s"
payload["telemetry"]["opentelemetry"] = {
"enabled": True,
"endpoints": [{"type": "full", "endpoint": "http://otel-collector:4317"}],
}
config = AgentConfig.model_validate(payload)

fabric_config = translate_agent_config(config)

relay = fabric_config.relay
assert relay is not None
observability = relay.observability
assert observability is not None
assert observability.model_dump(exclude_none=True)["atif"]["agent_name"] == "example-agent-hzwy9s"
opentelemetry = observability.opentelemetry
assert opentelemetry is not None
assert opentelemetry.endpoints[0].service_name == "example-agent-hzwy9s"

def test_relay_telemetry_keeps_explicit_atif_agent_name(self) -> None:
payload = copy.deepcopy(_example_yaml_config())
payload["telemetry"]["enabled"] = True
payload["telemetry"]["agent_name"] = "example-agent-hzwy9s"
payload["telemetry"]["atif"]["agent_name"] = "author-chosen-name"
config = AgentConfig.model_validate(payload)

fabric_config = translate_agent_config(config)

relay = fabric_config.relay
assert relay is not None
observability = relay.observability
assert observability is not None
assert observability.model_dump(exclude_none=True)["atif"]["agent_name"] == "author-chosen-name"

def test_relay_atof_endpoint_sinks_translate_to_stream_sinks(self) -> None:
payload = copy.deepcopy(_example_yaml_config())
payload["telemetry"]["enabled"] = True
Expand Down
Loading