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}" 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)) diff --git a/nemoguardrails/actions/v2_x/generation.py b/nemoguardrails/actions/v2_x/generation.py index 3958a0368..49b2d997f 100644 --- a/nemoguardrails/actions/v2_x/generation.py +++ b/nemoguardrails/actions/v2_x/generation.py @@ -114,15 +114,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 @@ -194,7 +188,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 ) ): @@ -453,8 +447,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 1db483f8c..3c6cf4d79 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 @@ -35,13 +33,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 @@ -71,8 +71,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 @@ -86,9 +86,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() @@ -100,9 +100,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() @@ -116,9 +116,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 @@ -197,15 +197,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() @@ -225,33 +225,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 # ---------------------------------- @@ -259,36 +259,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 # ---------------------------------- @@ -529,8 +529,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 084464554..439341b67 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.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 f65dcacb2..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, @@ -340,7 +339,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) @@ -355,11 +354,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 @@ -378,10 +377,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 9b1665f5f..0061d66dd 100644 --- a/nemoguardrails/colang/v2_x/runtime/runtime.py +++ b/nemoguardrails/colang/v2_x/runtime/runtime.py @@ -26,7 +26,7 @@ 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.lang.colang_ast import Decorator, Flow from nemoguardrails.colang.v2_x.runtime.errors import ( ColangRuntimeError, ColangSyntaxError, @@ -100,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, ) @@ -608,6 +610,20 @@ async def _run_action( } +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] = {} @@ -619,7 +635,7 @@ def create_flow_configs_from_flow_list(flows: List[Flow]) -> Dict[str, FlowConfi 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/nemoguardrails/colang/v2_x/runtime/statemachine.py b/nemoguardrails/colang/v2_x/runtime/statemachine.py index 7f19f03b6..226d04931 100644 --- a/nemoguardrails/colang/v2_x/runtime/statemachine.py +++ b/nemoguardrails/colang/v2_x/runtime/statemachine.py @@ -1411,19 +1411,23 @@ def _finish_flow( event = flow_state.finished_event(matching_scores) _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 @@ -1454,27 +1458,44 @@ 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 + 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.arguments.get("$0", None), + "intent_flow_id": intent, + }, + matching_scores, + ) - event = create_internal_event( - event_type, - { - "flow_id": flow_state.flow_id, - "parameter": flow_state.arguments.get("$0", None), - "intent_flow_id": intent, - }, - matching_scores, - ) _push_internal_event(state, event) log.info( 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 f55d359d1..064ee4fa2 100644 --- a/tests/v2_x/test_flow_mechanics.py +++ b/tests/v2_x/test_flow_mechanics.py @@ -1482,6 +1482,54 @@ 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", + }, + ], + ) + + def test_flow_parameter_await_mechanism(): """Test flow overriding mechanic.""" @@ -1494,7 +1542,6 @@ def test_flow_parameter_await_mechanism(): flow main await a "hi" $test=123 await UtteranceBotAction(script="Success") - match Event() """ state = run_to_completion(_init_state(content), start_main_flow_event) @@ -1511,7 +1558,7 @@ def test_flow_parameter_await_mechanism(): { "type": "StartUtteranceBotAction", "script": "Success", - }, + } ], ) @@ -1570,4 +1617,4 @@ def test_flow_context_sharing(): if __name__ == "__main__": - test_flow_context_sharing() + test_meta_decorators()