Exception Handling in Workflow Design #5436

Closed
opened 2026-02-21 18:10:57 -05:00 by yindo · 5 comments
Owner

Originally created by @lukefan on GitHub (Sep 6, 2024).

Self Checks

  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report (我已阅读并同意 Language Policy).
  • [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:)
  • Please do not modify this template :) and fill in all the required fields.

1. Is this request related to a challenge you're experiencing? Tell me about your story.

It would be highly beneficial to incorporate an exception handling mechanism into the workflow design process.

Currently, certain large language models (LLMs) encounter errors when they are unable to answer a question. Additionally, there are instances where network errors or concurrency limits occur during API calls to these models' servers. When these issues arise, the workflow is immediately terminated.

I propose adding an exception handling feature that allows for retries or alternative actions when an error occurs. For example, if a model fails to respond, the system could either retry using the same model after a short delay or switch to a backup model for another attempt. This approach could follow a similar pattern to the try-catch-finally syntax in programming.

By implementing this feature, we could significantly improve the robustness and reliability of the workflow, ensuring smoother and more resilient processes.

Thank you for considering this enhancement!

2. Additional context or comments

No response

3. Can you help us with this feature?

  • I am interested in contributing to this feature.
Originally created by @lukefan on GitHub (Sep 6, 2024). ### Self Checks - [X] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones. - [X] I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)). - [X] [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:) - [X] Please do not modify this template :) and fill in all the required fields. ### 1. Is this request related to a challenge you're experiencing? Tell me about your story. It would be highly beneficial to incorporate an exception handling mechanism into the workflow design process. Currently, certain large language models (LLMs) encounter errors when they are unable to answer a question. Additionally, there are instances where network errors or concurrency limits occur during API calls to these models' servers. When these issues arise, the workflow is immediately terminated. I propose adding an exception handling feature that allows for retries or alternative actions when an error occurs. For example, if a model fails to respond, the system could either retry using the same model after a short delay or switch to a backup model for another attempt. This approach could follow a similar pattern to the try-catch-finally syntax in programming. By implementing this feature, we could significantly improve the robustness and reliability of the workflow, ensuring smoother and more resilient processes. Thank you for considering this enhancement! ### 2. Additional context or comments _No response_ ### 3. Can you help us with this feature? - [X] I am interested in contributing to this feature.
yindo added the 💪 enhancement🌊 feat:workflow labels 2026-02-21 18:10:57 -05:00
yindo closed this issue 2026-02-21 18:10:57 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 6, 2024):

To incorporate an exception handling mechanism into the workflow design process to handle errors from large language models and network issues, similar to the try-catch-finally syntax in programming, you can use the provided callback methods in the LargeLanguageModel class. These methods allow you to trigger specific actions before, during, and after the invocation of the model, as well as when an error occurs.

Here is an example of how you can structure your workflow to include exception handling:

def invoke(self, model: str, credentials: dict,
           prompt_messages: list[PromptMessage], model_parameters: Optional[dict] = None,
           tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
           stream: bool = True, user: Optional[str] = None, callbacks: Optional[list[Callback]] = None) \
        -> Union[LLMResult, Generator]:
    """
    Invoke large language model

    :param model: model name
    :param credentials: model credentials
    :param prompt_messages: prompt messages
    :param model_parameters: model parameters
    :param tools: tools for tool calling
    :param stop: stop words
    :param stream: is stream response
    :param user: unique user id
    :param callbacks: callbacks
    :return: full response or stream response chunk generator result
    """
    # validate and filter model parameters
    if model_parameters is None:
        model_parameters = {}

    model_parameters = self._validate_and_filter_model_parameters(model, model_parameters, credentials)

    self.started_at = time.perf_counter()

    callbacks = callbacks or []

    if bool(os.environ.get("DEBUG", 'False').lower() == 'true'):
        callbacks.append(LoggingCallback())

    # trigger before invoke callbacks
    self._trigger_before_invoke_callbacks(
        model=model,
        credentials=credentials,
        prompt_messages=prompt_messages,
        model_parameters=model_parameters,
        tools=tools,
        stop=stop,
        stream=stream,
        user=user,
        callbacks=callbacks
    )

    try:
        if "response_format" in model_parameters:
            result = self._code_block_mode_wrapper(
                model=model,
                credentials=credentials,
                prompt_messages=prompt_messages,
                model_parameters=model_parameters,
                tools=tools,
                stop=stop,
                stream=stream,
                user=user,
                callbacks=callbacks
            )
        else:
            result = self._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
    except Exception as e:
        self._trigger_invoke_error_callbacks(
            model=model,
            ex=e,
            credentials=credentials,
            prompt_messages=prompt_messages,
            model_parameters=model_parameters,
            tools=tools,
            stop=stop,
            stream=stream,
            user=user,
            callbacks=callbacks
        )

        raise self._transform_invoke_error(e)

    if stream and isinstance(result, Generator):
        return self._invoke_result_generator(
            model=model,
            result=result,
            credentials=credentials,
            prompt_messages=prompt_messages,
            model_parameters=model_parameters,
            tools=tools,
            stop=stop,
            stream=stream,
            user=user,
            callbacks=callbacks
        )
    elif isinstance(result, LLMResult):
        self._trigger_after_invoke_callbacks(
            model=model,
            result=result,
            credentials=credentials,
            prompt_messages=prompt_messages,
            model_parameters=model_parameters,
            tools=tools,
            stop=stop,
            stream=stream,
            user=user,
            callbacks=callbacks
        )

    return result

