Skip to content

chore: replace Pyright with ty #2115

Merged
Pouyanpi merged 17 commits into
developfrom
chore/ty-type-checker
Jul 3, 2026
Merged

chore: replace Pyright with ty #2115
Pouyanpi merged 17 commits into
developfrom
chore/ty-type-checker

Conversation

@Pouyanpi

@Pouyanpi Pouyanpi commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Description

Replace Pyright with ty for the scopes configured in pyproject.toml, resolve the diagnostics found during migration, and run ty through the official pre-commit hook.

Related Issue(s)

Verification

  • make test WORKERS=1 TEST='tests/test_action_dispatcher.py tests/test_actions.py tests/test_basic_embeddings_index_numpy.py tests/test_embeddings_providers_mock.py tests/test_guardrail_exceptions.py tests/tracing/adapters/test_log_adapter_registry.py' — 77 passed
  • uv run --locked --group=dev pre-commit run --all-files

AI Assistance

  • No AI tools were used.
  • AI tools were used; a human reviewed and can explain every change (tool: Codex).

Checklist

  • I've read the CONTRIBUTING guidelines.
  • This PR links to a triaged issue assigned to me.
  • My PR title follows the project commit convention.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • I've noted any verification beyond CI and any checks I couldn't run.
  • I did not update generated changelog files manually.
  • I addressed all CodeRabbit, Greptile, and other review comments, or replied with why no change is needed.
  • @mentions of the person or team responsible for reviewing proposed changes.

@github-actions github-actions Bot added status: needs triage New issues that have not yet been reviewed or categorized. size: L labels Jul 1, 2026
@Pouyanpi Pouyanpi changed the title Chore/ty type checker chore: replace Pyright with ty Jul 1, 2026
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

@Pouyanpi Pouyanpi force-pushed the chore/uv-migration branch 2 times, most recently from b03158d to a1f9cbc Compare July 1, 2026 17:18
Base automatically changed from chore/uv-migration to develop July 2, 2026 19:10
@Pouyanpi Pouyanpi force-pushed the chore/ty-type-checker branch from a60a6c2 to b425610 Compare July 2, 2026 19:52
@Pouyanpi Pouyanpi marked this pull request as ready for review July 2, 2026 20:49
@Pouyanpi Pouyanpi added status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). and removed status: needs triage New issues that have not yet been reviewed or categorized. labels Jul 2, 2026
@Pouyanpi Pouyanpi self-assigned this Jul 2, 2026
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces Pyright with ty (v0.0.56) as the static type checker, updating .pre-commit-config.yaml, pyproject.toml, Makefile, and CI workflow accordingly. Alongside the tooling swap, it resolves all diagnostics ty surfaces: tightening type annotations, removing obsolete # type: ignore comments, and fixing genuine edge cases found during migration.

  • Tooling wiring: ty check is now run via the ty pre-commit hook (always_run: true, require_serial: true), the typing dependency group pins ty==0.0.56, and [tool.ty.src] / [tool.ty.analysis] / [tool.ty.overrides] replace [tool.pyright] in pyproject.toml.
  • Type-driven fixes: streaming.py introduces a named _EndOfStream class to replace the untyped object() sentinel, enabling isinstance-based narrowing throughout; action_dispatcher.py adds RegisteredAction TypeAlias and proper Protocol types; llmrails.py guards string concatenation with isinstance(message_content, str) before prepending reasoning traces or returning prompt-mode responses.
  • Test coverage: New tests verify the exception / non-string content paths introduced in llmrails.py, the threshold=None argument passed to KnowledgeBase.search, and the ainvoke-based async action dispatch path.

Confidence Score: 5/5

Safe to merge — the change is a tooling swap with targeted code fixes, all covered by the existing and new test suite.

