Skip to content

fix: allow the user to config api key in config file - #1036

Merged
mainred merged 5 commits into
masterfrom
mainred/config-api-key
Oct 8, 2025
Merged

mainred merged 5 commits into
masterfrom
mainred/config-api-key

Conversation

@mainred

@mainred mainred commented Oct 7, 2025

Copy link
Copy Markdown
Collaborator

We introduced LLM Model Registry in #954 to better manage the LLM model, and removed api_key from Config class, which leads to user configured api_key not working anymore.
This change brings the api_key back to config file, and introduces ModelEntry class to clarify the format of the LLM parameters.

@coderabbitai

coderabbitai Bot commented Oct 7, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Replaces dict-based LLM registry entries with a Pydantic ModelEntry, adds api_key: Optional[SecretStr] to Config, unwraps SecretStr when constructing LLMs, and forwards Azure api_base/api_version (and other model fields) to DefaultLLM. Tests updated for attribute access and Azure parameter forwarding.

Changes

Cohort / File(s) Summary
Config: api_key and LLM construction
holmes/config.py
Adds api_key: Optional[SecretStr] to Config. _get_llm now calls get_model_params(model_key) to obtain a ModelEntry, unwraps SecretStr via get_secret_value() if present, and forwards model, api_key, api_base, api_version, base_url, custom_args, etc., to DefaultLLM.
LLM registry refactor to ModelEntry
holmes/core/llm.py
Introduces ModelEntry (Pydantic) with fields (model, name, api_key, base_url, is_robusta_model, custom_args, api_base, api_version, model_config = ConfigDict(extra="allow")) and helpers (load_from_dict/model_validate). Replaces dict-based registry with ModelEntry across _llms, models property, get_model_params, _parse_models_file, _create_model_entry, and robusta defaults.
Tests: Azure params forwarding
tests/config_class/test_config_api_base_version.py
Adds test_config_get_llm_with_azure_openai_parameters() that mocks DefaultLLM and asserts _get_llm passes model, unwrapped api_key, api_base, and api_version (plus custom_args and tracer) to DefaultLLM. Minor import arrangement updated.
Tests: attribute access for models
tests/config_class/test_config_load_model_list.py
Updates tests to access model registry entries as objects (e.g., .model, .base_url, .name, .is_robusta_model) instead of dict lookups; preserves prior assertions for local, Sonnet, and Robusta models.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Config
  participant Registry as LLMModelRegistry
  participant DefaultLLM

  App->>Config: _get_llm(model_key)
  Config->>Registry: get_model_params(model_key)
  Registry-->>Config: ModelEntry
  Note over Config: extract fields from ModelEntry\nunwrap SecretStr api_key via get_secret_value()
  Config->>DefaultLLM: __init__(model, api_key, api_base, api_version, base_url, custom_args, tracer, ...)
  DefaultLLM-->>Config: llm_instance
  Config-->>App: llm_instance
Loading
sequenceDiagram
  autonumber
  participant Registry as LLMModelRegistry
  participant FS as Models YAML
  participant ModelEntry

  Registry->>FS: _parse_models_file(path)
  FS-->>Registry: raw dict entries
  Registry->>ModelEntry: ModelEntry.model_validate(entry_dict)
  ModelEntry-->>Registry: ModelEntry
  Note over Registry: store _llms as dict[string, ModelEntry]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • nherment

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely summarizes the primary change of restoring API key configuration in the config file and directly reflects the content of the pull request.
Description Check ✅ Passed The description provides relevant context about the regression introduced by the LLM Model Registry change, explains why api_key support was broken, and states that this PR reintroduces the api_key and adds the ModelEntry class, which aligns with the actual code changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mainred/config-api-key

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

❤️ Share

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

@mainred
mainred force-pushed the mainred/config-api-key branch from 450bd8c to b8b7eeb Compare October 7, 2025 05:24
Comment thread holmes/core/llm.py
@mainred
mainred requested review from moshemorad and nherment October 7, 2025 05:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

45-46: Duplicate concern: name field redundancy.

