Skip to content

Commit 0d7cbde

Browse files
Route default recursive calls to child backend
1 parent 7160b8b commit 0d7cbde

3 files changed

Lines changed: 38 additions & 7 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ rlm = RLM(
146146
)
147147
```
148148

149+
When `other_backends` is configured, its first client is the default for an
150+
`rlm_query(prompt)` recursive child as well as a max-depth plain sub-call. Passing
151+
`model=` to `rlm_query` explicitly overrides that default on the parent backend.
152+
149153
## Training
150154
We provide a simple RL training harness for training RLMs used in this repo (specifically the `local` REPL). The implementation uses no sandboxes for simplicity and slots easily your use case, but an ideal setup would use sandboxes for safety. Training logic is isolated to the [`training/`](https://github.com/alexzhang13/rlm/tree/main/training) folder, which exposes `rlm.RLM` as a [`verifiers`](https://github.com/willccbb/verifiers) `Environment` and plugs straight into [`prime-rl`](https://github.com/PrimeIntellect-ai/prime-rl). See the [training README](https://github.com/alexzhang13/rlm/tree/main/training#readme) for the launch command. The harness uses subprocess-isolated local REPL execution (no cloud sandboxes), matching the `local` environment above.
151155

‎rlm/core/rlm.py‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -730,21 +730,24 @@ def _subcall(self, prompt: str, model: str | None = None) -> RLMChatCompletion:
730730
"""
731731
next_depth = self.depth + 1
732732

733-
# Determine which backend/kwargs to use (model override or parent's default)
733+
# Determine which backend/kwargs to use. An explicit model stays on the
734+
# parent's backend. Without one, the configured recursive child backend is
735+
# the default for both child RLMs and max-depth plain calls.
734736
if model is not None:
737+
child_backend = self.backend
735738
child_backend_kwargs = (self.backend_kwargs or {}).copy()
736739
child_backend_kwargs["model_name"] = model
740+
elif self.other_backends and self.other_backend_kwargs:
741+
child_backend = self.other_backends[0]
742+
child_backend_kwargs = self.other_backend_kwargs[0]
737743
else:
744+
child_backend = self.backend
738745
child_backend_kwargs = self.backend_kwargs
739746
resolved_model = model or (child_backend_kwargs or {}).get("model_name", "unknown")
740747

741748
# If we'd hit/exceed the cap, do a normal LM completion (no REPL)
742749
if next_depth >= self.max_depth:
743-
# Use other_backend if available, otherwise use main backend
744-
if self.other_backends and self.other_backend_kwargs:
745-
client = self.client_factory(self.other_backends[0], self.other_backend_kwargs[0])
746-
else:
747-
client = self.client_factory(self.backend, child_backend_kwargs or {})
750+
client = self.client_factory(child_backend, child_backend_kwargs or {})
748751
root_model = model or client.model_name
749752
start_time = time.perf_counter()
750753
try:
@@ -814,7 +817,7 @@ def _subcall(self, prompt: str, model: str | None = None) -> RLMChatCompletion:
814817

815818
# Spawn a child RLM with its own LocalREPL
816819
child = RLM(
817-
backend=self.backend,
820+
backend=child_backend,
818821
backend_kwargs=child_backend_kwargs,
819822
environment=self.environment_type,
820823
environment_kwargs=self.environment_kwargs,

‎tests/test_subcall.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,30 @@ def client_factory(backend, backend_kwargs):
8181
assert result.response == "child answer"
8282
assert calls == [("openai", {"model_name": "review-model"})]
8383

84+
def test_recursive_child_defaults_to_configured_other_backend(self):
85+
calls = []
86+
87+
def client_factory(backend, backend_kwargs):
88+
calls.append((backend, dict(backend_kwargs)))
89+
return MockLM(
90+
model_name=backend_kwargs["model_name"],
91+
responses=[final("independent child answer")],
92+
)
93+
94+
parent = RLM(
95+
backend="owner-runtime",
96+
backend_kwargs={"model_name": "content-owner"},
97+
other_backends=["review-runtime"],
98+
other_backend_kwargs=[{"model_name": "self-assess"}],
99+
client_factory=client_factory,
100+
max_depth=3,
101+
)
102+
103+
result = parent._subcall("review this")
104+
105+
assert result.response == "independent child answer"
106+
assert calls[0] == ("review-runtime", {"model_name": "self-assess"})
107+
84108
def test_factory_is_used_by_max_depth_plain_lm_fallback(self):
85109
calls = []
86110

0 commit comments

Comments
 (0)