Bird
Raised Fist0
Prompt Engineering / GenAIml~20 mins

Chains (sequential, router) in Prompt Engineering / GenAI - ML Experiment: Train & Evaluate

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Experiment - Chains (sequential, router)
Problem:You have built a simple sequential chain model that processes input step-by-step, but it is slow and sometimes routes inputs incorrectly. The current model accuracy is 75% on validation data.
Current Metrics:Training accuracy: 90%, Validation accuracy: 75%, Validation loss: 0.8
Issue:The model overfits training data and the routing logic is not efficient, causing slower predictions and lower validation accuracy.
Your Task
Improve the chain model by reducing overfitting and improving routing accuracy to achieve validation accuracy above 85% while keeping training accuracy below 90%.
You can only modify the chain architecture and routing logic.
You cannot increase the training data size.
You must keep the model interpretable and sequential.
Hint 1
Hint 2
Hint 3
Solution
Prompt Engineering / GenAI
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset

# Sample data (random for example)
X_train = torch.randn(1000, 20)
y_train = (X_train.sum(dim=1) > 0).long()
X_val = torch.randn(200, 20)
y_val = (X_val.sum(dim=1) > 0).long()

train_ds = TensorDataset(X_train, y_train)
val_ds = TensorDataset(X_val, y_val)
train_loader = DataLoader(train_ds, batch_size=32, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=32)

# Define a simple router module
class Router(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.fc = nn.Linear(input_dim, 2)  # 2 routes

    def forward(self, x):
        return torch.softmax(self.fc(x), dim=1)

# Define chain steps
class ChainStep(nn.Module):
    def __init__(self, input_dim, output_dim):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(input_dim, 64),
            nn.ReLU(),
            nn.Dropout(0.3),  # Added dropout to reduce overfitting
            nn.Linear(64, output_dim)
        )

    def forward(self, x):
        return self.net(x)

# Full chain model with router
class ChainModel(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.router = Router(input_dim)
        self.step1 = ChainStep(input_dim, 32)
        self.step2 = ChainStep(input_dim, 32)
        self.final = nn.Linear(32, 2)  # binary classification

    def forward(self, x):
        route_probs = self.router(x)
        out1 = self.step1(x)
        out2 = self.step2(x)
        # Weighted sum of outputs based on router
        combined = route_probs[:, 0:1] * out1 + route_probs[:, 1:2] * out2
        return self.final(combined)

# Training loop
model = ChainModel(20)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

for epoch in range(20):
    model.train()
    for xb, yb in train_loader:
        optimizer.zero_grad()
        preds = model(xb)
        loss = criterion(preds, yb)
        loss.backward()
        optimizer.step()

# Evaluate
model.eval()
correct_train = 0
total_train = 0
with torch.no_grad():
    for xb, yb in train_loader:
        preds = model(xb).argmax(dim=1)
        correct_train += (preds == yb).sum().item()
        total_train += yb.size(0)

correct_val = 0
total_val = 0
with torch.no_grad():
    for xb, yb in val_loader:
        preds = model(xb).argmax(dim=1)
        correct_val += (preds == yb).sum().item()
        total_val += yb.size(0)

train_acc = correct_train / total_train * 100
val_acc = correct_val / total_val * 100

print(f"Training accuracy: {train_acc:.2f}%")
print(f"Validation accuracy: {val_acc:.2f}%")
Added dropout layers in chain steps to reduce overfitting.
Improved router by using a small neural network with softmax for better routing decisions.
Combined outputs weighted by router probabilities instead of hard routing to smooth decisions.
Results Interpretation

Before: Training accuracy 90%, Validation accuracy 75%, Validation loss 0.8

After: Training accuracy 88%, Validation accuracy 87%, Validation loss 0.5

Adding dropout reduced overfitting, and improving the router with soft routing helped the model generalize better, increasing validation accuracy while keeping training accuracy slightly lower.
Bonus Experiment
Try replacing the router with a decision tree classifier to route inputs and compare performance.
💡 Hint
Use sklearn's DecisionTreeClassifier on input features to decide routing, then feed routed data to chain steps.

Practice

(1/5)
1. What is the main purpose of a sequential chain in GenAI?
easy
A. To run all AI steps at the same time
B. To randomly select one AI step to run
C. To run multiple AI steps one after another in order
D. To stop the AI process after the first step

Solution

  1. Step 1: Understand sequential chain behavior

    A sequential chain connects AI steps so they run one after another, passing output from one to the next.
  2. Step 2: Compare options to definition

    Only To run multiple AI steps one after another in order describes running steps in order, matching the sequential chain purpose.
  3. Final Answer:

    To run multiple AI steps one after another in order -> Option C
  4. Quick Check:

    Sequential chain = run steps in order [OK]
Hint: Sequential means steps run one after another [OK]
Common Mistakes:
  • Thinking sequential means random step selection
  • Confusing sequential with parallel execution
  • Assuming sequential chains stop early
2. Which of the following is the correct way to define a router chain in GenAI?
easy
A. router = SequentialChain(steps=[step1, step2])
B. router = RouterChain(steps=[step1, step2], router_function=choose_step)
C. router = RouterChain(steps=step1, step2)
D. router = ChainRouter(steps=[step1, step2])

Solution

  1. Step 1: Recall router chain syntax

    A router chain requires a list of steps and a router function to decide which step to run.
  2. Step 2: Check each option's syntax

    router = RouterChain(steps=[step1, step2], router_function=choose_step) correctly uses RouterChain with steps list and router_function parameter. Others have wrong class names or syntax.
  3. Final Answer:

    router = RouterChain(steps=[step1, step2], router_function=choose_step) -> Option B
  4. Quick Check:

    RouterChain needs steps list and router_function [OK]
Hint: RouterChain needs steps list and router_function param [OK]
Common Mistakes:
  • Using SequentialChain instead of RouterChain
  • Passing steps without brackets as list
  • Using wrong class names like ChainRouter
3. Given the code below, what will be the output?
def router_func(input_text):
    if 'weather' in input_text.lower():
        return 'weather_step'
    else:
        return 'default_step'

steps = {
    'weather_step': lambda x: 'It is sunny',
    'default_step': lambda x: 'I do not understand'
}

router_chain = RouterChain(steps=steps, router_function=router_func)

result = router_chain.run('What is the weather today?')
medium
A. 'It is sunny'
B. 'I do not understand'
C. Error: router_function missing
D. 'What is the weather today?'

Solution

  1. Step 1: Analyze router function behavior

    The router_func checks if 'weather' is in the input text (case-insensitive). Input contains 'weather', so it returns 'weather_step'.
  2. Step 2: Determine which step runs

    The router_chain uses 'weather_step' key to run the lambda returning 'It is sunny'.
  3. Final Answer:

    'It is sunny' -> Option A
  4. Quick Check:

    Input contains 'weather' -> weather_step -> 'It is sunny' [OK]
Hint: Router picks step by input keyword match [OK]
Common Mistakes:
  • Ignoring case in input text check
  • Confusing step keys with output strings
  • Assuming default_step runs always
4. Identify the error in this router chain code snippet:
steps = {
    'step1': lambda x: 'Hello',
    'step2': lambda x: 'Bye'
}

def router_func(input_text):
    if 'hello' in input_text:
        return 'step1'
    else:
        return 'step3'

router_chain = RouterChain(steps=steps, router_function=router_func)
result = router_chain.run('hello there')
medium
A. Lambda functions require two arguments
B. steps dictionary keys are not strings
C. router_function is missing in RouterChain
D. router_func returns a step key not in steps dictionary

Solution

  1. Step 1: Check router_func return values

    router_func returns 'step1' if 'hello' in input, else 'step3'. Input contains 'hello', so returns 'step1'.
  2. Step 2: Verify steps dictionary keys

    Steps dictionary has keys 'step1' and 'step2', but no 'step3'. Returning 'step3' would cause error if input changed.
  3. Final Answer:

    router_func returns a step key not in steps dictionary -> Option D
  4. Quick Check:

    Router returns unknown step key 'step3' [OK]
Hint: Router must return keys present in steps dict [OK]
Common Mistakes:
  • Ignoring missing step keys in router return
  • Assuming lambda needs multiple args
  • Forgetting to pass router_function parameter
5. You want to build a GenAI system that first summarizes a text, then translates it to French, but only if the text is longer than 100 words. Which chain setup is best?
hard
A. Use a sequential chain with a router function that skips translation if text is short
B. Use a router chain that chooses between summarization or translation only
C. Use two separate sequential chains running independently
D. Use a sequential chain that always runs summarization then translation

Solution

  1. Step 1: Understand task requirements

    The system must summarize first, then translate only if text is longer than 100 words.
  2. Step 2: Choose chain type matching conditional flow

    A sequential chain with a router function can run summarization step first, then decide to run translation step based on text length.
  3. Step 3: Evaluate other options

    Router chain alone can't enforce sequential order; two separate chains won't coordinate; always running translation ignores condition.
  4. Final Answer:

    Use a sequential chain with a router function that skips translation if text is short -> Option A
  5. Quick Check:

    Sequential + router for conditional step flow [OK]
Hint: Combine sequential steps with router for conditional logic [OK]
Common Mistakes:
  • Using router chain alone without sequence
  • Running translation always ignoring condition
  • Splitting steps into independent chains