This TODO comment echoes the concern raised in the past review comment at this location. As noted previously, the name field appears unused except at line 493 and isn't utilized by DefaultLLM.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4fe4c and b8b7eeb.

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • holmes/core/llm.py
  • tests/config_class/test_config_api_base_version.py
  • tests/config_class/test_config_load_model_list.py
  • holmes/config.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • tests/config_class/test_config_api_base_version.py
  • tests/config_class/test_config_load_model_list.py
tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Test files should mirror the source structure under tests/

Files:

  • tests/config_class/test_config_api_base_version.py
  • tests/config_class/test_config_load_model_list.py
🧬 Code graph analysis (4)
holmes/core/llm.py (3)
holmes/clients/robusta_client.py (2)
  • RobustaModelsResponse (17-23)
  • fetch_robusta_models (27-42)
holmes/utils/file_utils.py (1)
  • load_yaml_file (19-56)
holmes/utils/env.py (1)
  • replace_env_vars_values (36-60)
tests/config_class/test_config_api_base_version.py (1)
holmes/config.py (1)
  • _get_llm (445-475)
tests/config_class/test_config_load_model_list.py (3)
tests/conftest.py (1)
  • server_config (33-56)
holmes/config.py (2)
  • Config (46-481)
  • llm_model_registry (126-129)
holmes/core/llm.py (1)
  • models (551-552)
holmes/config.py (1)
holmes/core/llm.py (1)
  • get_model_params (519-545)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.11)
  • GitHub Check: llm_evals
🔇 Additional comments (6)
holmes/core/llm.py (2)

416-416: LGTM: ModelEntry-based registry.

The refactor from dict-based to ModelEntry-based storage is consistent throughout the registry. The type hints, return types, and validation logic align correctly.

Also applies to: 519-519, 551-551, 554-563


578-596: LGTM: Azure OpenAI parameter support.

The updated signature and ModelEntry construction correctly support Azure-specific parameters (api_base, api_version) alongside the general api_key field.

tests/config_class/test_config_api_base_version.py (1)

60-84: LGTM: Comprehensive Azure parameter test.

The test correctly verifies that Config._get_llm forwards api_key, api_base, and api_version to DefaultLLM for Azure OpenAI models. The assertions validate the positional arguments match the expected DefaultLLM signature.

holmes/config.py (2)

48-50: LGTM: Restores api_key configuration support.

This addition addresses the regression introduced in PR #954, allowing users to configure api_key in the config file. The SecretStr type ensures secure handling, and the comment clarifies the fallback to environment variables.


447-448: LGTM: ModelEntry integration and SecretStr unwrapping.

The refactor to retrieve model_entry and use model_dump(exclude_none=True) aligns with the ModelEntry-based registry changes. The SecretStr unwrapping at lines 460-461 correctly extracts the actual key value for LLM initialization.

Also applies to: 460-461

tests/config_class/test_config_load_model_list.py (1)

24-24: LGTM: Attribute-based access for ModelEntry.

The migration from dict-style access (.get("model"), ["model"]) to attribute access (.model, .base_url, .name, .is_robusta_model) correctly reflects the ModelEntry refactor. The expected values remain unchanged, confirming test equivalence.

Also applies to: 31-31, 38-41

Comment thread holmes/core/llm.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

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

548-549: Fix the type mismatch in get_llm.

The method signature declares a return type of LLM, but self._llms[name] returns a ModelEntry instance. This will cause type errors and runtime issues if the method is called.

The TODO comment indicates awareness of this issue. Consider one of these solutions:

  1. Remove this method if it's unused
  2. Update the logic to construct an LLM instance from the ModelEntry
  3. Change the return type to ModelEntry if callers expect model configuration rather than an instantiated LLM

Run this script to check if the method is being called:

#!/bin/bash
# Check usage of get_llm method
rg -nP '\bget_llm\s*\(' --type=py -g '!holmes/core/llm.py'
🧹 Nitpick comments (1)
holmes/core/llm.py (1)

45-46: TODO: Consider removing redundant name field.

