Skip to content

Chatbot Workflow Cookbook

This cookbook collects common pipeline-building patterns, showing how nodes combine to solve real chatbot problems. It assumes you are already familiar with Pipelines and the available Node Types. The recipes below focus on the LLM, router, and Python nodes.

Split a chatbot into multiple smaller chatbots

For complex chatbots it may be the case that a single LLM node with a large prompt does not perform well. For example, a chatbot that is expected to perform multiple functions such as Role Play, Quiz, Q&A.

In such cases, it can be better to create smaller, narrowly focused prompts and use a router to select which 'mode' the chatbot is currently in.

Here is an example that uses an LLM Router Nodeto route the input to one of three linked downstream nodes. See LLM Router Configuration for the step-by-step configuration used here.

graph TB
  A@{ shape: stadium, label: "Input" } --> Router("`**LLM Router**
  Route to one of the linked nodes using an LLM`");
  Router -->|GENERAL| Gen(LLM);
  Router -->|ROLEPLAY| Rp(LLM);
  Router -->|QUIZ| Qz(LLM);
  Gen --> C@{ shape: stadium, label: "Output" };
  Qz --> C
  Rp --> C;

Conversation history works differently across these routed sub-chatbots than in a single-node pipeline — see Conversation History for how to keep the general, quiz, and roleplay nodes sharing the right context.

Safety check in parallel

In this example, we are using a router to determine if the participant's input complies with the usage policy of the chatbot. The router has two outputs, safe and unsafe. The safe output is not connected to any other nodes but the unsafe output is connected to a Python node which will abort the pipeline with an error message.

flowchart TD
    start["start"] --> Safety["SafetyRouter"] & LLM
    Safety -. safe .-> Dangle:::hidden
    Safety -. unsafe .-> PythonNode["PythonNode
    *abort_with_message('...')*"]
    LLM --> __end__(["<p>end</p>"])

     start:::first
     __end__:::last

If the Safety Router routes to the Python node, the participant will not see the output generated by the LLM node but will instead see a message generated by an LLM based on the message passed to the abort_with_message function.

The unconnected safe output is an example of a dangling node — a valid pattern for router outputs that don't need any further processing.

Router for classification

Router nodes can have unconnected outputs as seen above, enabling more flexible routing patterns where not all paths need to be explicitly handled.

You can also connect multiple router outputs to the same input of another node. This can be useful if you want the router node to categorize the input without affecting the execution flow.

This pattern for reading the selected route works the same whether the router is an LLM Router node or a Static Router node.

flowchart TD
    start["start"] --> Router[RouterA]
    Router -. categoryA .-> PythonNode
    Router -. categoryB .-> PythonNode
    PythonNode --> LLM
    LLM --> __end__(["<p>end</p>"])

     start:::first
     __end__:::last

You might use this to perform some logic in the Python node:

def main(input, **kwargs):
    route = get_selected_route("RouterA")
    if route == "categoryA":
        set_temp_state_key("question", "A")
    elif route == "categoryB":
        set_temp_state_key("question", "B")
    return input

Then in the LLM node prompt you can use the temp_state to inject the category:

The current category is {temp_state.category}

Reading participant-uploaded files

This workflow allows participants to upload files that your chatbot can process and analyze. See the supported file types for details.

Setup Steps

  1. Enable file uploads: In your chatbot settings, enable the "File uploads enabled" option.
  2. Create a Python Node: Use a Python node to read and process the uploaded file contents from the temporary state - specifically from the attachments key.
  3. Pass to LLM: Either return the participant's input along with the file contents directly to the LLM node, or save the file contents to the temporary state and inject them into your LLM prompt.

Workflow Structure

flowchart TD
    start["start"] --> PythonNode
    PythonNode --> LLM
    LLM --> __end__(["<p>end</p>"])

     start:::first
     __end__:::last

Python Node Implementation:

Option 1: Single File Processing Process only the first uploaded file:

def main(input: str, **kwargs) -> str:
    # Get uploaded files from temp state
    attachments = get_temp_state_key("attachments")
    if not attachments:
        return input

    # Read the first file's content
    file_content = attachments[0].read_text()
    set_temp_state_key("file_contents", file_content)

    return input

Option 2: Multiple Files Processing Process all uploaded files:

def main(input: str, **kwargs) -> str:
    # Get uploaded files from temp state
    attachments = get_temp_state_key("attachments")
    if not attachments:
        return input

    # Read all files and combine their contents
    all_file_contents = []
    for i, attachment in enumerate(attachments):
        file_content = attachment.read_text()
        filename = attachment.name if hasattr(attachment, 'name') else f"File {i+1}"
        all_file_contents.append(f"## {filename}\n{file_content}")

    # Save combined contents to temp state
    combined_contents = "\n\n".join(all_file_contents)
    set_temp_state_key("file_contents", combined_contents)

    return input

In these examples, the Python node reads the uploaded file(s) and saves their contents to the temp state under the key "file_contents". The participant's original input is passed through unchanged to the LLM node.

LLM Node Configuration:

Configure your LLM node to use the uploaded file contents by injecting them into the prompt using temp state variables.

Basic Prompt Template:

You are a helpful assistant. Answer the user's query as best you can.

Here are some file contents that you should consider when generating your answer:

## File Contents
{temp_state.file_contents}

User Query: {input}

Instructions:
- If the file contents are empty or not provided, inform the user that no files were uploaded
- Base your response on both the file contents and the user's query
- Be specific about what you found in the uploaded files
- If you cannot find relevant information in the files, clearly state this

Making external API calls

Pipelines can call external APIs directly from a Python node using the built-in http client. See How to call external APIs for worked examples that fetch external data, submit data to an external service, and enrich user data with details looked up from a third-party API. For authentication, file upload/download, and error handling, see the HTTP Client reference.