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
6 changes: 3 additions & 3 deletions ATTRIBUTIONS-Python.md
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,7 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
```

## deepagents (0.5.9)
## deepagents (0.6.12)

### Licenses
License: `MIT`
Expand Down Expand Up @@ -4760,7 +4760,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```

## langchain (1.3.12)
## langchain (1.3.14)

### Licenses
License: `MIT`
Expand Down Expand Up @@ -5460,7 +5460,7 @@ Copyright 2016 Andrew Svetlov and aio-libs contributors
limitations under the License.
```

## nemo-relay (0.5.0)
## nemo-relay (0.6.0)

### Licenses
License: `Apache-2.0`
Expand Down
68 changes: 51 additions & 17 deletions adapters/common/src/nemo_fabric_adapters/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from nemo_relay import plugin
from nemo_relay.observability import AtifConfig
from nemo_relay.observability import AtofConfig
from nemo_relay.observability import AtofFileSinkConfig
from nemo_relay.observability import AtofStreamSinkConfig
from nemo_relay.observability import HttpStorageConfig
from nemo_relay.observability import OtlpConfig
from nemo_relay.observability import S3StorageConfig
Expand Down Expand Up @@ -283,7 +285,10 @@ def relay_api_plugin_config(plugin_config: dict[str, Any]) -> plugin.PluginConfi
components.append(
ComponentSpec(
ObservabilityConfig(
version=int(config.get("version", 1)),
# Relay 0.6 only accepts the v2 observability API model.
# Fabric still accepts its existing flat/v1 configuration
# below and translates it at this API boundary.
version=2,
atof=_relay_api_atof_config(config.get("atof")),
atif=_relay_api_atif_config(
config.get("atif"),
Expand Down Expand Up @@ -330,27 +335,56 @@ def _relay_api_atof_config(value: Any) -> AtofConfig | None:
if not isinstance(value, dict):
return None
from nemo_relay.observability import AtofConfig
from nemo_relay.observability import AtofEndpointConfig

endpoint_configs = value.get("endpoints")
endpoints = None
if isinstance(endpoint_configs, list):
endpoints = [
AtofEndpointConfig(
url=str(endpoint.get("url", "")),
transport=endpoint.get("transport", "http_post"),
headers=endpoint.get("headers", {}),
timeout_millis=int(endpoint.get("timeout_millis", 3000)),
)
for endpoint in endpoint_configs
if isinstance(endpoint, dict)
]
from nemo_relay.observability import AtofFileSinkConfig
from nemo_relay.observability import AtofStreamSinkConfig

sinks: list[AtofFileSinkConfig | AtofStreamSinkConfig] = []
has_explicit_file_sink = False
for sink in value.get("sinks") or []:
if not isinstance(sink, dict):
continue
if sink.get("type") == "file":
has_explicit_file_sink = True
sinks.append(_relay_api_atof_file_sink_config(sink))
elif sink.get("type") == "stream":
sinks.append(_relay_api_atof_stream_sink_config(sink))

if not has_explicit_file_sink and any(
key in value for key in ("output_directory", "filename", "mode")
):
sinks.append(_relay_api_atof_file_sink_config(value))

for endpoint in value.get("endpoints") or []:
if isinstance(endpoint, dict):
sinks.append(_relay_api_atof_stream_sink_config(endpoint))

return AtofConfig(
enabled=bool(value.get("enabled", False)),
sinks=sinks,
)
Comment on lines +338 to +364

@coderabbitai coderabbitai Bot Jul 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm nemo-relay's Python bindings for AtofConfig/sinks and whether
# unknown sink "type" values are expected to be rejected during validation.
python - <<'EOF'
import inspect
try:
    from nemo_relay.observability import AtofConfig, AtofFileSinkConfig, AtofStreamSinkConfig
    print(inspect.signature(AtofConfig))
    print(inspect.signature(AtofFileSinkConfig))
    print(inspect.signature(AtofStreamSinkConfig))
except ImportError as e:
    print("nemo_relay not installed in this sandbox:", e)
EOF

Repository: NVIDIA/NeMo-Fabric

Length of output: 227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant converter and nearby validation helpers.
FILE='adapters/common/src/nemo_fabric_adapters/common/utils.py'
grep -n -A80 -B40 'def .*relay_api.*atof\|def relay_api_plugin_config\|ConfigPolicy\.unsupported_value\|sinks' "$FILE" | sed -n '1,240p'

printf '\n--- tests mentioning Atof or sink kinds ---\n'
rg -n "Atof|unsupported_value|sink.*type|output_directory|filename|mode|endpoints" adapters/common -g '*test*' -g '*py'

printf '\n--- definitions of ConfigPolicy ---\n'
rg -n "class ConfigPolicy|unsupported_value" adapters/common -g '*.py'

Repository: NVIDIA/NeMo-Fabric

Length of output: 18155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Narrow read of the converter to understand control flow and error handling.
FILE='adapters/common/src/nemo_fabric_adapters/common/utils.py'
python3 - <<'PY'
from pathlib import Path
path = Path('adapters/common/src/nemo_fabric_adapters/common/utils.py')
lines = path.read_text().splitlines()
for start in (300, 330, 360):
    end = min(len(lines), start + 80)
    print(f"\n### lines {start}-{end}")
    for i in range(start, end):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: NVIDIA/NeMo-Fabric

Length of output: 10461


Handle unknown ATOF sink types
Unrecognized or malformed sinks[] entries are skipped here, so a typo or future sink kind disappears without any diagnostic. Route them through the same unsupported-value policy or raise an error instead of dropping them.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 359-359: Use list.extend to create a transformed list

(PERF401)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/common/src/nemo_fabric_adapters/common/utils.py` around lines 338 -
364, Update the sink-processing loop in the ATOF configuration parser to handle
every unrecognized or malformed entry in value["sinks"] through the existing
unsupported-value policy, or raise a validation error when no such policy
applies. Do not silently continue for non-dict entries or unknown sink types;
preserve the existing handling for valid "file" and "stream" sinks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only relevant with the compatibility parser. See if we need this after getting peer feedback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bbednarski9, agreed. This is only relevant if the flat/v1 compatibility parser remains. I’ll leave the finding open pending the configuration decision; if the parser is removed in favor of typed v2 sinks, it no longer applies.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up opened in #114. Since NeMo Fabric has not been released, I removed the compatibility parser instead of adding more handling around it. Relay 0.6 v2 file and stream sinks are now the canonical Rust/Python config, and the adapters consume that sink shape directly. This finding therefore no longer applies.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!