The TODO comment and a past review comment both note that the name field appears redundant and unused except as a pass-through parameter. Consider removing it in a follow-up to simplify the ModelEntry structure.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8b7eeb and 15780b3.

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • holmes/core/llm.py
🧬 Code graph analysis (1)
holmes/core/llm.py (3)
holmes/clients/robusta_client.py (2)
  • RobustaModelsResponse (17-23)
  • fetch_robusta_models (27-42)
holmes/utils/file_utils.py (1)
  • load_yaml_file (19-56)
holmes/utils/env.py (1)
  • replace_env_vars_values (36-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: llm_evals
  • GitHub Check: Pre-commit checks
🔇 Additional comments (3)
holmes/core/llm.py (3)

60-62: LGTM: @classmethod decorator correctly added.

The previous review concern about the missing @classmethod decorator has been properly addressed.


434-441: LGTM: api_key and api_version properly forwarded from config.

The changes correctly restore api_key support from the configuration file and add api_version for Azure OpenAI Service, directly addressing the regression mentioned in the PR objectives.


528-528: [shell scripts to locate ModelEntry definition]

#!/bin/bash
# Locate the ModelEntry class definition to verify it's a Pydantic BaseModel subclass
rg -n 'class ModelEntry' -C3 --type=py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
holmes/core/llm.py (2)

520-546: Use .model_copy() instead of .copy() for Pydantic v2 models.

Pydantic v2 models use .model_copy() instead of .copy(). The calls at lines 528, 538, and 546 will raise AttributeError at runtime since ModelEntry is a Pydantic v2 model.

Apply this diff to fix all three occurrences:

             if model_params is not None:
                 logging.info(f"Using selected model: {model_key}")
-                return model_params.copy()
+                return model_params.model_copy()
 
             logging.error(f"Couldn't find model: {model_key} in model list")
 
         if self._default_robusta_model:
             model_params = self._llms.get(self._default_robusta_model)
             if model_params is not None:
                 logging.info(
                     f"Using default Robusta AI model: {self._default_robusta_model}"
                 )
-                return model_params.copy()
+                return model_params.model_copy()
 
             logging.error(
                 f"Couldn't find default Robusta AI model: {self._default_robusta_model} in model list"
             )
 
         model_key, first_model_params = next(iter(self._llms.items()))
         logging.debug(f"Using first available model: {model_key}")
-        return first_model_params.copy()
+        return first_model_params.model_copy()

548-549: Fix get_llm to return an LLM instance, not ModelEntry.

The method signature indicates it returns LLM, but it actually returns ModelEntry (from self._llms[name]). This type mismatch is suppressed with # type: ignore but will cause runtime type errors for callers expecting an LLM instance.

The method should likely construct and return a DefaultLLM instance from the ModelEntry, similar to how it's done elsewhere in the codebase.

Consider implementing the method like this:

     def get_llm(self, name: str) -> LLM:  # TODO: fix logic
-        return self._llms[name]  # type: ignore
+        model_entry = self._llms[name]
+        return DefaultLLM(
+            model=model_entry.model,
+            api_key=model_entry.api_key.get_secret_value() if model_entry.api_key else None,
+            api_base=model_entry.api_base or model_entry.base_url,
+            api_version=model_entry.api_version,
+            args=model_entry.custom_args or {},
+            name=model_entry.name,
+        )
🧹 Nitpick comments (1)
holmes/core/llm.py (1)

48-54: Consider clarifying the distinction between base_url and api_base.

ModelEntry has both base_url and api_base fields, which could be confusing. According to the inline comment, api_base is for Azure OpenAI Service, but this relationship isn't immediately clear from the field names alone.

Consider either:

  1. Renaming api_base to azure_api_base to make the purpose explicit
  2. Adding a comment on the base_url field explaining how it differs from api_base
  3. Consolidating these fields if they serve the same purpose in different contexts
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15780b3 and 5cf1a39.

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • holmes/core/llm.py
🧬 Code graph analysis (1)
holmes/core/llm.py (3)
holmes/clients/robusta_client.py (2)
  • RobustaModelsResponse (17-23)
  • fetch_robusta_models (27-42)
holmes/utils/file_utils.py (1)
  • load_yaml_file (19-56)
holmes/utils/env.py (1)
  • replace_env_vars_values (36-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals
🔇 Additional comments (5)
holmes/core/llm.py (5)

493-498: LGTM!

The refactor to create ModelEntry directly instead of a dict is clean and type-safe.


555-564: LGTM!

The refactor correctly uses ModelEntry.model_validate() (Pydantic v2 API) to parse model parameters from the YAML file, and the type annotations are consistent.


566-597: LGTM!

Both _create_robusta_model_entry and _create_model_entry are correctly refactored to return ModelEntry instances. The addition of api_key, api_base, and api_version parameters aligns with the PR objective to restore api_key support.


45-46: Verify instantiations and references of name before removal.

Run:

rg -nP 'ModelEntry\([^)]*name=' --type=py
rg -nP '\b(entry|model_entry)\.name\b' --type=py
rg -nP '\{.*name.*\}' --type=py

434-441: Ignore SecretStr unwrapping concern in DefaultLLM instantiation.

The config code unwraps api_key with .get_secret_value() before calling DefaultLLM.

Likely an incorrect or invalid review comment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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

41-63: Well-structured model with proper security practices.

The ModelEntry class provides a clean, type-safe representation of LLM configurations. Using SecretStr for api_key is appropriate for security, and ConfigDict(extra="allow") provides forward compatibility.

Note: The TODO comment about the redundant name field (line 45) has already been raised in previous review comments.

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

531-557: Consider using model_copy() instead of deprecated copy().

Pydantic v2 deprecated copy() in favor of model_copy(). While copy() still works for backward compatibility, using model_copy() aligns with current best practices and avoids potential deprecation warnings.

Apply this pattern at lines 539, 549, and 557:

-return model_params.copy()
+return model_params.model_copy()

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cf1a39 and 475bf9e.

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • holmes/core/llm.py
🧬 Code graph analysis (1)
holmes/core/llm.py (3)
holmes/clients/robusta_client.py (2)
  • RobustaModelsResponse (17-23)
  • fetch_robusta_models (27-42)
holmes/utils/file_utils.py (1)
  • load_yaml_file (19-56)
holmes/utils/env.py (1)
  • replace_env_vars_values (36-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Pre-commit checks
  • GitHub Check: llm_evals
🔇 Additional comments (8)
holmes/core/llm.py (8)

3-3: LGTM!

The added imports properly support the new ModelEntry class and enhanced type safety. All imports are correctly placed at the top of the file.

Also applies to: 6-6, 8-8, 11-13, 17-17


428-428: LGTM!

The type annotation change from dict[str, dict[str, Any]] to dict[str, ModelEntry] improves type safety and consistency with the new ModelEntry model.


445-452: LGTM!

The addition of api_key and api_version parameters correctly restores support for user-configured API keys, addressing the regression from PR #954.


504-509: LGTM!

The direct instantiation of ModelEntry for the default Robusta configuration is correct and consistent with the refactor.


563-564: LGTM!

The return type update is consistent with the ModelEntry refactor.


566-575: LGTM!

The refactor correctly uses ModelEntry.model_validate() to create type-safe model entries from YAML data. The environment variable replacement happens before validation, which is the correct order.


577-588: LGTM!

The method correctly returns a ModelEntry and properly handles the custom_args field.


590-608: LGTM!

The method signature and implementation correctly support the new api_key, api_base, and api_version parameters. The use of SecretStr for api_key maintains security, and all parameters properly align with the ModelEntry field definitions.

@mainred
mainred enabled auto-merge (squash) October 8, 2025 08:55
@mainred
mainred merged commit f8bf890 into master Oct 8, 2025
7 checks passed
@mainred
mainred deleted the mainred/config-api-key branch October 8, 2025 09:02
@github-actions

github-actions Bot commented Oct 8, 2025

Copy link
Copy Markdown
Contributor

Results of HolmesGPT evals

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

Legend

  • ✅ the test was successful
  • :minus: the test was skipped
  • ⚠️ the test failed but is known to be flaky or known to fail
  • 🚧 the test had a setup failure (not a code regression)
  • 🔧 the test failed due to mock data issues (not a code regression)
  • 🚫 the test was throttled by API rate limits/overload
  • ❌ the test failed and should be fixed before merging the PR

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants