Skip to content

Commit

Permalink
Merge branch 'master' into litellm
Browse files Browse the repository at this point in the history
  • Loading branch information
sfahad1414 committed Jul 3, 2024
2 parents 779e519 + 8fad4fc commit c3316a9
Show file tree
Hide file tree
Showing 25 changed files with 1,696 additions and 172 deletions.
9 changes: 9 additions & 0 deletions kairon/api/app/routers/bot/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,3 +602,12 @@ async def disable_live_agent(
mongo_processor.disable_live_agent(current_user.get_bot())
return Response(message="Live Agent Action disabled!")


@router.get("/actions", response_model=Response)
async def list_available_actions(
current_user: User = Security(Authentication.get_current_user_and_bot, scopes=TESTER_ACCESS)):
"""
Returns list of all actions for bot.
"""
actions = list(mongo_processor.list_all_actions(bot=current_user.get_bot()))
return Response(data=actions)
10 changes: 10 additions & 0 deletions kairon/api/app/routers/system.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import APIRouter
from rasa.shared.core.constants import DEFAULT_INTENTS, DEFAULT_ACTION_NAMES, DEFAULT_SLOT_NAMES
from kairon.shared.utils import Utility
from kairon.api.models import Response

Expand Down Expand Up @@ -27,3 +28,12 @@ async def get_templates():
Fetches use-case templates name
"""
return {"data": {"use-cases": Utility.list_directories("./template/use-cases")}}


@router.get("/default/names", response_model=Response)
async def get_default_names():
"""
Fetches the default intents, action names, and slot names.
"""
default_names = DEFAULT_INTENTS + DEFAULT_ACTION_NAMES + list(DEFAULT_SLOT_NAMES)
return Response(data={"default_names": default_names})
41 changes: 40 additions & 1 deletion kairon/shared/data/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,20 @@ def fetch_actions(self, bot: Text, status=True):
:return: list of actions
"""
actions = Actions.objects(bot=bot, status=status).values_list("name")
return list(actions)
actions_list = list(actions)

for story in Stories.objects(bot=bot, status=status):
for event in story.events:
if event.name == 'action_listen':
if 'action_listen' not in actions_list:
actions_list.append('action_listen')

for story in MultiflowStories.objects(bot=bot, status=status):
for event in story.events:
if event.step.name == 'stop_flow_action' and 'stop_flow_action' not in actions_list:
actions_list.append('stop_flow_action')

return actions_list

def __prepare_training_actions(self, bot: Text):
actions = self.fetch_actions(bot)
Expand Down Expand Up @@ -1698,6 +1711,10 @@ def __prepare_training_multiflow_story_events(self, events, metadata, timestamp)
story_events.append(
SlotSet(key=event.name, value=event.value, timestamp=timestamp)
)
elif event.step_type == StoryStepType.stop_flow_action.value:
story_events.append(
ActionExecuted(action_name=ACTION_LISTEN_NAME, timestamp=timestamp)
)
else:
story_events.append(
ActionExecuted(action_name=event.name, timestamp=timestamp)
Expand Down Expand Up @@ -3348,6 +3365,9 @@ def get_stories(self, bot: Text):
step["type"] = StoryStepType.web_search_action.value
elif event['name'] == 'live_agent_action':
step["type"] = StoryStepType.live_agent_action.value
elif event['name'] == 'action_listen':
step["type"] = StoryStepType.stop_flow_action.value
step["name"] = 'stop_flow_action'
elif str(event["name"]).startswith("utter_"):
step["type"] = StoryStepType.bot.value
else:
Expand Down Expand Up @@ -4325,6 +4345,25 @@ def list_web_search_actions(self, bot: Text, with_doc_id: bool = True):
action.pop("status")
yield action

def list_all_actions(self, bot: str, with_doc_id: bool = True):
"""
Fetches all actions from the collection
:param bot: bot id
:param with_doc_id: return document id along with action configuration if True
:return: List of actions.
"""
for action in Actions.objects(bot=bot, status=True):
action = action.to_mongo().to_dict()
if with_doc_id:
action["_id"] = str(action["_id"])
else:
action.pop("_id")
action.pop("user")
action.pop("bot")
action.pop("status")
action.pop("timestamp")
yield action

def add_slot(self, slot_value: Dict, bot, user, raise_exception_if_exists=True):
"""
Adds slot if it doesn't exist, updates slot if it exists
Expand Down
49 changes: 49 additions & 0 deletions kairon/shared/kairon_yaml_story_writer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import List, Text, Any, Union
from collections import OrderedDict

from rasa.shared.core.training_data.story_writer.yaml_story_writer import YAMLStoryWriter
from rasa.shared.core.training_data.structures import StoryStep
from rasa.shared.core.events import Event


from rasa.shared.core.training_data.story_reader.yaml_story_reader import KEY_STORY_NAME, KEY_STEPS


class kaironYAMLStoryWriter(YAMLStoryWriter):
"""Custom YAML Story Writer with overridden _filter_event function."""

def process_story_step(self, story_step: StoryStep) -> OrderedDict:
"""Converts a single story step into an ordered dict with a custom filter."""
result: OrderedDict[Text, Any] = OrderedDict()
result[KEY_STORY_NAME] = story_step.block_name
steps = self.process_checkpoints(story_step.start_checkpoints)
for event in story_step.events:
if not self._filter_event(event):
continue
processed = self.process_event(event)
if processed:
steps.append(processed)

steps.extend(self.process_checkpoints(story_step.end_checkpoints))

result[KEY_STEPS] = steps

return result

@staticmethod
def _filter_event(event: Union["Event", List["Event"]]) -> bool:
"""Identifies if the event should be converted/written.
Args:
event: target event to check.
Returns:
`True` if the event should be converted/written, `False` otherwise.
"""
if isinstance(event, list):
return True

return (
not StoryStep.is_action_unlikely_intent(event)
and not StoryStep.is_action_session_start(event)
)
12 changes: 11 additions & 1 deletion kairon/shared/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
SLOT_TYPE,
DEFAULT_LLM
)
from kairon.shared.kairon_yaml_story_writer import kaironYAMLStoryWriter
from .data.dto import KaironStoryStep
from .models import StoryStepType, LlmPromptType, LlmPromptSource
from ..exceptions import AppException
Expand Down Expand Up @@ -1167,7 +1168,8 @@ def write_training_data(
yaml.safe_dump(domain, open(domain_path, "w"))
Utility.write_to_file(nlu_path, nlu_as_str)
Utility.write_to_file(config_path, config_as_str)
YAMLStoryWriter().dump(stories_path, stories.story_steps)
story_writer = kaironYAMLStoryWriter()
story_writer.dump(stories_path, stories.story_steps)
if rules:
YAMLStoryWriter().dump(rules_path, rules.story_steps)
if actions:
Expand Down Expand Up @@ -2232,6 +2234,12 @@ def validate_steps(steps: List, flow_metadata: List):
raise AppException(
"Intent can only have one connection of action type or slot type"
)
if [
successor
for successor in story_graph.successors(story_node)
if successor.step_type == "STOP_FLOW_ACTION"
]:
raise AppException("STOP_FLOW_ACTION cannot be a successor of an intent!")
if story_node.step_type == "SLOT" and story_node.value:
if story_node.value is not None and not isinstance(
story_node.value, (str, int, bool)
Expand All @@ -2247,6 +2255,8 @@ def validate_steps(steps: List, flow_metadata: List):
raise AppException("Slots cannot be leaf nodes!")
if story_node.step_type == "INTENT" and story_node.node_id in leaf_node_ids:
raise AppException("Leaf nodes cannot be intent")
if story_node.step_type == "STOP_FLOW_ACTION" and story_node.node_id not in leaf_node_ids:
raise AppException("STOP_FLOW_ACTION should be a leaf node!")
if flow_metadata:
for value in flow_metadata:
if value.get("flow_type") == "RULE":
Expand Down
Loading

0 comments on commit c3316a9

Please sign in to comment.