-
Notifications
You must be signed in to change notification settings - Fork 548
chore(types): Type-clean /actions (189 errors) #1361
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
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1361 +/- ##
===========================================
- Coverage 71.68% 71.62% -0.07%
===========================================
Files 168 171 +3
Lines 16862 17053 +191
===========================================
+ Hits 12088 12214 +126
- Misses 4774 4839 +65
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
@tgasser-nv, any guidance on how to review this? I'm struggling a bit. If we're more strongly enforcing types, do we mainly rely on test coverage to ensure we haven't broken anything with the new constraints? |
Yes, I run the unit-tests continuously as I'm cleaning Pyright errors. We have far too many optional types in our Pydantic models and function/method signatures, the biggest bucket of fixes are asserting these aren't None before going on to use them. The lines which are either too complex to fix (i.e. generation.py |
Status: Pyright errors: 0, waived: 7. |
Fixing the types exposed a bug in an untested function. The
|
5fb3780
to
b751596
Compare
…mo_guardrails/nemoguardrails/actions/llm/generation.py:537:40 - error: Never is not awaitable (reportGeneralTypeIssues)
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
assert event | ||
assert event["type"] == "UserMessage" |
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.
not related to your changes, but should not use assert
if not event:
raise ValueError(
"No user message found in event stream. Unable to generate user intent."
)
if event["type"] != "UserMessage":
raise ValueError(
f"Expected UserMessage event, but found {event['type']}. "
"Cannot generate user intent from this event type."
)
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.
Yeah I agree. I wasn't sure if there was a good reason to use assert here rather than raising exceptions and wanted to be consistent with existing code. Will make this change.
assert event | ||
assert event["type"] == "UserMessage" |
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.
same as above we should not use assert in our non-test codes
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.
Yep agree here, added your sample code in from the above comment for this one too. Not worrying about DRYing the code too much since this file has three functions with untestable CC before refactoring:
/Users/tgasser/projects/nemo_guardrails/nemoguardrails/actions/llm/generation.py
M 1235:4 LLMGenerationActions.generate_intent_steps_message - F (50)
M 864:4 LLMGenerationActions.generate_bot_message - F (43)
M 381:4 LLMGenerationActions.generate_user_intent - F (42)
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.
I'll DRY this up when refactoring to address the Cyclomatic Complexity
results = ( | ||
await self.flows_index.search( | ||
text=f"${var_name} = ", max_results=5, threshold=None | ||
) | ||
if var_name | ||
else None | ||
) |
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.
nit: To me in line if-else in python is less readable
if var_name:
results = await self.flows_index.search(
text=f"${var_name} = ", max_results=5, threshold=None
)
else:
results = None
but no need to make this change, it is just a preference 👍🏻
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.
Agree, I reworked this to use the same structure as above
) | ||
|
||
if not results: | ||
raise Exception("No results found while generating value") |
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.
I am not sure about this Exception
I think None value should be OK, the system should generate a value based on instructions alone, without needing similar examples from the flows index.
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.
Sounds good, I added a guard statement before the examples string append loop:
# We add these in reverse order so the most relevant is towards the end.
if results:
for result in reversed(results):
# If the flow includes "GenerateValueAction", we ignore it as we don't want the LLM
# to learn to predict it.
if "GenerateValueAction" not in result.text:
examples += f"{result.text}\n\n"
triggering_flow_id = flow_id | ||
if not triggering_flow_id: | ||
raise Exception( | ||
f"No flow_id provided to generate flow." |
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.
I am not sure
f"No flow_id provided to generate flow." | |
"No flow_id provided to generate flow." |
relative_filepath = Path(module.__file__).relative_to(Path.cwd()) | ||
except ValueError: | ||
relative_filepath = Path(module.__file__).resolve() | ||
# todo! What are we trying to do here? |
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.
😆
I think the intent was to make it easier to traceback the problematic file.
So if there are multiple identical filenames, which one is problematic
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.
Makes sense, I added this back in again with some extra checking to make it type-clean
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.
Pull Request Overview
This PR performs type-cleaning for the NeMo Guardrails actions
module to address 189 type errors identified prior to enforcing type-checking. The changes include adding type annotations, import fixes, handling optional types, and adding necessary tests to cover edge cases.
- Adds comprehensive type annotations throughout the actions module
- Updates imports to use proper langchain-core modules and adds type checking compatibility
- Improves error handling and null checks in action dispatcher and generation functions
Reviewed Changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.
Show a summary per file
File | Description |
---|---|
nemoguardrails/actions/actions.py | Adds Protocol definitions and improved type annotations for action decorators |
nemoguardrails/actions/action_dispatcher.py | Enhances type safety with better annotations and error handling for action execution |
nemoguardrails/actions/llm/generation.py | Major type improvements for LLM generation actions with proper optional handling |
nemoguardrails/actions/llm/utils.py | Updates LLM utility functions with enhanced type annotations and null checks |
nemoguardrails/actions/v2_x/generation.py | Type-safe improvements for v2.x generation actions |
tests/ | Adds comprehensive test coverage for previously untested edge cases |
pyproject.toml | Enables type checking for the actions module |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
) | ||
|
||
result = await llm_call( | ||
llm, |
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.
The variable llm
is used here but should be generation_llm
to match the pattern used elsewhere in the function and maintain type consistency.
llm, | |
generation_llm, |
Copilot uses AI. Check for mistakes.
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.
This is a good catch, applied it locally and checked it with unit-tests
from langchain_core.language_models import BaseChatModel | ||
from langchain_core.language_models.llms import BaseLLM | ||
from langchain_text_splitters import ElementType | ||
from pytest_asyncio.plugin import event_loop |
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.
This import appears to be a development/testing dependency that shouldn't be in production code. This import should be removed as it's not used in the file and pytest_asyncio is a test-only dependency.
from pytest_asyncio.plugin import event_loop |
Copilot uses AI. Check for mistakes.
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.
There are some unsused imports including the pytest_asynci
from langchain_text_splitters import ElementType
from pytest_asyncio.plugin import event_loop
and
get_initial_actions,
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.
Removed these unused imports
triggering_flow_id = flow_id | ||
if not triggering_flow_id: | ||
raise Exception( | ||
f"No flow_id provided to generate flow." |
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.
The error message uses an f-string but doesn't include any variables. The message should be a plain string: \"No flow_id provided to generate flow.\"
f"No flow_id provided to generate flow." | |
"No flow_id provided to generate flow." |
Copilot uses AI. Check for mistakes.
|
||
event = get_last_user_utterance_event_v2_x(events) | ||
if not event: | ||
raise Exception("Passthrough LLM Action couldn't find last user utterance") |
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.
nit: better to avoid general Exception
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.
Agree, replaced with RuntimeError
log.info("Generating flow for name: {name}") | ||
|
||
if not self.instruction_flows_index: | ||
raise Exception("No instruction flows index has been created.") |
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.
RuntimeError
maybe
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.
Yep updated this.
nemoguardrails/actions/actions.py
Outdated
class Actionable(Protocol): | ||
"""Protocol for any object with ActionMeta metadata (i.e. decorated with @action)""" |
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.
The Protocol is not used and adds unnecessary complexity. We're already doing runtime attribute checking with hasattr(obj, 'action_meta')
throughout the codebase. maybe a util function like is_action
. duck typing
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.
I got rid of the Protocol, I think I tried to solve the type issues with this and couldn't get it working. That's when I went back to checking attributes dynamically. I removed this
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.
@tgasser-nv, thanks, looks good! Please merge after addressing the comments :+1
I addressed all feedback, and unit-tests are passing locally. For some reason the Github action Python 3.10 unit-tests are crashing with I ran locally and all the unit-tests passed on Python 3.10 locally. Full logs here:
With this I'm going to bypass the Python 3.10 unit-test result and merge. |
Type-cleaned all files under `nemoguard/actions` and added them to pyright pre-commit hooks so type-coverage doesn't regress.
Type-cleaned all files under `nemoguard/actions` and added them to pyright pre-commit hooks so type-coverage doesn't regress.
Description
Nemo Guardrails doesn't have type-checking enforced in the project today. This PR will be used to compile type-cleaning related to the
nemoguardrails/actions
module prior to merging into the top-level type-cleaning PR1367.Progress
Prior to this work the
actions
module had 189 errors, 0 warnings, and 0 informations.Related Issue(s)
Checklist