From 1083e602f8128c832d83cbab6b58f771e41cd879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Sch=C3=BCller?= Date: Fri, 22 Mar 2024 12:14:13 +0100 Subject: [PATCH 1/5] Refactor to use meta decorator for bot/user action/intent marking and `exclude_from_llm` tags --- nemoguardrails/actions/v2_x/generation.py | 18 ++-- .../colang/v2_x/lang/transformer.py | 10 ++- nemoguardrails/colang/v2_x/library/avatars.co | 48 +++++------ nemoguardrails/colang/v2_x/library/core.co | 4 +- nemoguardrails/colang/v2_x/runtime/flows.py | 25 ++++-- nemoguardrails/colang/v2_x/runtime/runtime.py | 8 +- .../colang/v2_x/runtime/statemachine.py | 84 ++++++++++++------- nemoguardrails/colang/v2_x/runtime/utils.py | 21 ++++- .../multi_modal_demo_v2_x/demo.co | 28 +++---- .../show_case_back_channelling_interaction.co | 4 +- tests/test_configs/mvp_v2_x_d/config.co | 19 ++--- tests/v2_x/test_decorator_parsing.py | 4 +- tests/v2_x/test_flow_mechanics.py | 50 ++++++++++- 13 files changed, 209 insertions(+), 114 deletions(-) diff --git a/nemoguardrails/actions/v2_x/generation.py b/nemoguardrails/actions/v2_x/generation.py index 5aba962ad..db5e461ce 100644 --- a/nemoguardrails/actions/v2_x/generation.py +++ b/nemoguardrails/actions/v2_x/generation.py @@ -117,15 +117,9 @@ async def _init_flows_index(self) -> None: if colang_flow: assert isinstance(flow, Flow) # Check if we need to exclude this flow. - # TODO: deprecate the use of the "# meta: " comment in favor of - # @meta(llm_exclude=True) - if "# meta: exclude from llm" in colang_flow or ( - "exclude_from_llm" not in flow.file_info - or flow.file_info["exclude_from_llm"] - or ( - "meta" in flow.decorators - and flow.decorators["meta"].parameters.get("llm_exclude") - ) + if flow.file_info.get("exclude_from_llm") or ( + "meta" in flow.decorators + and flow.decorators["meta"].parameters.get("llm_exclude") ): continue @@ -197,7 +191,7 @@ async def generate_user_intent( if isinstance(flow_id, str) and ( flow_config is None or ( - "# meta: user intent" in flow_config.source_code + flow_config.has_meta_tag("user_intent") and flow_id not in potential_user_intents ) ): @@ -456,8 +450,8 @@ async def generate_flow_continuation( return { "name": flow_name, "parameters": flow_parameters, - "body": f"flow {flow_name}\n" - + f' # meta: bot intent = "{intent}"\n' + "body": f'@meta(bot_intent="{intent}")\n' + + f"flow {flow_name}\n" + "\n".join([" " + l.strip(" ") for l in lines]), } diff --git a/nemoguardrails/colang/v2_x/lang/transformer.py b/nemoguardrails/colang/v2_x/lang/transformer.py index 964d5724a..746756812 100644 --- a/nemoguardrails/colang/v2_x/lang/transformer.py +++ b/nemoguardrails/colang/v2_x/lang/transformer.py @@ -209,7 +209,7 @@ def _flow_def(self, children: dict, meta: Meta) -> Flow: ) def _remove_source_code_comments(self, source: str) -> str: - pattern = r"#(?! (llm:|meta:))[^\n]*" + pattern = r"#[^\n]*" return re.sub(pattern, "", source) def _spec_op(self, children: list, meta: Meta) -> SpecOp: @@ -558,9 +558,11 @@ def __default__(self, data, children: list, meta: Meta) -> dict: # Transform tokens to dicts children = [ - child - if not isinstance(child, Token) - else {"_type": child.type, "elements": [child.value]} + ( + child + if not isinstance(child, Token) + else {"_type": child.type, "elements": [child.value]} + ) for child in children ] diff --git a/nemoguardrails/colang/v2_x/library/avatars.co b/nemoguardrails/colang/v2_x/library/avatars.co index 6f63d7729..44d697307 100644 --- a/nemoguardrails/colang/v2_x/library/avatars.co +++ b/nemoguardrails/colang/v2_x/library/avatars.co @@ -1,5 +1,3 @@ -# meta: exclude from llm - ################################################################ # COLANG 2.0 AVATARS CORE LIBRARY # VERSION 0.2.0 @@ -32,13 +30,15 @@ #------- ################################################################ +# meta: exclude from llm + # ----------------------------------- # User UMIM event wrapper flows # DON'T CHANGE! Currently, hard-wired with LLM prompt generation # ----------------------------------- +@meta(user_action='user said "{$transcript}"') flow user said $text -> $transcript - # meta: user action match UtteranceUserAction.Finished(final_transcript=$text) as $event $transcript = $event.final_transcript @@ -68,8 +68,8 @@ flow user saying something -> $transcript #send UserActionLog(flow_id="user saying", parameter=$event.interim_transcript, intent_flow_id="user saying something") $transcript = $event.interim_transcript +@meta(user_action='selected choice "{$choice}"') flow user selected choice $choice_id -> $choice - # meta: user action match VisualChoiceSceneAction.ChoiceUpdated(current_choice=[$choice_id]) as $event $choice = $event.current_choice @@ -83,9 +83,9 @@ flow unhandled user intent -> $intent $intent = $event.flow_id @loop("user_was_silent") +@meta(user_intent=True) flow user was silent $time_s """Triggers when user was silent for $time_s seconds.""" - # meta: user intent while True start wait $time_s as $timer_ref when $timer_ref.Finished() @@ -97,9 +97,9 @@ flow user was silent $time_s send $timer_ref.Stop() @loop("user_did_not_respond") +@meta(user_intent=True) flow user didnt respond $time_s """Triggers when user was silent for $time_s seconds while bot was silent.""" - # meta: user intent while True start wait $time_s as $timer_ref when $timer_ref.Finished() @@ -113,9 +113,9 @@ flow user didnt respond $time_s orwhen UtteranceUserAction.Finished() or UtteranceBotAction.Finished() send $timer_ref.Stop() +@meta(user_intent=True) flow user interrupted bot talking $sentence_length=15 """Triggers when the user talked while bot is speaking.""" - # meta: user intent global $bot_talking_state while True if $bot_talking_state @@ -194,15 +194,15 @@ flow bot started a posture -> $posture match FlowStarted(flow_id="bot posture") as $event $posture = $event.posture +@meta(bot_action=True) flow bot started an action -> $action - # meta: bot action match bot started saying something as $action or bot started a gesture as $action or bot started a posture as $action @loop("bot_was_silent") +@meta(bot_intent=True) flow bot was silent $time_s - # meta: bot intent while True start wait $time_s as $timer_ref when $timer_ref.Finished() @@ -222,33 +222,33 @@ flow _bot_say $text """It's an internal helper for higher semantic level flows""" await UtteranceBotAction(script=$text) as $action +@meta(bot_action=True) flow bot gesture $gesture - # meta: bot action await GestureBotAction(gesture=$gesture) as $action +@meta(bot_action=True) flow bot gesture with delay $gesture $delay - # meta: bot action wait $delay bot gesture $gesture +@meta(bot_action=True) flow bot posture $posture - # meta: bot action await PostureBotAction(posture=$posture) as $action +@meta(bot_action=True) flow scene show choice $prompt - # meta: bot action await VisualChoiceSceneAction(prompt=$prompt,choice_type="selection", allow_multiple_choices=False) as $action +@meta(bot_action=True) flow scene show textual information $title $text $header_image - # meta: bot action await VisualInformationSceneAction(title=$title, support_prompts=[], content=[{"image":$header_image},{"text":$text}]) as $action +@meta(bot_action=True) flow scene show short information $info - # meta: bot action await VisualInformationSceneAction(title=$info, support_prompts=[], content=[]) as $action +@meta(bot_action=True) flow scene show form $prompt - # meta: bot action await VisualInformationSceneAction(prompt=$prompt) as $action # ---------------------------------- @@ -256,36 +256,36 @@ flow scene show form $prompt # DON'T CHANGE! Currently, hard-wired with LLM prompt generation # ----------------------------------- +@meta(bot_action=True) flow bot say $text - # meta: bot action await _bot_say $text flow bot say something like $text $variation = i"Return a single string that is a new variation of: {$text}" await bot say $variation +@meta(bot_action=True) flow bot inform $text - # meta: bot action await _bot_say $text +@meta(bot_action=True) flow bot ask $text - # meta: bot action await _bot_say $text +@meta(bot_action=True) flow bot express $text - # meta: bot action await _bot_say $text +@meta(bot_action=True) flow bot respond $text - # meta: bot action await _bot_say $text +@meta(bot_action=True) flow bot clarify $text - # meta: bot action await _bot_say $text +@meta(bot_action=True) flow bot suggest $text - # meta: bot action await _bot_say $text # ---------------------------------- @@ -526,8 +526,8 @@ flow execute llm instruction $instructions match $event_ref.flow.Finished() await RemoveFlowsAction(flow_ids=[$flow_info.name]) +@meta(user_intent=True) flow user requested a task - # meta: user intent user said "do something" or user said "can you do something" or user said "please do" diff --git a/nemoguardrails/colang/v2_x/library/core.co b/nemoguardrails/colang/v2_x/library/core.co index 3f8c7da68..00e4daa34 100644 --- a/nemoguardrails/colang/v2_x/library/core.co +++ b/nemoguardrails/colang/v2_x/library/core.co @@ -1,5 +1,5 @@ +@meta(user_action='user said "{$transcript}"') flow user said $text -> $transcript - # meta: user action match UtteranceUserAction.Finished(final_transcript=$text) as $event $transcript = $event.arguments.final_transcript @@ -16,6 +16,6 @@ flow _bot_say $text """It's an internal helper for higher semantic level flows""" await UtteranceBotAction(script=$text) as $action +@meta(bot_action=True) flow bot say $text - # meta: bot action await _bot_say $text diff --git a/nemoguardrails/colang/v2_x/runtime/flows.py b/nemoguardrails/colang/v2_x/runtime/flows.py index f00b3590c..b7290627d 100644 --- a/nemoguardrails/colang/v2_x/runtime/flows.py +++ b/nemoguardrails/colang/v2_x/runtime/flows.py @@ -338,7 +338,7 @@ class FlowConfig: # The decorators for the flow. # Maps the name of the applied decorators to the arguments. # If positional arguments are provided, then the "$0", "$1", ... are used as the keys. - decorators: Dict[str, Decorator] = field(default_factory=dict) + decorators: Dict[str, Dict[str, Any]] = field(default_factory=dict) # The flow return member variables return_members: List[FlowReturnMemberDef] = field(default_factory=list) @@ -353,11 +353,11 @@ class FlowConfig: def loop_id(self) -> Optional[str]: """Return the interaction loop id if set.""" if "loop" in self.decorators: - decorator = self.decorators["loop"] - if "id" in decorator.parameters: - return decorator.parameters["id"] - elif "$0" in decorator.parameters: - return decorator.parameters["$0"] + parameters = self.decorators["loop"] + if "id" in parameters: + return parameters["id"] + elif "$0" in parameters: + return parameters["$0"] else: log.warning( "No loop id specified for @loop decorator for flow `%s`", self.id @@ -376,10 +376,21 @@ def loop_type(self) -> InteractionLoopType: return InteractionLoopType.PARENT @property - def is_override(self) -> True: + def is_override(self) -> bool: """Return True if flow is marked as override.""" return "override" in self.decorators + def has_meta_tag(self, tag_name: str) -> bool: + """Return True if flow is marked with given meta tag, e.g. `@meta(llm_exclude=True)`.""" + return "meta" in self.decorators and tag_name in self.decorators["meta"] + + def meta_tag(self, tag_name: str) -> Optional[Any]: + """Return the parameter of the meta tag or None if it does not exist.""" + if not self.has_meta_tag(tag_name): + return None + else: + return self.decorators["meta"][tag_name] + class FlowHeadStatus(Enum): """The status of a flow head.""" diff --git a/nemoguardrails/colang/v2_x/runtime/runtime.py b/nemoguardrails/colang/v2_x/runtime/runtime.py index 915ee0941..bf30c4d31 100644 --- a/nemoguardrails/colang/v2_x/runtime/runtime.py +++ b/nemoguardrails/colang/v2_x/runtime/runtime.py @@ -26,7 +26,6 @@ from nemoguardrails.actions.actions import ActionResult from nemoguardrails.colang import parse_colang_file from nemoguardrails.colang.runtime import Runtime -from nemoguardrails.colang.v2_x.lang.colang_ast import Flow from nemoguardrails.colang.v2_x.runtime.flows import ( ColangRuntimeError, Event, @@ -41,7 +40,10 @@ initialize_state, run_to_completion, ) -from nemoguardrails.colang.v2_x.runtime.utils import create_flow_configs_from_flow_list +from nemoguardrails.colang.v2_x.runtime.utils import ( + convert_decorator_list_to_dictionary, + create_flow_configs_from_flow_list, +) from nemoguardrails.rails.llm.config import RailsConfig from nemoguardrails.utils import new_event_dict @@ -98,7 +100,9 @@ async def _add_flows_action(self, state: "State", **args: dict) -> List[str]: flow_config = FlowConfig( id=flow.name, elements=expand_elements(flow.elements, state.flow_configs), + decorators=convert_decorator_list_to_dictionary(flow.decorators), parameters=flow.parameters, + return_members=flow.return_members, source_code=flow.source_code, ) diff --git a/nemoguardrails/colang/v2_x/runtime/statemachine.py b/nemoguardrails/colang/v2_x/runtime/statemachine.py index 4d4c44895..528274daf 100644 --- a/nemoguardrails/colang/v2_x/runtime/statemachine.py +++ b/nemoguardrails/colang/v2_x/runtime/statemachine.py @@ -1382,19 +1382,23 @@ def _finish_flow( ) _push_internal_event(state, event) - # Check if it was an user/bot intent/action flow a generate internal events + # Check if it was an user/bot intent/action flow and generate internal events # TODO: Let's refactor that once we have the new llm prompting event_type: Optional[str] = None - source_code = state.flow_configs[flow_state.flow_id].source_code - if source_code is not None: - if "meta: user intent" in source_code: - event_type = InternalEvents.USER_INTENT_LOG - elif "meta: bot intent" in source_code: - event_type = InternalEvents.BOT_INTENT_LOG - elif "meta: user action" in source_code: - event_type = InternalEvents.USER_ACTION_LOG - elif "meta: bot action" in source_code: - event_type = InternalEvents.BOT_ACTION_LOG + flow_config = state.flow_configs[flow_state.flow_id] + meta_tag_parameters = None + if flow_config.has_meta_tag("user_intent"): + meta_tag_parameters = flow_config.meta_tag("user_intent") + event_type = InternalEvents.USER_INTENT_LOG + elif flow_config.has_meta_tag("bot_intent"): + meta_tag_parameters = flow_config.meta_tag("bot_intent") + event_type = InternalEvents.BOT_INTENT_LOG + elif flow_config.has_meta_tag("user_action"): + meta_tag_parameters = flow_config.meta_tag("user_action") + event_type = InternalEvents.USER_ACTION_LOG + elif flow_config.has_meta_tag("bot_action"): + meta_tag_parameters = flow_config.meta_tag("bot_action") + event_type = InternalEvents.BOT_ACTION_LOG if ( event_type == InternalEvents.USER_INTENT_LOG @@ -1425,27 +1429,43 @@ def _finish_flow( # TODO: Generalize to multi intents intent = None for flow_state_uid in reversed(hierarchy): - flow_config = state.flow_configs[state.flow_states[flow_state_uid].flow_id] - if flow_config.source_code is not None: - match = re.search( - r'#\W*meta:\W*(bot intent|user intent)(\W*=\W*"([a-zA-Z0-9_ ]*)")?', - flow_config.source_code, - ) - if match: - if match.group(3) is not None: - intent = match.group(3) - else: - intent = flow_config.id - - event = create_internal_event( - event_type, - { - "flow_id": flow_state.flow_id, - "parameter": flow_state.context.get("$0", None), - "intent_flow_id": intent, - }, - matching_scores, - ) + intent_flow_config = state.flow_configs[ + state.flow_states[flow_state_uid].flow_id + ] + if intent_flow_config.has_meta_tag("bot_intent"): + intent = intent_flow_config.meta_tag("bot_intent") + elif intent_flow_config.has_meta_tag("user_intent"): + intent = intent_flow_config.meta_tag("user_intent") + + if not isinstance(intent, str): + intent = flow_state.flow_id + + # Create event based on meta tag + if isinstance(meta_tag_parameters, str): + name = eval_expression( + '"' + meta_tag_parameters.replace('"', '\\"') + '"', + _get_eval_context(state, flow_state), + ) + event = create_internal_event( + event_type, + { + "flow_id": name, + "parameter": None, + "intent_flow_id": intent, + }, + matching_scores, + ) + else: + # TODO: Generalize to multi flow parameters + event = create_internal_event( + event_type, + { + "flow_id": flow_state.flow_id, + "parameter": flow_state.context.get("$0", None), + "intent_flow_id": intent, + }, + matching_scores, + ) _push_internal_event(state, event) log.info( diff --git a/nemoguardrails/colang/v2_x/runtime/utils.py b/nemoguardrails/colang/v2_x/runtime/utils.py index ef3626083..056924741 100644 --- a/nemoguardrails/colang/v2_x/runtime/utils.py +++ b/nemoguardrails/colang/v2_x/runtime/utils.py @@ -14,9 +14,9 @@ # limitations under the License. import uuid -from typing import Dict, List +from typing import Any, Dict, List -from nemoguardrails.colang.v2_x.lang.colang_ast import Flow +from nemoguardrails.colang.v2_x.lang.colang_ast import Decorator, Flow from nemoguardrails.colang.v2_x.runtime.flows import ColangSyntaxError, FlowConfig @@ -46,6 +46,20 @@ def new_var_uid() -> str: return str(uuid.uuid4()).replace("-", "_") +def convert_decorator_list_to_dictionary( + decorators: List[Decorator], +) -> Dict[str, Dict[str, Any]]: + """Convert list of decorators to a dictionary merging the parameters of decorators with same name.""" + decorator_dict: Dict[str, Dict[str, Any]] = {} + for decorator in decorators: + item = decorator_dict.get(decorator.name, None) + if item: + item.update(decorator.parameters) + else: + decorator_dict[decorator.name] = decorator.parameters + return decorator_dict + + def create_flow_configs_from_flow_list(flows: List[Flow]) -> Dict[str, FlowConfig]: """Create a flow config dictionary and resolves flow overriding.""" flow_configs: Dict[str, FlowConfig] = {} @@ -54,10 +68,11 @@ def create_flow_configs_from_flow_list(flows: List[Flow]) -> Dict[str, FlowConfi # Create two dictionaries with normal and override flows for flow in flows: assert isinstance(flow, Flow) + config = FlowConfig( id=flow.name, elements=flow.elements, - decorators={decorator.name: decorator for decorator in flow.decorators}, + decorators=convert_decorator_list_to_dictionary(flow.decorators), parameters=flow.parameters, return_members=flow.return_members, source_code=flow.source_code, diff --git a/tests/test_configs/multi_modal_demo_v2_x/demo.co b/tests/test_configs/multi_modal_demo_v2_x/demo.co index 5850960cd..3b9a8dee5 100644 --- a/tests/test_configs/multi_modal_demo_v2_x/demo.co +++ b/tests/test_configs/multi_modal_demo_v2_x/demo.co @@ -4,15 +4,15 @@ import avatars # Skill interaction flows # ---------------------------------- +@meta(exclude_from_llm=True) flow reaction to user silence $time_s $text """Let the bot say something if the user was quite for the specified time.""" - # meta: exclude from llm user was silent $time_s bot inform $text +@meta(exclude_from_llm=True) flow reaction bot question repetition for user silence $time_s $max_repetitions=1 """Repeat previous bot question when use was silent for specified time.""" - # meta: exclude from llm bot asked something as $ref #start_new_flow_instance: $repetition = 0 @@ -40,42 +40,42 @@ flow reaction bot question repetition for user silence $time_s $max_repetitions= # Note: To enable the LLM prompt generation extraction use only one single statement # ----------------------------------- +@meta(bot_intent=True) flow bot express greeting - # meta: bot intent (bot express "Hi there!" or bot express "Welcome!" or bot express "Hello!") and bot gesture "Wave with one hand" +@meta(bot_intent=True) flow bot express feeling well - # meta: bot intent (bot express "I am good!" or bot express "I am great!") and (bot gesture "Thumbs up" or bot gesture "Smile") +@meta(bot_intent=True) flow bot express feeling bad - # meta: bot intent (bot express "I am not good!" or bot express "I am a bit under the weather!") and (bot gesture "Thumbs down" or bot gesture "Sad face") +@meta(bot_intent=True) flow bot inform about service - # meta: bot intent bot inform "You can ask or instruct me whatever you want and I will do it!" and bot gesture "Open up both hands making a presenting gesture" +@meta(bot_intent=True) flow bot ask how are you - # meta: bot intent (bot say "How are you doing?" or bot say "How is it going?") and bot gesture "Pay attention to user" +@meta(bot_intent=True) flow bot make short pause - # meta: bot intent wait 2.0 +@meta(bot_intent=True) flow bot make long pause - # meta: bot intent wait 5.0 # ---------------------------------- @@ -83,18 +83,18 @@ flow bot make long pause # Note: To enable the LLM prompt generation extraction use only one single statement # ----------------------------------- +@meta(user_intent=True) flow user expressed greeting - # meta: user intent user said "hi" or user said "Welcome!" or user said "Hello!" +@meta(user_intent=True) flow user expressed done - # meta: user intent user said (regex("(?i).*done.*|.*end.*showcase.*|.*exit.*")) +@meta(user_intent=True) flow user asked how are you - # meta: user intent user said "how are you" flow user picked number guessing game showcase @@ -278,8 +278,8 @@ flow showcase selector send $idle_posture.Stop() bot say "Welcome back!" +@meta(exclude_from_llm=True) flow main - # meta: exclude from llm #activate catching unexpected user utterance activate ignored_utterance_action_bugfix activate catching start of undefined flows @@ -303,9 +303,9 @@ flow main #showcase proactive turn taking wait indefinitely +@meta(exclude_from_llm=True) @loop("ignored_action_bugfix") flow ignored_utterance_action_bugfix - # meta: exclude from llm global $number_of_failed_utterance_actions if $number_of_failed_utterance_actions == None $number_of_failed_utterance_actions = 0 diff --git a/tests/test_configs/multi_modal_demo_v2_x/show_cases/show_case_back_channelling_interaction.co b/tests/test_configs/multi_modal_demo_v2_x/show_cases/show_case_back_channelling_interaction.co index 0146aa088..8b6fd89ba 100644 --- a/tests/test_configs/multi_modal_demo_v2_x/show_cases/show_case_back_channelling_interaction.co +++ b/tests/test_configs/multi_modal_demo_v2_x/show_cases/show_case_back_channelling_interaction.co @@ -34,9 +34,9 @@ flow bot reacts to user said something user said something with pause 1.0 bot say "Hmm?" +@meta(exclude_from_llm=True) +@meta(user_intent=True) flow user said something with pause $time_s - # meta: exclude from llm - # meta: user intent while True match UtteranceUserAction.Finished() as $event start wait $time_s as $timer_ref diff --git a/tests/test_configs/mvp_v2_x_d/config.co b/tests/test_configs/mvp_v2_x_d/config.co index fd3dcf0fb..91047724a 100644 --- a/tests/test_configs/mvp_v2_x_d/config.co +++ b/tests/test_configs/mvp_v2_x_d/config.co @@ -58,20 +58,20 @@ flow number guessing game # Common +@meta(exclude_from_llm=True) flow user said $text - # meta: exclude from llm match UtteranceUserAction.Finished(final_transcript=$text) +@meta(exclude_from_llm=True) flow user said something - # meta: exclude from llm match UtteranceUserAction.Finished() as $event +@meta(exclude_from_llm=True) flow bot say $text - # meta: exclude from llm await UtteranceBotAction(script=$text) +@meta(exclude_from_llm=True) flow wait - # meta: exclude from llm match SomeRandomEvent() # Custom @@ -113,9 +113,9 @@ flow unknown intent user unknown intent bot comment unsure how to respond +@meta(exclude_from_llm=True) flow fallback """This is the fallback flow that takes care of unhandled user utterances and will generate a flow continuation.""" - # meta: exclude from llm match UnhandledEvent(event="UtteranceUserActionFinished") as $event @@ -137,9 +137,9 @@ flow fallback match FlowStarted(flow_id=$flow_info['name']) as $event_ref match $event_ref.flow.Finished() +@meta(exclude_from_llm=True) flow handling start of undefined flow """If we reach a point where we need to start a flow that does not exist, we generate it.""" - # meta: exclude from llm match UnhandledEvent(event="StartFlow") as $event @@ -154,23 +154,22 @@ flow handling start of undefined flow await AddFlowsAction(config=$flow_source) send StartFlow(flow_id=$flow_id) - +@meta(exclude_from_llm=True) flow do $instructions - # meta: exclude from llm $flow_info = await GenerateFlowFromInstructionsAction(instructions=$instructions) await AddFlowsAction(config=$flow_info.body) send StartFlow(flow_id=$flow_info['name']) match FlowStarted(flow_id=$flow_info['name']) as $event_ref match $event_ref.flow.Finished() +@meta(exclude_from_llm=True) flow custom instructions - # meta: exclude from llm user provided custom instructions $instructions = await GetLastUserMessageAction() do $instructions +@meta(exclude_from_llm=True) flow main - # meta: exclude from llm activate greeting activate custom instructions activate unknown intent diff --git a/tests/v2_x/test_decorator_parsing.py b/tests/v2_x/test_decorator_parsing.py index a36ea75a1..612852ded 100644 --- a/tests/v2_x/test_decorator_parsing.py +++ b/tests/v2_x/test_decorator_parsing.py @@ -30,6 +30,7 @@ def test_1(): return @meta(llm_exclude=True) + @meta(user_intent=True) @loop(id="bla") flow some other flow return @@ -51,4 +52,5 @@ def test_1(): assert flows["some flow"].loop_id == "bla" assert flows["some other flow"].loop_id == "bla" - assert flows["some other flow"].decorators["meta"].parameters["llm_exclude"] + assert flows["some other flow"].decorators["meta"]["llm_exclude"] + assert flows["some other flow"].decorators["meta"]["user_intent"] diff --git a/tests/v2_x/test_flow_mechanics.py b/tests/v2_x/test_flow_mechanics.py index c43ee5c1e..59cea1cb8 100644 --- a/tests/v2_x/test_flow_mechanics.py +++ b/tests/v2_x/test_flow_mechanics.py @@ -1413,5 +1413,53 @@ def test_flow_overriding(): ) +def test_meta_decorators(): + """Test the meta decorator statement.""" + + content = """ + @meta(bot_action=True) + flow bot say $text + start UtteranceBotAction(script=$text) + + @meta(bot_intent=True) + flow bot greet + start bot say "Hi" or bot say "Hello" + + @loop("observer_1") + flow observer_1 + match BotActionLog(flow_id="bot say", intent_flow_id="bot say") + send Success1() + + @loop("observer_2") + flow observer_2 + match BotIntentLog(flow_id="bot greet") + send Success2() + + flow main + activate observer_1 and observer_2 + await bot greet + match Event() + """ + + state = run_to_completion(_init_state(content), start_main_flow_event) + assert is_data_in_events( + state.outgoing_events, + [ + { + "type": "StartUtteranceBotAction", + }, + { + "type": "StopUtteranceBotAction", + }, + { + "type": "Success1", + }, + { + "type": "Success2", + }, + ], + ) + + if __name__ == "__main__": - test_flow_overriding() + test_meta_decorators() From c1f53d39b0f3825fc415b9bb7426ebad5ccd9860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Sch=C3=BCller?= Date: Mon, 25 Mar 2024 10:19:23 +0100 Subject: [PATCH 2/5] Filter duplicated user action log events from llm prompting history --- nemoguardrails/actions/llm/utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/nemoguardrails/actions/llm/utils.py b/nemoguardrails/actions/llm/utils.py index 3a40e563b..0b2a67e43 100644 --- a/nemoguardrails/actions/llm/utils.py +++ b/nemoguardrails/actions/llm/utils.py @@ -173,8 +173,19 @@ def get_colang_history( action_group: List[InternalEvent] = [] current_intent: Optional[str] = None + previous_event = None for event in events: if not isinstance(event, InternalEvent): + # Skip non-internal events + continue + + if ( + event.name == InternalEvents.USER_ACTION_LOG + and previous_event + and events_to_dialog_history([previous_event]) + == events_to_dialog_history([event]) + ): + # Remove duplicated user action log events that stem from the same user event as the previous event continue if ( @@ -191,6 +202,8 @@ def get_colang_history( action_group.append(event) current_intent = event.arguments["intent_flow_id"] + + previous_event = event elif ( event.name == InternalEvents.BOT_INTENT_LOG or event.name == InternalEvents.USER_INTENT_LOG @@ -215,6 +228,8 @@ def get_colang_history( action_group.clear() current_intent = None + previous_event = event + if action_group: new_history.append(events_to_dialog_history(action_group)) From aacb8a4e5c23d2a6f23f114724d5dc1f6a5eee5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Sch=C3=BCller?= Date: Mon, 25 Mar 2024 15:17:12 +0100 Subject: [PATCH 3/5] Remove unused import --- nemoguardrails/colang/v2_x/runtime/flows.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nemoguardrails/colang/v2_x/runtime/flows.py b/nemoguardrails/colang/v2_x/runtime/flows.py index 03ca21d39..44c4ccbd2 100644 --- a/nemoguardrails/colang/v2_x/runtime/flows.py +++ b/nemoguardrails/colang/v2_x/runtime/flows.py @@ -28,7 +28,6 @@ from dataclasses_json import dataclass_json from nemoguardrails.colang.v2_x.lang.colang_ast import ( - Decorator, ElementType, FlowParamDef, FlowReturnMemberDef, From 3fce4c97833e438f40fa232453c03434d18a2472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Sch=C3=BCller?= Date: Mon, 25 Mar 2024 15:23:52 +0100 Subject: [PATCH 4/5] Fix merging issue --- nemoguardrails/colang/v2_x/runtime/statemachine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nemoguardrails/colang/v2_x/runtime/statemachine.py b/nemoguardrails/colang/v2_x/runtime/statemachine.py index caafec98a..226d04931 100644 --- a/nemoguardrails/colang/v2_x/runtime/statemachine.py +++ b/nemoguardrails/colang/v2_x/runtime/statemachine.py @@ -1466,8 +1466,8 @@ def _finish_flow( elif intent_flow_config.has_meta_tag("user_intent"): intent = intent_flow_config.meta_tag("user_intent") - if not isinstance(intent, str): - intent = flow_state.flow_id + if not isinstance(intent, str): + intent = flow_state.flow_id # Create event based on meta tag if isinstance(meta_tag_parameters, str): From 44afb591bd5bfc14f95d96d3dcc6bccf5f08a4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Sch=C3=BCller?= Date: Mon, 25 Mar 2024 15:33:55 +0100 Subject: [PATCH 5/5] Fix launch settings for VSCode --- .vscode/launch.json | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 334b4937e..dd3e7ac4a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,36 +11,13 @@ "PYTHONPATH": "${workspaceFolder}${pathSeparator}${env:PYTHONPATH}" } }, - { - "name": "Debug story very verbose (current directory)", - "type": "debugpy", - "request": "launch", - "console": "integratedTerminal", - "module": "nemoguardrails", - "args": [ - "chat", - "--config=${fileDirname}", - "--verbose", - "--debug-level=DEBUG" - ], - "justMyCode": true, - "env": { - "PYTHONPATH": "${workspaceFolder}${pathSeparator}${env:PYTHONPATH}" - } - }, { "name": "Debug story verbose (current directory)", "type": "debugpy", "request": "launch", "console": "integratedTerminal", "module": "nemoguardrails", - "args": [ - "chat", - "--config=${fileDirname}", - "--verbose", - "--verbose-llm-calls", - "--debug-level=INFO" - ], + "args": ["chat", "--config=${fileDirname}", "--debug-level=DEBUG"], "justMyCode": true, "env": { "PYTHONPATH": "${workspaceFolder}${pathSeparator}${env:PYTHONPATH}" @@ -52,7 +29,7 @@ "request": "launch", "console": "integratedTerminal", "module": "nemoguardrails", - "args": ["chat", "--config=${fileDirname}", "--verbose"], + "args": ["chat", "--config=${fileDirname}", "--debug-level=INFO"], "justMyCode": true, "env": { "PYTHONPATH": "${workspaceFolder}${pathSeparator}${env:PYTHONPATH}" @@ -64,7 +41,7 @@ "request": "launch", "console": "integratedTerminal", "module": "nemoguardrails", - "args": ["chat", "--config=${fileDirname}", "--verbose-llm-calls"], + "args": ["chat", "--config=${fileDirname}", "--verbose"], "justMyCode": true, "env": { "PYTHONPATH": "${workspaceFolder}${pathSeparator}${env:PYTHONPATH}"