Skip to content

Load default robusta model from API - #946

Merged
moshemorad merged 6 commits into
masterfrom
ROB-2058-slack-holmes-bot-not-using-the-selected-default-model-from-setting
Sep 7, 2025
Merged

moshemorad merged 6 commits into
masterfrom
ROB-2058-slack-holmes-bot-not-using-the-selected-default-model-from-setting

Conversation

@moshemorad

Copy link
Copy Markdown
Collaborator

No description provided.

@coderabbitai

coderabbitai Bot commented Sep 7, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds typed Robusta model response handling, centralizes model parameter resolution and default Robusta selection in Config, passes model-name into DefaultLLM, extends DefaultLLM to store a name, adds Supabase test mocks and Robusta HTTP/test scaffolding, updates tests, and adds pytest-responses dev dependency.

Changes

Cohort / File(s) Summary
Supabase test mocks
conftest.py
Adds autouse fixtures patch_supabase(monkeypatch) and storage_dal_mock() to override Supabase config and mock SupabaseDal methods for tests.
Robusta client typing
holmes/clients/robusta_client.py
Adds RobustaModelsResponse pydantic model and changes fetch_robusta_models(...) to return Optional[RobustaModelsResponse] populated from response JSON.
Config model/LLM resolution
holmes/config.py
Introduces _default_robusta_model, loads robusta models into _model_list with name/model/base_url/is_robusta_model, adds _get_model_params(model_key), and refactors _get_llm to resolve params, prefer robusta global key for robusta models, and pass model name to DefaultLLM.
LLM constructor update
holmes/core/llm.py
DefaultLLM.__init__ gains name: Optional[str] and types tracer as optional; assigns self.name.
Project tooling
pyproject.toml
Adds dev dependency pytest-responses = "^0.5.1".
Shared test scaffolding
tests/conftest.py
Adds DEFAULT_ROBUSTA_MODEL, ROBUSTA_MODELS, clear_all_caches() (autouse) and server_config fixture that registers mocked Robusta models endpoint and temp model-list file.
Config tests: API/base/version
tests/config_class/test_config_api_base_version.py
Updates tests for new DefaultLLM signature (final model_key/name arg) and Config behavior (no api_key in Config ctor).
Config tests: _get_llm selection
tests/config_class/test_config_get_llm.py
New tests validating model selection behavior (_get_llm) across default, explicit key, unknown key, and no-default scenarios.
Config tests: load model list
tests/config_class/test_config_load_model_list.py
Adds ROBUSTA_API_ENDPOINT import and test asserting robusta model entries include name, base_url, is_robusta_model, and default selection.
Config tests: load Robusta AI
tests/config_class/test_config_load_robusta_ai.py
Replaces list-return mocks with RobustaModelsResponse instances and adds ROBUSTA_TEST_MODELS.
Toolset tests: Internet
tests/plugins/toolsets/test_internet.py
Uses responses fixture to mock HTTP GET in test_fetch_webpage (registers mocked response).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Test
  participant Config as Config
  participant RobustaClient as Robusta Client
  participant ModelList as _model_list
  participant LLM as DefaultLLM

  rect rgb(250,250,255)
    note over Test,RobustaClient: Config load (Robusta AI enabled)
    Test->>RobustaClient: fetch_robusta_models(account_id, token)
    RobustaClient-->>Test: RobustaModelsResponse(models[], default_model?)
    Test->>Config: load_from_env()
    Config->>ModelList: configure_robusta_ai_model(robusta_models.models, robusta_models.default_model)
    ModelList-->>Config: entries with name, base_url, is_robusta_model, model
    Config->>Config: set _default_robusta_model (if provided)
  end

  rect rgb(245,255,245)
    note over Test,Config: Acquire LLM instance
    Test->>Config: _get_llm(model_key?)
    Config->>Config: _get_model_params(model_key) -> params
    alt params.is_robusta_model and Config has global robusta key
      Config->>LLM: DefaultLLM(model=params.model, api_key=<robusta_key>, api_base=params.base_url, api_version=params.api_version, args=params.args, tracer, name=params.name)
    else
      Config->>LLM: DefaultLLM(model=params.model, api_key=params.api_key, api_base=params.api_base, api_version=params.api_version, args=params.args, tracer, name=params.name)
    end
    LLM-->>Test: DefaultLLM instance (with .name)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • aantn
  • arikalon1

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ROB-2058-slack-holmes-bot-not-using-the-selected-default-model-from-setting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@moshemorad moshemorad changed the title ROB-2058 Load default robusta model from API Sep 7, 2025
@moshemorad
moshemorad force-pushed the ROB-2058-slack-holmes-bot-not-using-the-selected-default-model-from-setting branch from 06a1038 to 103320e Compare September 7, 2025 11:00
@moshemorad
moshemorad marked this pull request as ready for review September 7, 2025 11:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
holmes/config.py (1)

68-75: Environment variable substitution isn’t applied; also guard None.

You rebind the loop variable instead of updating the dict. If load_yaml_file returns None, .items() will fail.

Apply:

 def parse_models_file(path: str):
-    models = load_yaml_file(path, raise_error=False, warn_not_found=False)
-
-    for _, params in models.items():
-        params = replace_env_vars_values(params)
-
-    return models
+    models = load_yaml_file(path, raise_error=False, warn_not_found=False) or {}
+    for k, params in list(models.items()):
+        models[k] = replace_env_vars_values(params)
+    return models
🧹 Nitpick comments (16)
holmes/core/llm.py (1)

75-77: New tracer/name args: LGTM; add attribute type hints to satisfy mypy.

mypy may complain about dynamically added attributes. Declare class attributes for tracer/name.

Apply this diff near the existing class attributes:

 class DefaultLLM(LLM):
     model: str
     api_key: Optional[str]
     api_base: Optional[str]
     api_version: Optional[str]
     args: Dict
+    tracer: Optional[Any]
+    name: Optional[str]

Also applies to: 84-84

holmes/clients/robusta_client.py (1)

9-10: Unify timeouts via a constant.

POST uses a literal 10s while GET uses TIMEOUT=0.5s. Prefer explicit POST timeout constant.

Apply:

-HOLMES_GET_INFO_URL = f"{ROBUSTA_API_ENDPOINT}/api/holmes/get_info"
-TIMEOUT = 0.5
+HOLMES_GET_INFO_URL = f"{ROBUSTA_API_ENDPOINT}/api/holmes/get_info"
+TIMEOUT = 0.5
+POST_TIMEOUT = 10.0
@@
-        resp = requests.post(
+        resp = requests.post(
             f"{ROBUSTA_API_ENDPOINT}/api/llm/models",
             json=session_request,
-            timeout=10,
+            timeout=POST_TIMEOUT,
         )

Also applies to: 27-31

tests/plugins/toolsets/test_internet.py (1)

115-120: Mock response: add content-type header for fidelity.

Set Content-Type to mimic real HTML.

Apply:

-    responses.get(
+    responses.get(
         TEST_URL,
         status=200,
-        body=EXPECTED_TEST_RESULT,
+        body=EXPECTED_TEST_RESULT,
+        headers={"Content-Type": "text/html; charset=utf-8"},
     )
tests/config_class/test_config_load_robusta_ai.py (1)

10-14: Remove unused helper.

fake_load_robusta_api_key is defined but never used; delete to avoid confusion.

-def fake_load_robusta_api_key(config, _):
-    config.account_id = "mock-account"
-    config.session_token = SecretStr("mock-token")
-    config.api_key = SecretStr("mock-token")
-
tests/config_class/test_config_get_llm.py (2)

14-23: Typo in test name.

Rename test_confgi_get_llm_with_model_key_returns_model_from_config.

-def test_confgi_get_llm_with_model_key_returns_model_from_config(
+def test_config_get_llm_with_model_key_returns_model_from_config(

7-12: Avoid hardcoding Robusta API base URL.

Use ROBUSTA_API_ENDPOINT for stability across environments.

-from tests.conftest import DEFAULT_ROBUSTA_MODEL
+from tests.conftest import DEFAULT_ROBUSTA_MODEL
+from holmes.common.env_vars import ROBUSTA_API_ENDPOINT
@@
-    assert llm.api_base == f"https://api.robusta.dev/llm/{DEFAULT_ROBUSTA_MODEL}"
+    assert llm.api_base == f"{ROBUSTA_API_ENDPOINT}/llm/{DEFAULT_ROBUSTA_MODEL}"
@@
-    assert llm.api_base == f"https://api.robusta.dev/llm/{DEFAULT_ROBUSTA_MODEL}"
+    assert llm.api_base == f"{ROBUSTA_API_ENDPOINT}/llm/{DEFAULT_ROBUSTA_MODEL}"

Also applies to: 25-32

tests/config_class/test_config_api_base_version.py (2)

51-56: Add assertion for model_name (7th arg) to prevent regressions.

You’re already verifying args 0–5. Also assert the name forwarded to DefaultLLM.

Apply:

-        # Check that DefaultLLM was called with the right positional arguments
+        # Check that DefaultLLM was called with the right positional arguments
         call_args = mock_default_llm.call_args[0]
         assert call_args[0] == "test-model"
         assert call_args[1] is None
         assert call_args[2] == "https://test.api.base"
         assert call_args[3] == "2023-12-01"
         assert call_args[4] == {}
         assert call_args[5] is None  # tracer
+        assert call_args[6] == "test-model"

200-208: Consider stabilizing the “name” semantics.

Here the 7th arg becomes the model id ("gpt-4") rather than the model-list key (“first-model”), unlike other tests. If you want consistent labels for tracing/metrics, set a “name” in the model-list entry or change _get_llm to prefer the key when no explicit name exists.

Example change to the test data:

-        "first-model": {
+        "first-model": {
+            "name": "first-model",
             "model": "gpt-4",

Or adjust _get_llm to compute name as: model_key or name or model.

tests/conftest.py (3)

15-24: Don’t swallow all exceptions when clearing caches.

Silent failures can mask setup issues. Log at debug at least, or narrow to AttributeError/ImportError.

Apply:

-    except Exception:
-        pass
+    except (AttributeError, ImportError) as e:
+        import logging
+        logging.debug("Cache clear skipped: %s", e)

27-35: Mocked Robusta models may never be used in current fixture flow.

With a user-provided model list and default LOAD_ALL_ROBUSTA_MODELS=False, configure_robusta_ai_model won’t call fetch_robusta_models, so this responses stub is unused. Either enable loading or drop the stub to speed tests.

Option A (enable):

     monkeypatch.setattr("holmes.config.ROBUSTA_AI", True)
+    monkeypatch.setattr("holmes.config.LOAD_ALL_ROBUSTA_MODELS", True)

Option B: remove the responses.post in this fixture and keep it in tests that actually rely on it.


44-49: Fixture intent clarification.

Setting ROBUSTA_AI=True while also providing a local model list disables Robusta auto-loading (_should_load_robusta_ai returns False). If that’s intended, add a brief comment for future readers.

holmes/config.py (5)

151-154: Ensure _model_list is always a dict.

Be defensive in case parse_models_file ever returns None.

Apply:

-        self._model_list = parse_models_file(MODEL_LIST_FILE_LOCATION)
+        self._model_list = parse_models_file(MODEL_LIST_FILE_LOCATION) or {}

180-188: Hardcoding "model": "gpt-4o" for all Robusta entries.

If server returns a canonical underlying model, prefer that; otherwise fine as a default.


200-208: Default Robusta base_url shape inconsistent with per-model entries.

Per-model uses f"{ROBUSTA_API_ENDPOINT}/llm/{model}", default uses ROBUSTA_API_ENDPOINT only. If the endpoint requires /llm routing, default may differ at runtime.

Option:

-            self._model_list[ROBUSTA_AI_MODEL_NAME] = {
+            self._model_list[ROBUSTA_AI_MODEL_NAME] = {
                 "name": ROBUSTA_AI_MODEL_NAME,
-                "base_url": ROBUSTA_API_ENDPOINT,
+                "base_url": f"{ROBUSTA_API_ENDPOINT}/llm/{self.model or 'gpt-4o'}",
                 "is_robusta_model": True,
             }

571-577: Robusta API key handling: fail-fast if missing.

If a Robusta entry is chosen but api_key isn’t present on self (and none in model_params), we’ll pass None to DefaultLLM. Prefer an explicit error with guidance.

Apply:

-        if is_robusta_model and self.api_key:
+        if is_robusta_model and self.api_key:
             # we set here the api_key since it is being refresh when exprided and not as part of the model loading.
             api_key = self.api_key.get_secret_value()  # type: ignore
         else:
             api_key = model_params.pop("api_key", None)
+            if is_robusta_model and not api_key:
+                raise ValueError("Robusta model selected but no API key available")

585-589: Stabilize name computation (telemetry consistency).

Prefer model_key over “name” to keep labels tied to the model-list key; fall back to name/model.

Apply:

-        model_name = model_params.pop("name", None) or model_key or model
+        model_name = model_key or model_params.pop("name", None) or model
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec4307a and 78bcc1621aad107472d5af9f33be4760a638f6a8.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • conftest.py (2 hunks)
  • holmes/clients/robusta_client.py (2 hunks)
  • holmes/config.py (4 hunks)
  • holmes/core/llm.py (1 hunks)
  • pyproject.toml (1 hunks)
  • tests/config_class/test_config_api_base_version.py (9 hunks)
  • tests/config_class/test_config_get_llm.py (1 hunks)
  • tests/config_class/test_config_load_model_list.py (2 hunks)
  • tests/config_class/test_config_load_robusta_ai.py (5 hunks)
  • tests/conftest.py (1 hunks)
  • tests/plugins/toolsets/test_internet.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Type hints are required (mypy is configured in pyproject.toml)

Files:

  • tests/config_class/test_config_load_model_list.py
  • tests/plugins/toolsets/test_internet.py
  • holmes/clients/robusta_client.py
  • tests/config_class/test_config_get_llm.py
  • tests/conftest.py
  • conftest.py
  • holmes/core/llm.py
  • tests/config_class/test_config_load_robusta_ai.py
  • tests/config_class/test_config_api_base_version.py
  • holmes/config.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use invalid pytest markers; only use markers/tags declared in pyproject.toml

Files:

  • tests/config_class/test_config_load_model_list.py
  • tests/plugins/toolsets/test_internet.py
  • tests/config_class/test_config_get_llm.py
  • tests/conftest.py
  • tests/config_class/test_config_load_robusta_ai.py
  • tests/config_class/test_config_api_base_version.py
🧠 Learnings (1)
📚 Learning: 2025-07-08T08:45:41.069Z
Learnt from: nherment
PR: robusta-dev/holmesgpt#610
File: .github/workflows/llm-evaluation.yaml:39-42
Timestamp: 2025-07-08T08:45:41.069Z
Learning: The robusta-dev/holmesgpt codebase has comprehensive existing validation for Azure environment variables (AZURE_API_BASE, AZURE_API_KEY, AZURE_API_VERSION) and MODEL in tests/llm/utils/classifiers.py, tests/llm/conftest.py, and holmes/core/llm.py. Don't suggest adding redundant validation logic.

Applied to files:

  • tests/config_class/test_config_load_model_list.py
🔇 Additional comments (13)
pyproject.toml (1)

84-84: Add pytest-responses: looks good; verify compatibility matrix.

Plugin version ^0.5.1 generally pairs with responses ≥0.23 and pytest ≥7. Please double-check in CI that pytest (8.3.3) + responses (0.23.1) + pytest-responses (0.5.1) work together across all runners. If any flake appears, consider bumping responses to the latest 0.24.x in lockstep.

holmes/clients/robusta_client.py (1)

17-21: Typed response model: good change.

Using a Pydantic response object makes downstream usage safer and clearer.

conftest.py (1)

133-147: Autouse service patches: LGTM.

Good isolation of external deps (Supabase + credentials) without leaking real values.

Also applies to: 149-159

tests/config_class/test_config_load_robusta_ai.py (1)

4-8: Adapting to RobustaModelsResponse: LGTM.

Switching patches to return the typed response keeps tests aligned with client changes.

tests/config_class/test_config_api_base_version.py (7)

81-89: LLM call expectations look correct.

The model-list key is forwarded as the 7th arg and tracer is None.


118-126: Model-list overrides over config are asserted correctly.

Good precedence checks and name forwarding.


153-161: Fallback to config values is validated well.

Covers missing api_base/api_version in model list.


255-263: Specific model selection from model list is correctly asserted.

Arguments and name forwarding (“openai-gpt35”) look good.


289-297: base_url → api_base mapping verified.

Covers legacy field support without ambiguity.


324-332: api_base precedence over base_url is asserted correctly.


360-368: Config api_base fallback is asserted correctly.

holmes/config.py (2)

173-179: Network fetch path: OK with broad try/except; consider timeout/backoff at source.

Assuming fetch_robusta_models handles timeouts/retries. If not, add them there.


537-565: Model resolution flow: solid.

Good handling of explicit key, default Robusta model, then first model fallback with copies.

Comment thread holmes/clients/robusta_client.py Outdated
Comment thread tests/config_class/test_config_load_model_list.py Outdated
Comment thread holmes/config.py Outdated
Comment thread holmes/config.py Outdated
Comment thread holmes/core/llm.py Outdated
Comment thread tests/plugins/toolsets/test_internet.py Outdated
Comment thread holmes/config.py Outdated
Comment thread holmes/config.py Outdated

@Avi-Robusta Avi-Robusta left a comment

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.

Looks good, two small changes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
holmes/config.py (3)

173-194: Robusta models: typed response handling and default selection look solid.

  • Using the structured response and guarding with if not robusta_models or not robusta_models.models: matches the prior suggestion and prevents crashes.
  • Persisting default_model into _default_robusta_model helps centralize selection.

Optional: model names contain spaces (e.g., “sonnet-4 preview”). If these are ever used as URL path segments beyond base_url, ensure the HTTP client safely encodes them. No change requested here since tests assert the current form.


180-187: Include a concrete “model” for Robusta entries.

Setting "model": "gpt-4o" for Robusta entries and adding "name" are both correct and align with the new selection flow.


200-209: Set the default Robusta model when only the single default is loaded.

This keeps _get_model_params deterministic (it won’t fall back to “first model” ordering) and addresses the earlier ask to persist the default model key.

 def _load_default_robusta_config(self):
     if self._should_load_robusta_ai() and self.api_key:
         logging.info("Loading default Robusta AI model")
         self._model_list[ROBUSTA_AI_MODEL_NAME] = {
             "name": ROBUSTA_AI_MODEL_NAME,
             "base_url": ROBUSTA_API_ENDPOINT,
             "is_robusta_model": True,
             "model": "gpt-4o",
         }
+        # Keep selection logic consistent with multi-model flows
+        self._default_robusta_model = ROBUSTA_AI_MODEL_NAME
🧹 Nitpick comments (3)
holmes/config.py (3)

137-137: Track and actually use the default Robusta model key.

Good adding _default_robusta_model. To fully leverage it, also set it when loading the single default Robusta config so _get_model_params prefers it.

See diff in the _load_default_robusta_config comment below.


538-566: Tighten return typing for _get_model_params.

Return type is too generic for mypy config; prefer dict[str, Any]. You already import Any.

-def _get_model_params(self, model_key: Optional[str] = None) -> dict:
+def _get_model_params(self, model_key: Optional[str] = None) -> dict[str, Any]:

567-591: Add type for tracer and fix a minor comment typo.

  • Annotate tracer to satisfy the “type hints required” rule.
  • Typo: “exprided” → “expired”.
-def _get_llm(self, model_key: Optional[str] = None, tracer=None) -> "DefaultLLM":
+def _get_llm(self, model_key: Optional[str] = None, tracer: Optional[Any] = None) -> "DefaultLLM":
@@
-        if is_robusta_model and self.api_key:
-            # we set here the api_key since it is being refresh when exprided and not as part of the model loading.
+        if is_robusta_model and self.api_key:
+            # we set the api_key here since it is refreshed when expired, not at model loading time.
             api_key = self.api_key.get_secret_value()  # type: ignore

Heads-up: for non-Robusta models without a model-list entry, api_key is now None (by design per tests). Please ensure downstream DefaultLLM continues to pick credentials from env as intended.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78bcc1621aad107472d5af9f33be4760a638f6a8 and ff07c37e8ab63aa6024656ef6d0283e32b197e95.

📒 Files selected for processing (2)
  • holmes/clients/robusta_client.py (2 hunks)
  • holmes/config.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • holmes/clients/robusta_client.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Type hints are required (mypy is configured in pyproject.toml)

Files:

  • holmes/config.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals

Avi-Robusta
Avi-Robusta previously approved these changes Sep 7, 2025
@moshemorad
moshemorad enabled auto-merge (squash) September 7, 2025 12:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
holmes/config.py (3)

173-179: Defensive fallback looks good.

fetch_robusta_models(...) guarded with if not robusta_models or not robusta_models.models: addresses the empty/None response path.


180-187: Robusta models ingestion: shape looks correct.

Including "name", "is_robusta_model", and "model": "gpt-4o" per prior feedback is solid.


200-210: Default Robusta model entry is well-formed.

Adding "name" and "model": "gpt-4o" and setting _default_robusta_model = ROBUSTA_AI_MODEL_NAME resolves prior gaps.

🧹 Nitpick comments (3)
holmes/config.py (3)

137-137: Make this a private attribute (Pydantic v2).

Declare _default_robusta_model as a PrivateAttr so it won’t appear in .model_dump() or validation.

Apply within this range:

-    _default_robusta_model: Optional[str] = None
+    _default_robusta_model: Optional[str] = PrivateAttr(default=None)

Also add the import at the top:

from pydantic import BaseModel, ConfigDict, FilePath, SecretStr, PrivateAttr

539-567: Centralized model resolution: add precise return type and improve fallback logging.

  • Add dict[str, Any] return type for mypy.
  • Log the actual first model key to aid debugging.
-    def _get_model_params(self, model_key: Optional[str] = None) -> dict:
+    def _get_model_params(self, model_key: Optional[str] = None) -> dict[str, Any]:
@@
-        first_model_params = next(iter(self._model_list.values())).copy()
-        logging.info("Using first model")
-        return first_model_params
+        first_key, first_model_params = next(iter(self._model_list.items()))
+        logging.info(f"Using first model: {first_key}")
+        return first_model_params.copy()

575-577: Typo in comment.

“exprided” → “expired”; tighten the sentence.

-            # we set here the api_key since it is being refresh when exprided and not as part of the model loading.
+            # set api_key here because it is refreshed when expired, not as part of model loading.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff07c37e8ab63aa6024656ef6d0283e32b197e95 and 6f5321b140491c53d82af932cb64017f456e0cb5.

📒 Files selected for processing (1)
  • holmes/config.py (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Type hints are required (mypy is configured in pyproject.toml)

Files:

  • holmes/config.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.12)
  • GitHub Check: llm_evals
🔇 Additional comments (3)
holmes/config.py (3)

180-187: Verify base_url path consistency.

Here you use base_url=f"{ROBUSTA_API_ENDPOINT}/llm/{model}", while the default config below uses base_url=ROBUSTA_API_ENDPOINT. Confirm the server expects per-model routing on /llm/{model} and consider aligning the default path for consistency.

Would you like a patch that standardizes both paths?


189-194: Persisting the default Robusta model is correct.

Storing robusta_models.default_model into _default_robusta_model enables sane resolution in _get_model_params.


200-210: Double-check default base_url vs. multi-model base_url.

If per-model endpoints are required, consider:

-                "base_url": ROBUSTA_API_ENDPOINT,
+                "base_url": f"{ROBUSTA_API_ENDPOINT}/llm/{ROBUSTA_AI_MODEL_NAME}",

Otherwise, document why the default uses the root while enumerated models use /llm/{model}.

Comment thread holmes/config.py Outdated
@moshemorad
moshemorad force-pushed the ROB-2058-slack-holmes-bot-not-using-the-selected-default-model-from-setting branch from 6f5321b to eb7a4f7 Compare September 7, 2025 12:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
holmes/config.py (1)

568-592: Non-Robusta api_key fallback likely lost — restore global/env key.

When not a Robusta model, api_key now depends solely on model_params and may drop env/global Config.api_key. This can break non-Robusta usage. Also fix a small comment typo and annotate tracer.

Apply:

-    def _get_llm(self, model_key: Optional[str] = None, tracer=None) -> "DefaultLLM":
+    def _get_llm(self, model_key: Optional[str] = None, tracer: Optional[Any] = None) -> "DefaultLLM":
@@
-        if is_robusta_model and self.api_key:
-            # we set here the api_key since it is being refresh when exprided and not as part of the model loading.
+        if is_robusta_model and self.api_key:
+            # set api_key here since it is refreshed when expired and not as part of model loading.
             api_key = self.api_key.get_secret_value()  # type: ignore
         else:
-            api_key = model_params.pop("api_key", None)
+            # Prefer explicit model-level key; fall back to global Config.api_key (may come from env)
+            api_key = model_params.pop("api_key", None) or (
+                self.api_key.get_secret_value() if self.api_key else None
+            )
#!/bin/bash
# Sanity: confirm DefaultLLM accepts the added model_name arg and no internal api_key override exists.
rg -n -C3 $'class\\s+DefaultLLM\\b|def\\s+__init__\\s*\\(' holmes/core/llm.py
🧹 Nitpick comments (2)
holmes/config.py (2)

180-187: Avoid double slashes in constructed base_url.

If ROBUSTA_API_ENDPOINT ends with '/', this will yield '//llm/...'. Minor but easy to harden.

Apply:

-                    "base_url": f"{ROBUSTA_API_ENDPOINT}/llm/{model}",
+                    "base_url": f"{ROBUSTA_API_ENDPOINT.rstrip('/')}/llm/{model}",

539-567: Tighten typing and improve “first model” log.

Return type should be dict[str, Any], and logging the chosen first model aids debugging.

Apply:

-    def _get_model_params(self, model_key: Optional[str] = None) -> dict:
+    def _get_model_params(self, model_key: Optional[str] = None) -> dict[str, Any]:
@@
-        first_model_params = next(iter(self._model_list.values())).copy()
-        logging.info("Using first model")
-        return first_model_params
+        first_key = next(iter(self._model_list.keys()))
+        first_model_params = self._model_list[first_key].copy()
+        logging.info(f"Using first model: {first_key}")
+        return first_model_params
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f5321b140491c53d82af932cb64017f456e0cb5 and eb7a4f7.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • conftest.py (2 hunks)
  • holmes/clients/robusta_client.py (2 hunks)
  • holmes/config.py (4 hunks)
  • holmes/core/llm.py (1 hunks)
  • pyproject.toml (1 hunks)
  • tests/config_class/test_config_api_base_version.py (9 hunks)
  • tests/config_class/test_config_get_llm.py (1 hunks)
  • tests/config_class/test_config_load_model_list.py (2 hunks)
  • tests/config_class/test_config_load_robusta_ai.py (5 hunks)
  • tests/conftest.py (1 hunks)
  • tests/plugins/toolsets/test_internet.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • holmes/core/llm.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • tests/plugins/toolsets/test_internet.py
  • holmes/clients/robusta_client.py
  • pyproject.toml
  • tests/config_class/test_config_get_llm.py
  • conftest.py
  • tests/config_class/test_config_load_model_list.py
  • tests/config_class/test_config_load_robusta_ai.py
  • tests/conftest.py
  • tests/config_class/test_config_api_base_version.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Type hints are required (mypy is configured in pyproject.toml)

Files:

  • holmes/config.py
🧠 Learnings (1)
📚 Learning: 2025-07-08T08:45:41.069Z
Learnt from: nherment
PR: robusta-dev/holmesgpt#610
File: .github/workflows/llm-evaluation.yaml:39-42
Timestamp: 2025-07-08T08:45:41.069Z
Learning: The robusta-dev/holmesgpt codebase has comprehensive existing validation for Azure environment variables (AZURE_API_BASE, AZURE_API_KEY, AZURE_API_VERSION) and MODEL in tests/llm/utils/classifiers.py, tests/llm/conftest.py, and holmes/core/llm.py. Don't suggest adding redundant validation logic.

Applied to files:

  • holmes/config.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: llm_evals
🔇 Additional comments (4)
holmes/config.py (4)

137-137: Default Robusta model slot added — LGTM.

This enables consistent fallback/selection across the new resolution path.


173-179: Typed fetch + empty-list fallback — LGTM.

Good use of the typed response and graceful fallback to default config when models are missing.


189-194: Persisting server-provided default — LGTM.

Storing and later honoring default_model is the right call.


204-210: Confirm default Robusta base_url path consistency.

Multi-model entries use “…/llm/{model}” but the single default uses the endpoint root. Verify the server expects root here; if not, switch to the llm path.

If needed:

-                "base_url": ROBUSTA_API_ENDPOINT,
+                "base_url": f"{ROBUSTA_API_ENDPOINT.rstrip('/')}/llm/{ROBUSTA_AI_MODEL_NAME}",

@moshemorad
moshemorad merged commit 75fa8ad into master Sep 7, 2025
7 checks passed
@moshemorad
moshemorad deleted the ROB-2058-slack-holmes-bot-not-using-the-selected-default-model-from-setting branch September 7, 2025 13:21
@github-actions

github-actions Bot commented Sep 7, 2025

Copy link
Copy Markdown
Contributor

Results of HolmesGPT evals

  • ask_holmes: 30/37 test cases were successful, 1 regressions, 2 skipped, 3 setup failures
Test suite Test case Status
ask 01_how_many_pods ✅
ask 02_what_is_wrong_with_pod ✅
ask 04_related_k8s_events ↪️
ask 05_image_version ✅
ask 09_crashpod ✅
ask 10_image_pull_backoff ✅
ask 110_k8s_events_image_pull ✅
ask 11_init_containers ✅
ask 13a_pending_node_selector_basic ✅
ask 14_pending_resources ✅
ask 15_failed_readiness_probe ✅
ask 17_oom_kill ✅
ask 18_crash_looping_v2 🚧
ask 19_detect_missing_app_details ✅
ask 20_long_log_file_search ✅
ask 24_misconfigured_pvc ✅
ask 24a_misconfigured_pvc_basic ✅
ask 28_permissions_error 🚧
ask 29_events_from_alert_manager ↪️
ask 39_failed_toolset ✅
ask 41_setup_argo ✅
ask 42_dns_issues_steps_new_tools ⚠️
ask 43_current_datetime_from_prompt ✅
ask 45_fetch_deployment_logs_simple ✅
ask 51_logs_summarize_errors ✅
ask 53_logs_find_term ✅
ask 54_not_truncated_when_getting_pods ❌
ask 59_label_based_counting ✅
ask 60_count_less_than 🚧
ask 61_exact_match_counting ✅
ask 63_fetch_error_logs_no_errors ✅
ask 79_configmap_mount_issue ✅
ask 83_secret_not_found ✅
ask 86_configmap_like_but_secret ✅
ask 93_calling_datadog[0] ✅
ask 93_calling_datadog[1] ✅
ask 93_calling_datadog[2] ✅

Legend

  • ✅ the test was successful
  • ↪️ the test was skipped
  • ⚠️ the test failed but is known to be flaky or known to fail
  • 🚧 the test had a setup failure (not a code regression)
  • 🔧 the test failed due to mock data issues (not a code regression)
  • ❌ the test failed and should be fixed before merging the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants