Skip to content

feat: cache toolset status and add toolset management tool command #459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Jun 10, 2025

Conversation

mainred
Copy link
Collaborator

@mainred mainred commented May 28, 2025

fix: #426
fix: #424

  • use isort to sort the imported packaged
  • introduce toolset_manager to manage toolsets, which was Config's job.
  • use load_toolsets_config as the only entrypoint to load initialize and validate a toolset from a config/definition
  • Besides the mcp toolset type, this PR also introduces built-in and customized toolset type to differentiate the source of these toolsets
  • introduce cli command to list and refresh the toolset status from local cache
 ./dist/holmes/holmes toolset --help

 Usage: holmes toolset [OPTIONS] COMMAND [ARGS]...

 toolset management commands

╭─ Options ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --help          Show this message and exit.                                                                                                           │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ list      List build-int and custom toolsets status of CLI                                                                                            │
│ refresh   Refresh build-in and custom toolsets status of CLI                                                                                          │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

./dist/holmes/holmes toolset list
 name                              status    enabled    type      path                                 error
--------------------------------  --------  ---------  --------  -----------------------------------  ------------------------------------------------------------------
confluence                        failed    True       built-in                                       Environment variable CONFLUENCE_BASE_URL was not set
docker/core                       enabled   True       built-in
helm/core                         enabled   True       built-in
kubernetes/logs                   enabled   True       built-in
servicenow                        disabled  False      built-in
aks/node-health                   enabled   True       built-in
aws/security                      failed    True       built-in                                       `aws sts get-caller-identity` returned 127
aws/rds                           failed    True       built-in                                       `aws sts get-caller-identity` returned 127
argocd/core                       failed    True       built-in                                       `argocd version` returned 127
slab                              failed    True       built-in                                       Environment variable SLAB_API_KEY was not set
aks/core                          enabled   True       built-in
kubernetes/core                   enabled   True       built-in
kubernetes/live-metrics           enabled   True       built-in
kubernetes/kube-prometheus-stack  enabled   True       built-in
kubernetes/krew-extras            failed    True       built-in                                       `kubectl version --client && kubectl lineage --version` returned 1
kubernetes/kube-lineage-extras    failed    True       built-in                                       `kubectl version --client && kube-lineage --version` returned 127
internet                          enabled   True       built-in
robusta                           failed    True       built-in                                       The data access layer is not available
opensearch/status                 disabled  False      built-in
grafana/loki                      disabled  False      built-in
grafana/tempo                     disabled  False      built-in
newrelic                          failed    True       built-in                                       No configuration provided
grafana/grafana                   disabled  False      built-in
notion                            enabled   True       built-in
kafka/admin                       failed    True       built-in                                       The toolset is missing its configuration
datadog                           failed    True       built-in
prometheus/metrics                enabled   True       built-in
datetime                          enabled   True       built-in
opensearch/logs                   failed    True       built-in                                       Missing opensearch traces URL. Check your config
opensearch/traces                 failed    True       built-in                                       Missing opensearch traces URL. Check your config
coralogix/logs                    failed    True       built-in                                       The toolset is missing its configuration
rabbitmq/core                     failed    True       built-in                                       RabbitMQ toolset is misconfigured. 'management_url' is required.
git                               failed    True       built-in                                       Missing one or more required Git configuration values.
ig/core                           enabled   True       custom    /home/azureuser/inspect_gadget.yaml
./dist/holmes/holmes ask "detect why the k8s pod client under namespace test-ns cannot resolve dns" -f /home/azureuser/llm/demo/dns_troubleshooting_instructions.md --max-steps 20 -t ~/inspect_gadget.yaml
User: detect why the k8s pod client under namespace test-ns cannot resolve dns
Loading file /home/azureuser/llm/demo/dns_troubleshooting_instructions.md
Running tool kubectl_find_resource: kubectl get -A --show-labels -o wide pod | grep client                                                   tools.py:125
Running tool kubectl_describe: kubectl describe pod client -n test-ns                                                                        tools.py:125
Running tool kubectl_get_by_kind_in_namespace: kubectl get --show-labels -o wide pod -n kube-system                                          tools.py:125
Running tool kubectl_get_by_kind_in_namespace: kubectl get --show-labels -o wide svc -n kube-system                                          tools.py:125
Running tool kubectl_describe: kubectl describe pod coredns-57d886c994-8h9gt -n kube-system                                                  tools.py:125
Running tool kubectl_describe: kubectl describe pod coredns-57d886c994-vqmrz -n kube-system                                                  tools.py:125
Running tool kubectl_get_by_name: kubectl get --show-labels -o wide svc kube-dns -n kube-system                                              tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml pod client -n test-ns                                                                     tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml pod coredns-57d886c994-8h9gt -n kube-system                                               tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml pod coredns-57d886c994-vqmrz -n kube-system                                               tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml svc kube-dns -n kube-system                                                               tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml configmap coredns -n kube-system                                                          tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml configmap coredns-custom -n kube-system                                                   tools.py:125
Running tool kubectl_get_by_kind_in_namespace: kubectl get --show-labels -o wide networkpolicy -n test-ns                                    tools.py:125
Running tool kubectl_get_yaml: kubectl get -o yaml networkpolicy default-deny-egress -n test-ns                                              tools.py:125
tool_calling_llm.call - completed in 9 iterations - 27071ms                                                                      performance_timing.py:41
AI: DNS resolution fails for pod client in namespace test-ns because a default-deny-egress NetworkPolicy is present. This policy blocks all outbound traffic,
including DNS (UDP/TCP port 53) to the CoreDNS service at 10.0.0.10.

Key findings:

 • CoreDNS pods are healthy and running.
 • CoreDNS service is correctly configured.
 • Pod spec uses dnsPolicy: ClusterFirst (default, correct).
 • NetworkPolicy default-deny-egress in test-ns blocks all egress by default.

To resolve, update the NetworkPolicy to allow egress to 10.0.0.10 on port 53/UDP and 53/TCP.

See the official Kubernetes DNS debugging guide for more: https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/ (section:
"Network policies blocking DNS").

Copy link
Contributor

coderabbitai bot commented May 28, 2025

Summary by CodeRabbit

  • New Features

    • Added a CLI toolset management interface with commands to list and refresh statuses of built-in and custom toolsets.
    • Introduced centralized toolset management via a new ToolsetManager class for merging, validation, and status caching.
    • Added support for environment variable substitution in toolset configuration files.
    • Introduced structured resource instruction models to enhance investigation context.
  • Bug Fixes

    • Corrected a typographical error in the integration guide.
  • Refactor

    • Streamlined toolset status and error handling by replacing method calls with direct attribute access.
    • Centralized toolset management logic and removed redundant legacy code.
    • Improved import organization and code clarity across multiple modules.
    • Updated configuration loading to use Path objects and simplified tool executor creation.
    • Replaced legacy toolset loading and merging logic with the ToolsetManager class.
  • Chores

    • Updated tests and templates to align with new toolset status handling and configuration formats.
    • Enhanced error messages and validation for toolset configuration files.
    • Removed deprecated tests related to old custom toolset loading methods.
    • Added comprehensive unit tests for the ToolsetManager and toolset loading utilities.
    • Added new utility for environment variable replacement in configuration values.
    • Added tabulate dependency for improved CLI output formatting.

Walkthrough

This update centralizes toolset management by introducing a ToolsetManager class, refactors configuration and toolset loading logic, and adds new CLI commands for toolset status listing and refreshing. Several import statements are reorganized, and method calls for toolset status are replaced with direct attribute access. Additional utility modules and Pydantic models are introduced.

Changes

File(s) Change Summary
holmes/config.py Refactored to use ToolsetManager for toolset loading, merging, and status management; removed legacy toolset functions and methods; updated signatures and logic to use Path and new manager.
holmes/core/resource_instruction.py Added new Pydantic models: ResourceInstructionDocument and ResourceInstructions for investigation context.
holmes/core/supabase_dal.py, holmes/core/tool_calling_llm.py, holmes/core/tools.py Reorganized imports; removed Pydantic resource instruction models from tool_calling_llm.py; replaced toolset status method calls with attribute access; added/modified toolset types and status fields.
holmes/core/toolset_manager.py Introduced new ToolsetManager class for centralized toolset management, merging, prerequisite checking, and caching.
holmes/main.py Added CLI toolset group with list and refresh commands; updated config and custom toolset options; removed allowed_toolsets logic; improved CLI option defaults and help text.
holmes/plugins/prompts/_fetch_logs.jinja2, holmes/plugins/prompts/_toolsets_instructions.jinja2 Replaced get_status()/get_error() method calls with direct attribute access for toolset status and error.
holmes/plugins/toolsets/init.py Refactored toolset YAML loading and validation; added environment variable replacement; improved error handling and validation.
holmes/plugins/toolsets/consts.py Added constants for toolset configuration errors and standard parameter descriptions.
holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py, holmes/plugins/toolsets/grafana/base_grafana_toolset.py, holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py, holmes/plugins/toolsets/kafka.py, holmes/plugins/toolsets/opensearch/opensearch.py, holmes/plugins/toolsets/prometheus/prometheus.py Reorganized import statements; updated constant imports to use new consts.py; minor formatting changes.
holmes/plugins/toolsets/utils.py Removed unused constants and reordered imports.
holmes/utils/default_toolset_installation_guide.jinja2 Fixed typo in template text.
holmes/utils/env.py Added new utility module for recursive environment variable replacement in configs.
holmes/utils/holmes_sync_toolsets.py Replaced toolset status method calls with attribute access.
holmes/utils/pydantic_utils.py Changed load_model_from_file to accept Path instead of str; minor error message formatting.
tests/llm/utils/mock_toolset.py, tests/plugins/toolsets/grafana/test_grafana_loki.py, tests/plugins/toolsets/grafana/test_grafana_tempo.py, tests/plugins/toolsets/test_notion.py, tests/plugins/toolsets/test_prometheus_integration.py, tests/plugins/toolsets/test_tool_kafka.py Updated tests to use toolset status attribute instead of method; reorganized imports.
tests/core/test_toolset_manager.py Added comprehensive unit tests for ToolsetManager covering loading, merging, caching, filtering, and error handling.
tests/plugins/toolsets/test_load_config.py Added tests for load_toolsets_from_config validating old format rejection and environment variable substitution.
tests/config_class/test_config_load_custom_toolsets.py Removed legacy tests for custom toolset loading from Config class.
tests/config_class/test_config_load_remote_mcp_server.py Updated imports and calls to use new load_toolsets_from_config instead of removed legacy functions.
tests/test_check_prerequisites.py, tests/test_holmes_sync_toolsets.py Replaced calls to get_error() with direct access to error attribute in assertions.

Sequence Diagram(s)

Loading
sequenceDiagram
    participant CLI
    participant Config
    participant ToolsetManager
    participant Toolset
    participant Cache

    CLI->>Config: load_from_file(config_file, custom_toolsets)
    Config->>ToolsetManager: initialize(toolsets, custom_toolsets)
    Config->>ToolsetManager: .load_toolset_with_status()
    ToolsetManager->>Cache: load cached toolset status
    ToolsetManager->>Toolset: check prerequisites (if refresh needed)
    Toolset->>ToolsetManager: return status, error
    ToolsetManager->>Cache: update cache if needed
    ToolsetManager->>Config: return list of Toolset objects with status
    Config->>CLI: provide toolsets for CLI commands

    CLI->>CLI: pretty_print_toolset_status(toolsets)

Assessment against linked issues

Objective Addressed Explanation
Disable all toolsets by default and load only explicitly enabled toolsets from config (#426)
Simplify and speed up HolmesGPT startup by avoiding prerequisite checks on all toolsets (#426)
Provide CLI commands to list and refresh toolset statuses (#426)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Addition of new Pydantic models for resource instructions in holmes/core/resource_instruction.py Not related to toolset loading or CLI changes; unrelated to linked issue objectives.
Typo fix in holmes/utils/default_toolset_installation_guide.jinja2 Minor documentation fix, unrelated to linked issue objectives.
Removal of legacy test modules for custom toolset loading Testing cleanup related to refactor, not explicitly stated in linked issues.
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Sorry, something went wrong.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🔭 Outside diff range comments (2)
holmes/core/tools.py (2)

490-490: ⚠️ Potential issue

Fix incorrect attribute reference after refactoring.

The code references ts._path but the attribute has been changed from private to public as path.

Apply this diff to fix the attribute reference:

-                        f"Overriding existing tool '{tool.name} with new tool from {ts.name} at {ts._path}'!"
+                        f"Overriding existing tool '{tool.name} with new tool from {ts.name} at {ts.path}'!"

339-349: 💡 Verification agent

🧩 Analysis chain

Verify type consistency in override_with method.

The method signature changed from accepting ToolsetYamlFromConfig to YAMLToolset. However, based on the class structure and the docstring mentioning "ToolsetYamlFromConfig loaded from custom config", this might be incorrect.


🏁 Script executed:

#!/bin/bash
# Description: Check how override_with is called in the codebase to verify the correct parameter type

# Search for calls to override_with method
rg -A 3 "\.override_with\(" 

# Search for instantiations of ToolsetYamlFromConfig
rg -A 5 "ToolsetYamlFromConfig\("

Length of output: 1387


🏁 Script executed:

#!/bin/bash
# Search for YAMLToolset definition
rg -n "YAMLToolset" .

# Show override_with usage context in toolset_manager.py
rg -C 5 "override_with" -n holmes/core/toolset_manager.py

# Locate new_toolset declaration and type in toolset_manager.py
rg -n "new_toolset" -n holmes/core/toolset_manager.py

Length of output: 1903


Update docstring in override_with to reference YAMLToolset

The parameter type YAMLToolset is correct and matches all call sites (e.g. in toolset_manager.py). The docstring still refers to the old ToolsetYamlFromConfig and should be updated.

• File: holmes/core/tools.py, around line 339
• Change the docstring from mentioning ToolsetYamlFromConfig to YAMLToolset

Suggested diff:

     def override_with(self, override: "YAMLToolset") -> None:
-        """
-        Overrides the current attributes with values from the ToolsetYamlFromConfig loaded from custom config
-        if they are not None.
-        """
+        """
+        Overrides the current attributes with values from the YAMLToolset loaded from custom config
+        if they are not None.
+        """
         for field, value in override.model_dump(
             exclude_unset=True,
             exclude=("name"),  # type: ignore
         ).items():
             if field in self.model_fields and value not in (None, [], {}, ""):
                 setattr(self, field, value)
🧹 Nitpick comments (5)
holmes/utils/env.py (1)

23-30: Consider refactoring to reduce code duplication.

The pattern for handling str and SecretStr is very similar and could be refactored to reduce duplication.

Consider extracting a helper function:

def process_string_value(value, is_secret=False):
    str_value = value.get_secret_value() if is_secret else value
    env_var_value = get_env_replacement(str_value)
    if env_var_value:
        return SecretStr(env_var_value) if is_secret else env_var_value
    return value

Then simplify the main logic:

-        if isinstance(value, str):
-            env_var_value = get_env_replacement(value)
-            if env_var_value:
-                values[key] = env_var_value
-        elif isinstance(value, SecretStr):
-            env_var_value = get_env_replacement(value.get_secret_value())
-            if env_var_value:
-                values[key] = SecretStr(env_var_value)
+        if isinstance(value, str):
+            values[key] = process_string_value(value, is_secret=False)
+        elif isinstance(value, SecretStr):
+            values[key] = process_string_value(value, is_secret=True)
holmes/plugins/toolsets/__init__.py (1)

98-104: Simplify the boolean return statement.

The function can be simplified by directly returning the boolean condition.

Apply this diff as suggested by static analysis:

-def is_old_toolset_config(
-    toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
-) -> bool:
-    # old config is a list of toolsets
-    if isinstance(toolsets, list):
-        return True
-    return False
+def is_old_toolset_config(
+    toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
+) -> bool:
+    # old config is a list of toolsets
+    return isinstance(toolsets, list)
🧰 Tools
🪛 Ruff (0.11.9)

102-104: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/core/toolset_manager.py (2)

19-20: Address TODO: Make toolset status cache location configurable.

The hardcoded path might not work in all environments (e.g., containers, CI/CD).

Would you like me to implement a solution that reads from an environment variable with this as the default? For example:

DEFAULT_TOOLSET_STATUS_LOCATION = os.environ.get(
    "HOLMES_TOOLSET_STATUS_LOCATION", 
    os.path.expanduser("~/.holmes/toolsets_status.json")
)

311-311: Simplify dictionary membership check.

Remove redundant .keys() call when checking dictionary membership.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

311-311: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/core/tools.py (1)

334-337: Consider the implications of removing encapsulation.

The refactoring from private attributes (_path, _status, _error) to public fields removes encapsulation, which could lead to:

  • Direct modification of these fields from external code without validation
  • Breaking changes for any code that relied on getter/setter methods
  • Potential consistency issues if these fields are modified without proper state management

While this aligns with the PR's goal of direct attribute access, ensure that:

  1. All consuming code has been updated
  2. These fields won't be accidentally modified in ways that break invariants
  3. The simplified approach doesn't compromise the integrity of toolset state management
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd9215e and 5589981.

📒 Files selected for processing (28)
  • holmes/config.py (10 hunks)
  • holmes/core/resource_instruction.py (1 hunks)
  • holmes/core/supabase_dal.py (1 hunks)
  • holmes/core/tool_calling_llm.py (1 hunks)
  • holmes/core/tools.py (11 hunks)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/main.py (19 hunks)
  • holmes/plugins/prompts/_fetch_logs.jinja2 (3 hunks)
  • holmes/plugins/prompts/_toolsets_instructions.jinja2 (2 hunks)
  • holmes/plugins/toolsets/__init__.py (2 hunks)
  • holmes/plugins/toolsets/consts.py (1 hunks)
  • holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py (3 hunks)
  • holmes/plugins/toolsets/grafana/base_grafana_toolset.py (1 hunks)
  • holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (1 hunks)
  • holmes/plugins/toolsets/kafka.py (1 hunks)
  • holmes/plugins/toolsets/opensearch/opensearch.py (1 hunks)
  • holmes/plugins/toolsets/prometheus/prometheus.py (1 hunks)
  • holmes/plugins/toolsets/utils.py (1 hunks)
  • holmes/utils/default_toolset_installation_guide.jinja2 (1 hunks)
  • holmes/utils/env.py (1 hunks)
  • holmes/utils/holmes_sync_toolsets.py (3 hunks)
  • holmes/utils/pydantic_utils.py (3 hunks)
  • tests/llm/utils/mock_toolset.py (2 hunks)
  • tests/plugins/toolsets/grafana/test_grafana_loki.py (2 hunks)
  • tests/plugins/toolsets/grafana/test_grafana_tempo.py (2 hunks)
  • tests/plugins/toolsets/test_notion.py (2 hunks)
  • tests/plugins/toolsets/test_prometheus_integration.py (2 hunks)
  • tests/plugins/toolsets/test_tool_kafka.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (11)
tests/plugins/toolsets/test_notion.py (2)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
holmes/plugins/toolsets/internet/notion.py (2)
  • FetchNotion (17-108)
  • NotionToolset (111-127)
tests/plugins/toolsets/grafana/test_grafana_loki.py (3)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
holmes/plugins/toolsets/grafana/grafana_api.py (1)
  • get_health (20-33)
holmes/plugins/toolsets/grafana/toolset_grafana_loki.py (2)
  • GetLokiLogsForResource (130-205)
  • GrafanaLokiConfig (33-34)
holmes/plugins/toolsets/grafana/base_grafana_toolset.py (1)
holmes/core/tools.py (4)
  • CallablePrerequisite (288-289)
  • Tool (108-138)
  • Toolset (301-445)
  • ToolsetTag (90-93)
tests/plugins/toolsets/test_tool_kafka.py (2)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
holmes/plugins/toolsets/kafka.py (6)
  • DescribeConsumerGroup (184-241)
  • DescribeTopic (289-355)
  • FindConsumerGroupsByTopic (369-456)
  • KafkaToolset (480-559)
  • ListKafkaConsumers (117-181)
  • ListTopics (244-286)
tests/plugins/toolsets/grafana/test_grafana_tempo.py (3)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
holmes/plugins/toolsets/grafana/grafana_api.py (1)
  • get_health (20-33)
holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (1)
  • GetTempoTraces (69-193)
holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (6)
holmes/common/env_vars.py (1)
  • load_bool (5-7)
holmes/core/tools.py (4)
  • StructuredToolResult (27-50)
  • Tool (108-138)
  • ToolParameter (102-105)
  • ToolResultStatus (21-24)
holmes/plugins/toolsets/grafana/common.py (3)
  • GrafanaConfig (9-21)
  • build_headers (24-35)
  • get_base_url (51-55)
holmes/plugins/toolsets/grafana/tempo_api.py (2)
  • query_tempo_trace_by_id (80-124)
  • query_tempo_traces (54-77)
holmes/plugins/toolsets/grafana/trace_parser.py (1)
  • format_traces_list (177-195)
holmes/plugins/toolsets/utils.py (2)
  • get_param_or_raise (128-132)
  • process_timestamps_to_int (79-125)
holmes/core/tool_calling_llm.py (8)
tests/llm/utils/mock_utils.py (1)
  • Message (33-34)
holmes/core/investigation_structured_output.py (2)
  • parse_markdown_into_sections_from_hash_sign (101-141)
  • process_response_into_sections (213-226)
holmes/core/issue.py (1)
  • Issue (13-54)
holmes/core/llm.py (1)
  • LLM (30-58)
holmes/core/resource_instruction.py (1)
  • ResourceInstructions (15-17)
holmes/core/runbooks.py (1)
  • RunbookManager (7-26)
holmes/core/tools.py (3)
  • StructuredToolResult (27-50)
  • ToolExecutor (468-513)
  • ToolResultStatus (21-24)
holmes/plugins/prompts/__init__.py (1)
  • load_and_render_prompt (26-43)
holmes/plugins/toolsets/kafka.py (2)
holmes/core/tools.py (7)
  • CallablePrerequisite (288-289)
  • StructuredToolResult (27-50)
  • Tool (108-138)
  • ToolParameter (102-105)
  • ToolResultStatus (21-24)
  • Toolset (301-445)
  • ToolsetTag (90-93)
holmes/plugins/toolsets/utils.py (1)
  • get_param_or_raise (128-132)
holmes/core/supabase_dal.py (2)
holmes/core/resource_instruction.py (2)
  • ResourceInstructionDocument (6-12)
  • ResourceInstructions (15-17)
holmes/utils/definitions.py (1)
  • RobustaConfig (11-13)
holmes/utils/env.py (1)
holmes/config.py (2)
  • get_env_replacement (43-53)
  • replace_env_vars_values (56-80)
holmes/main.py (6)
holmes/config.py (6)
  • Config (101-431)
  • SourceFactory (442-523)
  • SupportedTicketSources (38-40)
  • create_console_issue_investigator (305-316)
  • load_from_file (185-198)
  • toolset_manager (159-164)
holmes/core/tool_calling_llm.py (1)
  • LLMResult (100-112)
holmes/core/tools.py (1)
  • Toolset (301-445)
holmes/plugins/prompts/__init__.py (1)
  • load_and_render_prompt (26-43)
holmes/utils/file_utils.py (1)
  • write_json_file (7-16)
holmes/core/toolset_manager.py (1)
  • load_toolset_with_status (172-207)
🪛 GitHub Actions: Build and test HolmesGPT
tests/llm/utils/mock_toolset.py

[error] 1-1: ImportError: cannot import name 'parse_toolsets_file' from 'holmes.config' in holmes/config.py

🪛 GitHub Actions: Evaluate LLM test cases
holmes/config.py

[error] 1-1: ImportError: cannot import name 'parse_toolsets_file' from 'holmes.config'. This caused test loading failure in tests/llm/conftest.py.

🪛 Ruff (0.11.9)
holmes/core/toolset_manager.py

67-79: Combine if branches using logical or operator

Combine if branches

(SIM114)


311-311: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/plugins/toolsets/__init__.py

102-104: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

🔇 Additional comments (41)
holmes/utils/default_toolset_installation_guide.jinja2 (1)

7-7: Good catch on the typo fix!

The correction from "intrgration" to "integration" improves the documentation quality and user experience.

holmes/plugins/toolsets/consts.py (1)

1-5: Excellent refactoring to centralize string constants!

This follows best practices by:

  • Eliminating string literal duplication across toolset modules
  • Ensuring consistent error messaging and parameter descriptions
  • Supporting the broader toolset management centralization effort

The constant names are descriptive and their purposes are clear.

holmes/utils/pydantic_utils.py (3)

2-3: Good import reorganization for better readability.

Moving the Path import and reordering typing imports improves code organization.


40-40: Excellent type safety improvement.

Changing the parameter type from str to Path makes the function signature more explicit and type-safe while maintaining backward compatibility.


51-51: Nice formatting fix in the error message.

Removing the extra space before the file path improves the error message formatting.

tests/plugins/toolsets/test_prometheus_integration.py (2)

6-8: Good formatting improvements.

The simplified import statement and additional blank line improve code readability and follow Python style conventions.


23-23: 💡 Verification agent

🧩 Analysis chain

Verify the status API change is consistent with the refactor.

The change from toolset.get_status() to toolset.status aligns with the broader refactor mentioned in the AI summary where status methods were replaced by public attributes. This should improve the API consistency.

Let me verify that this change is consistent across the codebase and that the status attribute is properly defined:


🏁 Script executed:

#!/bin/bash
# Description: Verify that toolset status is accessed via attribute instead of method calls

echo "=== Searching for any remaining get_status() method calls ==="
rg "\.get_status\(\)" --type py

echo "=== Searching for status attribute access in toolset contexts ==="
rg "toolset\.status" --type py -A 2 -B 2

echo "=== Checking if status is defined as a property or attribute in toolset classes ==="
ast-grep --pattern $'class $_ {
  $$$
  status = $_
  $$$
}'

ast-grep --pattern $'class $_ {
  $$$
  @property
  def status($_):
    $$$
  $$$
}'

Length of output: 7396


Replace remaining .get_status() calls with the status attribute

The refactor to use toolset.status is correct, but there are still legacy method calls in tests that must be updated:

• tests/test_holmes_sync_toolsets.py
• tests/test_check_prerequisites.py
• tests/plugins/toolsets/test_tool_kafka.py

Example diff:

-    assert toolset.get_status() == ToolsetStatusEnum.ENABLED
+    assert toolset.status == ToolsetStatusEnum.ENABLED

And in test_tool_kafka.py:

-    assert kafka_toolset.status() == ToolsetStatusEnum.ENABLED
+    assert kafka_toolset.status == ToolsetStatusEnum.ENABLED

Likely an incorrect or invalid review comment.

tests/plugins/toolsets/grafana/test_grafana_loki.py (2)

6-10: LGTM! Import organization and new imports are appropriate.

The addition of ToolsetStatusEnum import and GrafanaLokiConfig import align with the updated status handling and provide better type safety for the test assertions.


41-41: LGTM! Status access pattern updated correctly.

The change from toolset.get_status() to toolset.status is consistent with the broader refactor to expose toolset status as a direct attribute instead of through a getter method. This improves code consistency across the codebase.

holmes/plugins/prompts/_fetch_logs.jinja2 (1)

7-7: LGTM! Template status checks updated consistently.

All four toolset status checks have been correctly updated from get_status() method calls to direct status attribute access. The template logic remains intact and will continue to properly filter enabled toolsets for log fetching instructions.

Also applies to: 17-17, 26-26, 31-31

holmes/plugins/toolsets/prometheus/prometheus.py (1)

1-31: LGTM! Import organization improved.

The import statements have been properly reorganized and grouped for better clarity:

  • Standard library imports (json, logging, os, re, string, time, typing, urllib.parse)
  • Third-party imports (requests, pydantic)
  • Internal imports (holmes.core.tools, holmes.plugins.toolsets modules)

The addition of specific imports like RequestException, StructuredToolResult, and ToolResultStatus appears to align with their usage throughout the file. The import from consts module follows the centralization pattern mentioned in the PR objectives.

tests/plugins/toolsets/test_notion.py (2)

5-6: LGTM! Import improvements enhance type safety.

The addition of ToolsetStatusEnum import provides proper type safety for status comparisons, and the reordering of imports (FetchNotion before NotionToolset) follows Python import conventions.


23-23: LGTM! Status access pattern updated correctly.

The change from toolset.get_status() to toolset.status is consistent with the toolset management refactor. The assertion logic remains the same while using the new direct attribute access pattern.

holmes/utils/holmes_sync_toolsets.py (2)

15-15: LGTM! Status access pattern updated correctly.

The change from toolset.get_status().value to toolset.status.value aligns with the broader refactor to use direct attribute access instead of method calls for toolset status.

Also applies to: 18-18


57-57: LGTM! Consistent status attribute access.

The update to use toolset.status directly instead of toolset.get_status() is consistent with the refactoring effort across the codebase.

holmes/plugins/toolsets/grafana/base_grafana_toolset.py (1)

2-5: LGTM! Import reorganization improves code organization.

The consolidation of imports and relocation of TOOLSET_CONFIG_MISSING_ERROR to the consts module enhances code organization and follows good practices for constant management.

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

4-8: LGTM! Import reorganization enhances readability.

The consolidation and grouping of imports improves code organization.

Also applies to: 14-16

tests/plugins/toolsets/grafana/test_grafana_tempo.py (2)

3-3: LGTM! Import reorganization improves readability.

The grouping of related imports enhances code organization.

Also applies to: 5-5, 7-7, 9-9


84-84: LGTM! Status access pattern updated correctly.

The change from toolset.get_status() to toolset.status correctly follows the refactor pattern to use direct attribute access.

holmes/plugins/toolsets/utils.py (1)

3-5: LGTM: Import reorganization follows best practices.

The reordering of imports to group typing imports after standard library and before third-party imports follows Python import conventions and improves code organization.

holmes/plugins/toolsets/opensearch/opensearch.py (1)

4-16: LGTM: Import reorganization improves code structure.

The consolidation of imports from holmes.core.tools and the move of TOOLSET_CONFIG_MISSING_ERROR to the new consts module aligns with the PR's goal of centralizing constants and improving import organization.

holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (2)

3-26: LGTM: Import reorganization follows best practices.

The grouping of imports (standard library, third-party, local modules) improves code readability and follows Python conventions.


30-30: LGTM: Local constant definition is appropriate.

Defining ONE_HOUR_IN_SECONDS = 3600 locally instead of importing from utils makes sense since this constant is only used within this file. The value is correct (3600 seconds = 1 hour).

holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py (2)

12-15: LGTM: Constants moved to centralized location.

Moving STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION and TOOLSET_CONFIG_MISSING_ERROR to the new consts module aligns with the PR's goal of centralizing shared constants.


114-116: LGTM: Formatting improvement without logic change.

The reformatting of the conditional expression to a single line improves readability while maintaining the same logic: return ERROR status if logs_data.error exists, otherwise return SUCCESS.

holmes/core/tool_calling_llm.py (1)

5-34: LGTM! Import reorganization improves code clarity.

The import statements have been properly reorganized and consolidated, grouping related imports together. The relocation of ResourceInstructions to the new holmes.core.resource_instruction module follows good separation of concerns principles by isolating data models into their own module.

tests/llm/utils/mock_toolset.py (1)

2-8: LGTM! Import organization follows standard conventions.

The imports have been properly organized with standard library imports first, followed by third-party imports, and then local imports. The addition of required modules (logging, os, re) is appropriate.

holmes/plugins/toolsets/kafka.py (3)

2-2: LGTM! Import consolidation improves readability.

Consolidating multiple typing imports into a single line reduces clutter and follows standard import organization practices.


10-20: LGTM! Import organization follows logical grouping.

The reordering of confluent_kafka.admin imports groups related classes together, and separating the _TopicPartition import on its own line improves clarity since it uses a different import pattern.


23-33: LGTM! Import reorganization and constant centralization.

The reorganization of imports from holmes.core.tools groups related classes together logically. The change to import TOOLSET_CONFIG_MISSING_ERROR from holmes.plugins.toolsets.consts instead of utils aligns with the broader effort to centralize constants in dedicated modules, improving maintainability.

holmes/core/resource_instruction.py (1)

1-18: LGTM! Clean modularization of resource instruction models.

The new module properly separates resource instruction data models from the main tool calling logic. Both Pydantic models are well-defined:

  • ResourceInstructionDocument has a clear purpose with good documentation explaining its role in providing investigation context via URLs
  • ResourceInstructions properly aggregates instructions and documents with appropriate default values

This modularization improves code organization and follows the single responsibility principle.

holmes/core/supabase_dal.py (1)

1-33: LGTM! Import organization improvements.

The import reorganization improves code structure by:

  • Grouping related imports together (datetime imports, postgrest imports)
  • Following standard Python import ordering conventions
  • Reflecting the new module structure with ResourceInstructionDocument and ResourceInstructions moved to their dedicated module
holmes/plugins/toolsets/__init__.py (1)

44-47: Good error handling for empty YAML files.

The explicit check for None and raising a descriptive error message improves debugging experience when configuration files are empty or invalid.

holmes/core/toolset_manager.py (2)

127-140: Good error handling for prerequisite checks.

The approach of logging detailed errors while storing user-friendly messages is excellent. Continuing to process other toolsets even when one fails ensures resilience.


266-298: Well-structured custom toolset loading with proper validation.

The method handles both toolsets and MCP servers configurations correctly, with appropriate error handling for missing files and invalid configurations.

holmes/main.py (2)

1147-1164: Clean table formatting implementation.

The use of Rich tables for displaying toolset status is well-implemented and provides a good user experience.


142-152: Good improvement using Path types for file paths.

The change from Optional[str] to Optional[Path] for file paths improves type safety and is consistent throughout the codebase.

holmes/config.py (2)

158-165: Well-implemented lazy initialization for ToolsetManager.

The property pattern with lazy initialization is a good design choice that delays ToolsetManager creation until it's actually needed.


184-199: Good type improvement for config file handling.

The change to use Path instead of str for the config file parameter improves type safety and is consistent with the changes in main.py.

holmes/core/tools.py (2)

1-12: Import organization looks good!

The imports have been properly sorted using isort as mentioned in the PR objectives, improving code organization.


517-546: Well-structured partial override class!

The new ToolsetYamlFromConfig class provides a clean way to handle partial overrides of built-in toolsets, with:

  • Clear documentation explaining its purpose
  • Appropriate use of optional fields for override scenarios
  • Defaults that align with the configuration-driven approach (enabled=True by default)

This design aligns well with the PR objectives of centralizing toolset management and configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (4)
holmes/core/toolset_manager.py (4)

55-57: Fix incorrect docstring for server_tool_tags property.

The docstring incorrectly refers to "CLI tools" when it should mention "server tools".


70-82: Simplify conditional branches using logical OR.

The separate if/elif branches can be combined for better readability as suggested in previous reviews.

🧰 Tools
🪛 Ruff (0.11.9)

70-82: Combine if branches using logical or operator

Combine if branches

(SIM114)


159-170: Consider using the existing write_json_file utility or add error handling.

The file writing operation lacks error handling as noted in previous reviews. Consider using the existing utility function.

🧰 Tools
🪛 GitHub Actions: Build and test HolmesGPT

[error] 159-160: No overload variant of "dirname" matches argument type "Optional[list[Any]]" [call-overload]


[error] 161-161: No overload variant of "open" matches argument types "Optional[list[Any]]", "str" [call-overload]


185-187: Add error handling for cache file loading.

The JSON loading could fail if the cache file is corrupted or has permission issues, as noted in previous reviews.

🧰 Tools
🪛 GitHub Actions: Build and test HolmesGPT

[error] 186-186: No overload variant of "open" matches argument types "Optional[list[Any]]", "str" [call-overload]

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

312-312: Remove unnecessary .keys() call.

Checking membership directly in the dictionary is more efficient and readable.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

312-312: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5589981 and 804ea40.

📒 Files selected for processing (1)
  • holmes/core/toolset_manager.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
holmes/core/toolset_manager.py

70-82: Combine if branches using logical or operator

Combine if branches

(SIM114)


312-312: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 GitHub Actions: Build and test HolmesGPT
holmes/core/toolset_manager.py

[error] 42-42: Incompatible types in assignment (expression has type "Optional[str]", variable has type "Optional[list[Any]]") [assignment]


[error] 159-160: No overload variant of "dirname" matches argument type "Optional[list[Any]]" [call-overload]


[error] 161-161: No overload variant of "open" matches argument types "Optional[list[Any]]", "str" [call-overload]


[error] 181-181: Argument 1 to "exists" has incompatible type "Optional[list[Any]]"; expected "Union[int, Union[str, bytes, PathLike[str], PathLike[bytes]]]" [arg-type]


[error] 186-186: No overload variant of "open" matches argument types "Optional[list[Any]]", "str" [call-overload]

🔇 Additional comments (2)
holmes/core/toolset_manager.py (2)

118-124: Verify custom toolset override requirements.

The current implementation prevents custom toolsets from overriding built-in toolsets, but previous comments suggest this might be needed for server deployments. Please clarify if this restriction should be configurable or if certain contexts should allow overrides.


22-316: Well-structured toolset management implementation.

The ToolsetManager class provides a clean separation of concerns for toolset lifecycle management, with good integration of caching, status tracking, and filtering capabilities. The design properly centralizes toolset operations that were previously scattered across the codebase.

🧰 Tools
🪛 Ruff (0.11.9)

70-82: Combine if branches using logical or operator

Combine if branches

(SIM114)


312-312: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 GitHub Actions: Build and test HolmesGPT

[error] 42-42: Incompatible types in assignment (expression has type "Optional[str]", variable has type "Optional[list[Any]]") [assignment]


[error] 159-160: No overload variant of "dirname" matches argument type "Optional[list[Any]]" [call-overload]


[error] 161-161: No overload variant of "open" matches argument types "Optional[list[Any]]", "str" [call-overload]


[error] 181-181: Argument 1 to "exists" has incompatible type "Optional[list[Any]]"; expected "Union[int, Union[str, bytes, PathLike[str], PathLike[bytes]]]" [arg-type]


[error] 186-186: No overload variant of "open" matches argument types "Optional[list[Any]]", "str" [call-overload]

@mainred mainred force-pushed the cache-and-manage-toolset branch from 3745f08 to be53a9b Compare May 30, 2025 15:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (7)
holmes/plugins/toolsets/__init__.py (1)

131-140: ⚠️ Potential issue

Fix critical logic and type annotation issues.

The function has several critical issues:

  1. type variable is undefined on line 131, which will cause a NameError
  2. The logic flow is broken - missing elif keywords
  3. Type annotation inconsistencies as noted in previous reviews

Apply this diff to fix the issues:

             try:
-                if type is ToolsetType.MCP:
-                    validated_toolset: RemoteMCPToolset = RemoteMCPToolset(
-                        **config, name=name
-                    )
-                if strict_check:
-                    validated_toolset: YAMLToolset = YAMLToolset(**config, name=name)  # type: ignore
+                # Determine toolset type from config
+                toolset_type = config.get('type', ToolsetType.YAML)
+                
+                if toolset_type == ToolsetType.MCP:
+                    validated_toolset = RemoteMCPToolset(**config, name=name)
+                elif strict_check:
+                    validated_toolset = YAMLToolset(**config, name=name)
                 else:
-                    validated_toolset: ToolsetYamlFromConfig = ToolsetYamlFromConfig(  # type: ignore
-                        **config, name=name
-                    )
+                    validated_toolset = ToolsetYamlFromConfig(**config, name=name)
holmes/main.py (1)

1120-1122: Fix typo in command help text.

The help text contains a typo where "build-int" should be "built-in".

-    """
-    List build-int and custom toolsets status of CLI
-    """
+    """
+    List built-in and custom toolsets status of CLI
+    """
holmes/core/toolset_manager.py (4)

49-55: Fix incorrect docstring for server_tool_tags property.

The docstring incorrectly refers to "CLI tools" when it should mention "server tools".

-        """
-        Returns the list of toolset tags that are relevant for CLI tools.
-        A toolset is considered a CLI tool if it has any of UI tool tags:
-        """
+        """
+        Returns the list of toolset tags that are relevant for server tools.
+        A toolset is considered a server tool if it has any of server tool tags.
+        """

67-79: Simplify conditional branches using logical OR.

The separate if/elif branches can be combined for better readability.

-            if (
-                isinstance(toolset_config["enabled"], bool)
-                and not toolset_config["enabled"]
-            ):
-                return False
-            # Normally benedict should translate the value of enabled to bool when well assigned,
-            # but in case it doesn't, and to avoid unexpected behavior, we check if the value is a
-            # string and if it is 'false' (case insensitive).
-            elif (
-                isinstance(toolset_config["enabled"], str)
-                and toolset_config["enabled"].lower() == "false"
-            ):
-                return False
+            if (
+                (isinstance(toolset_config["enabled"], bool) and not toolset_config["enabled"])
+                or (isinstance(toolset_config["enabled"], str) and toolset_config["enabled"].lower() == "false")
+            ):
+                return False
🧰 Tools
🪛 Ruff (0.11.9)

67-79: Combine if branches using logical or operator

Combine if branches

(SIM114)


180-194: Consider using the existing write_json_file utility.

The file writing operation lacks error handling. Consider using the existing write_json_file utility from holmes.utils.file_utils which already has proper error handling.

+        from holmes.utils.file_utils import write_json_file
         
-        if self.toolset_status_location and not os.path.exists(
-            os.path.dirname(self.toolset_status_location)
-        ):
-            os.makedirs(os.path.dirname(self.toolset_status_location))
-        with open(self.toolset_status_location, "w") as f:
-            toolset_status = [
-                json.loads(
-                    toolset.model_dump_json(
-                        include={"name", "status", "enabled", "type", "path", "error"}
-                    )
-                )
-                for toolset in all_toolsets
-            ]
-            json.dump(toolset_status, f, indent=2)
+        toolset_status = [
+            json.loads(
+                toolset.model_dump_json(
+                    include={"name", "status", "enabled", "type", "path", "error"}
+                )
+            )
+            for toolset in all_toolsets
+        ]
+        write_json_file(self.toolset_status_location, toolset_status)

208-210: Add error handling for cache file loading.

The JSON loading could fail if the cache file is corrupted or has permission issues.

         cached_toolsets: List[dict[str, Any]] = []
-        with open(self.toolset_status_location, "r") as f:
-            cached_toolsets = json.load(f)
+        try:
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)
+        except (json.JSONDecodeError, IOError) as e:
+            logging.error(f"Failed to load toolset cache: {e}. Refreshing status.")
+            self.refresh_toolset_status(dal)
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)
holmes/config.py (1)

1-12: ⚠️ Potential issue

Critical: Tests still reference removed parse_toolsets_file

The removal of parse_toolsets_file from holmes.config has left tests failing in tests/llm/utils/mock_toolset.py. Please update this file to use the new parser API or correct import to fix the pipeline failure.

🧰 Tools
🪛 GitHub Actions: Evaluate LLM test cases

[error] 1-1: ImportError: cannot import name 'parse_toolsets_file' from 'holmes.config'. This caused the test run to fail.

🧹 Nitpick comments (5)
holmes/plugins/toolsets/__init__.py (1)

100-107: Consider simplifying the boolean return as suggested by static analysis.

The function can be simplified by returning the condition directly.

Apply this diff:

-def is_old_toolset_config(
-    toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
-) -> bool:
-    # old config is a list of toolsets
-    if isinstance(toolsets, list):
-        return True
-    return False
+def is_old_toolset_config(
+    toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
+) -> bool:
+    # old config is a list of toolsets
+    return isinstance(toolsets, list)
🧰 Tools
🪛 Ruff (0.11.9)

104-106: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/main.py (1)

1135-1137: Fix typo in refresh command help text.

The help text contains a typo where "build-in" should be "built-in".

-    """
-    Refresh build-in and custom toolsets status of CLI
-    """
+    """
+    Refresh built-in and custom toolsets status of CLI
+    """
holmes/core/toolset_manager.py (3)

240-240: Remove unnecessary .keys() call for performance.

Use key in dict instead of key in dict.keys() for better performance.

-            if custom_toolset_from_cli in toolsets_status_by_name.keys():
+            if custom_toolset_from_cli.name in toolsets_status_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

240-240: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


375-375: Remove unnecessary .keys() call for performance.

Use key in dict instead of key in dict.keys() for better performance.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

375-375: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


292-294: Improve exception chaining for better debugging.

Use raise ... from e to preserve the original exception context for better debugging.

             except Exception as e:
-                raise ValueError(
-                    f"Failed to load toolsets from {toolset_path}, error: {e}"
-                )
+                raise ValueError(
+                    f"Failed to load toolsets from {toolset_path}, error: {e}"
+                ) from e
🧰 Tools
🪛 Ruff (0.11.9)

292-294: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 804ea40 and be53a9b.

📒 Files selected for processing (35)
  • holmes/config.py (11 hunks)
  • holmes/core/resource_instruction.py (1 hunks)
  • holmes/core/supabase_dal.py (1 hunks)
  • holmes/core/tool_calling_llm.py (1 hunks)
  • holmes/core/tools.py (11 hunks)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/main.py (20 hunks)
  • holmes/plugins/prompts/_fetch_logs.jinja2 (3 hunks)
  • holmes/plugins/prompts/_toolsets_instructions.jinja2 (2 hunks)
  • holmes/plugins/toolsets/__init__.py (2 hunks)
  • holmes/plugins/toolsets/consts.py (1 hunks)
  • holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py (3 hunks)
  • holmes/plugins/toolsets/grafana/base_grafana_toolset.py (1 hunks)
  • holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (1 hunks)
  • holmes/plugins/toolsets/kafka.py (1 hunks)
  • holmes/plugins/toolsets/opensearch/opensearch.py (1 hunks)
  • holmes/plugins/toolsets/prometheus/prometheus.py (1 hunks)
  • holmes/plugins/toolsets/utils.py (1 hunks)
  • holmes/utils/default_toolset_installation_guide.jinja2 (1 hunks)
  • holmes/utils/definitions.py (1 hunks)
  • holmes/utils/env.py (1 hunks)
  • holmes/utils/holmes_sync_toolsets.py (3 hunks)
  • holmes/utils/pydantic_utils.py (3 hunks)
  • tests/config_class/test_config_load_custom_toolsets.py (0 hunks)
  • tests/config_class/test_config_load_remote_mcp_server.py (2 hunks)
  • tests/core/test_toolset_manager.py (1 hunks)
  • tests/llm/utils/mock_toolset.py (2 hunks)
  • tests/plugins/toolsets/grafana/test_grafana_loki.py (2 hunks)
  • tests/plugins/toolsets/grafana/test_grafana_tempo.py (2 hunks)
  • tests/plugins/toolsets/test_load_config.py (1 hunks)
  • tests/plugins/toolsets/test_notion.py (2 hunks)
  • tests/plugins/toolsets/test_prometheus_integration.py (2 hunks)
  • tests/plugins/toolsets/test_tool_kafka.py (2 hunks)
  • tests/test_check_prerequisites.py (11 hunks)
  • tests/test_holmes_sync_toolsets.py (2 hunks)
💤 Files with no reviewable changes (1)
  • tests/config_class/test_config_load_custom_toolsets.py
✅ Files skipped from review due to trivial changes (7)
  • holmes/utils/default_toolset_installation_guide.jinja2
  • holmes/utils/definitions.py
  • tests/plugins/toolsets/test_prometheus_integration.py
  • holmes/plugins/toolsets/utils.py
  • holmes/utils/holmes_sync_toolsets.py
  • tests/test_check_prerequisites.py
  • holmes/core/resource_instruction.py
🚧 Files skipped from review as they are similar to previous changes (19)
  • holmes/plugins/prompts/_fetch_logs.jinja2
  • holmes/plugins/toolsets/grafana/base_grafana_toolset.py
  • tests/plugins/toolsets/test_tool_kafka.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • tests/plugins/toolsets/test_notion.py
  • holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py
  • tests/plugins/toolsets/grafana/test_grafana_loki.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/consts.py
  • tests/plugins/toolsets/grafana/test_grafana_tempo.py
  • tests/llm/utils/mock_toolset.py
  • holmes/core/supabase_dal.py
  • holmes/core/tool_calling_llm.py
  • holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py
  • holmes/plugins/prompts/_toolsets_instructions.jinja2
  • holmes/plugins/toolsets/kafka.py
  • holmes/utils/pydantic_utils.py
  • holmes/utils/env.py
  • holmes/core/tools.py
🧰 Additional context used
🧬 Code Graph Analysis (2)
tests/config_class/test_config_load_remote_mcp_server.py (1)
holmes/plugins/toolsets/__init__.py (1)
  • load_toolsets_from_config (109-153)
holmes/main.py (5)
holmes/__init__.py (1)
  • get_version (14-70)
holmes/config.py (4)
  • Config (101-438)
  • create_console_issue_investigator (312-323)
  • load_from_file (192-205)
  • toolset_manager (164-171)
holmes/core/tools.py (1)
  • Toolset (301-445)
holmes/utils/file_utils.py (1)
  • write_json_file (7-16)
holmes/core/toolset_manager.py (1)
  • load_toolset_with_status (196-246)
🪛 Ruff (0.11.9)
holmes/plugins/toolsets/__init__.py

104-106: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/core/toolset_manager.py

67-79: Combine if branches using logical or operator

Combine if branches

(SIM114)


240-240: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


292-294: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


375-375: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 GitHub Actions: Evaluate LLM test cases
holmes/config.py

[error] 1-1: ImportError: cannot import name 'parse_toolsets_file' from 'holmes.config'. This caused the test run to fail.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build (3.12)
🔇 Additional comments (20)
tests/config_class/test_config_load_remote_mcp_server.py (2)

6-6: LGTM! Import updated correctly for refactored toolset loading.

The import change from load_toolsets_definitions to load_toolsets_from_config correctly aligns with the refactoring that moved toolset loading functionality to the holmes.plugins.toolsets module.


51-53: LGTM! Function call updated correctly with appropriate parameters.

The function call has been properly updated to use load_toolsets_from_config with strict_check=False, which aligns with the new function signature and maintains the same test behavior.

tests/test_holmes_sync_toolsets.py (2)

1-2: LGTM! Import reorganization improves code organization.

The imports have been properly reorganized by grouping related imports together (e.g., standard library imports at the top, related tool imports together), which improves code readability and follows Python best practices.

Also applies to: 5-5, 8-8, 10-11, 19-20


355-360: LGTM! Updated to use direct attribute access.

The changes correctly update from method calls (get_status(), get_error()) to direct attribute access (status, error), which aligns with the refactoring where these became public attributes on the Toolset class.

tests/plugins/toolsets/test_load_config.py (3)

9-26: LGTM! Comprehensive test for old format detection.

This test correctly verifies that the function properly rejects old list-based toolset configurations and raises an appropriate error with a helpful message.


28-55: LGTM! Good test coverage for multiple old format toolsets.

This test ensures that the old format detection works correctly even when multiple toolsets are provided in the deprecated format.


71-94: LGTM! Excellent test for environment variable substitution.

This test thoroughly validates:

  • Loading toolsets in the new dictionary-based format
  • Environment variable substitution in configuration values
  • Proper environment cleanup in the finally block

The test ensures that environment variables are correctly resolved and injected into the toolset configuration.

holmes/plugins/toolsets/__init__.py (2)

36-51: LGTM! Well-refactored file loading function.

The load_toolsets_from_file function has been properly refactored to:

  • Parse YAML files with proper error handling for empty/invalid files
  • Extract the "toolsets" dictionary from the parsed YAML
  • Delegate validation to the new load_toolsets_from_config function

This separation of concerns improves maintainability.


89-96: LGTM! Good default behavior for built-in toolsets.

The logic to disable built-in toolsets by default and mark them with appropriate metadata (type, is_default, path=None) provides better control over toolset activation and prevents exposing internal paths.

holmes/main.py (4)

14-44: LGTM: Import additions support new functionality.

The new imports for json, logging, Table, and write_json_file are appropriately added to support the new toolset management CLI commands and their table display functionality.


141-145: LGTM: Config option type change aligns with Path usage.

The change from Optional[str] to Optional[Path] for the config file option is consistent with the broader refactoring to use Path objects for file paths throughout the codebase.


1144-1161: LGTM: Well-implemented table display function.

The pretty_print_toolset_status function correctly creates a Rich table with appropriate columns and handles missing fields gracefully using .get() with default values.


383-383: LGTM: Method call changes align with ToolsetManager refactoring.

The removal of the allowed_toolsets parameter and the use of custom_toolsets_from_cli align with the centralized toolset management approach using ToolsetManager.

Also applies to: 589-589, 718-718, 834-834, 909-909, 993-993, 1079-1079

tests/core/test_toolset_manager.py (1)

1-278: LGTM: Comprehensive test coverage for ToolsetManager.

This test file provides excellent coverage of the ToolsetManager functionality including:

  • Utility methods and property access
  • Loading and merging toolsets from various sources
  • Caching and status management
  • Error handling for invalid configurations
  • Edge cases with missing files and empty configurations

The tests use appropriate mocking, temporary files, and parametrization to ensure reliable and isolated testing.

holmes/core/toolset_manager.py (2)

27-27: LGTM: Type annotation is correctly fixed.

The toolset_status_location field is now correctly typed as FilePath, which matches its usage throughout the code as a file path.


16-40: LGTM: Well-designed centralized toolset management.

The ToolsetManager class provides excellent centralization of toolset management functionality, with clear separation of concerns for loading, merging, caching, and filtering toolsets. The design properly handles different toolset types and sources.

holmes/config.py (4)

163-171: LGTM: Clean ToolsetManager integration.

The toolset_manager property provides a clean integration point for the centralized toolset management, lazily initializing the ToolsetManager with the appropriate configuration parameters.


191-205: LGTM: Improved config loading with better typing and logging.

The changes to load_from_file provide better type safety with Path parameters, improved debug logging, and clearer variable naming. The logic remains correct while being more maintainable.


256-285: LGTM: Simplified tool executor creation using ToolsetManager.

The refactoring of create_console_tool_executor and create_tool_executor methods properly delegates toolset management to ToolsetManager, removing manual filtering logic and the allowed_toolsets parameter. This simplification improves maintainability while preserving functionality.

🧰 Tools
🪛 Ruff (0.11.9)

282-282: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


453-453: LGTM: Consistent Path type usage in SourceFactory.

The change to use Optional[Path] for the config_file parameter is consistent with the broader refactoring to use Path objects for file paths throughout the codebase.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (4)
holmes/core/toolset_manager.py (4)

60-62: Fix incorrect docstring for server_tool_tags property.

The docstring incorrectly refers to "CLI tools" when it should mention "server tools".


75-87: Simplify conditional branches using logical OR.

The separate if/elif branches can be combined for better readability.

🧰 Tools
🪛 Ruff (0.11.9)

75-87: Combine if branches using logical or operator

Combine if branches

(SIM114)


174-200: Consider using the existing write_json_file utility or add error handling.

The file writing operation lacks error handling. Consider using the existing write_json_file utility from holmes.utils.file_utils which already has proper error handling.


202-252: Add error handling for cache file loading.

The JSON loading could fail if the cache file is corrupted or has permission issues.

🧰 Tools
🪛 Ruff (0.11.9)

246-246: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🧹 Nitpick comments (2)
holmes/core/toolset_manager.py (2)

246-246: Use key in dict instead of key in dict.keys().

This is more efficient and pythonic.

-            if custom_toolset_from_cli in toolsets_status_by_name.keys():
+            if custom_toolset_from_cli.name in toolsets_status_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

246-246: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


381-381: Use key in dict instead of key in dict.keys().

This is more efficient and pythonic.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

381-381: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec98f5 and 70a59f9.

📒 Files selected for processing (3)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/utils/definitions.py (1 hunks)
  • tests/llm/utils/mock_toolset.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • holmes/utils/definitions.py
  • tests/llm/utils/mock_toolset.py
🧰 Additional context used
🪛 Ruff (0.11.9)
holmes/core/toolset_manager.py

75-87: Combine if branches using logical or operator

Combine if branches

(SIM114)


246-246: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


298-300: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


381-381: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.11)
🔇 Additional comments (9)
holmes/core/toolset_manager.py (9)

1-15: LGTM - Clean imports and module organization.

The imports are well-organized and the default cache location is appropriately set using os.path.expanduser().


17-29: LGTM - Well-defined class structure.

The class attributes are properly typed and the docstring clearly explains the purpose of the ToolsetManager.


30-47: LGTM - Robust initialization with automatic custom toolset discovery.

The constructor properly handles the automatic inclusion of CUSTOM_TOOLSET_LOCATION when it exists, which is useful for containerized environments.


91-138: LGTM - Comprehensive toolset loading with proper override hierarchy.

The method correctly implements the override hierarchy: built-in → configured → custom toolsets. The prerequisite checking logic is well-implemented with proper error handling.


140-172: LGTM - Proper separation of built-in and custom toolset handling.

The method correctly distinguishes between built-in and custom toolsets, applying appropriate validation levels (strict vs non-strict) based on the toolset type.


254-278: LGTM - Clear and efficient filtering methods.

Both methods properly filter toolsets based on their tags and use the cached status for efficiency.


280-330: LGTM - Robust custom toolset loading with conflict detection.

The method properly handles YAML parsing, validates required keys, and includes conflict checking for built-in toolsets when requested.

🧰 Tools
🪛 Ruff (0.11.9)

298-300: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


332-369: LGTM - Clear custom toolset loading implementation.

The method includes helpful documentation with configuration examples and properly delegates to the path-based loading method.


371-384: LGTM - Simple and effective toolset merging logic.

The method correctly handles both overriding existing toolsets and adding new ones.

🧰 Tools
🪛 Ruff (0.11.9)

381-381: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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/utils/env.py (1)

33-33: Fix missing assignment for recursive dictionary processing.

The recursive call to replace_env_vars_values(value) doesn't assign the result back to the dictionary, which means nested dictionaries won't be updated correctly.

Apply this diff:

-            replace_env_vars_values(value)
+            values[key] = replace_env_vars_values(value)
holmes/main.py (1)

1121-1121: Fix typo in command help text.

-    List build-in and custom toolsets status of CLI
+    List built-in and custom toolsets status of CLI
holmes/config.py (1)

1-491: Critical: Acknowledge the duplicate comment about breaking test imports.

As noted in the previous review, the removal of parse_toolsets_file breaks test imports. This critical issue must be resolved.

The previous review correctly identified that tests in tests/llm/utils/mock_toolset.py still import the removed parse_toolsets_file function, causing test failures. Please update the test file to use the new ToolsetManager API or the appropriate replacement function.

🧰 Tools
🪛 Ruff (0.11.9)

47-49: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)


55-55: Loop control variable model not used within loop body

Rename unused model to _model

(B007)


242-242: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 Pylint (3.3.7)

[convention] 100-100: Line too long (113/100)

(C0301)


[convention] 101-101: Line too long (101/100)

(C0301)


[convention] 102-102: Line too long (115/100)

(C0301)


[convention] 146-146: Line too long (123/100)

(C0301)


[convention] 218-218: Line too long (107/100)

(C0301)


[convention] 223-223: Line too long (107/100)

(C0301)


[convention] 242-242: Line too long (111/100)

(C0301)


[convention] 422-422: Line too long (104/100)

(C0301)


[convention] 450-450: Line too long (186/100)

(C0301)


[convention] 480-480: Line too long (198/100)

(C0301)


[warning] 188-188: TODO

(W0511)


[convention] 1-1: Missing module docstring

(C0114)


[error] 10-10: Unable to import 'pydantic'

(E0401)


[convention] 38-38: Missing class docstring

(C0115)


[convention] 43-43: Missing function or method docstring

(C0116)


[convention] 52-52: Missing function or method docstring

(C0116)


[warning] 55-55: Unused variable 'model'

(W0612)


[convention] 61-61: Missing class docstring

(C0115)


[convention] 116-116: Missing function or method docstring

(C0116)


[convention] 124-124: Missing function or method docstring

(C0116)


[convention] 133-133: Missing function or method docstring

(C0116)


[warning] 142-142: Use lazy % formatting in logging functions

(W1203)


[convention] 152-152: Missing function or method docstring

(C0116)


[warning] 154-154: Use lazy % formatting in logging functions

(W1203)


[error] 159-159: Possibly using variable 'config_from_file' before assignment

(E0606)


[warning] 162-162: Use lazy % formatting in logging functions

(W1203)


[convention] 168-168: Missing function or method docstring

(C0116)


[warning] 205-205: Use lazy % formatting in logging functions

(W1203)


[warning] 208-208: Use lazy % formatting in logging functions

(W1203)


[warning] 209-209: Using open without explicitly specifying an encoding

(W1514)


[warning] 241-243: Use lazy % formatting in logging functions

(W1203)


[refactor] 242-242: Unnecessary use of a comprehension, use list(self._server_tool_executor.tools_by_name.keys()) instead.

(R1721)


[convention] 242-242: Consider iterating the dictionary directly instead of calling .keys()

(C0201)


[convention] 247-247: Missing function or method docstring

(C0116)


[convention] 253-253: Missing function or method docstring

(C0116)


[convention] 259-259: Missing function or method docstring

(C0116)


[convention] 272-272: Missing function or method docstring

(C0116)


[convention] 285-285: Missing function or method docstring

(C0116)


[convention] 297-297: Missing function or method docstring

(C0116)


[convention] 307-307: Missing function or method docstring

(C0116)


[convention] 317-317: Missing function or method docstring

(C0116)


[convention] 338-338: Missing function or method docstring

(C0116)


[convention] 348-348: Missing function or method docstring

(C0116)


[convention] 362-362: Missing function or method docstring

(C0116)


[convention] 371-371: Missing function or method docstring

(C0116)


[convention] 394-394: Missing function or method docstring

(C0116)


[warning] 136-136: Attribute '_model_list' defined outside init

(W0201)


[convention] 401-401: Missing class docstring

(C0115)


[refactor] 401-401: Too few public methods (0/2)

(R0903)


[convention] 409-409: Missing class docstring

(C0115)


[convention] 411-411: Missing function or method docstring

(C0116)


[refactor] 411-411: Too many arguments (6/5)

(R0913)


[refactor] 411-411: Too many positional arguments (6/5)

(R0917)


[refactor] 425-490: Unnecessary "elif" after "return", remove the leading "el" from "elif"

(R1705)


[refactor] 409-409: Too few public methods (1/2)

(R0903)

🧹 Nitpick comments (5)
holmes/utils/env.py (1)

1-7: Add module docstring for better documentation.

Consider adding a module docstring to describe the purpose of this utility module.

+"""
+Utility module for environment variable replacement in nested data structures.
+
+This module provides functions to find and replace environment variable patterns
+like {{ env.VAR_NAME }} in strings, dictionaries, and lists with their actual
+environment variable values.
+"""
+
 import logging
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


[error] 6-6: Unable to import 'pydantic'

(E0401)

holmes/core/tools.py (1)

427-429: Consider using membership test for cleaner comparison.

The static analysis suggestion to use in operator for multiple comparisons would improve readability.

-            if (
-                self.status == ToolsetStatusEnum.DISABLED
-                or self.status == ToolsetStatusEnum.FAILED
-            ):
+            if self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED):
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)

holmes/config.py (3)

100-104: Long line violations and clear documentation.

The comments clearly explain the distinction between custom_toolsets and custom_toolsets_from_cli, which is helpful for understanding the configuration hierarchy.

Consider breaking the long comments to comply with line length limits:

-    # custom_toolsets is defined in the config file, and can override built-in toolsets as stable custom toolsets
-    # While custom_toolsets_from_cli is defined in the CLI and should not override built-in toolsets.
-    # custom_toolsets status can be cached while custom_toolsets_from_cli should be loaded every time the CLI runs.
+    # custom_toolsets is defined in the config file, and can override built-in toolsets
+    # as stable custom toolsets. While custom_toolsets_from_cli is defined in the CLI
+    # and should not override built-in toolsets.
+    # custom_toolsets status can be cached while custom_toolsets_from_cli should be
+    # loaded every time the CLI runs.
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 100-100: Line too long (113/100)

(C0301)


[convention] 101-101: Line too long (101/100)

(C0301)


[convention] 102-102: Line too long (115/100)

(C0301)


123-131: Add docstring for the toolset_manager property.

The lazy initialization pattern is correct, but the property lacks documentation.

Add a docstring to explain the property's purpose:

 @property
 def toolset_manager(self) -> ToolsetManager:
+    """
+    Lazily initializes and returns the ToolsetManager instance.
+    
+    The manager handles built-in, configured, and custom toolsets,
+    providing filtered lists for CLI and server contexts.
+    """
     if not self._toolset_manager:
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 124-124: Missing function or method docstring

(C0116)


216-227: Simplified toolset loading with centralized management.

The refactoring to use ToolsetManager simplifies the method significantly and centralizes toolset management logic, which is a good architectural improvement.

Consider adding a docstring to explain the updated behavior and fix the line length:

 def create_console_tool_executor(self, dal: Optional[SupabaseDal]) -> ToolExecutor:
     """
     Creates a ToolExecutor instance configured for CLI usage. This executor manages the available tools
     and their execution in the command-line interface.

     The method loads toolsets in this order, with later sources overriding earlier ones:
     1. Built-in toolsets (tagged as CORE or CLI)
-    2. toolsets from config file will override and be merged into built-in toolsets with the same name.
-    3. Custom toolsets from config files which can not override built-in toolsets
+    2. Toolsets from config file will override and be merged into built-in toolsets
+       with the same name.
+    3. Custom toolsets from config files which cannot override built-in toolsets
     """
-    cli_toolsets = self.toolset_manager.list_enabled_console_toolsets(dal=dal)
+    cli_toolsets = self.toolset_manager.list_enabled_console_toolsets(dal=dal)
     return ToolExecutor(cli_toolsets)
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 218-218: Line too long (107/100)

(C0301)


[convention] 223-223: Line too long (107/100)

(C0301)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70a59f9 and 2566d02.

📒 Files selected for processing (6)
  • holmes/config.py (11 hunks)
  • holmes/core/tools.py (9 hunks)
  • holmes/main.py (20 hunks)
  • holmes/utils/env.py (1 hunks)
  • tests/plugins/toolsets/test_tool_kafka.py (2 hunks)
  • tests/test_issue_investigator.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/test_issue_investigator.py
🧰 Additional context used
🪛 Pylint (3.3.7)
holmes/main.py

[convention] 14-14: Import "import json" should be placed at the top of the module

(C0413)


[convention] 15-15: Import "import logging" should be placed at the top of the module

(C0413)


[convention] 16-16: Import "import socket" should be placed at the top of the module

(C0413)


[convention] 17-17: Import "import uuid" should be placed at the top of the module

(C0413)


[convention] 18-18: Import "import warnings" should be placed at the top of the module

(C0413)


[convention] 19-19: Import "from enum import Enum" should be placed at the top of the module

(C0413)


[convention] 20-20: Import "from pathlib import Path" should be placed at the top of the module

(C0413)


[convention] 21-21: Import "from typing import List, Optional" should be placed at the top of the module

(C0413)


[error] 23-23: Unable to import 'typer'

(E0401)


[convention] 23-23: Import "import typer" should be placed at the top of the module

(C0413)


[convention] 24-24: Import "from rich.console import Console" should be placed at the top of the module

(C0413)


[convention] 25-25: Import "from rich.logging import RichHandler" should be placed at the top of the module

(C0413)


[convention] 26-26: Import "from rich.markdown import Markdown" should be placed at the top of the module

(C0413)


[convention] 27-27: Import "from rich.rule import Rule" should be placed at the top of the module

(C0413)


[convention] 28-28: Import "from rich.table import Table" should be placed at the top of the module

(C0413)


[convention] 30-30: Import "from holmes import get_version" should be placed at the top of the module

(C0413)


[convention] 31-36: Import "from holmes.config import DEFAULT_CONFIG_LOCATION, Config, SourceFactory, SupportedTicketSources" should be placed at the top of the module

(C0413)


[convention] 37-37: Import "from holmes.core.resource_instruction import ResourceInstructionDocument" should be placed at the top of the module

(C0413)


[convention] 38-38: Import "from holmes.core.tool_calling_llm import LLMResult, ToolCallingLLM" should be placed at the top of the module

(C0413)


[convention] 39-39: Import "from holmes.core.tools import Toolset" should be placed at the top of the module

(C0413)


[convention] 40-40: Import "from holmes.plugins.destinations import DestinationType" should be placed at the top of the module

(C0413)


[convention] 41-41: Import "from holmes.plugins.interfaces import Issue" should be placed at the top of the module

(C0413)


[convention] 42-42: Import "from holmes.plugins.prompts import load_and_render_prompt" should be placed at the top of the module

(C0413)


[convention] 43-43: Import "from holmes.plugins.sources.opsgenie import OPSGENIE_TEAM_INTEGRATION_KEY_HELP" should be placed at the top of the module

(C0413)


[convention] 44-44: Import "from holmes.utils.file_utils import write_json_file" should be placed at the top of the module

(C0413)


[convention] 14-14: standard import "json" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 15-15: standard import "logging" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 16-16: standard import "socket" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 17-17: standard import "uuid" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 18-18: standard import "warnings" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 19-19: standard import "enum.Enum" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 20-20: standard import "pathlib.Path" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 21-21: standard import "typing.List" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 23-23: third party import "typer" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 24-24: third party import "rich.console.Console" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 25-25: third party import "rich.logging.RichHandler" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 26-26: third party import "rich.markdown.Markdown" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 27-27: third party import "rich.rule.Rule" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 28-28: third party import "rich.table.Table" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 30-30: Imports from package holmes are not grouped

(C0412)


[convention] 144-144: Line too long (151/100)

(C0301)


[convention] 1158-1158: Line too long (103/100)

(C0301)


[convention] 1144-1144: Missing function or method docstring

(C0116)

holmes/config.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 10-10: Unable to import 'pydantic'

(E0401)


[convention] 100-100: Line too long (113/100)

(C0301)


[convention] 101-101: Line too long (101/100)

(C0301)


[convention] 102-102: Line too long (115/100)

(C0301)


[convention] 124-124: Missing function or method docstring

(C0116)


[convention] 152-152: Missing function or method docstring

(C0116)


[warning] 154-154: Use lazy % formatting in logging functions

(W1203)


[error] 159-159: Possibly using variable 'config_from_file' before assignment

(E0606)


[warning] 162-162: Use lazy % formatting in logging functions

(W1203)


[convention] 218-218: Line too long (107/100)

(C0301)


[convention] 223-223: Line too long (107/100)

(C0301)

holmes/core/tools.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 13-13: Unable to import 'sentry_sdk'

(E0401)


[error] 15-15: Unable to import 'pydantic'

(E0401)


[convention] 305-305: Line too long (107/100)

(C0301)


[convention] 399-399: Line too long (101/100)

(C0301)


[warning] 422-422: Catching too general exception Exception

(W0718)


[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)


[warning] 430-430: Use lazy % formatting in logging functions

(W1203)


[convention] 523-523: Line too long (104/100)

(C0301)

holmes/utils/env.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 6-6: Unable to import 'pydantic'

(E0401)


[convention] 9-9: Missing function or method docstring

(C0116)


[convention] 22-22: Missing function or method docstring

(C0116)

tests/plugins/toolsets/test_tool_kafka.py

[error] 7-7: Unable to import 'confluent_kafka.admin'

(E0401)

🔇 Additional comments (19)
holmes/utils/env.py (2)

17-17: Good fix for exception type specificity.

The change from generic Exception to ValueError improves error handling clarity and follows best practices.


36-45: Good fix for variable naming convention.

The change from iter to item removes the shadowing of the built-in iter() function and improves code clarity.

tests/plugins/toolsets/test_tool_kafka.py (2)

4-17: Good import reorganization.

The import statements have been properly reorganized for better clarity and consistency with the codebase structure.

🧰 Tools
🪛 Pylint (3.3.7)

[error] 7-7: Unable to import 'confluent_kafka.admin'

(E0401)


40-40: Status access pattern correctly implemented.

The status check now uses direct attribute access (kafka_toolset.status) which is consistent with the refactor pattern used throughout the codebase where status is accessed as a direct attribute rather than via method calls.

holmes/main.py (4)

142-145: Good refactor of config file parameter type.

The change from Optional[str] to Optional[Path] with a default value improves type safety and consistency with the codebase.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 144-144: Line too long (151/100)

(C0301)


383-383: Consistent parameter naming after refactor.

The parameter name change to custom_toolsets_from_cli aligns with the centralized toolset management refactor and removal of the allowed_toolsets concept.


1115-1142: Well-structured CLI commands for toolset management.

The new list and refresh commands provide useful toolset management functionality that integrates well with the new ToolsetManager approach. The commands correctly use config.toolset_manager.load_toolset_with_status() with appropriate refresh parameters.


1144-1161: Clean implementation of toolset status display.

The pretty_print_toolset_status function provides a good user interface for displaying toolset information using Rich tables. The use of model_dump_json with specific field inclusion is appropriate for extracting the status data.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1158-1158: Line too long (103/100)

(C0301)


[convention] 1144-1144: Missing function or method docstring

(C0116)

holmes/core/tools.py (6)

334-337: Good refactor to public attributes for status management.

Converting the private attributes _path, _status, and _error to public attributes path, status, error, and adding the type attribute improves the API design and aligns with the centralized toolset management approach. This change makes the status information directly accessible without method calls.


97-99: Appropriate enum extensions for toolset types.

The addition of BUILTIN and CUSTOMIZED to the ToolsetType enum provides clear categorization of toolset sources, which is essential for the new toolset management functionality.


452-459: Sensible defaults for YAMLToolset configuration.

Setting enabled = True by default for user-defined YAML toolsets and providing an empty list default for prerequisites makes sense from a usability perspective - users expect their custom toolsets to be enabled unless explicitly disabled.


380-425: Direct attribute assignment improves code clarity.

The changes to directly assign to self.status and self.error attributes instead of using private attributes with methods makes the code more straightforward and easier to understand.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 399-399: Line too long (101/100)

(C0301)


[warning] 422-422: Catching too general exception Exception

(W0718)


476-476: Consistent status access in ToolExecutor.

The update to use toolset.status directly instead of a method call is consistent with the public attribute refactor and improves code readability.


518-549: Well-designed partial override mechanism.

The ToolsetYamlFromConfig class provides a clean way to handle partial toolset configuration overrides without requiring all fields to be specified. The optional field definitions and clear documentation explain its purpose for bypassing pydantic validation while allowing selective field overrides.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 523-523: Line too long (104/100)

(C0301)

holmes/config.py (5)

56-56: Environment variable replacement refactored correctly.

The move from inline environment variable replacement to using the dedicated env_utils.replace_env_vars_values function improves code organization and reusability.


237-237: Approve delegation to ToolsetManager.

The change to use self.toolset_manager.list_enabled_server_toolsets(dal=dal) correctly delegates toolset filtering to the centralized manager.


248-250: Method signature simplification removes unnecessary parameter.

The removal of the allowed_toolsets parameter from these methods is consistent with centralizing toolset management in ToolsetManager. The toolset filtering is now handled internally.

Also applies to: 273-274, 280-280


413-413: Type safety improvement with Path parameter.

Changing the config_file parameter from str to Optional[Path] improves type safety and is consistent with the updated Config.load_from_file method signature.


1-31:

✅ Verification successful

Import reorganization looks good, but verify new utility modules exist.

The import reorganization with isort is clean and the new imports for ToolsetManager and utility modules align with the architectural changes.

Let me verify that the new utility modules are available:


🏁 Script executed:

#!/bin/bash
# Verify that the new imported modules exist
fd -t f "env.py" holmes/utils/ --exec echo "Found: {}"
fd -t f "toolset_manager.py" holmes/core/ --exec echo "Found: {}"
fd -t f "file_utils.py" holmes/utils/ --exec echo "Found: {}"
fd -t f "pydantic_utils.py" holmes/utils/ --exec echo "Found: {}"

Length of output: 385


Import verification complete: new utility modules found

All newly referenced utility modules are present in the codebase:

  • holmes/utils/env.py
  • holmes/core/toolset_manager.py
  • holmes/utils/file_utils.py
  • holmes/utils/pydantic_utils.py

No further action required—imports are valid and the refactor stands.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


[error] 10-10: Unable to import 'pydantic'

(E0401)

mainred added 13 commits May 31, 2025 16:20

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao
@mainred mainred force-pushed the cache-and-manage-toolset branch from 2566d02 to 24725ed Compare May 31, 2025 16:20

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao
@mainred mainred force-pushed the cache-and-manage-toolset branch from b384219 to d316b6c Compare May 31, 2025 16:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (8)
tests/plugins/toolsets/test_tool_kafka.py (1)

40-40: Status access pattern is now correct.

The change to kafka_toolset.status == ToolsetStatusEnum.ENABLED (without parentheses) is consistent with the refactor where status is accessed as a direct attribute rather than a method call.

holmes/main.py (1)

1121-1121: Fix typo in command help text.

There's a typo where "build-int" should be "built-in".

Apply this diff:

-    List build-int and custom toolsets status of CLI
+    List built-in and custom toolsets status of CLI
holmes/core/toolset_manager.py (5)

60-62: Fix incorrect docstring for server_tool_tags property.

The docstring incorrectly refers to "CLI tools" when it should mention "server tools".

-        """
-        Returns the list of toolset tags that are relevant for server tools.
-        A toolset is considered a server tool if it has any of UI tool tags:
-        """
+        """
+        Returns the list of toolset tags that are relevant for server tools.
+        A toolset is considered a server tool if it has any of server tool tags.
+        """

79-85: Simplify conditional branches using logical OR.

The conditional logic can be streamlined for better readability.

-            if (
-                isinstance(toolset_config["enabled"], bool)
-                and not toolset_config["enabled"]
-            ) or (
-                isinstance(toolset_config["enabled"], str)
-                and toolset_config["enabled"].lower() == "false"
-            ):
-                return False
+            if (
+                (isinstance(toolset_config["enabled"], bool) and not toolset_config["enabled"])
+                or (isinstance(toolset_config["enabled"], str) and toolset_config["enabled"].lower() == "false")
+            ):
+                return False
🧰 Tools
🪛 Ruff (0.11.9)

78-85: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)


189-198: Consider using the existing write_json_file utility for better error handling.

The manual file writing lacks proper error handling. The existing utility provides robust error handling.

-        if self.toolset_status_location and not os.path.exists(
-            os.path.dirname(self.toolset_status_location)
-        ):
-            os.makedirs(os.path.dirname(self.toolset_status_location))
-        with open(self.toolset_status_location, "w") as f:
-            toolset_status = [
-                json.loads(
-                    toolset.model_dump_json(
-                        include={"name", "status", "enabled", "type", "path", "error"}
-                    )
-                )
-                for toolset in all_toolsets
-            ]
-            json.dump(toolset_status, f, indent=2)
+        from holmes.utils.file_utils import write_json_file
+        
+        toolset_status = [
+            json.loads(
+                toolset.model_dump_json(
+                    include={"name", "status", "enabled", "type", "path", "error"}
+                )
+            )
+            for toolset in all_toolsets
+        ]
+        write_json_file(self.toolset_status_location, toolset_status)
🧰 Tools
🪛 Pylint (3.3.7)

[warning] 189-189: Using open without explicitly specifying an encoding

(W1514)


214-215: Add error handling for cache file loading.

The JSON loading could fail if the cache file is corrupted or has permission issues.

-        cached_toolsets: List[dict[str, Any]] = []
-        with open(self.toolset_status_location, "r") as f:
-            cached_toolsets = json.load(f)
+        cached_toolsets: List[dict[str, Any]] = []
+        try:
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)
+        except (json.JSONDecodeError, IOError) as e:
+            logging.error(f"Failed to load toolset cache: {e}. Refreshing status.")
+            self.refresh_toolset_status(dal)
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)
🧰 Tools
🪛 Pylint (3.3.7)

[warning] 214-214: Using open without explicitly specifying an encoding

(W1514)


297-299: Improve exception chaining for better error traceability.

Use proper exception chaining to preserve the original error context.

-            except Exception as e:
-                raise ValueError(
-                    f"Failed to load toolsets from {toolset_path}, error: {e}"
-                )
+            except Exception as e:
+                raise ValueError(
+                    f"Failed to load toolsets from {toolset_path}, error: {e}"
+                ) from e
holmes/config.py (1)

163-175: ⚠️ Potential issue

Fix undefined variable usage in load_from_file method.

The variable config_from_file may be used before assignment if the file doesn't exist.

 @classmethod
 def load_from_file(cls, config_file: Optional[Path], **kwargs) -> "Config":
     """
     Load configuration from file and merge with CLI options.

     Args:
         config_file: Path to configuration file
         **kwargs: CLI options to override config file values

     Returns:
         Config instance with merged settings
     """
+    config_from_file = None
     if config_file is not None and config_file.exists():
         logging.debug(f"Loading config from {config_file}")
         config_from_file = load_model_from_file(cls, config_file)

     cli_options = {k: v for k, v in kwargs.items() if v is not None and v != []}

     if config_from_file is None:
         return cls(**cli_options)

     logging.debug(f"Overriding config from cli options {cli_options}")
     merged_config = config_from_file.dict()
     merged_config.update(cli_options)
     return cls(**merged_config)
🧰 Tools
🪛 Pylint (3.3.7)

[warning] 164-164: Use lazy % formatting in logging functions

(W1203)


[error] 169-169: Possibly using variable 'config_from_file' before assignment

(E0606)


[warning] 172-172: Use lazy % formatting in logging functions

(W1203)

🧹 Nitpick comments (11)
tests/llm/utils/mock_toolset.py (1)

145-153: Remove unused parameter.

The run_live parameter is not used within the method body and should be removed to eliminate the pylint warning.

Apply this diff to remove the unused parameter:

-    def _load_toolsets_definitions(self, run_live) -> List[Toolset]:
+    def _load_toolsets_definitions(self) -> List[Toolset]:

Also update the method call on line 158:

-        toolset_definitions = self._load_toolsets_definitions(run_live)
+        toolset_definitions = self._load_toolsets_definitions()
🧰 Tools
🪛 Pylint (3.3.7)

[warning] 145-145: Unused argument 'run_live'

(W0613)

holmes/plugins/toolsets/consts.py (1)

1-5: LGTM: Good centralization of toolset constants.

The new constants file effectively centralizes common strings used across multiple toolset plugins:

  • TOOLSET_CONFIG_MISSING_ERROR: Provides a consistent error message for missing configurations
  • STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION: Standardizes parameter descriptions for datetime fields

This approach reduces duplication and ensures consistency across the codebase.

Consider adding a module docstring to improve documentation:

+"""Constants for toolset plugins."""
+
 TOOLSET_CONFIG_MISSING_ERROR = "The toolset is missing its configuration"
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)

holmes/core/resource_instruction.py (1)

1-18: Add module docstring for better documentation

The module is well-structured and the Pydantic models are appropriately defined. Consider adding a module-level docstring to describe the purpose of this module.

+"""
+Resource instruction models for investigation context.
+
+This module defines Pydantic models for representing investigation instructions
+and document references that can be used to provide additional context during
+Holmes investigations.
+"""
 from typing import List
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


[error] 3-3: Unable to import 'pydantic'

(E0401)


[refactor] 6-6: Too few public methods (0/2)

(R0903)


[convention] 15-15: Missing class docstring

(C0115)


[refactor] 15-15: Too few public methods (0/2)

(R0903)

tests/plugins/toolsets/test_load_config.py (2)

65-68: Use mock values instead of realistic API keys

The test data contains what appears to be a realistic API key format. For security and clarity, consider using clearly fake values in test data.

 env_vars = {
-    "GRAFANA_API_KEY": "glsa_sdj1q2o3prujpqfd",
+    "GRAFANA_API_KEY": "test_api_key_123",
     "GRAFANA_URL": "https://my-grafana.com/",
 }
🧰 Tools
🪛 Gitleaks (8.26.0)

66-66: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


1-6: Add module and function docstrings

Consider adding a module docstring and docstrings for the test functions to improve code documentation.

+"""Tests for toolset configuration loading functionality."""
 import os

 import pytest
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)

holmes/plugins/toolsets/__init__.py (3)

40-48: Add encoding parameter for file operations.

The file is opened without explicitly specifying encoding, which can lead to issues on different platforms.

Apply this diff:

-    with open(toolsets_path) as file:
+    with open(toolsets_path, encoding='utf-8') as file:
🧰 Tools
🪛 Pylint (3.3.7)

[warning] 40-40: Using open without explicitly specifying an encoding

(W1514)


125-127: Line length and formatting improvement needed.

The line is too long and the error message formatting could be improved.

Apply this diff:

-        message = "Old toolset config format detected, please update to the new format: https://docs.robusta.dev/master/configuration/holmesgpt/custom_toolsets.html"
+        message = (
+            "Old toolset config format detected, please update to the new format: "
+            "https://docs.robusta.dev/master/configuration/holmesgpt/custom_toolsets.html"
+        )
🧰 Tools
🪛 Pylint (3.3.7)

[convention] 125-125: Line too long (165/100)

(C0301)


104-106: Simplify boolean return.

The condition can be simplified as suggested by static analysis.

Apply this diff:

-    if isinstance(toolsets, list):
-        return True
-    return False
+    return isinstance(toolsets, list)
🧰 Tools
🪛 Ruff (0.11.9)

104-106: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/core/toolset_manager.py (2)

245-245: Optimize dictionary key checking.

Using .keys() is unnecessary when checking for key existence.

-            if custom_toolset_from_cli in toolsets_status_by_name.keys():
+            if custom_toolset_from_cli.name in toolsets_status_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

245-245: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


380-380: Optimize dictionary key checking.

Using .keys() is unnecessary when checking for key existence.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

380-380: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/core/tools.py (1)

427-428: Optimize status comparison using 'in' operator.

The status comparison can be simplified for better readability.

-            if (
-                self.status == ToolsetStatusEnum.DISABLED
-                or self.status == ToolsetStatusEnum.FAILED
-            ):
+            if self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED):
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2566d02 and d316b6c.

📒 Files selected for processing (38)
  • holmes/config.py (11 hunks)
  • holmes/core/resource_instruction.py (1 hunks)
  • holmes/core/supabase_dal.py (1 hunks)
  • holmes/core/tool_calling_llm.py (1 hunks)
  • holmes/core/tools.py (9 hunks)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/main.py (20 hunks)
  • holmes/plugins/prompts/_fetch_logs.jinja2 (3 hunks)
  • holmes/plugins/prompts/_toolsets_instructions.jinja2 (2 hunks)
  • holmes/plugins/toolsets/__init__.py (2 hunks)
  • holmes/plugins/toolsets/consts.py (1 hunks)
  • holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py (3 hunks)
  • holmes/plugins/toolsets/grafana/base_grafana_toolset.py (1 hunks)
  • holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (1 hunks)
  • holmes/plugins/toolsets/kafka.py (1 hunks)
  • holmes/plugins/toolsets/opensearch/opensearch.py (1 hunks)
  • holmes/plugins/toolsets/prometheus/prometheus.py (1 hunks)
  • holmes/plugins/toolsets/utils.py (1 hunks)
  • holmes/utils/default_toolset_installation_guide.jinja2 (1 hunks)
  • holmes/utils/definitions.py (1 hunks)
  • holmes/utils/env.py (1 hunks)
  • holmes/utils/holmes_sync_toolsets.py (2 hunks)
  • holmes/utils/pydantic_utils.py (3 hunks)
  • tests/config_class/test_config_load_custom_toolsets.py (0 hunks)
  • tests/config_class/test_config_load_remote_mcp_server.py (2 hunks)
  • tests/core/test_toolset_manager.py (1 hunks)
  • tests/llm/utils/mock_toolset.py (4 hunks)
  • tests/plugins/prompt/test_generic_ask_conversation.py (1 hunks)
  • tests/plugins/toolsets/grafana/test_grafana_loki.py (2 hunks)
  • tests/plugins/toolsets/grafana/test_grafana_tempo.py (2 hunks)
  • tests/plugins/toolsets/test_internet.py (2 hunks)
  • tests/plugins/toolsets/test_load_config.py (1 hunks)
  • tests/plugins/toolsets/test_notion.py (2 hunks)
  • tests/plugins/toolsets/test_prometheus_integration.py (2 hunks)
  • tests/plugins/toolsets/test_tool_kafka.py (2 hunks)
  • tests/test_check_prerequisites.py (10 hunks)
  • tests/test_holmes_sync_toolsets.py (2 hunks)
  • tests/test_issue_investigator.py (1 hunks)
💤 Files with no reviewable changes (1)
  • tests/config_class/test_config_load_custom_toolsets.py
✅ Files skipped from review due to trivial changes (3)
  • tests/plugins/toolsets/test_prometheus_integration.py
  • tests/test_issue_investigator.py
  • holmes/utils/default_toolset_installation_guide.jinja2
🚧 Files skipped from review as they are similar to previous changes (7)
  • holmes/plugins/prompts/_fetch_logs.jinja2
  • holmes/plugins/toolsets/utils.py
  • holmes/utils/holmes_sync_toolsets.py
  • holmes/plugins/toolsets/grafana/base_grafana_toolset.py
  • tests/plugins/toolsets/test_notion.py
  • holmes/plugins/prompts/_toolsets_instructions.jinja2
  • tests/config_class/test_config_load_remote_mcp_server.py
🧰 Additional context used
🧬 Code Graph Analysis (10)
tests/plugins/prompt/test_generic_ask_conversation.py (1)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
holmes/core/supabase_dal.py (2)
holmes/core/resource_instruction.py (2)
  • ResourceInstructionDocument (6-12)
  • ResourceInstructions (15-17)
holmes/utils/definitions.py (1)
  • RobustaConfig (11-13)
holmes/core/tool_calling_llm.py (8)
tests/llm/utils/mock_utils.py (1)
  • Message (33-34)
holmes/core/investigation_structured_output.py (3)
  • is_response_an_incorrect_tool_call (229-264)
  • parse_markdown_into_sections_from_hash_sign (101-141)
  • process_response_into_sections (213-226)
holmes/core/issue.py (1)
  • Issue (13-54)
holmes/core/llm.py (1)
  • LLM (30-58)
holmes/core/resource_instruction.py (1)
  • ResourceInstructions (15-17)
holmes/core/runbooks.py (1)
  • RunbookManager (7-26)
holmes/core/tools.py (3)
  • StructuredToolResult (27-50)
  • ToolExecutor (470-515)
  • ToolResultStatus (21-24)
holmes/plugins/prompts/__init__.py (1)
  • load_and_render_prompt (26-43)
holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (6)
holmes/common/env_vars.py (1)
  • load_bool (5-7)
holmes/core/tools.py (4)
  • StructuredToolResult (27-50)
  • Tool (108-138)
  • ToolParameter (102-105)
  • ToolResultStatus (21-24)
holmes/plugins/toolsets/grafana/common.py (3)
  • GrafanaConfig (9-21)
  • build_headers (24-35)
  • get_base_url (51-55)
holmes/plugins/toolsets/grafana/tempo_api.py (2)
  • query_tempo_trace_by_id (80-124)
  • query_tempo_traces (54-77)
holmes/plugins/toolsets/grafana/trace_parser.py (1)
  • format_traces_list (177-195)
holmes/plugins/toolsets/utils.py (2)
  • get_param_or_raise (128-132)
  • process_timestamps_to_int (79-125)
holmes/plugins/toolsets/kafka.py (2)
holmes/core/tools.py (7)
  • CallablePrerequisite (288-289)
  • StructuredToolResult (27-50)
  • Tool (108-138)
  • ToolParameter (102-105)
  • ToolResultStatus (21-24)
  • Toolset (301-445)
  • ToolsetTag (90-93)
holmes/plugins/toolsets/utils.py (1)
  • get_param_or_raise (128-132)
holmes/plugins/toolsets/opensearch/opensearch.py (1)
holmes/core/tools.py (7)
  • CallablePrerequisite (288-289)
  • StructuredToolResult (27-50)
  • Tool (108-138)
  • ToolParameter (102-105)
  • ToolResultStatus (21-24)
  • Toolset (301-445)
  • ToolsetTag (90-93)
tests/plugins/toolsets/grafana/test_grafana_loki.py (3)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
holmes/plugins/toolsets/grafana/grafana_api.py (1)
  • get_health (19-34)
holmes/plugins/toolsets/grafana/toolset_grafana_loki.py (2)
  • GetLokiLogsForResource (130-205)
  • GrafanaLokiConfig (33-34)
tests/plugins/toolsets/test_internet.py (1)
holmes/core/tools.py (1)
  • ToolsetStatusEnum (84-87)
tests/plugins/toolsets/test_load_config.py (1)
holmes/plugins/toolsets/__init__.py (1)
  • load_toolsets_from_config (109-153)
tests/test_check_prerequisites.py (3)
holmes/core/tools.py (8)
  • CallablePrerequisite (288-289)
  • StaticPrerequisite (283-285)
  • Tool (108-138)
  • Toolset (301-445)
  • ToolsetCommandPrerequisite (292-294)
  • ToolsetEnvironmentPrerequisite (297-298)
  • ToolsetStatusEnum (84-87)
  • check_prerequisites (379-434)
tests/utils/toolsets.py (3)
  • callable_failure_no_message (12-13)
  • callable_failure_with_message (8-9)
  • callable_success (4-5)
tests/test_holmes_sync_toolsets.py (1)
  • SampleToolset (38-40)
🪛 Pylint (3.3.7)
holmes/plugins/toolsets/__init__.py

[error] 7-7: Unable to import 'pydantic'

(E0401)


[convention] 36-36: Missing function or method docstring

(C0116)


[warning] 40-40: Using open without explicitly specifying an encoding

(W1514)


[convention] 125-125: Line too long (165/100)

(C0301)


[convention] 100-100: Missing function or method docstring

(C0116)


[warning] 150-150: Catching too general exception Exception

(W0718)


[warning] 148-148: Use lazy % formatting in logging functions

(W1203)

holmes/core/toolset_manager.py

[convention] 70-70: Line too long (120/100)

(C0301)


[convention] 71-71: Line too long (118/100)

(C0301)


[convention] 87-87: Line too long (116/100)

(C0301)


[convention] 99-99: Line too long (121/100)

(C0301)


[convention] 108-108: Line too long (106/100)

(C0301)


[convention] 121-121: Line too long (107/100)

(C0301)


[convention] 162-162: Line too long (120/100)

(C0301)


[convention] 176-176: Line too long (115/100)

(C0301)


[convention] 237-237: Line too long (118/100)

(C0301)


[convention] 243-243: Line too long (106/100)

(C0301)


[convention] 247-247: Line too long (109/100)

(C0301)


[convention] 324-324: Line too long (115/100)

(C0301)


[convention] 350-350: Line too long (110/100)

(C0301)


[convention] 1-1: Missing module docstring

(C0114)


[error] 6-6: Unable to import 'benedict'

(E0401)


[error] 7-7: Unable to import 'pydantic'

(E0401)


[warning] 143-143: Unused argument 'dal'

(W0613)


[warning] 189-189: Using open without explicitly specifying an encoding

(W1514)


[warning] 199-199: Use lazy % formatting in logging functions

(W1203)


[warning] 214-214: Using open without explicitly specifying an encoding

(W1514)


[warning] 246-248: Raising too general exception: Exception

(W0719)


[warning] 322-325: Raising too general exception: Exception

(W0719)

holmes/main.py

[convention] 14-14: Import "import json" should be placed at the top of the module

(C0413)


[convention] 15-15: Import "import logging" should be placed at the top of the module

(C0413)


[convention] 16-16: Import "import socket" should be placed at the top of the module

(C0413)


[convention] 17-17: Import "import uuid" should be placed at the top of the module

(C0413)


[convention] 18-18: Import "import warnings" should be placed at the top of the module

(C0413)


[convention] 19-19: Import "from enum import Enum" should be placed at the top of the module

(C0413)


[convention] 20-20: Import "from pathlib import Path" should be placed at the top of the module

(C0413)


[convention] 21-21: Import "from typing import List, Optional" should be placed at the top of the module

(C0413)


[error] 23-23: Unable to import 'typer'

(E0401)


[convention] 23-23: Import "import typer" should be placed at the top of the module

(C0413)


[convention] 24-24: Import "from rich.console import Console" should be placed at the top of the module

(C0413)


[convention] 25-25: Import "from rich.logging import RichHandler" should be placed at the top of the module

(C0413)


[convention] 26-26: Import "from rich.markdown import Markdown" should be placed at the top of the module

(C0413)


[convention] 27-27: Import "from rich.rule import Rule" should be placed at the top of the module

(C0413)


[convention] 28-28: Import "from rich.table import Table" should be placed at the top of the module

(C0413)


[convention] 30-30: Import "from holmes import get_version" should be placed at the top of the module

(C0413)


[convention] 31-36: Import "from holmes.config import DEFAULT_CONFIG_LOCATION, Config, SourceFactory, SupportedTicketSources" should be placed at the top of the module

(C0413)


[convention] 37-37: Import "from holmes.core.resource_instruction import ResourceInstructionDocument" should be placed at the top of the module

(C0413)


[convention] 38-38: Import "from holmes.core.tool_calling_llm import LLMResult, ToolCallingLLM" should be placed at the top of the module

(C0413)


[convention] 39-39: Import "from holmes.core.tools import Toolset" should be placed at the top of the module

(C0413)


[convention] 40-40: Import "from holmes.plugins.destinations import DestinationType" should be placed at the top of the module

(C0413)


[convention] 41-41: Import "from holmes.plugins.interfaces import Issue" should be placed at the top of the module

(C0413)


[convention] 42-42: Import "from holmes.plugins.prompts import load_and_render_prompt" should be placed at the top of the module

(C0413)


[convention] 43-43: Import "from holmes.plugins.sources.opsgenie import OPSGENIE_TEAM_INTEGRATION_KEY_HELP" should be placed at the top of the module

(C0413)


[convention] 44-44: Import "from holmes.utils.file_utils import write_json_file" should be placed at the top of the module

(C0413)


[convention] 14-14: standard import "json" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 15-15: standard import "logging" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 16-16: standard import "socket" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 17-17: standard import "uuid" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 18-18: standard import "warnings" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 19-19: standard import "enum.Enum" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 20-20: standard import "pathlib.Path" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 21-21: standard import "typing.List" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 23-23: third party import "typer" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 24-24: third party import "rich.console.Console" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 25-25: third party import "rich.logging.RichHandler" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 26-26: third party import "rich.markdown.Markdown" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 27-27: third party import "rich.rule.Rule" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 28-28: third party import "rich.table.Table" should be placed before first party import "holmes.utils.cert_utils.add_custom_certificate"

(C0411)


[convention] 30-30: Imports from package holmes are not grouped

(C0412)


[convention] 144-144: Line too long (151/100)

(C0301)


[convention] 1158-1158: Line too long (103/100)

(C0301)


[convention] 1144-1144: Missing function or method docstring

(C0116)

holmes/config.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 10-10: Unable to import 'pydantic'

(E0401)


[convention] 100-100: Line too long (113/100)

(C0301)


[convention] 101-101: Line too long (101/100)

(C0301)


[convention] 102-102: Line too long (115/100)

(C0301)


[convention] 124-124: Missing function or method docstring

(C0116)


[warning] 164-164: Use lazy % formatting in logging functions

(W1203)


[error] 169-169: Possibly using variable 'config_from_file' before assignment

(E0606)


[warning] 172-172: Use lazy % formatting in logging functions

(W1203)


[convention] 228-228: Line too long (107/100)

(C0301)


[convention] 233-233: Line too long (107/100)

(C0301)

holmes/core/resource_instruction.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 3-3: Unable to import 'pydantic'

(E0401)


[refactor] 6-6: Too few public methods (0/2)

(R0903)


[convention] 15-15: Missing class docstring

(C0115)


[refactor] 15-15: Too few public methods (0/2)

(R0903)

holmes/core/supabase_dal.py

[error] 12-12: Unable to import 'cachetools'

(E0401)


[error] 13-13: Unable to import 'postgrest._sync.request_builder'

(E0401)


[error] 14-14: Unable to import 'postgrest.exceptions'

(E0401)


[error] 15-15: Unable to import 'postgrest.types'

(E0401)


[error] 16-16: Unable to import 'pydantic'

(E0401)


[error] 17-17: Unable to import 'supabase'

(E0401)


[error] 18-18: Unable to import 'supabase.lib.client_options'

(E0401)

holmes/core/tool_calling_llm.py

[error] 8-8: Unable to import 'sentry_sdk'

(E0401)


[error] 9-9: Unable to import 'litellm.types.utils'

(E0401)


[error] 10-10: Unable to import 'openai'

(E0401)


[error] 11-13: Unable to import 'openai.types.chat.chat_completion_message_tool_call'

(E0401)


[error] 14-14: Unable to import 'pydantic'

(E0401)


[error] 15-15: Unable to import 'pydantic_core'

(E0401)

holmes/core/tools.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 13-13: Unable to import 'sentry_sdk'

(E0401)


[error] 15-15: Unable to import 'pydantic'

(E0401)


[convention] 305-305: Line too long (107/100)

(C0301)


[convention] 399-399: Line too long (101/100)

(C0301)


[warning] 422-422: Catching too general exception Exception

(W0718)


[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)


[warning] 430-430: Use lazy % formatting in logging functions

(W1203)


[convention] 523-523: Line too long (104/100)

(C0301)

holmes/plugins/toolsets/consts.py

[convention] 1-1: Missing module docstring

(C0114)

holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py

[error] 3-11: Unable to import 'holmes.core.tools'

(E0401)


[error] 12-15: Unable to import 'holmes.plugins.toolsets.consts'

(E0401)


[error] 29-29: Unable to import 'holmes.plugins.toolsets.utils'

(E0401)

holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py

[error] 6-6: Unable to import 'pydantic'

(E0401)

holmes/plugins/toolsets/kafka.py

[error] 5-19: Unable to import 'confluent_kafka.admin'

(E0401)


[error] 20-20: Unable to import 'confluent_kafka.admin'

(E0401)


[error] 21-21: Unable to import 'pydantic'

(E0401)

holmes/plugins/toolsets/opensearch/opensearch.py

[error] 4-4: Unable to import 'opensearchpy'

(E0401)


[error] 5-5: Unable to import 'pydantic'

(E0401)

holmes/plugins/toolsets/prometheus/prometheus.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 12-12: Unable to import 'pydantic'

(E0401)


[error] 15-23: Unable to import 'holmes.core.tools'

(E0401)


[error] 24-24: Unable to import 'holmes.plugins.toolsets.consts'

(E0401)


[error] 25-25: Unable to import 'holmes.plugins.toolsets.service_discovery'

(E0401)

holmes/utils/definitions.py

[error] 4-4: Unable to import 'pydantic'

(E0401)

holmes/utils/env.py

[convention] 1-1: Missing module docstring

(C0114)


[error] 6-6: Unable to import 'pydantic'

(E0401)


[convention] 9-9: Missing function or method docstring

(C0116)


[convention] 22-22: Missing function or method docstring

(C0116)

holmes/utils/pydantic_utils.py

[error] 5-5: Unable to import 'typer'

(E0401)


[error] 6-6: Unable to import 'benedict'

(E0401)


[error] 7-7: Unable to import 'pydantic'

(E0401)


[convention] 51-51: Line too long (110/100)

(C0301)

tests/core/test_toolset_manager.py

[convention] 1-1: Missing module docstring

(C0114)


[convention] 14-14: Missing function or method docstring

(C0116)


[convention] 30-30: Missing function or method docstring

(C0116)


[convention] 34-34: Missing function or method docstring

(C0116)


[warning] 34-34: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 40-40: Missing function or method docstring

(C0116)


[warning] 40-40: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 48-48: Missing function or method docstring

(C0116)


[warning] 49-49: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 63-63: Access to a protected member _list_all_toolsets of a client class

(W0212)


[convention] 70-70: Missing function or method docstring

(C0116)


[warning] 70-70: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 94-94: Using open without explicitly specifying an encoding

(W1514)


[convention] 100-100: Missing function or method docstring

(C0116)


[warning] 100-100: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 122-122: Using open without explicitly specifying an encoding

(W1514)


[convention] 131-131: Missing function or method docstring

(C0116)


[warning] 131-131: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 141-141: Missing function or method docstring

(C0116)


[warning] 141-141: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 151-151: Missing function or method docstring

(C0116)


[warning] 151-151: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 167-167: Missing function or method docstring

(C0116)


[warning] 168-168: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 168-168: Unused argument 'mock_benedict'

(W0613)


[warning] 168-168: Unused argument 'mock_load_toolsets_from_config'

(W0613)


[convention] 175-175: Missing function or method docstring

(C0116)


[warning] 175-175: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 181-181: Missing function or method docstring

(C0116)


[convention] 195-195: Missing function or method docstring

(C0116)


[convention] 203-203: Missing function or method docstring

(C0116)


[warning] 203-203: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 224-224: Missing function or method docstring

(C0116)


[warning] 224-224: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 247-247: Missing function or method docstring

(C0116)


[warning] 247-247: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 259-259: Missing function or method docstring

(C0116)


[warning] 259-259: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 269-269: Missing function or method docstring

(C0116)


[warning] 269-269: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)

tests/llm/utils/mock_toolset.py

[error] 7-7: Unable to import 'pydantic'

(E0401)


[warning] 145-145: Unused argument 'run_live'

(W0613)

tests/plugins/toolsets/grafana/test_grafana_loki.py

[error] 6-6: Unable to import 'holmes.core.tools'

(E0401)


[error] 7-7: Unable to import 'holmes.plugins.toolsets.grafana.grafana_api'

(E0401)

tests/plugins/toolsets/grafana/test_grafana_tempo.py

[error] 6-6: Unable to import 'holmes.core.tools'

(E0401)


[error] 7-7: Unable to import 'holmes.plugins.toolsets.grafana.grafana_api'

(E0401)

tests/plugins/toolsets/test_internet.py

[error] 7-7: Unable to import 'pydantic'

(E0401)

tests/plugins/toolsets/test_load_config.py

[convention] 1-1: Missing module docstring

(C0114)


[convention] 9-9: Missing function or method docstring

(C0116)


[convention] 28-28: Missing function or method docstring

(C0116)


[convention] 57-57: Constant name "toolsets_config_str" doesn't conform to UPPER_CASE naming style

(C0103)


[convention] 71-71: Missing function or method docstring

(C0116)

tests/plugins/toolsets/test_tool_kafka.py

[error] 7-7: Unable to import 'confluent_kafka.admin'

(E0401)

tests/test_check_prerequisites.py

[convention] 53-53: Missing function or method docstring

(C0116)


[convention] 61-61: Missing function or method docstring

(C0116)


[convention] 114-114: Missing function or method docstring

(C0116)


[convention] 123-123: Missing function or method docstring

(C0116)


[convention] 131-131: Missing function or method docstring

(C0116)


[convention] 139-139: Missing function or method docstring

(C0116)


[convention] 147-147: Missing function or method docstring

(C0116)

tests/test_holmes_sync_toolsets.py

[convention] 1-1: Missing module docstring

(C0114)

🪛 Ruff (0.11.9)
holmes/plugins/toolsets/__init__.py

104-106: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/core/toolset_manager.py

78-85: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)


245-245: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


380-380: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 Gitleaks (8.26.0)
tests/plugins/toolsets/test_load_config.py

66-66: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (43)
holmes/utils/definitions.py (1)

4-4: LGTM: Import reordering improvement.

The import reorganization aligns with the PR's objective of using isort for better code organization. The pylint error about being unable to import 'pydantic' appears to be a false positive since pydantic is a standard dependency and the import syntax is correct.

🧰 Tools
🪛 Pylint (3.3.7)

[error] 4-4: Unable to import 'pydantic'

(E0401)

tests/llm/utils/mock_toolset.py (2)

4-11: LGTM: Import updates align with toolset management refactoring.

The import changes properly reflect the refactoring where toolset loading functionality has been moved to holmes.plugins.toolsets and status handling has been updated in the core tools module.

🧰 Tools
🪛 Pylint (3.3.7)

[error] 7-7: Unable to import 'pydantic'

(E0401)


227-227: LGTM: Updated status access pattern.

The changes from get_status() method calls to direct status attribute access align with the broader refactoring that moved from private methods/attributes to public attributes for toolset status management.

Also applies to: 236-236

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

3-3: LGTM: Import reordering improvement.

The import reorganization follows proper grouping conventions and aligns with the isort-based cleanup mentioned in the PR objectives.

Also applies to: 7-7


116-116: LGTM: Updated to public status attribute.

The change from private _status to public status attribute is consistent with the broader refactoring that standardized toolset status access patterns across the codebase.

tests/plugins/prompt/test_generic_ask_conversation.py (1)

11-11: LGTM: Consistent status attribute migration.

The change from private _status to public status attribute maintains consistency with the broader refactoring that standardized toolset status access patterns throughout the codebase.

tests/plugins/toolsets/grafana/test_grafana_loki.py (2)

3-3: LGTM: Import reorganization is well-structured.

The addition of imports for ToolsetStatusEnum, get_health, and GrafanaLokiConfig properly supports the test functionality and follows good import organization practices.

Also applies to: 6-7, 10-10


40-41: LGTM: Consistent with toolset refactor pattern.

The change from method calls (get_error(), get_status()) to direct attribute access (error, status) is consistent with the broader refactor where toolset status and error are now public attributes instead of getter methods.

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

4-6: LGTM: Import reorganization improves code structure.

The reordering of imports groups related modules logically and follows good organization practices.

Also applies to: 8-8, 14-16

holmes/plugins/toolsets/opensearch/opensearch.py (1)

4-4: LGTM: Import reorganization and constant centralization.

The import reorganization groups related imports logically, and moving TOOLSET_CONFIG_MISSING_ERROR from utils to consts module supports better centralized constant management across the codebase.

Also applies to: 6-6, 9-9, 12-12, 16-16

🧰 Tools
🪛 Pylint (3.3.7)

[error] 4-4: Unable to import 'opensearchpy'

(E0401)

holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py (2)

3-3: LGTM: Import reorganization follows good practices.

The import reordering groups related imports logically and consolidates imports from the same module for better readability.

Also applies to: 5-7, 10-10, 13-13, 21-24


30-30: LGTM: Local constant definition reduces dependencies.

Defining ONE_HOUR_IN_SECONDS = 3600 locally is a good approach that reduces external dependencies for a simple constant value. The value is correct and clearly represents one hour in seconds.

tests/test_holmes_sync_toolsets.py (2)

1-2: LGTM: Import reorganization improves code organization.

The import statements have been properly reorganized with standard library imports (os, subprocess) at the top, followed by project imports grouped logically. This enhances readability and follows Python import conventions.

Also applies to: 5-5, 8-8, 10-11, 19-20

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


352-352: LGTM: Updated to use direct attribute access.

The changes correctly replace method calls (get_status(), get_error()) with direct attribute access (.status, .error). This aligns with the broader refactor of the Toolset class API and improves simplicity by using public attributes instead of getter methods.

Also applies to: 355-355, 357-360

tests/plugins/toolsets/grafana/test_grafana_tempo.py (2)

3-3: LGTM: Import reorganization improves clarity.

The import statements have been reorganized to group related imports together, with get_health moved alongside other Grafana-related imports and GetTempoTraces repositioned within the toolset_grafana_tempo imports. This enhances code readability and logical grouping.

Also applies to: 5-5, 7-7, 9-9


83-84: LGTM: Updated to use direct attribute access.

The assertions have been correctly updated to access the status and error attributes directly instead of calling get_status() and get_error() methods. This change is consistent with the refactored Toolset class API that uses public attributes.

holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py (2)

2-2: LGTM: Centralizing constants improves maintainability.

The constants STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION and TOOLSET_CONFIG_MISSING_ERROR have been moved from holmes.plugins.toolsets.utils to the new dedicated holmes.plugins.toolsets.consts module. This centralization reduces duplication, improves maintainability, and provides a single source of truth for toolset-related constants.

Also applies to: 8-8, 12-15, 29-29


114-116: LGTM: Improved code formatting.

The return statement formatting has been improved by using a single-line conditional expression within parentheses for the status field. This is more concise and readable while maintaining the same functionality.

holmes/utils/pydantic_utils.py (3)

2-3: LGTM: Improved import organization and Path support

Good addition of Path import and reorganization of typing imports for better structure.


40-40: Excellent type safety improvement

Changing the parameter type from str to Path improves type safety and aligns with modern Python best practices for file path handling. This change is consistent with the broader refactoring mentioned in the AI summary.


51-51: Minor formatting improvement addressed

The extra space removal in the error message improves consistency.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 51-51: Line too long (110/100)

(C0301)

tests/test_check_prerequisites.py (2)

2-4: LGTM: Improved import organization

Good reorganization of imports by grouping related imports together for better readability.

Also applies to: 7-8, 13-13, 17-18


49-50: Excellent refactor: Direct attribute access

The systematic replacement of getter methods (get_status(), get_error()) with direct attribute access (status, error) aligns perfectly with the broader refactor described in the AI summary. This change:

  • Simplifies the API by removing unnecessary getter methods
  • Maintains consistency with the new Toolset class design
  • Preserves all existing test logic and coverage

The relevant code snippets from holmes/core/tools.py confirm that status and error are now public attributes on the Toolset class.

Also applies to: 57-58, 66-67, 78-79, 98-99, 110-111, 118-119, 127-128, 135-136, 143-144, 151-152, 167-168, 187-188, 211-212, 264-265, 286-287, 291-291

holmes/plugins/toolsets/kafka.py (2)

2-3: LGTM: Improved import organization

Excellent reorganization of imports by:

  • Grouping all typing imports together
  • Consolidating confluent_kafka imports logically
  • Organizing holmes.core.tools imports for better readability

This improves code maintainability without affecting functionality.

Also applies to: 10-10, 20-21, 23-31


32-33: Good modularization: Constants moved to dedicated module

Moving TOOLSET_CONFIG_MISSING_ERROR from utils to the new consts module improves code organization and aligns with the broader refactoring effort described in the PR objectives. This promotes better separation of concerns.

holmes/plugins/toolsets/prometheus/prometheus.py (2)

1-1: LGTM: Well-organized import structure

Excellent reorganization of imports by:

  • Grouping standard library imports (json, os, re, etc.) together
  • Consolidating third-party imports logically
  • Organizing holmes.core.tools imports for better readability

This significantly improves code organization and maintainability.

Also applies to: 3-3, 5-5, 8-9, 13-14, 17-17, 20-20

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


24-24: Good modularization: Constants moved to dedicated module

Moving STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION from utils to the new consts module while keeping utility functions in utils demonstrates excellent separation of concerns. This aligns with the broader refactoring effort described in the PR objectives.

Also applies to: 29-29

🧰 Tools
🪛 Pylint (3.3.7)

[error] 24-24: Unable to import 'holmes.plugins.toolsets.consts'

(E0401)

holmes/core/tool_calling_llm.py (1)

5-34: LGTM! Excellent import reorganization

The import statements have been well-organized by grouping related imports together and extracting ResourceInstructions and ResourceInstructionDocument to a dedicated module. This improves code modularity and maintainability.

🧰 Tools
🪛 Pylint (3.3.7)

[error] 8-8: Unable to import 'sentry_sdk'

(E0401)


[error] 9-9: Unable to import 'litellm.types.utils'

(E0401)


[error] 10-10: Unable to import 'openai'

(E0401)


[error] 11-13: Unable to import 'openai.types.chat.chat_completion_message_tool_call'

(E0401)


[error] 14-14: Unable to import 'pydantic'

(E0401)


[error] 15-15: Unable to import 'pydantic_core'

(E0401)

holmes/core/supabase_dal.py (1)

7-33: LGTM! Import reorganization aligns with module extraction

The import reorganization correctly reflects the extraction of ResourceInstructionDocument and ResourceInstructions to the dedicated holmes.core.resource_instruction module. The grouping of imports is also improved.

🧰 Tools
🪛 Pylint (3.3.7)

[error] 12-12: Unable to import 'cachetools'

(E0401)


[error] 13-13: Unable to import 'postgrest._sync.request_builder'

(E0401)


[error] 14-14: Unable to import 'postgrest.exceptions'

(E0401)


[error] 15-15: Unable to import 'postgrest.types'

(E0401)


[error] 16-16: Unable to import 'pydantic'

(E0401)


[error] 17-17: Unable to import 'supabase'

(E0401)


[error] 18-18: Unable to import 'supabase.lib.client_options'

(E0401)

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

71-94: LGTM! Comprehensive test coverage

The test properly verifies environment variable substitution in toolset configuration. The setup and teardown of environment variables is handled correctly to avoid test pollution.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 71-71: Missing function or method docstring

(C0116)

holmes/utils/env.py (3)

32-33: LGTM! Assignment issue has been resolved.

The recursive call to replace_env_vars_values(value) is correctly missing assignment since dictionaries are passed by reference and modified in-place. This is the correct implementation.


17-17: Good improvement: Using specific exception type.

The change from generic Exception to ValueError is a good improvement over the previous implementation, making the error handling more specific and meaningful.


36-45: Well implemented list comprehension with proper variable naming.

The list comprehension correctly uses item as the variable name instead of the problematic iter that would shadow the built-in function. The logic properly handles nested dictionaries and environment variable replacement in lists.

holmes/main.py (3)

1144-1161: Well-implemented toolset status display function.

The pretty_print_toolset_status function is well-structured and provides a clear table format for displaying toolset information. The use of model_dump_json with specific field inclusion is appropriate for this purpose.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1158-1158: Line too long (103/100)

(C0301)


[convention] 1144-1144: Missing function or method docstring

(C0116)


1115-1142: New toolset management commands are well designed.

The new list and refresh commands provide good functionality for managing toolsets. The commands properly initialize logging, load configuration, and handle the toolset manager integration. The separation of concerns between listing and refreshing is appropriate.


141-145: Configuration parameter type change is appropriate.

The change from typer.Option(None, ...) to typer.Option(DEFAULT_CONFIG_LOCATION, ...) for the config file parameter makes the default behavior more explicit and aligns with the centralized configuration management approach.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 144-144: Line too long (151/100)

(C0301)

tests/core/test_toolset_manager.py (1)

1-278: Excellent comprehensive test coverage for ToolsetManager.

This test file demonstrates excellent testing practices:

  1. Good test organization: Clear fixtures, parameterized tests, and logical grouping
  2. Proper mocking: Effective use of @patch and MagicMock to isolate dependencies
  3. Comprehensive scenarios: Tests cover success paths, error conditions, edge cases, and different input types
  4. Realistic test data: Uses temporary files and realistic configuration structures
  5. Clear assertions: Tests verify both positive and negative outcomes appropriately

The test coverage includes:

  • Static method validation (get_toolset_definition_enabled)
  • Internal methods (_list_all_toolsets)
  • Caching functionality (refresh_toolset_status, load_toolset_with_status)
  • Filtering methods (list_enabled_console_toolsets, list_enabled_server_toolsets)
  • Custom toolset loading with various file conditions
  • Merging and adding toolset logic

This provides strong confidence in the ToolsetManager implementation.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


[convention] 14-14: Missing function or method docstring

(C0116)


[convention] 30-30: Missing function or method docstring

(C0116)


[convention] 34-34: Missing function or method docstring

(C0116)


[warning] 34-34: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 40-40: Missing function or method docstring

(C0116)


[warning] 40-40: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 48-48: Missing function or method docstring

(C0116)


[warning] 49-49: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 63-63: Access to a protected member _list_all_toolsets of a client class

(W0212)


[convention] 70-70: Missing function or method docstring

(C0116)


[warning] 70-70: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 94-94: Using open without explicitly specifying an encoding

(W1514)


[convention] 100-100: Missing function or method docstring

(C0116)


[warning] 100-100: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 122-122: Using open without explicitly specifying an encoding

(W1514)


[convention] 131-131: Missing function or method docstring

(C0116)


[warning] 131-131: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 141-141: Missing function or method docstring

(C0116)


[warning] 141-141: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 151-151: Missing function or method docstring

(C0116)


[warning] 151-151: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 167-167: Missing function or method docstring

(C0116)


[warning] 168-168: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[warning] 168-168: Unused argument 'mock_benedict'

(W0613)


[warning] 168-168: Unused argument 'mock_load_toolsets_from_config'

(W0613)


[convention] 175-175: Missing function or method docstring

(C0116)


[warning] 175-175: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 181-181: Missing function or method docstring

(C0116)


[convention] 195-195: Missing function or method docstring

(C0116)


[convention] 203-203: Missing function or method docstring

(C0116)


[warning] 203-203: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 224-224: Missing function or method docstring

(C0116)


[warning] 224-224: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 247-247: Missing function or method docstring

(C0116)


[warning] 247-247: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 259-259: Missing function or method docstring

(C0116)


[warning] 259-259: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)


[convention] 269-269: Missing function or method docstring

(C0116)


[warning] 269-269: Redefining name 'toolset_manager' from outer scope (line 14)

(W0621)

holmes/core/tools.py (3)

97-99: LGTM! New ToolsetType enum values are well-defined.

The addition of BUILTIN and CUSTOMIZED enum values provides clear categorization for different toolset sources.


334-337: LGTM! Public attributes improve accessibility for ToolsetManager.

The change from private attributes to public fields enables the ToolsetManager to directly access and cache toolset status information, which is a good design improvement.


518-549: LGTM! ToolsetYamlFromConfig provides flexible configuration override.

This class design allows partial overrides of built-in toolsets without requiring all fields to be specified, which improves usability and configuration flexibility.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 523-523: Line too long (104/100)

(C0301)

holmes/config.py (3)

123-131: LGTM! Lazy initialization of ToolsetManager is well-implemented.

The property pattern provides clean access to the ToolsetManager while avoiding unnecessary initialization.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 124-124: Missing function or method docstring

(C0116)


226-237: LGTM! Simplified tool executor creation using ToolsetManager.

The delegation to toolset_manager.list_enabled_console_toolsets() removes complex toolset filtering logic and centralizes it appropriately.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 228-228: Line too long (107/100)

(C0301)


[convention] 233-233: Line too long (107/100)

(C0301)


1-12:

❌ Incorrect review comment

Address the critical test failure with removed parse_toolsets_file.

Based on past review comments, tests are still trying to import parse_toolsets_file which was removed from this module.


🏁 Script executed:

#!/bin/bash
# Search for remaining references to parse_toolsets_file that need updating
rg "parse_toolsets_file" --type py -A 2 -B 2

Length of output: 44


No references to parse_toolsets_file detected – review comment no longer applies.
A ripgrep search across all .py files returned zero occurrences of parse_toolsets_file, so there are no imports or calls to update.

Likely an incorrect or invalid review comment.

🧰 Tools
🪛 Pylint (3.3.7)

[convention] 1-1: Missing module docstring

(C0114)


[error] 10-10: Unable to import 'pydantic'

(E0401)

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao
…anage-toolset
@moshemorad
Copy link
Contributor