This code includes the following steps:

  1. Before Invoke Callbacks: Triggered before invoking the model.
  2. Try Block: Attempts to invoke the model.
  3. Exception Handling: Catches any exceptions, triggers error callbacks, and raises a transformed error.
  4. After Invoke Callbacks: Triggered after successfully invoking the model.

This structure ensures that you handle errors gracefully and can perform necessary cleanup or logging actions, similar to the try-catch-finally syntax in traditional programming.

Additionally, in the context of Dify's LLMNode class, the _run method also demonstrates a similar approach to exception handling within a workflow node:

def _run(self, variable_pool: VariablePool) -> NodeRunResult:
    """
    Run node
    :param variable_pool: variable pool
    :return:
    """
    node_data = cast(LLMNodeData, deepcopy(self.node_data))

    node_inputs = None
    process_data = None

    try:
        # init messages template
        node_data.prompt_template = self._transform_chat_messages(node_data.prompt_template)

        # fetch variables and fetch values from variable pool
        inputs = self._fetch_inputs(node_data, variable_pool)

        # fetch jinja2 inputs
        jinja_inputs = self._fetch_jinja_inputs(node_data, variable_pool)

        # merge inputs
        inputs.update(jinja_inputs)

        node_inputs = {}

        # fetch files
        files = self._fetch_files(node_data, variable_pool)

        if files:
            node_inputs['#files#'] = [file.to_dict() for file in files]

        # fetch context value
        context = self._fetch_context(node_data, variable_pool)

        if context:
            node_inputs['#context#'] = context

        # fetch model config
        model_instance, model_config = self._fetch_model_config(node_data.model)

        # fetch memory
        memory = self._fetch_memory(node_data.memory, variable_pool, model_instance)

        # fetch prompt messages
        prompt_messages, stop = self._fetch_prompt_messages(
            node_data=node_data,
            query=variable_pool.get_any(['sys', SystemVariableKey.QUERY.value])
            if node_data.memory else None,
            query_prompt_template=node_data.memory.query_prompt_template if node_data.memory else None,
            inputs=inputs,
            files=files,
            context=context,
            memory=memory,
            model_config=model_config
        )
        process_data = {
            'model_mode': model_config.mode,
            'prompts': PromptMessageUtil.prompt_messages_to_prompt_for_saving(
                model_mode=model_config.mode,
                prompt_messages=prompt_messages
            ),
            'model_provider': model_config.provider,
            'model_name': model_config.model,      
        }

        # handle invoke result
        result_text, usage, finish_reason = self._invoke_llm(
            node_data_model=node_data.model,
            model_instance=model_instance,
            prompt_messages=prompt_messages,
            stop=stop
        )
    except Exception as e:
        return NodeRunResult(
            status=WorkflowNodeExecutionStatus.FAILED,
            error=str(e),
            inputs=node_inputs,
            process_data=process_data
        )

    outputs = {
        'text': result_text,
        'usage': jsonable_encoder(usage),
        'finish_reason': finish_reason
    }

    return NodeRunResult(
        status=WorkflowNodeExecutionStatus.SUCCEEDED,
        inputs=node_inputs,
        process_data=process_data,
        outputs=outputs,
        metadata={
            NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens,
            NodeRunMetadataKey.TOTAL_PRICE: usage.total_price,
            NodeRunMetadataKey.CURRENCY: usage.currency
        }
    )

