Skip to content

refactor: add TypedDicts for untyped dict parameters in backend#1592

Open
giwaov wants to merge 1 commit intogenlayerlabs:mainfrom
giwaov:refactor/308-add-type-annotations
Open

refactor: add TypedDicts for untyped dict parameters in backend#1592
giwaov wants to merge 1 commit intogenlayerlabs:mainfrom
giwaov:refactor/308-add-type-annotations

Conversation

@giwaov
Copy link
Copy Markdown

@giwaov giwaov commented Apr 8, 2026

Summary

Adds TypedDict definitions for previously untyped dict parameters across the backend, improving static type checking and IDE support.

Changes

New TypedDicts in backend/domain/types.py

  • LLMProviderConfig - typed config for LLM providers (temperature, max_tokens, use_max_completion_tokens)
  • PluginConfig - typed config for plugins (api_key_env_var, api_url, mock_response)
  • ProviderParams - typed params for add/update provider RPC endpoints

New TypedDict in backend/node/types.py

  • NodeConfig - typed validator node config used in Receipt.node_config (supports both flat validator dict and enhanced format with primary_model/secondary_model)

Updated signatures

  • LLMProvider.config / plugin_config now use LLMProviderConfig / PluginConfig
  • SimValidatorConfig.config / plugin_config now use LLMProviderConfig / PluginConfig
  • add_provider / update_provider params now use ProviderParams
  • create_validator / update_validator config params now use LLMProviderConfig / PluginConfig
  • Receipt.node_config now uses NodeConfig
  • decide_accepted / decide_finalizing leader_node_config now uses NodeConfig
  • Node._create_enhanced_node_config return type now uses NodeConfig

Motivation

As noted in the issue, ~90% of bugs have been due to incorrect field access on untyped dicts. These TypedDicts enable static analysis tools (Pylance, mypy) to catch field access errors at development time.

Closes #308

Summary by CodeRabbit

  • Refactor
    • Enhanced internal type safety by introducing structured configuration schemas for node, provider, and validator settings, replacing generic dictionary types with specific typed definitions. No changes to user-facing functionality.

- Add LLMProviderConfig TypedDict for provider config (temperature, max_tokens, use_max_completion_tokens)
- Add PluginConfig TypedDict for plugin config (api_key_env_var, api_url, mock_response)
- Add ProviderParams TypedDict for add/update provider RPC params
- Add NodeConfig TypedDict for Receipt.node_config validator dict
- Update LLMProvider, SimValidatorConfig to use typed config dicts
- Update add_provider, update_provider, create_validator, update_validator signatures
- Update Receipt.node_config, consensus decisions to use NodeConfig

Closes genlayerlabs#308
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

Introduces typed dictionary structures for node and LLM provider configurations, replacing generic dict type annotations with specific TypedDict definitions (NodeConfig, LLMProviderConfig, PluginConfig, ProviderParams) across consensus, domain, node, and RPC modules to improve type safety.

Changes

Cohort / File(s) Summary
Type Definitions
backend/domain/types.py, backend/node/types.py
Added NodeConfig, LLMProviderConfig, PluginConfig, and ProviderParams TypedDict structures to define configuration schemas with typed fields, replacing unstructured dict usage.
Node Layer Type Updates
backend/node/base.py
Updated Node._create_enhanced_node_config() return type annotation from dict to NodeConfig.
Consensus Layer Type Updates
backend/consensus/decisions.py
Updated leader_node_config parameter type annotations in decide_accepted() and decide_finalizing() functions from dict to NodeConfig.
RPC Layer Type Updates
backend/protocol_rpc/endpoints.py, backend/protocol_rpc/rpc_methods.py
Refined RPC endpoint and method parameter types to use ProviderParams, LLMProviderConfig, and PluginConfig instead of generic dict for provider, validator configuration, and plugin configuration parameters.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Our code now speaks in clearer types so grand,
No more lost dicts in a confusing land!
With TypedDict structures, bugs have nowhere to hide,
Static analysis keeps runtime errors pushed aside! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding TypedDict definitions to replace untyped dict parameters for better type safety across the backend.
Description check ✅ Passed The description comprehensively covers the changes made, provides clear motivation, and lists all updated signatures. However, it lacks explicit testing details and does not follow the exact template structure.
Linked Issues check ✅ Passed The PR successfully implements the core objective of issue #308 by introducing TypedDict definitions for previously untyped dict parameters across multiple backend modules, directly addressing the ~90% of bugs caused by incorrect field access.
Out of Scope Changes check ✅ Passed All changes are narrowly scoped to type annotations and TypedDict definitions. No functional logic modifications, unrelated refactoring, or feature additions outside the typing scope were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
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

🧹 Nitpick comments (3)
backend/domain/types.py (1)

27-32: Consider making ProviderParams fields optional with NotRequired.

ProviderParams uses total=True (the default), making all fields required. However, NotRequired is imported on line 9 but not used. If some fields can be optional in certain RPC calls (e.g., when using defaults), consider using NotRequired for consistency with how config and plugin_config can be None in validators.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/domain/types.py` around lines 27 - 32, ProviderParams currently makes
all fields required; update it to mark fields that can be omitted using
NotRequired (which is already imported) so RPC/validator paths that accept
missing values work correctly: change the TypedDict declaration for
ProviderParams to use NotRequired for any of provider, model, config, plugin,
plugin_config that may be absent (and keep config/plugin_config types as
Optional or allow None in validators as needed). Locate the ProviderParams
TypedDict in backend/domain/types.py and update the field annotations to
NotRequired[...] for the optional ones to reflect actual usage in your
validators and RPC handlers.
backend/node/types.py (2)

3-3: Unused import: NotRequired is not used.

With total=False on the TypedDict, all fields are already optional. The NotRequired import is unnecessary unless you plan to switch to total=True with selectively optional fields.

🧹 Remove unused import
-from typing import Iterable, Optional, Literal, NotRequired, TypedDict
+from typing import Iterable, Optional, Literal, TypedDict
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/node/types.py` at line 3, The import list in backend/node/types.py
includes an unused symbol NotRequired; remove NotRequired from the typing import
(leave Iterable, Optional, Literal, TypedDict) so the unused import is
eliminated and linting errors go away; if you intended to use NotRequired later,
instead switch the TypedDict usage to total=True and apply NotRequired to
specific keys, otherwise simply delete NotRequired from the import line.

229-242: Consider using the domain-level TypedDicts for nested config fields.

The PR introduces LLMProviderConfig and PluginConfig in backend/domain/types.py. For improved type safety on nested fields, consider referencing those types here instead of generic dict:

config: LLMProviderConfig  # instead of dict
plugin_config: PluginConfig  # instead of dict

However, if primary_model and secondary_model have a different structure than the domain types, keeping them as dict | None is acceptable for now.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/node/types.py` around lines 229 - 242, The NodeConfig TypedDict
currently uses generic dict for the nested fields; update the NodeConfig
definition so that the config field uses the domain-level LLMProviderConfig type
and plugin_config uses PluginConfig (importing them from
backend/domain/types.py) instead of plain dict to improve type safety while
leaving primary_model and secondary_model as dict | None if their shapes differ
from domain types; ensure you update the imports and any mypy/typing references
that expect these fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/domain/types.py`:
- Around line 21-24: The PluginConfig TypedDict is missing the mock_web_response
key used elsewhere; update the PluginConfig definition to include a
mock_web_response field (same shape as other mock fields, e.g.,
mock_web_response: dict) so type checkers recognize accesses like
plugin_config["mock_web_response"] and avoid warnings; modify the PluginConfig
class in types.py to add this field.

---

Nitpick comments:
In `@backend/domain/types.py`:
- Around line 27-32: ProviderParams currently makes all fields required; update
it to mark fields that can be omitted using NotRequired (which is already
imported) so RPC/validator paths that accept missing values work correctly:
change the TypedDict declaration for ProviderParams to use NotRequired for any
of provider, model, config, plugin, plugin_config that may be absent (and keep
config/plugin_config types as Optional or allow None in validators as needed).
Locate the ProviderParams TypedDict in backend/domain/types.py and update the
field annotations to NotRequired[...] for the optional ones to reflect actual
usage in your validators and RPC handlers.

In `@backend/node/types.py`:
- Line 3: The import list in backend/node/types.py includes an unused symbol
NotRequired; remove NotRequired from the typing import (leave Iterable,
Optional, Literal, TypedDict) so the unused import is eliminated and linting
errors go away; if you intended to use NotRequired later, instead switch the
TypedDict usage to total=True and apply NotRequired to specific keys, otherwise
simply delete NotRequired from the import line.
- Around line 229-242: The NodeConfig TypedDict currently uses generic dict for
the nested fields; update the NodeConfig definition so that the config field
uses the domain-level LLMProviderConfig type and plugin_config uses PluginConfig
(importing them from backend/domain/types.py) instead of plain dict to improve
type safety while leaving primary_model and secondary_model as dict | None if
their shapes differ from domain types; ensure you update the imports and any
mypy/typing references that expect these fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fd95247-53af-4f11-9cc3-db9e05e39386

📥 Commits

Reviewing files that changed from the base of the PR and between cd554a6 and 378c23c.

📒 Files selected for processing (6)
  • backend/consensus/decisions.py
  • backend/domain/types.py
  • backend/node/base.py
  • backend/node/types.py
  • backend/protocol_rpc/endpoints.py
  • backend/protocol_rpc/rpc_methods.py

Comment thread backend/domain/types.py
Comment on lines +21 to +24
class PluginConfig(TypedDict, total=False):
api_key_env_var: str
api_url: str
mock_response: dict
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.

⚠️ Potential issue | 🟡 Minor

Missing mock_web_response field in PluginConfig.

Based on the relevant code snippet from backend/validators/__init__.py:203-215, the mock_web_response key is actively read from plugin_config:

if (
    "mock_web_response" in val.llmprovider.plugin_config
    and len(val.llmprovider.plugin_config["mock_web_response"]) > 0
):

This field should be added to PluginConfig to ensure type consistency and avoid type checker warnings.

Proposed fix
 class PluginConfig(TypedDict, total=False):
     api_key_env_var: str
     api_url: str
     mock_response: dict
+    mock_web_response: dict
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/domain/types.py` around lines 21 - 24, The PluginConfig TypedDict is
missing the mock_web_response key used elsewhere; update the PluginConfig
definition to include a mock_web_response field (same shape as other mock
fields, e.g., mock_web_response: dict) so type checkers recognize accesses like
plugin_config["mock_web_response"] and avoid warnings; modify the PluginConfig
class in types.py to add this field.

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.

SIM-BE-Type everything we can

1 participant