-
Notifications
You must be signed in to change notification settings - Fork 550
chore(types): Type-clean /cli (37 errors) #1380
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
Conversation
Thank you @tgasser-nv . I also tested it against #1339 and all tests pass. Some of the changes might require further test cases, for example response type handling changes in chat.py:117-144. The existing tests only mock generate_async() to return simple dict responses, but don't test the new type-checking logic for: if type(response) == Tuple[Dict, Dict]:
elif type(response) == GenerationResponse: # untested
elif type(response) == Dict: # untested fallback logic |
I agree we need to add test-cases to cover these new options. Should we do this after #1339 is merged? |
Converting to draft while I rebase on the latest changes to develop. |
506155e
to
89b9860
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Rebased this onto develop branch and re-ran the test plan. This is ready for review now cc @Pouyanpi @trebedea @cparisien |
added test cases for this and it looks good. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, thank you Tim.
Yep I agree. In general our test line coverage will go down after adding type-checking since we now explicitly check types on multiple if branches. This isn't a regression in test coverage. I'll add test cases to get these in a follow-up PR. I created a JIRA (NGUARD-496) so this doesn't get forgotten. |
89b9860
to
21d48a8
Compare
Description
Clean up type errors in the nemoguards/cli directory
Type-cleaning summary
This report summarizes the type-safety fixes applied to the
nemoguardrails
project. The changes are categorized into high, medium, and low-risk buckets based on their potential to impact existing functionality.🔴 High-Risk Changes
This category includes changes to core logic that handle variable return types. While necessary for type safety, an incorrect assumption about the data structure could disrupt the primary chat functionality.
Handling Multiple Return Types from
generate_async
The
generate_async
method can return several different types. The original implementation did not account for this, leading to potential runtime errors. The fix introduces a series ofisinstance
checks to correctly parse the response.nemoguardrails/cli/chat.py
TypeError
orAttributeError
by assuming the return value fromgenerate_async
is always adict
with a'content'
key.response
is atuple
,GenerationResponse
object, ordict
, and correctly extracts the bot message from each structure. This makes the chat response handling significantly more robust. A safe.get()
method is also used later to access the message content, preventingKeyError
.response
is aGenerationResponse
object, itsresponse
attribute will be a list containing the desired message dictionary. It also assumes that ifresponse
is a tuple, the bot message is the first element.generate_async
to always return a consistent object (e.g., aGenerationResponse
dataclass). However, that would be a more invasive change. The current fix correctly handles the existing behavior without modifying the called function.🟡 Medium-Risk Changes
These changes involve data type conversions that rely on structural assumptions. They are medium risk because a mismatch between the expected and actual data structure would lead to a runtime
TypeError
.1. Converting State Between Dataclass and Dictionary
The
process_events_async
method expects the session state as a dictionary, but it was being stored as aState
dataclass. The fix converts the dataclass to a dictionary before passing it and converts the returned dictionary back into a dataclass.nemoguardrails/cli/chat.py
TypeError
becauseprocess_events_async
expected aDict
but received aState
object. The return value was also aDict
that needed to be converted back.asdict
utility fromdataclasses
is used to convert thechat_state.state
object into a dictionary. After the call, the returnedoutput_state
dictionary is unpacked using**
to initialize a newState
dataclass object. Thecast
function informs the static type checker of this conversion.output_state
dictionary perfectly match the field names of theState
dataclass. Any discrepancy (missing or extra keys) would raise aTypeError
at runtime.process_events_async
to directly accept and returnState
objects. This would provide better end-to-end type safety but is a more significant refactoring.2. Normalizing
Spec
Object in DebuggerIn the debugger, an AST node's
spec
attribute could be either aSpec
object or a dictionary. The code now normalizes this attribute into aSpec
object before use.nemoguardrails/cli/debugger.py
AttributeError
from trying to access attributes (e.g.,spec.spec_type
) on adict
.spec
is already aSpec
instance. If not, it assumes it's a dictionary and unpacks it into theSpec
constructor to create a proper object.dict
found inhead_element_spec_op.spec
has the necessary keys to construct aSpec
object.3. Using Enum for CLI Argument Types
The
convert
command'sfrom-version
option was a free-form string, replaced now by atyper
supportedEnum
.nemoguardrails/cli/__init__.py
--from-version
, potentially causing errors if the value was not one of the supported versions ("1.0"
or"2.0-alpha"
).ColangVersions
enum was created to represent the valid choices.typer
automatically uses this to validate and parse the command-line argument. The enum's stringvalue
is then extracted for use in themigrate
function, which expects aLiteral
string type.migrate
function is correctly typed to expect aLiteral
string, making the.value
access necessary.Enum
approach provides stronger static typing and allowstyper
to generate more helpful CLI error messages automatically.🟢 Low-Risk Changes
These fixes are defensive additions, such as
None
checks and safer data access patterns, that increase robustness without altering logic. They are highly unlikely to introduce any new bugs.Null-Safety Checks and Defensive Programming
Numerous checks were added across the
cli
module to handleOptional
types and preventTypeError
orAttributeError
when dealing with values that could beNone
.cli/chat.py
,cli/debugger.py
,cli/migration.py
TypeError
from operating onNone
(e.g.,None * int
,function(None)
where a string is expected) orAttributeError
(e.g.,None.group(1)
).if config_path is None: raise RuntimeError(...)
to ensure critical variables are present. (cli/chat.py:64
)next_line or ""
andsample_conv_indent or 0
to safely handleOptional
variables. (cli/migration.py:259
,cli/migration.py:1082
)leading_whitespace_match.group(1) if leading_whitespace_match else ""
. (cli/migration.py:234
)setattr(api.app, "rails_config_path", ...)
to explicitly set dynamic attributes on theFastAPI
app instance, which silences static analysis warnings. (cli/__init__.py:146
)if chat_state is not None:
blocks in the debugger to prevent operations on aNone
global state. (cli/debugger.py:61
)Test plan
Type-checking
$ poetry run pre-commit run --all-files check yaml...............................................................Passed fix end of files.........................................................Passed trim trailing whitespace.................................................Passed isort (python)...........................................................Passed black....................................................................Passed Insert license in comments...............................................Passed pyright..................................................................Passed
Unit-tests
Chat local integration test
Checklist