By following these patterns, you can effectively incorporate exception handling into your workflow design process, ensuring robust error management and graceful recovery [1][2].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Sep 6, 2024): <!-- Answer --> To incorporate an exception handling mechanism into the workflow design process to handle errors from large language models and network issues, similar to the try-catch-finally syntax in programming, you can use the provided callback methods in the `LargeLanguageModel` class. These methods allow you to trigger specific actions before, during, and after the invocation of the model, as well as when an error occurs. Here is an example of how you can structure your workflow to include exception handling: ```python def invoke(self, model: str, credentials: dict, prompt_messages: list[PromptMessage], model_parameters: Optional[dict] = None, tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None, stream: bool = True, user: Optional[str] = None, callbacks: Optional[list[Callback]] = None) \ -> Union[LLMResult, Generator]: """ Invoke large language model :param model: model name :param credentials: model credentials :param prompt_messages: prompt messages :param model_parameters: model parameters :param tools: tools for tool calling :param stop: stop words :param stream: is stream response :param user: unique user id :param callbacks: callbacks :return: full response or stream response chunk generator result """ # validate and filter model parameters if model_parameters is None: model_parameters = {} model_parameters = self._validate_and_filter_model_parameters(model, model_parameters, credentials) self.started_at = time.perf_counter() callbacks = callbacks or [] if bool(os.environ.get("DEBUG", 'False').lower() == 'true'): callbacks.append(LoggingCallback()) # trigger before invoke callbacks self._trigger_before_invoke_callbacks( model=model, credentials=credentials, prompt_messages=prompt_messages, model_parameters=model_parameters, tools=tools, stop=stop, stream=stream, user=user, callbacks=callbacks ) try: if "response_format" in model_parameters: result = self._code_block_mode_wrapper( model=model, credentials=credentials, prompt_messages=prompt_messages, model_parameters=model_parameters, tools=tools, stop=stop, stream=stream, user=user, callbacks=callbacks ) else: result = self._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user) except Exception as e: self._trigger_invoke_error_callbacks( model=model, ex=e, credentials=credentials, prompt_messages=prompt_messages, model_parameters=model_parameters, tools=tools, stop=stop, stream=stream, user=user, callbacks=callbacks ) raise self._transform_invoke_error(e) if stream and isinstance(result, Generator): return self._invoke_result_generator( model=model, result=result, credentials=credentials, prompt_messages=prompt_messages, model_parameters=model_parameters, tools=tools, stop=stop, stream=stream, user=user, callbacks=callbacks ) elif isinstance(result, LLMResult): self._trigger_after_invoke_callbacks( model=model, result=result, credentials=credentials, prompt_messages=prompt_messages, model_parameters=model_parameters, tools=tools, stop=stop, stream=stream, user=user, callbacks=callbacks ) return result ``` This code includes the following steps: 1. **Before Invoke Callbacks**: Triggered before invoking the model. 2. **Try Block**: Attempts to invoke the model. 3. **Exception Handling**: Catches any exceptions, triggers error callbacks, and raises a transformed error. 4. **After Invoke Callbacks**: Triggered after successfully invoking the model. This structure ensures that you handle errors gracefully and can perform necessary cleanup or logging actions, similar to the `try-catch-finally` syntax in traditional programming. Additionally, in the context of Dify's `LLMNode` class, the `_run` method also demonstrates a similar approach to exception handling within a workflow node: ```python def _run(self, variable_pool: VariablePool) -> NodeRunResult: """ Run node :param variable_pool: variable pool :return: """ node_data = cast(LLMNodeData, deepcopy(self.node_data)) node_inputs = None process_data = None try: # init messages template node_data.prompt_template = self._transform_chat_messages(node_data.prompt_template) # fetch variables and fetch values from variable pool inputs = self._fetch_inputs(node_data, variable_pool) # fetch jinja2 inputs jinja_inputs = self._fetch_jinja_inputs(node_data, variable_pool) # merge inputs inputs.update(jinja_inputs) node_inputs = {} # fetch files files = self._fetch_files(node_data, variable_pool) if files: node_inputs['#files#'] = [file.to_dict() for file in files] # fetch context value context = self._fetch_context(node_data, variable_pool) if context: node_inputs['#context#'] = context # fetch model config model_instance, model_config = self._fetch_model_config(node_data.model) # fetch memory memory = self._fetch_memory(node_data.memory, variable_pool, model_instance) # fetch prompt messages prompt_messages, stop = self._fetch_prompt_messages( node_data=node_data, query=variable_pool.get_any(['sys', SystemVariableKey.QUERY.value]) if node_data.memory else None, query_prompt_template=node_data.memory.query_prompt_template if node_data.memory else None, inputs=inputs, files=files, context=context, memory=memory, model_config=model_config ) process_data = { 'model_mode': model_config.mode, 'prompts': PromptMessageUtil.prompt_messages_to_prompt_for_saving( model_mode=model_config.mode, prompt_messages=prompt_messages ), 'model_provider': model_config.provider, 'model_name': model_config.model, } # handle invoke result result_text, usage, finish_reason = self._invoke_llm( node_data_model=node_data.model, model_instance=model_instance, prompt_messages=prompt_messages, stop=stop ) except Exception as e: return NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, error=str(e), inputs=node_inputs, process_data=process_data ) outputs = { 'text': result_text, 'usage': jsonable_encoder(usage), 'finish_reason': finish_reason } return NodeRunResult( status=WorkflowNodeExecutionStatus.SUCCEEDED, inputs=node_inputs, process_data=process_data, outputs=outputs, metadata={ NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens, NodeRunMetadataKey.TOTAL_PRICE: usage.total_price, NodeRunMetadataKey.CURRENCY: usage.currency } ) ``` By following these patterns, you can effectively incorporate exception handling into your workflow design process, ensuring robust error management and graceful recovery <sup>[[1]](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/model_providers/__base/large_language_model.py)</sup><sup>[[2]](https://github.com/langgenius/dify/blob/main/api/core/workflow/nodes/llm/llm_node.py)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@lukefan commented on GitHub (Sep 6, 2024):

I don't want to implement exception handling through code. Instead, I hope that exception handling functionality is included in the workflow of Dify.

@lukefan commented on GitHub (Sep 6, 2024): I don't want to implement exception handling through code. Instead, I hope that exception handling functionality is included in the workflow of Dify.
Author
Owner

@dosubot[bot] commented on GitHub (Sep 6, 2024):

Yes, exception handling functionality can be included in the workflow of Dify without requiring implementation through code. Dify workflows offer a variety of logic nodes, including IF/ELSE nodes, which can be used to handle different conditions and exceptions within the workflow. Additionally, Dify provides other logic nodes such as code nodes, template conversion nodes, and iteration nodes, as well as the ability to trigger by time and event. These features enhance the ability to manage complex business logic and exceptions effectively, reducing system complexity and improving stability and fault tolerance [1][2][3][4].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Sep 6, 2024): <!-- Answer --> Yes, exception handling functionality can be included in the workflow of Dify without requiring implementation through code. Dify workflows offer a variety of logic nodes, including IF/ELSE nodes, which can be used to handle different conditions and exceptions within the workflow. Additionally, Dify provides other logic nodes such as code nodes, template conversion nodes, and iteration nodes, as well as the ability to trigger by time and event. These features enhance the ability to manage complex business logic and exceptions effectively, reducing system complexity and improving stability and fault tolerance <sup>[[1]](https://github.com/langgenius/dify-docs/blob/main/en/guides/workflow/README.md)</sup><sup>[[2]](https://github.com/langgenius/dify-docs/blob/main/jp/guides/workflow/README.md)</sup><sup>[[3]](https://github.com/langgenius/dify-docs/blob/main/en/guides/workflow/introduce.md)</sup><sup>[[4]](https://github.com/langgenius/dify-docs/blob/main/zh_CN/guides/workflow/README.md)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@crazywoola commented on GitHub (Sep 6, 2024):

Duplicated https://github.com/langgenius/dify/issues/7891

@crazywoola commented on GitHub (Sep 6, 2024): Duplicated https://github.com/langgenius/dify/issues/7891
Author
Owner

@mrkenknight commented on GitHub (Sep 26, 2024):

Not only try-except with the LLM block, suppose we have a workflow operating with many nodes. If one node fails, the entire workflow stops working. So, what needs to be try-excepted here is the workflow itself

@mrkenknight commented on GitHub (Sep 26, 2024): Not only try-except with the LLM block, suppose we have a workflow operating with many nodes. If one node fails, the entire workflow stops working. So, what needs to be try-excepted here is the workflow itself
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#5436