Skip to content
Closed
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
4 changes: 2 additions & 2 deletions examples/codex_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)

from openrange.agent import agent_briefing
from openrange.agent_backend import CodexAgentBackend
from openrange.agent_backend import LLMAgentBackend
from openrange.core import PACKS, auto_evolve, consequence_gate
from openrange.core.episode import AgentTurn, EpisodeReport
from openrange.llm import CodexBackend
Expand Down Expand Up @@ -104,7 +104,7 @@ def main() -> None:
npc_backend = (
None
if args.no_npc_llm
else CodexAgentBackend(
else LLMAgentBackend(
backend=CodexBackend(
command=args.codex_command,
model=args.model,
Expand Down
2 changes: 1 addition & 1 deletion packages/openrange-pack-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Two real semantic changes to know about:
in-process fakes no longer need a no-op `preflight`.

Concrete classes (`CodexBackend`, `StrandsAgentBackend`,
`CodexAgentBackend`, the `admit()` function, `AdmissionFailure`,
`LLMAgentBackend`, the `admit()` function, `AdmissionFailure`,
`PackRegistry`, `NPCRegistry`, the `EpisodeService` runtime) STAY in
`openrange`. Packs should never need them — the runtime supplies any
concrete backend via `RunConfig.npc_agent_backend` and the harness drives
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class LLMBackend(Protocol):

Concrete backends MAY also define a ``preflight()`` method to validate
binaries/credentials once. Callers that need preflight (e.g.,
CodexAgentBackend) check for it via ``hasattr`` and call it
LLMAgentBackend) check for it via ``hasattr`` and call it
defensively — the Protocol does not require it so a minimal in-process
fake stays free of boilerplate."""

Expand Down
4 changes: 2 additions & 2 deletions src/openrange/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
run_rollouts,
)
from openrange.agent_backend import (
CodexAgentBackend,
LLMAgentBackend,
StrandsAgentBackend,
)
from openrange.core import (
Expand Down Expand Up @@ -108,7 +108,6 @@
"AgentTurn",
"AttrSpec",
"AttrType",
"CodexAgentBackend",
"CodexBackend",
"CurriculumPolicy",
"DashboardServerHandle",
Expand All @@ -128,6 +127,7 @@
"EvalPool",
"GraphPatch",
"Issue",
"LLMAgentBackend",
"LiteLLMBackend",
"NPCS",
"NPCRegistry",
Expand Down
13 changes: 7 additions & 6 deletions src/openrange/agent_backend.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Concrete agent backends. `StrandsAgentBackend` + `CodexAgentBackend` ship.
"""Concrete agent backends. `StrandsAgentBackend` + `LLMAgentBackend` ship.

The ``AgentBackend`` Protocol and ``AgentBackendError`` live in
``openrange_pack_sdk``.
Expand Down Expand Up @@ -72,8 +72,9 @@ def _import_agent_class(self) -> Any:
return Agent


class CodexAgentBackend:
"""Wraps an `LLMBackend` (Codex CLI) for single-shot, tool-less agents.
class LLMAgentBackend:
"""Wraps any `LLMBackend` for single-shot, tool-less agents.
Defaults to `CodexBackend` when no backend is given.
Raises on non-empty `tools`."""

def __init__(
Expand All @@ -84,7 +85,7 @@ def __init__(
) -> None:
if backend is not None and model is not None:
raise AgentBackendError(
"CodexAgentBackend: pass either 'backend' or 'model', not both",
"LLMAgentBackend: pass either 'backend' or 'model', not both",
)
self._backend: LLMBackend = (
backend if backend is not None else CodexBackend(model=model)
Expand All @@ -101,7 +102,7 @@ def preflight(self) -> None:
backend_preflight()
except LLMBackendError as exc:
raise AgentBackendError(
f"CodexAgentBackend: backend preflight failed: {exc}",
f"LLMAgentBackend: backend preflight failed: {exc}",
) from exc

def build_agent(
Expand All @@ -112,7 +113,7 @@ def build_agent(
) -> AgentSession:
if tools:
raise AgentBackendError(
"CodexAgentBackend does not support tool injection. "
"LLMAgentBackend does not support tool injection. "
"Use StrandsAgentBackend for NPCs that need tool dispatch.",
)
backend = self._backend
Expand Down
20 changes: 10 additions & 10 deletions tests/test_agent_backend.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""AgentBackend protocol + StrandsAgentBackend / CodexAgentBackend tests."""
"""AgentBackend protocol + StrandsAgentBackend / LLMAgentBackend tests."""

from __future__ import annotations

Expand All @@ -12,7 +12,7 @@
)

from openrange.agent_backend import (
CodexAgentBackend,
LLMAgentBackend,
StrandsAgentBackend,
)

Expand Down Expand Up @@ -66,7 +66,7 @@ def my_tool(x: str) -> str:


# ---------------------------------------------------------------------------
# CodexAgentBackend
# LLMAgentBackend
# ---------------------------------------------------------------------------


Expand All @@ -87,8 +87,8 @@ def preflight(self) -> None:


def test_codex_backend_rejects_tools() -> None:
"""CodexAgentBackend errors loudly if handed any tools."""
backend = CodexAgentBackend(backend=_RecordingLLMBackend())
"""LLMAgentBackend errors loudly if handed any tools."""
backend = LLMAgentBackend(backend=_RecordingLLMBackend())

def some_tool() -> None:
return None
Expand All @@ -100,7 +100,7 @@ def some_tool() -> None:
def test_codex_backend_drives_llm_for_tool_less_prompts() -> None:
"""Without tools, build_agent returns a callable that hits the LLM backend."""
fake = _RecordingLLMBackend()
backend = CodexAgentBackend(backend=fake)
backend = LLMAgentBackend(backend=fake)
session = backend.build_agent(system_prompt="be terse", tools=())
result = session("hello")
assert isinstance(result, LLMResult)
Expand All @@ -112,13 +112,13 @@ def test_codex_backend_drives_llm_for_tool_less_prompts() -> None:

def test_codex_backend_rejects_both_backend_and_model_args() -> None:
with pytest.raises(AgentBackendError, match="not both"):
CodexAgentBackend(backend=_RecordingLLMBackend(), model="some-model")
LLMAgentBackend(backend=_RecordingLLMBackend(), model="some-model")


def test_codex_backend_preflight_delegates_to_custom_llm_backend() -> None:
"""A caller-supplied LLMBackend gets its own preflight called."""
fake = _RecordingLLMBackend()
backend = CodexAgentBackend(backend=fake)
backend = LLMAgentBackend(backend=fake)
backend.preflight()
assert fake.preflight_calls == 1

Expand All @@ -131,7 +131,7 @@ class _BadBackend(_RecordingLLMBackend):
def preflight(self) -> None:
raise LLMBackendError("custom probe failed")

backend = CodexAgentBackend(backend=_BadBackend())
backend = LLMAgentBackend(backend=_BadBackend())
with pytest.raises(AgentBackendError, match="custom probe failed"):
backend.preflight()

Expand All @@ -142,6 +142,6 @@ def test_codex_backend_preflight_errors_if_codex_cli_missing(tmp_path: Any) -> N
from openrange.llm import CodexBackend

nonexistent = tmp_path / "codex_does_not_exist"
backend = CodexAgentBackend(backend=CodexBackend(command=nonexistent))
backend = LLMAgentBackend(backend=CodexBackend(command=nonexistent))
with pytest.raises(AgentBackendError, match="codex CLI not found"):
backend.preflight()
12 changes: 6 additions & 6 deletions tests/test_office_persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

* ``_PermissiveBackend`` — accepts any ``tools`` argument.
* ``_CodexLikeBackend`` — rejects non-empty ``tools``; confirms the
persona works against :class:`CodexAgentBackend`.
persona works against :class:`LLMAgentBackend`.
"""

from __future__ import annotations
Expand Down Expand Up @@ -62,7 +62,7 @@ def session(self) -> _StubSession:


class _CodexLikeBackend:
"""Rejects non-empty ``tools`` — mimics real ``CodexAgentBackend``.
"""Rejects non-empty ``tools`` — mimics real ``LLMAgentBackend``.

The OfficePersona must build_agent with empty tools; if it doesn't,
this fixture surfaces the same AgentBackendError that broke
Expand All @@ -86,7 +86,7 @@ def build_agent(
) -> Any:
if tools:
raise AgentBackendError(
"CodexAgentBackend does not support tool injection.",
"LLMAgentBackend does not support tool injection.",
)
self.builds.append((system_prompt, []))
return self._session
Expand Down Expand Up @@ -279,7 +279,7 @@ def test_persona_records_speech_and_visit_via_single_shot(
npc.step(_interface(http_calls))

# build_agent must be called with EMPTY tools — that's what makes
# the persona compatible with CodexAgentBackend.
# the persona compatible with LLMAgentBackend.
assert backend.builds, "build_agent should fire on first step"
_system_prompt, tools = backend.builds[0]
assert tools == [], "single-shot persona must not inject tools"
Expand All @@ -300,9 +300,9 @@ def test_persona_ignores_codex_like_backend_when_episode_runs() -> None:
"""End-to-end smoke: codex-shape backend → no broken_reason set.

Mirrors what happens when ``RunConfig.npc_agent_backend`` is a
``CodexAgentBackend`` and the runtime invokes ``start()`` then
``LLMAgentBackend`` and the runtime invokes ``start()`` then
``step()`` repeatedly. Was failing before the AgentNPC → NPC
refactor with: "CodexAgentBackend does not support tool injection".
refactor with: "LLMAgentBackend does not support tool injection".
"""
backend = _CodexLikeBackend(response='{"speak": "ok", "visit": "/"}')
npc = OfficePersona(
Expand Down
Loading