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
32 changes: 19 additions & 13 deletions raven/cli/onboard_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2687,20 +2687,27 @@ def _everos_section(section: str) -> dict[str, Any]:
return load_everos_config().get(section, {}) or {}


def _everos_role_configured(section: str) -> bool:
"""True iff the user really configured this EverOS role.

Sole criterion for "configured", shared by every caller: model AND api_key.
The shipped everos.toml template seeds each section's model name with an
empty api_key, so a model alone also holds on a fresh install — two callers
disagreeing on this made the wizard's Back loop on itself forever.
"""
sec = _everos_section(section)
return bool(sec.get("model") and sec.get("api_key"))


def _memory_enabled() -> bool:
"""True iff EverOS memory is both selected AND usable on disk.

"Usable" requires both required models (llm and embedding) with api_key
in the EverOS toml. The seed/schema default sets model names but leaves
api_key empty, so checking model alone would mis-report a fresh,
unconfigured install as "enabled".
"Usable" requires both required roles (llm and embedding) configured.
"""
data = _load_raw_config()
if (data.get("memory") or {}).get("backend") != "everos":
return False
llm = _everos_section("llm")
emb = _everos_section("embedding")
return bool(llm.get("model") and llm.get("api_key") and emb.get("model") and emb.get("api_key"))
return _everos_role_configured("llm") and _everos_role_configured("embedding")


# Providers whose main model can be reused as the EverOS memory LLM: they
Expand Down Expand Up @@ -3691,8 +3698,7 @@ def _config_everos_role(
)

while True: # role-menu loop — a back-out of the source picker returns here
sec = _everos_section(section)
current = sec.get("model") if sec.get("api_key") else None
current = _everos_section(section).get("model") if _everos_role_configured(section) else None
if current:
choices = [
questionary.Choice(_t(f"Keep current: {current}", f"沿用当前:{current}"), value="keep"),
Expand Down Expand Up @@ -3740,10 +3746,10 @@ def _config_everos_role(
recommendation=role.get("recommendation"),
)
if result is _BACK:
if optional or _everos_section(section).get("model"):
# Optional roles offer Skip; a required role that already has a
# value on disk falls back to its keep/reconfigure menu. Either
# way, re-show the role menu rather than forcing the give-up exit.
if optional or _everos_role_configured(section):
# Optional roles offer Skip; a required role already configured
# falls back to its keep/reconfigure menu. Either way, re-show
# the role menu rather than forcing the give-up exit.
continue
# A required role with nothing configured has no Skip, so backing out
# of the picker would loop forever. Offer a bounded exit: keep trying,
Expand Down
45 changes: 45 additions & 0 deletions tests/test_cli_onboard_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,51 @@ def ask(self):
assert everos["rerank"]["base_url"] == "https://api.deepinfra.com/v1/inference"


def test_memory_seeded_role_is_not_configured(tmp_env: Path, everos_isolated: Path) -> None:
"""A seeded model with an empty api_key does not count as configured."""
from raven.config.update_everos import set_everos_section

assert onboard_commands._everos_role_configured("llm") is False
set_everos_section("llm", {"model": "openai/gpt-4.1-mini", "api_key": ""})
assert onboard_commands._everos_role_configured("llm") is False
set_everos_section("llm", {"api_key": "sk-real"})
assert onboard_commands._everos_role_configured("llm") is True


def test_memory_required_role_back_reaches_give_up_menu(
tmp_env: Path, everos_isolated: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Backing out of the picker must offer the give-up exit even when the
shipped everos.toml template already seeded a model with an empty api_key —
otherwise Back re-asks the provider picker forever."""
from raven.config.update_everos import set_everos_section

set_everos_section(
"llm",
{"model": "openai/gpt-4.1-mini", "api_key": "", "base_url": "https://openrouter.ai/api/v1"},
)

import questionary

asked: list[str] = []

class _FQ:
def __init__(self, *a: object, **kw: object) -> None:
values = [getattr(c, "value", None) for c in kw.get("choices", [])] # type: ignore[union-attr]
self._can_abort = "abort" in values

def ask(self) -> object:
asked.append("give-up" if self._can_abort else "picker")
assert len(asked) <= 4, f"Back re-asked the picker instead of offering an exit: {asked}"
return "abort" if self._can_abort else onboard_commands._BACK

monkeypatch.setattr(questionary, "select", _FQ)

out = onboard_commands._config_everos_role(section="llm", main_model=None, non_interactive=False, warnings=[])
assert out is onboard_commands._ABORT_EVEROS
assert asked == ["picker", "give-up"]


def test_model_openai_compatible_heuristic(tmp_env: Path) -> None:
"""Compat heuristic gates whether the memory LLM can reuse the main model."""
f = onboard_commands._model_is_openai_compatible
Expand Down
Loading