Skip to content

Commit

Permalink
Merge branch 'develop' into feature/server-side-threads
Browse files Browse the repository at this point in the history
Signed-off-by: Razvan Dinu <[email protected]>
  • Loading branch information
drazvan authored Jan 12, 2024
2 parents bfa67b0 + fac5f1a commit e905636
Show file tree
Hide file tree
Showing 26 changed files with 838 additions and 119 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
## [Unreleased]

### Added

- Support for [server-side threads](./docs/user_guides/server-guide.md#threads).
- [#253](https://github.com/NVIDIA/NeMo-Guardrails/pull/253) Support for [server-side threads](./docs/user_guides/server-guide.md#threads).

### Fixed

- [#239](https://github.com/NVIDIA/NeMo-Guardrails/pull/239) Fixed logging issue where `verbose=true` flag did not trigger expected log output.
- [#228](https://github.com/NVIDIA/NeMo-Guardrails/pull/228) Fix docstrings for various functions.

## [0.6.1] - 2023-12-20

Expand Down
81 changes: 64 additions & 17 deletions nemoguardrails/actions/action_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ class ActionDispatcher:
def __init__(
self, load_all_actions: bool = True, config_path: Optional[str] = None
):
"""Initializes an actions dispatcher.
:param load_all_actions: When set, it will load all actions in the `actions` folder
both in the current working directory and in the package.
:param config_path: The path from which the configuration was loaded. If there are
actions at the specified path, we load them as well.
"""
Initializes an actions dispatcher.
Args:
load_all_actions (bool, optional): When set to True, it loads all actions in the
'actions' folder both in the current working directory and in the package.
config_path (str, optional): The path from which the configuration was loaded.
If there are actions at the specified path, it loads them as well.
"""
log.info("Initializing action dispatcher")

Expand Down Expand Up @@ -72,13 +73,21 @@ def __init__(

@property
def registered_actions(self):
"""
Gets the dictionary of registered actions.
Returns:
dict: A dictionary where keys are action names and values are callable action functions.
"""
return self._registered_actions

def load_actions_from_path(self, path: str):
"""Loads all actions from the specified path.
It will load all actions in the `actions.py` file if it exists and all actions
inside the `actions` folder if it exists.
This method loads all actions from the `actions.py` file if it exists and
all actions inside the `actions` folder if it exists.
Args:
path (str): A string representing the path from which to load actions.
"""
actions_path = os.path.join(path, "actions")
if os.path.exists(actions_path):
Expand All @@ -95,9 +104,10 @@ def register_action(
):
"""Registers an action with the given name.
:param name: The name of the action.
:param action: The action function.
:param override: If an action already exists, whether it should be overriden or not.
Args:
action (callable): The action function.
name (Optional[str]): The name of the action. Defaults to None.
override (bool): If an action already exists, whether it should be overridden or not.
"""
if name is None:
action_meta = getattr(action, "action_meta", None)
Expand All @@ -110,7 +120,13 @@ def register_action(
self._registered_actions[name] = action

def register_actions(self, actions_obj: any, override: bool = True):
"""Registers all the actions from the given object."""
"""Registers all the actions from the given object.
Args:
actions_obj (any): The object containing actions.
override (bool): If an action already exists, whether it should be overridden or not.
"""

# Register the actions
for attr in dir(actions_obj):
val = getattr(actions_obj, attr)
Expand All @@ -119,13 +135,27 @@ def register_actions(self, actions_obj: any, override: bool = True):
self.register_action(val, override=override)

def get_action(self, name: str) -> callable:
"""Get the registered action by name.
Args:
name (str): The name of the action.
Returns:
callable: The registered action.
"""
return self._registered_actions.get(name)

async def execute_action(
self, action_name: str, params: Dict[str, Any]
) -> Tuple[Union[str, Dict[str, Any]], str]:
"""Endpoint called from action server to execute an action.
This endpoint interacts with different supported actions
"""Execute a registered action.
Args:
action_name (str): The name of the action to execute.
params (Dict[str, Any]): Parameters for the action.
Returns:
Tuple[Union[str, Dict[str, Any]], str]: A tuple containing the result and status.
"""

if action_name in self._registered_actions:
Expand Down Expand Up @@ -176,12 +206,23 @@ async def execute_action(
return None, "failed"

def get_registered_actions(self) -> List[str]:
"""Endpoint called from action server to get the list of available actions"""
"""Get the list of available actions.
Returns:
List[str]: List of available actions.
"""
return list(self._registered_actions.keys())

@staticmethod
def _load_actions_from_module(filepath: str):
"""Loads the actions from the specified python module."""
"""Loads the actions from the specified python module.
Args:
filepath (str): The path of the Python module.
Returns:
Dict: Dictionary of loaded actions.
"""
action_objects = {}
filename = os.path.basename(filepath)

Expand Down Expand Up @@ -218,7 +259,13 @@ def _load_actions_from_module(filepath: str):
@staticmethod
def _find_actions(directory) -> Dict:
"""Loop through all the subdirectories and check for the class with @action
decorator and add in action_classes dict
decorator and add in action_classes dict.
Args:
directory: The directory to search for actions.
Returns:
Dict: Dictionary of found actions.
"""
action_objects = {}

Expand Down
23 changes: 23 additions & 0 deletions nemoguardrails/actions/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,22 @@

# A decorator that sets a property on the function to indicate if it's a system action or not.
def action(is_system_action: bool = False, name: Optional[str] = None):
"""Decorator to mark a function or class as an action.
Args:
is_system_action (bool): Flag indicating if the action is a system action.
name (Optional[str]): The name to associate with the action.
Returns:
callable: The decorated function or class.
"""

def decorator(fn_or_cls):
"""Inner decorator function to add metadata to the action.
Args:
fn_or_cls: The function or class being decorated.
"""
fn_or_cls.action_meta = {
"name": name or fn_or_cls.__name__,
"is_system_action": is_system_action,
Expand All @@ -31,6 +46,14 @@ def decorator(fn_or_cls):

@dataclass
class ActionResult:
"""Data class representing the result of an action.
Attributes:
return_value (Optional[Any]): The value returned by the action.
events (Optional[List[dict]]): The events to be added to the stream.
context_updates (Optional[dict]): Updates made to the context by this action.
"""

# The value returned by the action
return_value: Optional[Any] = None

Expand Down
10 changes: 9 additions & 1 deletion nemoguardrails/actions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ async def create_event(
event: dict,
context: Optional[dict] = None,
):
"""Checks the facts for the bot response."""
"""Creates an event for the bot based on the provided data.
Args:
event (dict): The input event data.
context (Optional[dict]): The context for the action. Defaults to None.
Returns:
ActionResult: An action result containing the created event.
"""

event_dict = new_event_dict(
event["_type"], **{k: v for k, v in event.items() if k != "_type"}
Expand Down
13 changes: 10 additions & 3 deletions nemoguardrails/actions/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,17 @@
async def wolfram_alpha_request(
query: Optional[str] = None, context: Optional[dict] = None
):
"""Makes a request to the Wolfram Alpha API
"""Makes a request to the Wolfram Alpha API.
:param context: The context for the execution of the action.
:param query: The query for Wolfram.
Args:
query (Optional[str]): The query for Wolfram Alpha. Defaults to None.
context (Optional[dict]): The context for the execution of the action. Defaults to None.
Returns:
ActionResult or str: The result of the Wolfram Alpha request.
Raises:
Exception: If no query is provided to Wolfram Alpha.
"""
# If we don't have an explicit query, we take the last user message
if query is None and context is not None:
Expand Down
21 changes: 20 additions & 1 deletion nemoguardrails/actions/retrieve_relevant_chunks.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,26 @@ async def retrieve_relevant_chunks(
context: Optional[dict] = None,
kb: Optional[KnowledgeBase] = None,
):
"""Retrieve relevant chunks from the knowledge base and add them to the context."""
"""Retrieve relevant knowledge chunks and update the context.
Args:
context (Optional[dict]): The context for the execution of the action. Defaults to None.
kb (Optional[KnowledgeBase]): The KnowledgeBase to search for relevant chunks. Defaults to None.
Returns:
ActionResult: An action result containing the retrieved relevant chunks.
Note:
This action retrieves relevant chunks from the KnowledgeBase based on the user's last message
and updates the context with the information.
Example:
```
result = await retrieve_relevant_chunks(context=my_context, kb=my_knowledge_base)
print(result.return_value) # Relevant chunks as a string
print(result.context_updates) # Updated context with relevant chunks
```
"""
user_message = context.get("last_user_message")
context_updates = {}

Expand Down
15 changes: 13 additions & 2 deletions nemoguardrails/actions/summarize_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,20 @@

@action(name="summarize_document")
class SummarizeDocument:
"""Sample implementation of a document summarization action.
"""Action for summarizing a document.
The implementation uses the summarization chain from LangChain.
This class provides a sample implementation of document summarization using LangChain's summarization chain.
Args:
document_path (str): The path to the document to be summarized.
llm (BaseLLM): The Language Model for the summarization process.
Example:
```python
summarizer = SummarizeDocument(document_path="path/to/document.txt", llm=my_language_model)
result = summarizer.run()
print(result) # The summarized document
```
"""

def __init__(self, document_path: str, llm: BaseLLM):
Expand Down
13 changes: 12 additions & 1 deletion nemoguardrails/actions_server/actions_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@


class RequestBody(BaseModel):
"""Request body for executing an action."""

action_name: str = ""
action_parameters: Dict = Field(
default={}, description="The list of action parameters."
)


class ResponseBody(BaseModel):
"""Response body for action execution."""

status: str = "success" # success / failed
result: Optional[str]

Expand All @@ -55,7 +59,14 @@ class ResponseBody(BaseModel):
response_model=ResponseBody,
)
async def run_action(body: RequestBody):
"""Execute action_name with action_parameters and return result."""
"""Execute the specified action and return the result.
Args:
body (RequestBody): The request body containing action_name and action_parameters.
Returns:
dict: The response containing the execution status and result.
"""

log.info(f"Request body: {body}")
result, status = await app.action_dispatcher.execute_action(
Expand Down
27 changes: 26 additions & 1 deletion nemoguardrails/cli/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@


async def input_async(prompt_message: str = "") -> str:
"""Asynchronously read user input with a prompt.
Args:
prompt_message (str): The message to display as a prompt. Defaults to an empty string.
Returns:
str: The user's input.
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, input, prompt_message)

Expand All @@ -37,6 +45,15 @@ async def run_chat_async(
server_url: Optional[str] = None,
config_id: Optional[str] = None,
):
"""Asynchronously run a chat session in the terminal.
Args:
config_path (Optional[str]): The path to the configuration file. Defaults to None.
verbose (bool): Whether to run in verbose mode. Defaults to False.
streaming (bool): Whether to enable streaming mode. Defaults to False.
server_url (Optional[str]): The URL of the chat server. Defaults to None.
config_id (Optional[str]): The configuration ID. Defaults to None.
"""
if config_path is None and server_url is None:
raise RuntimeError(
"At least one of `config_path` or `server-url` must be provided."
Expand Down Expand Up @@ -114,7 +131,15 @@ def run_chat(
server_url: Optional[str] = None,
config_id: Optional[str] = None,
):
"""Runs a chat session in the terminal."""
"""Run a chat session in the terminal.
Args:
config_path (Optional[str]): The path to the configuration file. Defaults to None.
verbose (bool): Whether to run in verbose mode. Defaults to False.
streaming (bool): Whether to enable streaming mode. Defaults to False.
server_url (Optional[str]): The URL of the chat server. Defaults to None.
config_id (Optional[str]): The configuration ID. Defaults to None.
"""
asyncio.run(
run_chat_async(
config_path=config_path,
Expand Down
Loading

0 comments on commit e905636

Please sign in to comment.