I have resolved code conflicts multiple times since my change touch the config.py. I'll focus on the PR comments if there are incoming ones, and until all looks good, I'll resolve the conflicts. cc @moshemorad

The fix is due to the missed error when we changed private attr in the Toolset Model. So it might raise more issue. Let me know if you bump into anything that need my help.

@mainred
Copy link
Collaborator Author

mainred commented Jun 5, 2025

Let me know if you bump into anything that need my help.

This PR is ready for review. Please take another look. Thanks.

@mainred
Copy link
Collaborator Author

mainred commented Jun 5, 2025

Please ignore the conflicts lets focus on the PR change first. I will definitely resolve the conflicts after addressing all comments to reduce the chance of resolving conflicts.

mainred added 2 commits June 6, 2025 01:19

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao
…and-manage-toolset

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (4)
holmes/core/toolset_manager.py (3)

153-166: 🛠️ Refactor suggestion

Add error handling for file operations.

The file write operation could fail due to permission issues or disk space. Consider adding error handling:

-        if self.toolset_status_location and not os.path.exists(
-            os.path.dirname(self.toolset_status_location)
-        ):
-            os.makedirs(os.path.dirname(self.toolset_status_location))
-        with open(self.toolset_status_location, "w") as f:
-            toolset_status = [
-                json.loads(
-                    toolset.model_dump_json(
-                        include={"name", "status", "enabled", "type", "path", "error"}
-                    )
-                )
-                for toolset in all_toolsets
-            ]
-            json.dump(toolset_status, f, indent=2)
+        from holmes.utils.file_utils import write_json_file
+        
+        toolset_status = [
+            json.loads(
+                toolset.model_dump_json(
+                    include={"name", "status", "enabled", "type", "path", "error"}
+                )
+            )
+            for toolset in all_toolsets
+        ]
+        try:
+            write_json_file(self.toolset_status_location, toolset_status)
+        except Exception as e:
+            logging.error(f"Failed to write toolset status cache: {e}")
+            raise

182-184: ⚠️ Potential issue

Add error handling for cache file loading.

The JSON loading could fail if the cache file is corrupted or has permission issues.

         logging.info("loading toolset status from cache")
         cached_toolsets: List[dict[str, Any]] = []
-        with open(self.toolset_status_location, "r") as f:
-            cached_toolsets = json.load(f)
+        try:
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)
+        except (json.JSONDecodeError, IOError) as e:
+            logging.error(f"Failed to load toolset cache: {e}. Refreshing status.")
+            self.refresh_toolset_status(dal)
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)

214-217: ⚠️ Potential issue

Fix incorrect comparison between Toolset object and string keys.

The code is comparing a Toolset object with dictionary string keys, which will always evaluate to False.

         for custom_toolset_from_cli in custom_toolsets_from_cli:
-            if custom_toolset_from_cli in toolsets_status_by_name.keys():
+            if custom_toolset_from_cli.name in toolsets_status_by_name:
                 raise ValueError(
                     f"Toolset {custom_toolset_from_cli.name} from cli is already defined in existing toolset"
                 )
🧰 Tools
🪛 Ruff (0.11.9)

214-214: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/config.py (1)

163-171: ⚠️ Potential issue

Fix uninitialized variable usage.

The variable config_from_file may not be initialized if the config file is None or doesn't exist, causing a NameError.

         """
         Load configuration from file and merge with CLI options.
 
         Args:
             config_file: Path to configuration file
             **kwargs: CLI options to override config file values
 
         Returns:
             Config instance with merged settings
         """
-        config_from_file: Optional[Config] = None
+        config_from_file = None
         if config_file is not None and config_file.exists():
             logging.debug(f"Loading config from {config_file}")
             config_from_file = load_model_from_file(cls, config_file)
🧹 Nitpick comments (3)
holmes/core/toolset_manager.py (1)

360-360: Simplify dictionary membership check.

Remove unnecessary .keys() call for cleaner code.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

360-360: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/core/tools.py (1)

427-428: Simplify status comparison using the 'in' operator.

The separate comparisons can be combined for better readability.

-            if (
-                self.status == ToolsetStatusEnum.DISABLED
-                or self.status == ToolsetStatusEnum.FAILED
-            ):
+            if self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED):
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)

holmes/config.py (1)

165-165: Use lazy formatting in logging statements.

Replace f-strings with lazy formatting for better performance.

-            logging.debug(f"Loading config from {config_file}")
+            logging.debug("Loading config from %s", config_file)
-        logging.debug(f"Overriding config from cli options {cli_options}")
+        logging.debug("Overriding config from cli options %s", cli_options)

Also applies to: 173-173

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6b8ad7 and 02eb6ca.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • holmes/config.py (10 hunks)
  • holmes/core/tools.py (9 hunks)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/main.py (20 hunks)
  • pyproject.toml (1 hunks)
  • tests/llm/utils/mock_toolset.py (4 hunks)
  • tests/plugins/toolsets/test_prometheus_integration.py (2 hunks)
  • tests/test_check_prerequisites.py (10 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/plugins/toolsets/test_prometheus_integration.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • pyproject.toml
  • tests/test_check_prerequisites.py
  • holmes/main.py
  • tests/llm/utils/mock_toolset.py
🧰 Additional context used
🪛 Ruff (0.11.9)
holmes/core/toolset_manager.py

214-214: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


360-360: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 Pylint (3.3.7)
holmes/core/tools.py

[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.12)

mainred added 2 commits June 6, 2025 03:24

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao

Verified

This commit was signed with the committer’s verified signature.
mainred Qingchuan Hao
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (4)
holmes/core/toolset_manager.py (4)

56-59: Fix incorrect docstring for server_tool_tags property.

The docstring incorrectly refers to "UI tool tags" when it should mention "server tool tags".


153-166: Consider using the existing write_json_file utility or add error handling.

The file writing operation lacks error handling. Consider using the existing write_json_file utility from holmes.utils.file_utils which already has proper error handling.


183-184: Add error handling for cache file loading.

The JSON loading could fail if the cache file is corrupted or has permission issues.


276-279: Improve exception chaining for better error traceability.

Use proper exception chaining to preserve the original error context.

🧹 Nitpick comments (2)
holmes/core/toolset_manager.py (1)

360-360: Remove unnecessary .keys() call.

When checking if a key exists in a dictionary, you don't need to call .keys().

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

360-360: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/config.py (1)

166-166: Use lazy % formatting in logging functions.

Replace f-string logging with lazy formatting to avoid unnecessary string interpolation when the log level is higher than debug.

-            logging.debug(f"Loading config from {config_file}")
+            logging.debug("Loading config from %s", config_file)
-        logging.debug(f"Overriding config from cli options {cli_options}")
+        logging.debug("Overriding config from cli options %s", cli_options)

Also applies to: 174-174

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02eb6ca and 641e156.

📒 Files selected for processing (3)
  • holmes/config.py (10 hunks)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/main.py (20 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • holmes/main.py
🧰 Additional context used
🪛 Ruff (0.11.9)
holmes/core/toolset_manager.py

360-360: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.12)
🔇 Additional comments (1)
holmes/config.py (1)

228-239: Well-structured refactoring to use ToolsetManager.

The method has been nicely simplified by delegating toolset management to the dedicated ToolsetManager class. The documentation clearly explains the loading order.

@mainred
Copy link
Collaborator Author

mainred commented Jun 6, 2025

@moshemorad could you please help find the root cause of llm evaluation error. The following error message raised from the pipeline seems to be common issue? Thanks.
`httpx.LocalProtocolError: Illegal header value b'Bearer '

@moshemorad
Copy link
Contributor

@moshemorad could you please help find the root cause of llm evaluation error. The following error message raised from the pipeline seems to be common issue? Thanks. `httpx.LocalProtocolError: Illegal header value b'Bearer '

Looks like for some reason it isn't adding the OpenAI key, trying to understand why.

@moshemorad
Copy link
Contributor

@mainred Looks like the issue is due to forked repos don't use the original repo secrets.
https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-secrets-in-a-workflow

@arikalon1 @nherment any idea what we want to do in those cases?

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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/plugins/toolsets/__init__.py (2)

41-41: Specify encoding when opening files

Always specify the encoding when opening text files to ensure consistent behavior across different platforms.

-    with open(toolsets_path) as file:
+    with open(toolsets_path, encoding="utf-8") as file:

131-149: ⚠️ Potential issue

Fix critical validation logic issues

The validation logic has several critical issues that make it broken:

  1. Line 134: Comparing string value with enum using is operator will always be False
  2. Line 134: The MCP check executes outside the strict_check block, causing validation inconsistency
  3. Line 138: The strict_check block will always execute after the MCP check
  4. Type annotations are inconsistent and cause type checking errors

Apply this diff to fix the validation logic:

         try:
             toolset_type = config.get("type", ToolsetType.BUILTIN.value)
-            # MCP server is not a built-in toolset, so we need to set the type explicitly
-            if toolset_type is ToolsetType.MCP:
-                validated_toolset: RemoteMCPToolset = RemoteMCPToolset(
-                    **config, name=name
-                )
             if strict_check:
+                if toolset_type == ToolsetType.MCP.value:
+                    validated_toolset = RemoteMCPToolset(**config, name=name)
+                else:
-                validated_toolset: YAMLToolset = YAMLToolset(**config, name=name)  # type: ignore
+                    validated_toolset = YAMLToolset(**config, name=name)
             else:
-                validated_toolset: ToolsetYamlFromConfig = ToolsetYamlFromConfig(  # type: ignore
-                    **config, name=name
-                )
+                validated_toolset = ToolsetYamlFromConfig(**config, name=name)
holmes/main.py (1)

1117-1144: Fix typo in command help text

There's a typo in the help text that was flagged in previous reviews.

     """
-    List build-in and custom toolsets status of CLI
+    List built-in and custom toolsets status of CLI
     """

Also apply the same fix to the refresh command:

     """
-    Refresh build-in and custom toolsets status of CLI
+    Refresh built-in and custom toolsets status of CLI
     """
🧹 Nitpick comments (1)
holmes/plugins/toolsets/__init__.py (1)

105-107: Simplify boolean return statement

The function can return the condition directly instead of using an if-else block.

-def is_old_toolset_config(
-    toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
-) -> bool:
-    # old config is a list of toolsets
-    if isinstance(toolsets, list):
-        return True
-    return False
+def is_old_toolset_config(
+    toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
+) -> bool:
+    # old config is a list of toolsets
+    return isinstance(toolsets, list)
🧰 Tools
🪛 Ruff (0.11.9)

105-107: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 641e156 and 17de23b.

📒 Files selected for processing (3)
  • holmes/main.py (20 hunks)
  • holmes/plugins/toolsets/__init__.py (2 hunks)
  • holmes/utils/env.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • holmes/utils/env.py
🧰 Additional context used
🪛 Ruff (0.11.9)
holmes/plugins/toolsets/__init__.py

105-107: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.12)
🔇 Additional comments (3)
holmes/main.py (3)

6-6: LGTM: Appropriate type ignore for external library

The # type: ignore comment is appropriate for the tabulate external library import.


64-70: LGTM: Well-structured CLI group addition

The new toolset management CLI group is properly structured and integrated with the main app.


1146-1166: LGTM: Well-implemented table formatting function

The pretty_print_toolset_status function correctly uses OrderedDict to maintain field order and tabulate for clean table output. The JSON serialization approach for field filtering is appropriate.

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (5)
holmes/plugins/toolsets/__init__.py (2)

41-41: Specify encoding when opening files

Always specify the encoding when opening text files to ensure consistent behavior across different platforms.

-    with open(toolsets_path) as file:
+    with open(toolsets_path, encoding="utf-8") as file:

131-142: ⚠️ Potential issue

Fix validation logic and type comparison issues

The validation logic has several critical issues:

  1. Line 133 uses is to compare with enum, but toolset_type is a string from the config
  2. The MCP toolset validation happens outside the strict_check block
  3. The same variable validated_toolset is assigned different types
             toolset_type = config.get("type", ToolsetType.BUILTIN.value)
             # MCP server is not a built-in toolset, so we need to set the type explicitly
-            if toolset_type is ToolsetType.MCP:
-                validated_toolset: RemoteMCPToolset = RemoteMCPToolset(
-                    **config, name=name
-                )
             if strict_check:
-                validated_toolset: YAMLToolset = YAMLToolset(**config, name=name)  # type: ignore
+                if toolset_type == ToolsetType.MCP.value:
+                    validated_toolset = RemoteMCPToolset(**config, name=name)
+                else:
+                    validated_toolset = YAMLToolset(**config, name=name)
             else:
-                validated_toolset: ToolsetYamlFromConfig = ToolsetYamlFromConfig(  # type: ignore
-                    **config, name=name
-                )
+                validated_toolset = ToolsetYamlFromConfig(**config, name=name)
holmes/core/toolset_manager.py (3)

56-59: Fix incorrect docstring reference

The docstring incorrectly refers to "UI tool tags" when it should mention "server tool tags".

         """
         Returns the list of toolset tags that are relevant for server tools.
-        A toolset is considered a server tool if it has any of UI tool tags:
+        A toolset is considered a server tool if it has any of server tool tags.
         """

213-215: ⚠️ Potential issue

Add error handling for cache file loading

The JSON loading could fail if the cache file is corrupted or has permission issues.

         logging.info("loading toolset status from cache")
         cached_toolsets: List[dict[str, Any]] = []
-        with open(self.toolset_status_location, "r") as f:
-            cached_toolsets = json.load(f)
+        try:
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)
+        except (json.JSONDecodeError, IOError) as e:
+            logging.error(f"Failed to load toolset cache: {e}. Refreshing status.")
+            self.refresh_toolset_status(dal, enable_all_toolsets=enable_all_toolsets, toolset_tags=toolset_tags)
+            with open(self.toolset_status_location, "r") as f:
+                cached_toolsets = json.load(f)

246-248: 🛠️ Refactor suggestion

Use more specific exception type

The generic Exception is too broad. Use a more specific exception type for better error handling.

-                raise ValueError(
+                raise ValueError(
                     f"Toolset {custom_toolset_from_cli.name} from cli is already defined in existing toolset"
                 )
🧹 Nitpick comments (4)
holmes/plugins/toolsets/__init__.py (1)

104-106: Simplify return statement

The condition can be returned directly without the if statement.

-    if isinstance(toolsets, list):
-        return True
-    return False
+    return isinstance(toolsets, list)
🧰 Tools
🪛 Ruff (0.11.9)

104-106: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/core/toolset_manager.py (1)

389-389: Remove unnecessary .keys() call

When checking membership in a dictionary, .keys() is redundant.

-            if new_toolset.name in existing_toolsets_by_name.keys():
+            if new_toolset.name in existing_toolsets_by_name:
🧰 Tools
🪛 Ruff (0.11.9)

389-389: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

holmes/core/tools.py (1)

427-428: Simplify status check using 'in' operator

Use the in operator for cleaner and more readable code.

-            if (
-                self.status == ToolsetStatusEnum.DISABLED
-                or self.status == ToolsetStatusEnum.FAILED
-            ):
+            if self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED):
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)

holmes/config.py (1)

166-166: Use lazy formatting in logging functions

Replace f-strings with lazy formatting to avoid unnecessary string interpolation when the log level is higher than debug.

-            logging.debug(f"Loading config from {config_file}")
+            logging.debug("Loading config from %s", config_file)
-        logging.debug(f"Overriding config from cli options {cli_options}")
+        logging.debug("Overriding config from cli options %s", cli_options)

Also applies to: 174-174

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58b9db9 and d0ffdab.

📒 Files selected for processing (6)
  • holmes/config.py (10 hunks)
  • holmes/core/tools.py (9 hunks)
  • holmes/core/toolset_manager.py (1 hunks)
  • holmes/main.py (20 hunks)
  • holmes/plugins/toolsets/__init__.py (2 hunks)
  • tests/core/test_toolset_manager.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/core/test_toolset_manager.py
  • holmes/main.py
🧰 Additional context used
🪛 Ruff (0.11.9)
holmes/plugins/toolsets/__init__.py

104-106: Return the condition bool(isinstance(toolsets, list)) directly

Replace with return bool(isinstance(toolsets, list))

(SIM103)

holmes/core/toolset_manager.py

389-389: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 Pylint (3.3.7)
holmes/core/tools.py

[refactor] 427-428: Consider merging these comparisons with 'in' by using 'self.status in (ToolsetStatusEnum.DISABLED, ToolsetStatusEnum.FAILED)'. Use a set instead if elements are hashable.

(R1714)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.12)
🔇 Additional comments (6)
holmes/core/toolset_manager.py (1)

34-36: Good handling of default parameter

The implementation correctly avoids function calls in default parameters by setting the default inside the method body.

holmes/core/tools.py (2)

334-337: Good refactoring to public fields

The change from private attributes to public fields simplifies the API and removes unnecessary getters/setters, making the code more Pythonic and easier to use.


508-539: Well-designed class for partial toolset configurations

The ToolsetYamlFromConfig class effectively handles partial YAML configurations for overriding built-in toolsets. The documentation clearly explains its purpose and the default values are appropriate.

holmes/config.py (3)

125-133: Excellent use of lazy initialization pattern

The toolset_manager property implements lazy initialization effectively, ensuring the ToolsetManager is only created when needed. This improves performance and properly delegates all toolset management responsibilities.


228-239: Clean refactoring of tool executor creation

The simplified implementation effectively delegates all toolset management to ToolsetManager, removing complex loading and merging logic. This makes the code much more maintainable and follows the single responsibility principle.

Also applies to: 241-257


425-425: Good type safety improvement

Changing config_file parameter from str to Optional[Path] improves type safety and aligns with the broader refactoring to use Path objects throughout the codebase.

@moshemorad moshemorad self-requested a review June 10, 2025 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Change toolset loading in cli Speed up cli startup time
3 participants