def _relay_api_atof_file_sink_config(value: dict[str, Any]) -> AtofFileSinkConfig:
from nemo_relay.observability import AtofFileSinkConfig

return AtofFileSinkConfig(
output_directory=value.get("output_directory"),
filename=value.get("filename"),
mode=value.get("mode", "append"),
endpoints=endpoints,
)


def _relay_api_atof_stream_sink_config(value: dict[str, Any]) -> AtofStreamSinkConfig:
from nemo_relay.observability import AtofStreamSinkConfig

return AtofStreamSinkConfig(
url=str(value.get("url", "")),
transport=value.get("transport", "http_post"),
headers=value.get("headers", {}),
header_env=value.get("header_env", {}),
timeout_millis=int(value.get("timeout_millis", 3000)),
field_name_policy=value.get("field_name_policy", "preserve"),
name=value.get("name"),
)


Expand Down
7 changes: 2 additions & 5 deletions adapters/deepagents/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"nemo-fabric-adapters-common == 0.1.0",
"deepagents>=0.5.3,<0.6.0",
"deepagents>=0.6.12,<0.7.0",
"langchain>=1.3,<2.0",
"langchain-mcp-adapters>=0.1,<0.3.0",
"langchain-openai>=0.3",
Expand All @@ -42,11 +42,8 @@ dependencies = [
# when telemetry.providers.relay is enabled, so the core install stays
# Relay-neutral at import time.
[project.optional-dependencies]
# Allow both the current 0.5 line and the upcoming 0.6 release (which ships the
# same SDK-native Deep Agents API). Resolves to 0.5.x today and adopts 0.6
# automatically once it is published.
relay = [
"nemo-relay[deepagents]>=0.5.0,<0.7",
"nemo-relay[deepagents]>=0.6.0,<0.7",
]

[project.urls]
Expand Down
34 changes: 17 additions & 17 deletions adapters/deepagents/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ hermes-agent = [
]

relay = [
# Allow the current 0.5 line and the upcoming 0.6 release; resolves to 0.5.x
# today and adopts 0.6 automatically once it is published.
"nemo-relay>=0.5.0,<0.7",
"nemo-relay>=0.6.0,<0.7",
"tomli-w~=1.2", # Needed by adapters to write relay config files
]

Expand Down
57 changes: 57 additions & 0 deletions tests/adapters/test_adapaters_common_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0

import builtins
import dataclasses
import json
import os
import sys
Expand Down Expand Up @@ -376,6 +377,62 @@ def test_collect_relay_artifacts(tmp_path: Path):
]


def test_relay_api_plugin_config_translates_flat_atof_to_relay_v06_sinks():
os.environ["TOKEN"] = "test-token"
plugin_config = {
"version": 1,
"components": [
{
"kind": "observability",
"enabled": True,
"config": {
"version": 1,
"atof": {
"enabled": True,
"output_directory": "/tmp/atof",
"filename": "events.jsonl",
"mode": "overwrite",
"endpoints": [
{
"url": "https://example.test/events",
"headers": {"x-test": "value"},
"header_env": {"authorization": "TOKEN"},
"timeout_millis": 1000,
"field_name_policy": "replace_dots",
"name": "phoenix",
}
],
},
},
}
],
}

rendered = common_utils.relay_api_plugin_config(plugin_config)
observability = rendered.components[0].config

assert observability.version == 2
assert dataclasses.asdict(observability.atof) == {
"enabled": True,
"sinks": [
{
"output_directory": "/tmp/atof",
"filename": "events.jsonl",
"mode": "overwrite",
},
{
"url": "https://example.test/events",
"transport": "http_post",
"headers": {"x-test": "value"},
"header_env": {"authorization": "TOKEN"},
"timeout_millis": 1000,
"field_name_policy": "replace_dots",
"name": "phoenix",
},
],
}


@pytest.mark.parametrize(
("relay_config", "plugin_config", "expected_names"),
[
Expand Down
Loading
Loading