Skip to content

ROB-2346 resync robusta models if robusta model is not found - #1110

Merged
moshemorad merged 12 commits into
masterfrom
ROB-2346-holmes-sass-model-selection
Nov 25, 2025
Merged

moshemorad merged 12 commits into
masterfrom
ROB-2346-holmes-sass-model-selection

Conversation

@RoiGlinik

Copy link
Copy Markdown
Collaborator

This case probably means robusta models is out of sync

This case probably means robusta models is out of sync
@coderabbitai

coderabbitai Bot commented Nov 9, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Removed caching from fetch_robusta_models. Added a reentrant lock _lock to LLMModelRegistry. models and get_model_params now acquire the lock; get_llm was removed. get_model_params returns model_copy() and, for keys starting with Robusta/, triggers a Robusta reconfigure-and-retry flow.

Changes

Cohort / File(s) Summary
Robusta client caching
holmes/clients/robusta_client.py
Removed @cache decorator from fetch_robusta_models; calls now fetch fresh data on each invocation.
LLM registry concurrency & Robusta sync
holmes/core/llm.py
Added _lock (threading.RLock) to LLMModelRegistry; removed get_llm. models property and get_model_params acquire the lock. get_model_params returns model_copy(); if model_key starts with Robusta/ it triggers a reconfigure (calls into Robusta fetch path) and retries lookup before falling back or erroring.
Tests — get_model_params behavior
tests/core/test_llm_model_registry_get_model_params.py
Added unit tests covering valid key lookup, fallback-to-first, default-Robusta handling, Robusta resync behavior (including logs) and still-not-found scenarios; includes fixtures for mock config/DAL and model entries.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant LLMRegistry as LLMModelRegistry
    participant Configurer as configure_robusta_ai_model
    participant Robusta as fetch_robusta_models

    Caller->>LLMRegistry: get_model_params(model_key)
    LLMRegistry->>LLMRegistry: acquire _lock
    LLMRegistry->>LLMRegistry: lookup model_key
    alt Found
        LLMRegistry-->>Caller: return model_copy()
        LLMRegistry->>LLMRegistry: release _lock
    else Not found
        LLMRegistry->>LLMRegistry: release _lock
        alt model_key starts with "Robusta/"
            Note right of LLMRegistry `#ffd9b3`: Robusta reconfigure-and-retry
            LLMRegistry->>Configurer: configure()
            Configurer->>Robusta: fetch_robusta_models (no cache)
            Robusta-->>Configurer: fresh data
            Configurer-->>LLMRegistry: replace _llms
            LLMRegistry->>LLMRegistry: acquire _lock
            LLMRegistry->>LLMRegistry: re-lookup model_key
            alt Found after sync
                LLMRegistry-->>Caller: return model_copy()
            else Still not found
                LLMRegistry-->>Caller: raise error / fallback to first
            end
            LLMRegistry->>LLMRegistry: release _lock
        else Non-Robusta model
            LLMRegistry-->>Caller: raise error / fallback to first
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Check lock scope/ordering in holmes/core/llm.py to avoid deadlocks and minimize hold time.
  • Verify callers handle model_copy() semantics (deep copy behavior) versus previous behavior.
  • Confirm configure_robusta_ai_model() replacing self._llms is safe for concurrent readers and that tests cover the resync/logging behavior.

Possibly related PRs

Suggested reviewers

  • moshemorad
  • nherment

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is related to the changeset—it explains the motivation (models being out of sync) for the primary change implemented across the modified files.
Title check ✅ Passed The title clearly describes the main change: adding Robusta model resync functionality when a Robusta model is not found, which aligns with the primary objective reflected in all three files' changes (cache removal, thread-safety/resync logic, and comprehensive tests).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ROB-2346-holmes-sass-model-selection

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 previously approved these changes Nov 9, 2025
@moshemorad
moshemorad self-requested a review November 10, 2025 07:46

@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/core/llm.py (1)

640-666: Inconsistent use of copy() vs model_copy().

Lines 640 and 648 use model_copy() (correct for Pydantic v2), but lines 658 and 666 still use copy(). This inconsistency could lead to issues if copy() doesn't exist or behaves differently.

Apply this diff to use model_copy() consistently:

             logging.info(
                 f"Using default Robusta AI model: {self._default_robusta_model}"
             )
-            return model_params.copy()
+            return model_params.model_copy()
 
         logging.error(
             f"Couldn't find default Robusta AI model: {self._default_robusta_model} in model list"
         )
 
     model_key, first_model_params = next(iter(self._llms.items()))
     logging.debug(f"Using first available model: {model_key}")
-    return first_model_params.copy()
+    return first_model_params.model_copy()
🧹 Nitpick comments (1)
holmes/core/llm.py (1)

672-675: Consider returning a shallow copy for improved thread safety.

The lock guards access to _llms, but returning the dictionary directly exposes it to potential concurrent modifications by the caller. For stronger isolation, consider returning a shallow copy.

 @property
 def models(self) -> dict[str, ModelEntry]:
     with self._lock:
-        return self._llms
+        return self._llms.copy()

Note: This adds a performance cost, so only apply if concurrent modification by callers is a realistic concern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b559dbc and 8447d2c.

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for formatting and linting (configured in pyproject.toml) for all Python code
Type hints are required; code should pass mypy (configured in pyproject.toml)
ALWAYS place Python imports at the top of the file, not inside functions or methods

Files:

  • holmes/core/llm.py
🧠 Learnings (1)
📚 Learning: 2025-08-08T06:15:30.784Z
Learnt from: nilo19
Repo: robusta-dev/holmesgpt PR: 695
File: holmes/core/transformers/registry.py:9-19
Timestamp: 2025-08-08T06:15:30.784Z
Learning: holmes/core/transformers/registry.py: TransformerRegistry is intentionally single-threaded and not designed to be thread-safe; avoid proposing locks unless multi-threaded access is introduced later.

Applied to files:

  • holmes/core/llm.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). (3)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals
  • GitHub Check: build
🔇 Additional comments (3)
holmes/core/llm.py (3)

4-4: LGTM: Threading import properly placed.

The import is correctly positioned at the top of the file as required by the coding guidelines.


642-648: Good fallback logic for out-of-sync Robusta models.

The resync mechanism appropriately handles the case where a Robusta model key is requested but not found in the registry. The prefix check and re-fetch logic align with the PR objective.

However, ensure the thread-safety issue with configure_robusta_ai_model() (raised in the earlier comment on line 528) is addressed.


668-670: Lock acquisition protects dictionary access.

The lock appropriately guards the lookup in _llms. Note that the returned ModelEntry object itself is not protected from concurrent modifications, but this is acceptable if the caller treats it as read-only.

Comment thread holmes/core/llm.py
Comment thread holmes/core/llm.py Outdated

@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

♻️ Duplicate comments (1)
holmes/core/llm.py (1)

528-528: Thread-safety remains incomplete: configure_robusta_ai_model() still unprotected.

The RLock initialization is correct, but the critical issue from the previous review persists: configure_robusta_ai_model() (lines 565-600) modifies self._llms without acquiring self._lock. While the call at line 645 is safe because it executes under the lock (RLock is reentrant), configure_robusta_ai_model() should defensively acquire the lock itself to protect all modification paths.

Wrap the body of configure_robusta_ai_model() with the lock:

def configure_robusta_ai_model(self) -> None:
    with self._lock:
        try:
            if not self.config.cluster_name or not LOAD_ALL_ROBUSTA_MODELS:
                self._load_default_robusta_config()
                return
            # ... rest of method
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8447d2c and 7283ff3.

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for formatting and linting (configured in pyproject.toml) for all Python code
Type hints are required; code should pass mypy (configured in pyproject.toml)
ALWAYS place Python imports at the top of the file, not inside functions or methods

Files:

  • holmes/core/llm.py
🧠 Learnings (1)
📚 Learning: 2025-08-08T06:15:30.784Z
Learnt from: nilo19
Repo: robusta-dev/holmesgpt PR: 695
File: holmes/core/transformers/registry.py:9-19
Timestamp: 2025-08-08T06:15:30.784Z
Learning: holmes/core/transformers/registry.py: TransformerRegistry is intentionally single-threaded and not designed to be thread-safe; avoid proposing locks unless multi-threaded access is introduced later.

Applied to files:

  • holmes/core/llm.py
🪛 Ruff (0.14.4)
holmes/core/llm.py

635-635: Create your own exception

(TRY002)


635-635: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (3)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals
  • GitHub Check: build
🔇 Additional comments (2)
holmes/core/llm.py (2)

4-4: LGTM: Import correctly placed.

The threading import is properly placed at the top of the file as per coding guidelines.


633-667: Good thread-safe implementation with consistent defensive copying.

The method correctly:

  • Wraps all state access under self._lock
  • Returns model_copy() on all paths, ensuring callers receive defensive copies
  • Implements Robusta model resync logic (lines 643-649) safely within the lock

The lock granularity is appropriate for this method's complexity.

Comment thread holmes/core/llm.py Outdated
Comment thread holmes/core/llm.py

@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

♻️ Duplicate comments (2)
holmes/core/llm.py (2)

528-528: Critical: Lock initialization incomplete—configure_robusta_ai_model still unguarded.

While self._lock is now initialized, the synchronization remains incomplete. configure_robusta_ai_model() (lines 565-603) modifies self._llms at line 594 without acquiring the lock, creating a race condition with get_model_params() which reads self._llms under the lock. This is especially problematic when the resync logic at line 648 calls configure_robusta_ai_model() concurrently.

Wrap the model loading logic in configure_robusta_ai_model with the lock:

 def configure_robusta_ai_model(self) -> None:
+    with self._lock:
         try:
             if not self.config.cluster_name or not LOAD_ALL_ROBUSTA_MODELS:

And ensure the lock is held throughout the method body, including the assignment at line 594.


674-675: Critical: Exposes mutable dictionary, bypassing synchronization.

Returning self._llms directly allows callers to mutate the dictionary (add, remove, or modify entries) without acquiring the lock, completely bypassing the synchronization mechanism. For example: registry.models["key"] = malicious_entry would modify the internal state without thread-safety guarantees.

Return a defensive copy to prevent external mutation:

 @property
 def models(self) -> dict[str, ModelEntry]:
     with self._lock:
-        return self._llms
+        return {k: v.model_copy() for k, v in self._llms.items()}

This ensures callers receive an isolated snapshot that cannot affect the registry's internal state.

🧹 Nitpick comments (1)
holmes/core/llm.py (1)

638-638: Consider defining a custom exception class.

Static analysis (Ruff TRY002, TRY003) suggests creating a custom exception rather than raising a generic Exception with a string message. This improves error handling specificity and makes it easier for callers to catch specific error conditions.

Based on static analysis hints.

Define a custom exception class (e.g., at module level):

class NoModelsLoadedError(Exception):
    """Raised when no LLM models have been loaded into the registry."""
    pass

Then use it:

-            raise Exception("No llm models were loaded")
+            raise NoModelsLoadedError("No llm models were loaded")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7283ff3 and 5bde6b5.

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for formatting and linting (configured in pyproject.toml) for all Python code
Type hints are required; code should pass mypy (configured in pyproject.toml)
ALWAYS place Python imports at the top of the file, not inside functions or methods

Files:

  • holmes/core/llm.py
🧠 Learnings (1)
📚 Learning: 2025-08-08T06:15:30.784Z
Learnt from: nilo19
Repo: robusta-dev/holmesgpt PR: 695
File: holmes/core/transformers/registry.py:9-19
Timestamp: 2025-08-08T06:15:30.784Z
Learning: holmes/core/transformers/registry.py: TransformerRegistry is intentionally single-threaded and not designed to be thread-safe; avoid proposing locks unless multi-threaded access is introduced later.

Applied to files:

  • holmes/core/llm.py
🪛 Ruff (0.14.4)
holmes/core/llm.py

638-638: Create your own exception

(TRY002)


638-638: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (3)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals
  • GitHub Check: build
🔇 Additional comments (1)
holmes/core/llm.py (1)

4-4: LGTM: Threading import correctly placed.

The threading import is properly positioned at the top of the file as required by the coding guidelines.

Comment thread holmes/core/llm.py Outdated
Comment thread holmes/core/llm.py

@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 (2)
holmes/core/llm.py (2)

528-528: Good choice of RLock for re-entrant locking, but thread-safety remains incomplete.

Using RLock correctly handles the resync scenario where get_model_params calls _init_models() while holding the lock. However, critical thread-safety issues from previous reviews remain unaddressed:

  1. Line 672: The models property returns self._llms directly, allowing callers to mutate the dictionary without lock protection
  2. Lines 565-601: configure_robusta_ai_model() modifies self._llms at line 587 without acquiring the lock

While the RLock prevents deadlock during re-entrance, these issues still create race conditions.

Apply these fixes:

Fix 1: Return defensive copy in models property

 @property
 def models(self) -> dict[str, ModelEntry]:
     with self._lock:
-        return self._llms
+        return {k: v.model_copy() for k, v in self._llms.items()}

Fix 2: Guard mutations in configure_robusta_ai_model

 def configure_robusta_ai_model(self) -> None:
+    with self._lock:
         try:
             if not self.config.cluster_name or not LOAD_ALL_ROBUSTA_MODELS:

Based on learnings


670-672: Critical: Property still exposes mutable dictionary directly.

Despite previous review feedback, the models property returns self._llms directly, allowing callers to mutate the dictionary (add, remove, or modify entries) without acquiring the lock. This bypasses the synchronization mechanism and creates race conditions with configure_robusta_ai_model() and other operations.

Return a defensive copy:

 @property
 def models(self) -> dict[str, ModelEntry]:
     with self._lock:
-        return self._llms
+        return {k: v.model_copy() for k, v in self._llms.items()}
🧹 Nitpick comments (1)
holmes/core/llm.py (1)

633-667: Thread-safe implementation with correct defensive copying.

The lock guards all reads from self._llms and all return paths use .model_copy() for defensive copying. The resync logic (lines 643-649) correctly relies on the RLock's re-entrant capability when calling self._init_models().

Minor: Consider custom exception classes (optional)

Static analysis suggests creating custom exception classes for better error handling (lines 635, 651). This is a minor style improvement:

class LLMModelNotFoundError(Exception):
    """Raised when a requested LLM model is not found in the registry."""
    pass
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bde6b5 and a2c993b.

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for formatting and linting (configured in pyproject.toml) for all Python code
Type hints are required; code should pass mypy (configured in pyproject.toml)
ALWAYS place Python imports at the top of the file, not inside functions or methods

Files:

  • holmes/core/llm.py
🧠 Learnings (1)
📚 Learning: 2025-08-08T06:15:30.784Z
Learnt from: nilo19
Repo: robusta-dev/holmesgpt PR: 695
File: holmes/core/transformers/registry.py:9-19
Timestamp: 2025-08-08T06:15:30.784Z
Learning: holmes/core/transformers/registry.py: TransformerRegistry is intentionally single-threaded and not designed to be thread-safe; avoid proposing locks unless multi-threaded access is introduced later.

Applied to files:

  • holmes/core/llm.py
🪛 Ruff (0.14.4)
holmes/core/llm.py

635-635: Create your own exception

(TRY002)


635-635: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (3)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals
  • GitHub Check: build
🔇 Additional comments (1)
holmes/core/llm.py (1)

4-4: LGTM: Threading import added correctly.

The import is properly placed at the top of the file as per the coding guidelines.

@RoiGlinik

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 16, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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 (2)
holmes/core/llm.py (2)

671-672: Critical: Returning mutable dictionary bypasses thread-safety.

Returning self._llms directly allows callers to mutate the internal dictionary (add, remove, or replace entries) without acquiring the lock, completely bypassing the synchronization mechanism. This was flagged as critical in previous reviews.

Return a defensive copy:

 @property
 def models(self) -> dict[str, ModelEntry]:
     with self._lock:
-        return self._llms
+        return {k: v.model_copy() for k, v in self._llms.items()}

Deep-copying the values (via .model_copy()) ensures callers receive isolated snapshots and cannot mutate the registry state.


528-528: Critical: Lock initialization is insufficient without guarding all self._llms mutations.

While the RLock is now initialized, the thread-safety implementation remains incomplete as flagged in previous reviews:

  1. Unguarded writes during initialization: _init_models() (line 537) and configure_robusta_ai_model() (lines 587, 594-595) directly assign to self._llms without acquiring self._lock.

  2. Race condition window: The initial call from __init__ → _init_models() → configure_robusta_ai_model() executes without the lock, while get_model_params() reads self._llms under the lock. This creates a classic reader-writer race condition.

  3. Resync path is safe by accident: The resync logic (line 645) calls _init_models() while already holding the lock. Because RLock is reentrant, nested mutations work correctly, but this is fragile and inconsistent with the unlocked initialization path.

Wrap all mutations to self._llms with the lock:

 def _init_models(self):
-    self._llms = self._parse_models_file(MODEL_LIST_FILE_LOCATION)
+    with self._lock:
+        self._llms = self._parse_models_file(MODEL_LIST_FILE_LOCATION)
 
-    if self._should_load_robusta_ai():
-        self.configure_robusta_ai_model()
+        if self._should_load_robusta_ai():
+            self.configure_robusta_ai_model()
 
-    if self._should_load_config_model():
-        self._llms[self.config.model] = self._create_model_entry(
-            model=self.config.model,
-            model_name=self.config.model,
-            base_url=self.config.api_base,
-            is_robusta_model=False,
-            api_key=self.config.api_key,
-            api_version=self.config.api_version,
-        )
+        if self._should_load_config_model():
+            self._llms[self.config.model] = self._create_model_entry(
+                model=self.config.model,
+                model_name=self.config.model,
+                base_url=self.config.api_base,
+                is_robusta_model=False,
+                api_key=self.config.api_key,
+                api_version=self.config.api_version,
+            )

Since _init_models() will now acquire the lock, and the resync path (line 645) already holds the lock, you need to handle reentrant acquisition. With RLock this works automatically, but consider documenting that _init_models() and its callees (configure_robusta_ai_model()) should only be called while holding the lock, or refactor to separate "build new registry" from "swap registry under lock" logic.

🧹 Nitpick comments (1)
holmes/core/llm.py (1)

635-635: Consider using a custom exception class.

Raising a generic Exception with a string message makes error handling less specific. Consider defining a custom exception or using a more specific built-in exception like ValueError or RuntimeError.

Based on coding guidelines (Ruff configuration).

-            if not self._llms:
-                raise Exception("No llm models were loaded")
+            if not self._llms:
+                raise RuntimeError("No llm models were loaded")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2c993b and 657e5d8.

📒 Files selected for processing (2)
  • holmes/core/llm.py (3 hunks)
  • tests/core/test_llm_model_registry_get_model_params.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for formatting and linting (configured in pyproject.toml) for all Python code
Type hints are required; code should pass mypy (configured in pyproject.toml)
ALWAYS place Python imports at the top of the file, not inside functions or methods

Files:

  • holmes/core/llm.py
  • tests/core/test_llm_model_registry_get_model_params.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Only use pytest markers that are defined in pyproject.toml; never introduce undefined markers/tags

Files:

  • tests/core/test_llm_model_registry_get_model_params.py
tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Test files should mirror the source structure under tests/

Files:

  • tests/core/test_llm_model_registry_get_model_params.py
🧠 Learnings (1)
📚 Learning: 2025-08-08T06:15:30.784Z
Learnt from: nilo19
Repo: robusta-dev/holmesgpt PR: 695
File: holmes/core/transformers/registry.py:9-19
Timestamp: 2025-08-08T06:15:30.784Z
Learning: holmes/core/transformers/registry.py: TransformerRegistry is intentionally single-threaded and not designed to be thread-safe; avoid proposing locks unless multi-threaded access is introduced later.

Applied to files:

  • holmes/core/llm.py
🧬 Code graph analysis (1)
tests/core/test_llm_model_registry_get_model_params.py (2)
holmes/config.py (2)
  • Config (46-521)
  • dal (121-124)
holmes/core/llm.py (3)
  • LLMModelRegistry (522-715)
  • ModelEntry (67-88)
  • get_model_params (632-667)
🪛 Ruff (0.14.4)
holmes/core/llm.py

635-635: Create your own exception

(TRY002)


635-635: Avoid specifying long messages outside the exception class

(TRY003)

tests/core/test_llm_model_registry_get_model_params.py

55-55: Unused lambda argument: self

(ARG005)


55-55: Unused lambda argument: path

(ARG005)


71-71: Unused lambda argument: self

(ARG005)


71-71: Unused lambda argument: path

(ARG005)


87-87: Unused lambda argument: self

(ARG005)


87-87: Unused lambda argument: path

(ARG005)


97-97: Unused method argument: caplog

(ARG002)


105-105: Unused lambda argument: self

(ARG005)


105-105: Unused lambda argument: path

(ARG005)


110-110: Unused lambda argument: self

(ARG005)


110-110: Unused lambda argument: path

(ARG005)


132-132: Unused lambda argument: self

(ARG005)


132-132: Unused lambda argument: path

(ARG005)

⏰ 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). (3)
  • GitHub Check: llm_evals
  • GitHub Check: Pre-commit checks
  • GitHub Check: build
🔇 Additional comments (3)
holmes/core/llm.py (1)

633-667: The lock acquisition and defensive copying are implemented correctly.

This method properly:

  1. Acquires self._lock for the entire operation (line 633)
  2. Returns .model_copy() in all paths (lines 641, 649, 659, 667) for thread-safe defensive copying
  3. Implements Robusta resync logic that detects missing models and retries after reloading

However, the correctness depends on _init_models() (called on line 645) being properly synchronized, which requires the fix mentioned in the previous comment.

tests/core/test_llm_model_registry_get_model_params.py (2)

12-46: Well-structured test class with reusable fixtures.

The test organization is clean with appropriate fixtures for mock objects and test data. The fixtures properly mock dependencies (Config, DAL) and provide ModelEntry instances for testing different scenarios.


48-143: Comprehensive test coverage of get_model_params behavior.

The test suite effectively covers:

  • Valid model key retrieval (lines 48-61)
  • Fallback to first available model when key is invalid (lines 63-77)
  • Default Robusta model preference (lines 79-94)
  • Robusta resync success path (lines 96-123)
  • Robusta resync failure with fallback (lines 124-143)

The tests properly verify both return values and logging behavior, ensuring the new resync logic works as intended.

Comment thread tests/core/test_llm_model_registry_get_model_params.py
@RoiGlinik RoiGlinik changed the title WIP ROB-2346 resync robusta models if robusta model is not found ROB-2346 resync robusta models if robusta model is not found Nov 17, 2025
@github-actions

Copy link
Copy Markdown
Contributor

Results of HolmesGPT evals

  • ask_holmes: 30/36 test cases were successful, 4 regressions, 1 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_oom_kill_from_issues_history ✅
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 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
  • :minus: 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 was throttled by API rate limits/overload
  • ❌ the test failed and should be fixed before merging the PR

@moshemorad
moshemorad merged commit 3b2fd95 into master Nov 25, 2025
7 checks passed
@moshemorad
moshemorad deleted the ROB-2346-holmes-sass-model-selection branch November 25, 2025 11:40
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