fix(agent): omit unsupported metadata on Relay scope.pop
Older nemo-relay bindings reject metadata= on scope.pop, which aborted turn finalization and left scopes open. Filter kwargs to what the live binding accepts so close paths can complete.
This commit is contained in:
@@ -897,7 +897,8 @@ def _complete_logical(
|
||||
output["response_model"] = response_model_name
|
||||
lease.host.run_in_session(
|
||||
lease.session,
|
||||
lease.host.relay.scope.pop,
|
||||
relay_runtime.pop_relay_scope,
|
||||
lease.host.relay,
|
||||
handle,
|
||||
output=output,
|
||||
metadata={
|
||||
|
||||
@@ -94,6 +94,40 @@ def _run_bounded_on_exit_thread(fn: Callable[[], Any], timeout: float) -> Any:
|
||||
return result[0] if result else None
|
||||
|
||||
|
||||
def pop_relay_scope(
|
||||
relay: Any,
|
||||
handle: Any,
|
||||
*,
|
||||
output: Any = None,
|
||||
metadata: Any = None,
|
||||
timestamp: Any = None,
|
||||
) -> Any:
|
||||
"""Pop a Relay scope without passing kwargs the binding rejects.
|
||||
|
||||
NeMo Relay ``scope.pop`` gained ``metadata`` in 0.4+. Older wheels (e.g.
|
||||
0.3.x) raise ``TypeError: pop() got an unexpected keyword argument
|
||||
'metadata'`` when Hermes finalization forwards runtime metadata. Filter to
|
||||
parameters the live binding accepts so turn/session close can complete.
|
||||
"""
|
||||
pop = relay.scope.pop
|
||||
kwargs: dict[str, Any] = {}
|
||||
if output is not None:
|
||||
kwargs["output"] = output
|
||||
if metadata is not None:
|
||||
kwargs["metadata"] = metadata
|
||||
if timestamp is not None:
|
||||
kwargs["timestamp"] = timestamp
|
||||
try:
|
||||
params = inspect.signature(pop).parameters
|
||||
except (TypeError, ValueError):
|
||||
params = {}
|
||||
if params and not any(
|
||||
param.kind == inspect.Parameter.VAR_KEYWORD for param in params.values()
|
||||
):
|
||||
kwargs = {key: value for key, value in kwargs.items() if key in params}
|
||||
return pop(handle, **kwargs)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RelaySession:
|
||||
"""One isolated Relay scope stack owned by a Hermes session."""
|
||||
@@ -609,7 +643,8 @@ class RelayRuntime:
|
||||
return a_uuid is not None and a_uuid == b_uuid
|
||||
|
||||
try:
|
||||
self.relay.scope.pop(
|
||||
pop_relay_scope(
|
||||
self.relay,
|
||||
handle,
|
||||
output=close_output,
|
||||
metadata=metadata,
|
||||
@@ -630,7 +665,8 @@ class RelayRuntime:
|
||||
):
|
||||
break
|
||||
try:
|
||||
self.relay.scope.pop(
|
||||
pop_relay_scope(
|
||||
self.relay,
|
||||
top,
|
||||
output={
|
||||
"outcome": "cancelled",
|
||||
@@ -654,7 +690,8 @@ class RelayRuntime:
|
||||
handle,
|
||||
)
|
||||
try:
|
||||
self.relay.scope.pop(
|
||||
pop_relay_scope(
|
||||
self.relay,
|
||||
handle,
|
||||
output=close_output,
|
||||
metadata=metadata,
|
||||
|
||||
@@ -1026,7 +1026,8 @@ class _Runtime:
|
||||
try:
|
||||
self._run_in_task(
|
||||
task,
|
||||
self.relay.scope.pop,
|
||||
relay_runtime.pop_relay_scope,
|
||||
self.relay,
|
||||
task.handle,
|
||||
output=fields,
|
||||
metadata=self._event_metadata(),
|
||||
|
||||
121
tests/agent/test_relay_scope_pop_metadata.py
Normal file
121
tests/agent/test_relay_scope_pop_metadata.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""Regression for #78993: scope.pop metadata kwarg on older NeMo Relay."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import tempfile
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from agent import relay_runtime
|
||||
|
||||
|
||||
def test_pop_relay_scope_omits_unsupported_metadata_kwarg():
|
||||
calls: list[tuple[object, dict]] = []
|
||||
|
||||
def pop_without_metadata(handle, *, output=None):
|
||||
calls.append((handle, {"output": output}))
|
||||
|
||||
relay = SimpleNamespace(scope=SimpleNamespace(pop=pop_without_metadata))
|
||||
handle = ("scope", "hermes.turn", 1)
|
||||
|
||||
relay_runtime.pop_relay_scope(
|
||||
relay,
|
||||
handle,
|
||||
output={"outcome": "success"},
|
||||
metadata={"hermes.relay.schema_version": "hermes.relay.runtime.v1"},
|
||||
)
|
||||
|
||||
assert calls == [(handle, {"output": {"outcome": "success"}})]
|
||||
|
||||
|
||||
def test_pop_relay_scope_forwards_metadata_when_supported():
|
||||
calls: list[tuple[object, dict]] = []
|
||||
|
||||
def pop_with_metadata(handle, *, output=None, metadata=None, timestamp=None):
|
||||
calls.append(
|
||||
(
|
||||
handle,
|
||||
{
|
||||
"output": output,
|
||||
"metadata": metadata,
|
||||
"timestamp": timestamp,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
relay = SimpleNamespace(scope=SimpleNamespace(pop=pop_with_metadata))
|
||||
handle = ("scope", "hermes.turn", 2)
|
||||
metadata = {"hermes.relay.runtime_instance": "abc"}
|
||||
|
||||
relay_runtime.pop_relay_scope(
|
||||
relay,
|
||||
handle,
|
||||
output={"outcome": "error"},
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
(
|
||||
handle,
|
||||
{
|
||||
"output": {"outcome": "error"},
|
||||
"metadata": metadata,
|
||||
"timestamp": None,
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_end_turn_finalization_survives_pop_without_metadata(monkeypatch, caplog):
|
||||
"""Mirror #78993: nemo-relay 0.3.x rejects metadata= on scope.pop."""
|
||||
pytest.importorskip("nemo_relay")
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", tempfile.mkdtemp())
|
||||
relay_runtime._reset_for_tests()
|
||||
lease = relay_runtime.SESSION_COORDINATOR.acquire_conversation(
|
||||
profile_key=relay_runtime.current_profile_key(),
|
||||
session_id="session-78993",
|
||||
platform="cli",
|
||||
)
|
||||
turn = relay_runtime.SESSION_COORDINATOR.begin_turn(
|
||||
lease,
|
||||
turn_id="turn-1",
|
||||
task_id="task-1",
|
||||
)
|
||||
lease.host.retain_managed_execution("test.relay_scope_pop")
|
||||
|
||||
original_pop = lease.host.relay.scope.pop
|
||||
assert "metadata" in inspect.signature(original_pop).parameters
|
||||
|
||||
def pop_without_metadata(handle, *, output=None, timestamp=None):
|
||||
return original_pop(handle, output=output, timestamp=timestamp)
|
||||
|
||||
monkeypatch.setattr(lease.host.relay.scope, "pop", pop_without_metadata)
|
||||
|
||||
logical = lease.host.run_in_session(
|
||||
lease.session,
|
||||
lease.host.relay.scope.push,
|
||||
"logical-llm",
|
||||
lease.host.relay.ScopeType.Custom,
|
||||
handle=turn.handle,
|
||||
input={},
|
||||
metadata={"hermes.test": True},
|
||||
)
|
||||
turn.logical_llm_calls["api-1"] = logical
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="agent.relay_runtime"):
|
||||
relay_runtime.SESSION_COORDINATOR.end_turn(turn, outcome="success")
|
||||
|
||||
joined = "\n".join(record.getMessage() for record in caplog.records)
|
||||
assert "unexpected keyword argument 'metadata'" not in joined
|
||||
assert "turn finalization failed" not in joined
|
||||
assert "logical LLM finalization failed" not in joined
|
||||
assert turn.logical_llm_calls == {}
|
||||
assert turn.closed is True
|
||||
|
||||
lease.host.release_managed_execution("test.relay_scope_pop")
|
||||
relay_runtime.SESSION_COORDINATOR.release_conversation(lease)
|
||||
relay_runtime._reset_for_tests()
|
||||
Reference in New Issue
Block a user