All source changes are narrow, type-annotation–driven fixes: string concatenation guards in llmrails.py are backed by new exception-path tests; the _EndOfStream sentinel refactor in streaming.py preserves all identity checks; the threshold=None addition in kb.py matches the existing abstract-base signature. No runtime logic was restructured without tests.

No files require special attention.

Important Files Changed

Filename Overview
.pre-commit-config.yaml Replaces pyright hook with ty check; adds always_run: true and require_serial: true, drops types: [python] (correct since pass_filenames: false renders it irrelevant).
pyproject.toml Switches typing dep-group from pyright to ty==0.0.56; migrates [tool.pyright] to [tool.ty.src], [tool.ty.analysis] (with allowed-unresolved-imports), and [tool.ty.overrides].
nemoguardrails/streaming.py Introduces _EndOfStream named class to replace object() sentinel, enabling isinstance-based narrowing; push_chunk and _process type signatures updated accordingly; all sentinel checks preserved as identity (is) tests.
nemoguardrails/actions/action_dispatcher.py Adds AsyncInvokableAction, RunnableAction Protocols and RegisteredAction TypeAlias; replaces cast(Callable, fn) with typed walrus-operator extraction for ainvoke; raises ValueError for actions without __name__.
nemoguardrails/rails/llm/llmrails.py Guards reasoning-trace prepend and prompt-mode response construction with isinstance(message_content, str), preventing silent TypeError when content is an exception dict; uses # ty: ignore for two genuinely unresolvable dict-access diagnostics.
nemoguardrails/kb/kb.py Passes explicit threshold=None to self.index.search(); BasicEmbeddingsIndex.search and the abstract base already accept this parameter, so no compatibility concern.
nemoguardrails/llm/taskmanager.py Widens parse_task_output task parameter to Union[str, Task] (matching actual callers); adds ValueError guard in register_filter for callables without __name__.
nemoguardrails/server/api.py Rewrites optional chainlit import to use else clause of try/except, letting ty understand the type of mount_chainlit in each branch without a suppression comment.
.github/workflows/lint.yml Changes uv run make pre-commit to make pre-commit; the make target now internally calls uv run --locked --group=dev, so CI still runs inside the venv.
tests/test_guardrail_exceptions.py Adds two tests covering the new isinstance(message_content, str) branches: exception payload with generation options yields GenerationResponse, and reasoning trace is not prepended to a non-string content dict.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["push_chunk(chunk)"] --> B{chunk is None?}
    B -- Yes --> C[chunk = END_OF_STREAM]
    B -- No --> D{chunk is END_OF_STREAM?}
    D -- Yes --> E[pass – already sentinel]
    D -- No --> F{isinstance chunk, str?}
    F -- Yes --> G[valid string chunk]
    F -- No --> H[invalid type – ignored]
    C --> I["_process(END_OF_STREAM)"]
    E --> I
    G --> J["_process(chunk: str)"]
    I --> K{enable_buffer?}
    J --> K
    K -- Yes, str chunk --> L[buffer += chunk]
    K -- Yes, END_OF_STREAM --> M[noop / fall through]
    K -- No, str chunk --> N[completion += chunk, check stop tokens]
    K -- No, END_OF_STREAM --> O{pipe_to set?}
    N --> O
    O -- Yes --> P[asyncio.create_task pipe_to.push_chunk]
    O -- No, include_metadata --> Q[queue.put chunk_dict]
    O -- No, plain --> R[queue.put chunk]
    Q --> S{chunk is END_OF_STREAM?}
    R --> S
    S -- Yes --> T[streaming_finished_event.set]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["push_chunk(chunk)"] --> B{chunk is None?}
    B -- Yes --> C[chunk = END_OF_STREAM]
    B -- No --> D{chunk is END_OF_STREAM?}
    D -- Yes --> E[pass – already sentinel]
    D -- No --> F{isinstance chunk, str?}
    F -- Yes --> G[valid string chunk]
    F -- No --> H[invalid type – ignored]
    C --> I["_process(END_OF_STREAM)"]
    E --> I
    G --> J["_process(chunk: str)"]
    I --> K{enable_buffer?}
    J --> K
    K -- Yes, str chunk --> L[buffer += chunk]
    K -- Yes, END_OF_STREAM --> M[noop / fall through]
    K -- No, str chunk --> N[completion += chunk, check stop tokens]
    K -- No, END_OF_STREAM --> O{pipe_to set?}
    N --> O
    O -- Yes --> P[asyncio.create_task pipe_to.push_chunk]
    O -- No, include_metadata --> Q[queue.put chunk_dict]
    O -- No, plain --> R[queue.put chunk]
    Q --> S{chunk is END_OF_STREAM?}
    R --> S
    S -- Yes --> T[streaming_finished_event.set]
Loading

Reviews (10): Last reviewed commit: "update" | Re-trigger Greptile

Comment thread nemoguardrails/guardrails/actions/tool_result_action.py Outdated
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Migrates the project's static type checker from Pyright to ty across pre-commit config, pyproject.toml, and documentation. Removes numerous type: ignore comments and cast usages, replacing them with runtime narrowing and validation. Adds explicit name-validation for action/filter registration, tightens streaming sentinel typing, adjusts llmrails state/response handling, and adds corresponding tests and triage/gotchas documentation.

Changes

Pyright to ty migration

Layer / File(s) Summary
Tooling and docs switch to ty
.pre-commit-config.yaml, pyproject.toml, AGENTS.md, CONTRIBUTING.md, gotchas.md, triage.md
Pre-commit hook, dependency group, and tool configuration switch from Pyright to ty; docs updated to reference ty check; new gotchas and triage documentation added.
Action/filter naming validation
nemoguardrails/actions/action_dispatcher.py, nemoguardrails/actions/actions.py, nemoguardrails/llm/taskmanager.py, nemoguardrails/tracing/adapters/registry.py, tests/test_action_dispatcher.py, tests/test_actions.py, tests/tracing/adapters/test_log_adapter_registry.py
register_action, the action decorator, register_filter, and register_log_adapter now derive names via getattr/__name__ and raise ValueError when no valid name is available; execute_action uses getattr+callable for ainvoke detection; new tests cover the validation.
Streaming sentinel and chunk type safety
nemoguardrails/streaming.py
END_OF_STREAM becomes a dedicated _EndOfStream instance; buffering, completion, and prefix/suffix handling are gated behind isinstance(chunk, str) checks.
LLM rails state/response type-safety
nemoguardrails/rails/llm/llmrails.py, nemoguardrails/server/schemas/utils.py, tests/test_guardrail_exceptions.py
State parameters typed as Dict[str, Any]/Union, state.get(...) replaces indexing, response/reasoning-content construction gated on string checks, tool-call normalization widened to support custom/function tool-call types; new exception-flow tests added.
Suppression removal and narrowing across supporting modules
nemoguardrails/actions/llm/generation.py, nemoguardrails/cli/chat.py, nemoguardrails/cli/debugger.py, nemoguardrails/cli/providers.py, nemoguardrails/embeddings/..., nemoguardrails/guardrails/actions/*, nemoguardrails/kb/kb.py, nemoguardrails/llm/clients/base.py, nemoguardrails/server/api.py, nemoguardrails/tracing/adapters/opentelemetry.py, tests/test_basic_embeddings_index_numpy.py
Removes leftover type: ignore/cast suppressions, adds explicit threshold=None in KB search, tuple-typed traversal queue in debugger, guarded task cancellation and typed streaming accumulation in chat.py, and a new test validating explicit threshold forwarding.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

  • NVIDIA-NeMo/Guardrails#2030: Both PRs modify nemoguardrails/guardrails/actions/tool_result_action.py around tool-result validation logic.

Suggested labels: refactoring

Suggested reviewers: cparisien

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Results For Major Changes ⚠️ Warning FAIL: This is a broad refactor/migration PR, but the description’s Verification section is just a placeholder and includes no test results or testing details. Add concrete verification in the PR body (tests run, CI/manual checks, and any residual risk). If performance/numerics are affected, include before/after evidence.
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: migrating type checking from Pyright to ty.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ty-type-checker

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

@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

Caution

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

⚠️ Outside diff range comments (1)
AGENTS.md (1)

98-98: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stale "Pyright" reference after the ty migration.

Line 98 still says "Standalone Ruff, Ruff format, and Pyright runs are local diagnosis only" even though the Validation table above (line 86) and the pre-commit/pyproject config now use ty. This is inconsistent with the rest of the migration.

📝 Proposed fix
-- Standalone Ruff, Ruff format, and Pyright runs are local diagnosis only; run
+- Standalone Ruff, Ruff format, and ty runs are local diagnosis only; run
   pre-commit on changed files before handoff and report if it is skipped.

As per coding guidelines, **/*.md files should be updated when changing configuration syntax or tooling behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 98, Update the stale tooling guidance in AGENTS.md so it
matches the ty migration: replace the Pyright reference in the local diagnosis
note with ty, keeping the wording consistent with the Validation table and the
pre-commit/pyproject configuration. Locate the sentence near the standalone
Ruff/Ruff format/pyright guidance and revise it to refer to Ruff, Ruff format,
and ty instead.

Source: Coding guidelines

🧹 Nitpick comments (2)
nemoguardrails/actions/action_dispatcher.py (2)

120-134: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Docstring still says Callable; param is now Any.

Minor staleness after the signature change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoguardrails/actions/action_dispatcher.py` around lines 120 - 134, The
docstring for register_action is stale after the signature change from Callable
to Any. Update the Args description for action in action_dispatcher.py so it
matches the current register_action(action: Any, name: Optional[str] = None,
override: bool = True) signature and reflects that action may be a broader
object, while keeping the rest of the behavior and action_meta/__name__ lookup
in sync.

128-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Name-validation logic duplicated across three files.

The name or getattr(x, "__name__", None) + isinstance(..., str)/ValueError pattern here is repeated almost verbatim in nemoguardrails/actions/actions.py (action decorator) and nemoguardrails/llm/taskmanager.py (register_filter). Consider extracting a small shared helper (e.g. resolve_explicit_name(name, obj, kind)) to avoid triple maintenance of the same validation and error message.

♻️ Example shared helper
def _require_explicit_name(name: Optional[str], obj: Any, kind: str) -> str:
    resolved = name or getattr(obj, "__name__", None)
    if not isinstance(resolved, str) or not resolved:
        raise ValueError(f"An explicit name is required for {kind} without __name__.")
    return resolved
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoguardrails/actions/action_dispatcher.py` around lines 128 - 134, The name
resolution and validation logic is duplicated in the action dispatcher path, the
action decorator, and register_filter, so extract a shared helper for resolving
explicit names and enforcing the string/non-empty check. Update the logic in
action_dispatcher, actions, and taskmanager to call the shared helper instead of
repeating the `name or getattr(..., "__name__", None)` plus `isinstance(...,
str)`/`ValueError` pattern, and keep the error message consistent via the
helper’s kind parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nemoguardrails/embeddings/providers/openai.py`:
- Around line 49-53: The OpenAI provider import flow currently imports OpenAI
before checking the installed openai version, so 0.x installs fail before the
unsupported-version guard can run. Update the import/validation sequence in the
openai provider module so the version check happens before accessing OpenAI, and
preserve the original ImportError context by re-raising with either from err or
from None in the import handling path.

---

Outside diff comments:
In `@AGENTS.md`:
- Line 98: Update the stale tooling guidance in AGENTS.md so it matches the ty
migration: replace the Pyright reference in the local diagnosis note with ty,
keeping the wording consistent with the Validation table and the
pre-commit/pyproject configuration. Locate the sentence near the standalone
Ruff/Ruff format/pyright guidance and revise it to refer to Ruff, Ruff format,
and ty instead.

---

Nitpick comments:
In `@nemoguardrails/actions/action_dispatcher.py`:
- Around line 120-134: The docstring for register_action is stale after the
signature change from Callable to Any. Update the Args description for action in
action_dispatcher.py so it matches the current register_action(action: Any,
name: Optional[str] = None, override: bool = True) signature and reflects that
action may be a broader object, while keeping the rest of the behavior and
action_meta/__name__ lookup in sync.
- Around line 128-134: The name resolution and validation logic is duplicated in
the action dispatcher path, the action decorator, and register_filter, so
extract a shared helper for resolving explicit names and enforcing the
string/non-empty check. Update the logic in action_dispatcher, actions, and
taskmanager to call the shared helper instead of repeating the `name or
getattr(..., "__name__", None)` plus `isinstance(..., str)`/`ValueError`
pattern, and keep the error message consistent via the helper’s kind parameter.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 08f0fedd-6268-4a00-aa9b-ee1a9e401644

📥 Commits

Reviewing files that changed from the base of the PR and between a3f5be9 and b425610.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • .pre-commit-config.yaml
  • AGENTS.md
  • CONTRIBUTING.md
  • gotchas.md
  • nemoguardrails/actions/action_dispatcher.py
  • nemoguardrails/actions/actions.py
  • nemoguardrails/actions/llm/generation.py
  • nemoguardrails/cli/chat.py
  • nemoguardrails/cli/debugger.py
  • nemoguardrails/cli/providers.py
  • nemoguardrails/embeddings/cache.py
  • nemoguardrails/embeddings/providers/azureopenai.py
  • nemoguardrails/embeddings/providers/fastembed.py
  • nemoguardrails/embeddings/providers/nim.py
  • nemoguardrails/embeddings/providers/openai.py
  • nemoguardrails/guardrails/actions/content_safety_action.py
  • nemoguardrails/guardrails/actions/tool_result_action.py
  • nemoguardrails/kb/kb.py
  • nemoguardrails/llm/clients/base.py
  • nemoguardrails/llm/taskmanager.py
  • nemoguardrails/rails/llm/llmrails.py
  • nemoguardrails/server/api.py
  • nemoguardrails/server/schemas/utils.py
  • nemoguardrails/streaming.py
  • nemoguardrails/tracing/adapters/opentelemetry.py
  • nemoguardrails/tracing/adapters/registry.py
  • pyproject.toml
  • tests/test_action_dispatcher.py
  • tests/test_actions.py
  • tests/test_basic_embeddings_index_numpy.py
  • tests/test_guardrail_exceptions.py
  • tests/tracing/adapters/test_log_adapter_registry.py
  • triage.md

Comment thread nemoguardrails/embeddings/providers/openai.py
@Pouyanpi Pouyanpi force-pushed the chore/ty-type-checker branch 2 times, most recently from 2a8b7b2 to ffc90ce Compare July 3, 2026 08:24
Pouyanpi added 7 commits July 3, 2026 10:36
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
@Pouyanpi Pouyanpi force-pushed the chore/ty-type-checker branch from ffc90ce to cc8b190 Compare July 3, 2026 08:42
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Pouyanpi added 3 commits July 3, 2026 11:09
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Pouyanpi added 3 commits July 3, 2026 11:09
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
@Pouyanpi Pouyanpi force-pushed the chore/ty-type-checker branch from cc8b190 to 4d03229 Compare July 3, 2026 09:10
Pouyanpi added 4 commits July 3, 2026 11:33
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
@Pouyanpi Pouyanpi merged commit 5edf674 into develop Jul 3, 2026
15 checks passed
@Pouyanpi Pouyanpi deleted the chore/ty-type-checker branch July 3, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: L status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: use ty instead of pyright

1 participant