/n8n-tutorials

How to stop repeated answers from a language model in n8n workflows?

Learn how to stop repeated answers from language models in n8n workflows by adjusting parameters, using memory nodes, filtering responses, and implementing retry logic for diverse outputs.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to stop repeated answers from a language model in n8n workflows?

To stop repeated answers from a language model in n8n workflows, you need to implement specific techniques like setting temperature parameters, using memory nodes to track previous responses, applying regex-based filtering, or implementing custom JavaScript code to ensure output variety. These approaches help prevent the language model from generating redundant or repetitive content in your automation workflows.

 

Comprehensive Guide: Preventing Repetitive Responses from Language Models in n8n

 

Step 1: Understanding the Problem of Repetitive Responses

 

When working with language models (LMs) like GPT or other AI services in n8n workflows, you might encounter the issue of receiving repetitive or duplicated responses. This can happen because:

  • Language models sometimes get "stuck" in response patterns
  • Default parameters may favor consistency over variety
  • The workflow doesn't maintain context of previous responses
  • Input prompts might be too similar, triggering similar outputs

Before implementing solutions, it's essential to understand when and why repetition occurs in your specific workflow.

 

Step 2: Adjusting Language Model Parameters

 

Most language model nodes in n8n (like OpenAI, ChatGPT, or other LLM nodes) provide parameters you can adjust to influence response variety:

Temperature Setting:

Increasing the temperature parameter (typically between 0.7-1.0) introduces more randomness in the responses:


// In the OpenAI node configuration:
{
  "model": "gpt-4",
  "messages": [
    {
      "role": "user",
      "content": "{{$node["Previous Node"].json.question}}"
    }
  ],
  "temperature": 0.8,  // Increase this value for more variety
  "maxTokens": 500
}

Other Diversity Parameters:

Depending on the language model, you might also have access to:

  • Top-p (nucleus sampling): Try values like 0.9
  • Frequency penalty: Higher values (0.5-1.0) discourage repetition
  • Presence penalty: Higher values encourage the model to talk about new topics

// Example of additional parameters in OpenAI node:
{
  "model": "gpt-4",
  "messages": [...],
  "temperature": 0.8,
  "frequencyPenalty": 0.7,
  "presencePenalty": 0.6,
  "topP": 0.9
}

 

Step 3: Implementing Memory for Context Management

 

One effective approach is to maintain memory of previous responses and include that context in new prompts:

Using n8n's Set Node for Basic Memory:


// Set up a memory array in a Set node
{
  "keepOnlySet": true,
  "values": {
    "previousResponses": "{{$json.previousResponses || []}}"
  }
}

// Then add the current response to the array
{
  "keepOnlySet": true,
  "values": {
    "previousResponses": "{{$node["Set"].json.previousResponses.concat([$json.currentResponse])}}"
  }
}

Creating a Memory System with Function Node:


// More sophisticated memory management with a Function node
function addToMemory(items) {
  // Get the memory or initialize it
  const memory = items[0].json.memory || {
    responses: [],
    maxMemorySize: 5  // Adjust based on your needs
  };
  
  // Add current response
  const currentResponse = items[0].json.aiResponse;
  memory.responses.push(currentResponse);
  
  // Trim memory if it gets too large
  if (memory.responses.length > memory.maxMemorySize) {
    memory.responses.shift();  // Remove oldest entry
  }
  
  // Return updated memory
  items[0].json.memory = memory;
  return items;
}

 

Step 4: Including Previous Responses in Prompts

 

Use your memory system to inform the language model about previous responses:


// Function to create a prompt with context
function createContextualPrompt(items) {
  const memory = items[0].json.memory || { responses: [] };
  const userQuestion = items[0].json.question;
  
  let prompt = userQuestion;
  
  // Add context about previous responses
  if (memory.responses.length > 0) {
    prompt = \`The following is your previous response: "${memory.responses.join('" and "')}". 
    Please provide a different response to this question: ${userQuestion}\`;
  }
  
  items[0].json.contextualPrompt = prompt;
  return items;
}

Then use this contextual prompt in your language model node.

 

Step 5: Using a Filter Node to Detect Repetition

 

Implement a Filter node to check if the new response is too similar to previous ones:


// In a Function node, create a similarity checker
function checkSimilarity(items) {
  const memory = items[0].json.memory || { responses: [] };
  const newResponse = items[0].json.aiResponse;
  
  // Simple exact match check
  const isDuplicate = memory.responses.includes(newResponse);
  
  // More sophisticated check for similarity (optional)
  let isTooSimilar = false;
  for (const oldResponse of memory.responses) {
    // Calculate similarity (example using Levenshtein distance)
    const similarity = calculateSimilarity(newResponse, oldResponse);
    if (similarity > 0.8) { // 80% similar
      isTooSimilar = true;
      break;
    }
  }
  
  items[0].json.isDuplicate = isDuplicate;
  items[0].json.isTooSimilar = isTooSimilar;
  return items;
}

// Simple similarity function (you can replace with more sophisticated ones)
function calculateSimilarity(str1, str2) {
  // Basic implementation - you can use better algorithms
  const longer = str1.length > str2.length ? str1 : str2;
  const shorter = str1.length > str2.length ? str2 : str1;
  
  if (longer.length === 0) return 1.0;
  
  return (longer.length - levenshteinDistance(longer, shorter)) / longer.length;
}

function levenshteinDistance(str1, str2) {
  const track = Array(str2.length + 1).fill(null).map(() => 
    Array(str1.length + 1).fill(null));
  
  for (let i = 0; i <= str1.length; i += 1) {
    track\[0]\[i] = i;
  }
  
  for (let j = 0; j <= str2.length; j += 1) {
    track\[j]\[0] = j;
  }
  
  for (let j = 1; j <= str2.length; j += 1) {
    for (let i = 1; i <= str1.length; i += 1) {
      const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
      track\[j]\[i] = Math.min(
        track\[j]\[i - 1] + 1, // deletion
        track\[j - 1]\[i] + 1, // insertion
        track\[j - 1]\[i - 1] + indicator, // substitution
      );
    }
  }
  
  return track\[str2.length]\[str1.length];
}

 

Step 6: Implementing Retry Logic for Repetitive Responses

 

If a response is detected as repetitive, set up a retry mechanism:


// In a Function node after similarity check
function handleRepetition(items) {
  const isDuplicate = items[0].json.isDuplicate;
  const isTooSimilar = items[0].json.isTooSimilar;
  
  if (isDuplicate || isTooSimilar) {
    // Create a stronger prompt to discourage repetition
    items[0].json.retryPrompt = \`Please provide a completely different response. 
    Your previous answers were: "${items[0].json.memory.responses.join('" and "')}". 
    The question is: ${items[0].json.question}. 
    DO NOT repeat similar concepts, examples, or structure as your previous responses.\`;
    
    items[0].json.shouldRetry = true;
  } else {
    items[0].json.shouldRetry = false;
  }
  
  return items;
}

Then use an IF node to route to a retry or continue with the workflow.

 

Step 7: Building a Complete Anti-Repetition Workflow

 

Now, let's put everything together into a complete workflow:

  1. Start with your trigger node (HTTP Request, Scheduler, etc.)
  2. Add a Function node to initialize or retrieve memory
  3. Create a contextual prompt using previous responses
  4. Send to Language Model node (OpenAI, etc.)
  5. Check response for similarity/repetition
  6. Use IF node to either:
    • If repetitive: Retry with stronger anti-repetition prompt
    • If unique: Continue workflow and update memory
  7. Update memory with the final response

Here's a complete Function node implementation that combines these steps:


function processLLMResponse(items) {
  // 1. Initialize or get memory
  const memory = items[0].json.memory || { 
    responses: [],
    maxMemorySize: 5,
    retryCount: 0,
    maxRetries: 3
  };
  
  // 2. Get current response and question
  const currentResponse = items[0].json.aiResponse;
  const question = items[0].json.originalQuestion;
  
  // 3. Check for repetition
  let isDuplicate = false;
  let isTooSimilar = false;
  
  for (const oldResponse of memory.responses) {
    // Exact match
    if (oldResponse === currentResponse) {
      isDuplicate = true;
      break;
    }
    
    // Similarity check
    const similarity = calculateSimilarity(currentResponse, oldResponse);
    if (similarity > 0.75) { // 75% similarity threshold
      isTooSimilar = true;
      break;
    }
  }
  
  // 4. Prepare result object
  const result = {
    originalQuestion: question,
    aiResponse: currentResponse,
    memory: memory,
    needsRetry: (isDuplicate || isTooSimilar) && memory.retryCount < memory.maxRetries,
    isDuplicate: isDuplicate,
    isTooSimilar: isTooSimilar,
    retryCount: memory.retryCount
  };
  
  // 5. If repetition detected and retries left, prepare retry prompt
  if (result.needsRetry) {
    memory.retryCount++;
    result.retryPrompt = \`I need a completely different response. 
      Your previous responses were: "${memory.responses.join('" and "')}".
      Please provide a unique perspective on: ${question}.
      Use different examples, structure, and concepts than before.\`;
  } 
  // 6. If unique response or max retries reached, update memory
  else {
    memory.responses.push(currentResponse);
    if (memory.responses.length > memory.maxMemorySize) {
      memory.responses.shift(); // Remove oldest
    }
    memory.retryCount = 0; // Reset retry counter
  }
  
  result.memory = memory;
  items[0].json = result;
  return items;
}

// Helper function for similarity calculation
function calculateSimilarity(str1, str2) {
  // Implementation as shown in Step 5
  // ...
}

 

Step 8: Using Split Node for Advanced Retry Logic

 

For more complex workflows, use a Split node after your repetition check:

  1. Configure a Split node to route based on "needsRetry" property
  2. Connect one output to a new OpenAI node with the retry prompt
  3. Connect the other output to continue your regular workflow
  4. Merge the paths back together afterward

This approach allows for multiple retry attempts while keeping your workflow clean.

 

Step 9: Implementing Regex-Based Filtering

 

For specific types of repetition, regex filtering can be highly effective:


function filterRepetitivePatterns(items) {
  let response = items[0].json.aiResponse;
  
  // Detect and remove repeated sentences or phrases
  const repeatedPhraseRegex = /(.{20,}?)(?:\s\*\1){1,}/gi;
  response = response.replace(repeatedPhraseRegex, "$1");
  
  // Detect and remove list items with very similar content
  const listItemsRegex = /(\d+.\s._?)\n(\d+.\s._?)\n/g;
  const listItems = [...response.matchAll(listItemsRegex)];
  
  for (const match of listItems) {
    const item1 = match[1];
    const item2 = match[2];
    
    if (calculateSimilarity(item1, item2) > 0.7) {
      response = response.replace(item2 + "\n", "");
    }
  }
  
  items[0].json.filteredResponse = response;
  return items;
}

 

Step 10: Persistent Storage for Long-Term Memory

 

For workflows that run repeatedly over time, implement persistent storage:

Using n8n Variables:


// Store memory in n8n variables
function storeMemory(items) {
  const memory = items[0].json.memory;
  const variableName = 'AIMemory\_' + items[0].json.workflowIdentifier;
  
  // Get current variable value
  const currentValue = $workflow.variables[variableName] || '[]';
  let memoryArray;
  
  try {
    memoryArray = JSON.parse(currentValue);
  } catch (e) {
    memoryArray = [];
  }
  
  // Update with new responses
  memoryArray = [...memoryArray, ...memory.responses];
  
  // Keep only most recent items
  if (memoryArray.length > 20) {
    memoryArray = memoryArray.slice(-20);
  }
  
  // Save back to variable
  $workflow.variables[variableName] = JSON.stringify(memoryArray);
  
  return items;
}

Using External Database:

For more robust storage, connect to a database using n8n's database nodes:


// Example workflow to store in MongoDB
// First retrieve existing memory:
{
  "collection": "ai\_memory",
  "options": {},
  "query": {
    "workflowId": "{{$workflow.id}}"
  }
}

// Then update with new data
{
  "collection": "ai\_memory",
  "options": {
    "upsert": true
  },
  "query": {
    "workflowId": "{{$workflow.id}}"
  },
  "update": {
    "$set": {
      "lastUpdated": "{{$now}}",
      "workflowName": "{{$workflow.name}}"
    },
    "$push": {
      "responses": {
        "$each": ["{{$json.aiResponse}}"],
        "$slice": -20  // Keep only the latest 20 responses
      }
    }
  }
}

 

Step 11: Advanced Topic Tracking to Prevent Conceptual Repetition

 

Beyond exact or similar phrasing, language models might repeat concepts or topics. Implement topic tracking:


async function trackTopics(items) {
  const response = items[0].json.aiResponse;
  const memory = items[0].json.memory || { topics: {} };
  
  // Extract key topics (you might use a separate API call to a keyword extractor)
  // For this example, we'll use a simple approach
  const words = response.toLowerCase().split(/\W+/);
  const stopWords = ['the', 'and', 'is', 'in', 'to', 'a', 'of', 'for', 'with'];
  
  // Count word frequencies
  const wordFrequency = {};
  for (const word of words) {
    if (word.length > 3 && !stopWords.includes(word)) {
      wordFrequency[word] = (wordFrequency[word] || 0) + 1;
    }
  }
  
  // Find top keywords
  const keywords = Object.keys(wordFrequency)
    .sort((a, b) => wordFrequency[b] - wordFrequency[a])
    .slice(0, 5);
  
  // Update topic memory
  for (const topic of keywords) {
    memory.topics[topic] = (memory.topics[topic] || 0) + 1;
  }
  
  // Find overused topics
  const overusedTopics = Object.keys(memory.topics)
    .filter(topic => memory.topics[topic] > 2)
    .sort((a, b) => memory.topics[b] - memory.topics[a]);
  
  items[0].json.memory = memory;
  items[0].json.keywords = keywords;
  items[0].json.overusedTopics = overusedTopics;
  
  return items;
}

Then use the detected overused topics in your prompt engineering:


function createDiversityPrompt(items) {
  const overusedTopics = items[0].json.overusedTopics || [];
  const question = items[0].json.originalQuestion;
  
  let prompt = question;
  
  if (overusedTopics.length > 0) {
    prompt = \`${question} 
    
    When answering, please avoid focusing too much on these topics that have been covered extensively in previous responses: ${overusedTopics.join(', ')}.
    
    Try to provide fresh perspectives or alternative approaches.\`;
  }
  
  items[0].json.diversityPrompt = prompt;
  return items;
}

 

Step 12: Rotation of Models or Prompt Strategies

 

Another effective technique is to rotate between different models or prompt strategies:


function rotateModelsOrStrategies(items) {
  const memory = items[0].json.memory || { 
    requestCount: 0,
    strategies: [
      { model: "gpt-4", temperature: 0.7, prefix: "Explain concisely:" },
      { model: "gpt-3.5-turbo", temperature: 0.9, prefix: "Give a fresh perspective on:" },
      { model: "gpt-4", temperature: 0.5, prefix: "Provide a unique analysis of:" }
    ]
  };
  
  // Increment counter
  memory.requestCount++;
  
  // Select strategy based on rotation
  const strategyIndex = memory.requestCount % memory.strategies.length;
  const strategy = memory.strategies[strategyIndex];
  
  // Apply the strategy
  items[0].json.modelToUse = strategy.model;
  items[0].json.temperatureToUse = strategy.temperature;
  items[0].json.prompt = `${strategy.prefix} ${items[0].json.originalQuestion}`;
  items[0].json.memory = memory;
  
  return items;
}

 

Step 13: Implementing a Full n8n Workflow for Anti-Repetition

 

Here's how to implement a complete anti-repetition workflow in n8n:

  1. Create a new workflow with your desired trigger
  2. Add a Function node for memory initialization
  3. Connect to a Code node that creates your context-aware prompt
  4. Connect to the OpenAI/ChatGPT node
  5. Add a Function node for repetition detection
  6. Add an IF node that checks the "needsRetry" property
  7. If needsRetry is true:
    • Connect to a new OpenAI node with the retry prompt
    • Connect back to the repetition detection Function node (creating a loop)
  8. If needsRetry is false:
    • Connect to a Function node that updates the memory
    • Continue with the rest of your workflow
  9. Add a Function node at the end to store the memory for future workflow runs

 

Step 14: Monitoring and Analyzing Response Patterns

 

To improve your anti-repetition system over time, implement monitoring:


function monitorResponsePatterns(items) {
  const memory = items[0].json.memory || {};
  memory.analytics = memory.analytics || {
    totalResponses: 0,
    retryRates: [],
    averageSimilarity: []
  };
  
  // Update metrics
  memory.analytics.totalResponses++;
  
  // Calculate retry rate for last 10 responses
  if (memory.retryCount > 0) {
    memory.analytics.retryRates.push(memory.retryCount);
    if (memory.analytics.retryRates.length > 10) {
      memory.analytics.retryRates.shift();
    }
  }
  
  // Calculate similarity between latest response and previous ones
  if (memory.responses && memory.responses.length > 1) {
    const latestResponse = memory.responses[memory.responses.length - 1];
    let totalSimilarity = 0;
    let comparisonCount = 0;
    
    for (let i = 0; i < memory.responses.length - 1; i++) {
      const similarity = calculateSimilarity(latestResponse, memory.responses[i]);
      totalSimilarity += similarity;
      comparisonCount++;
    }
    
    const avgSimilarity = totalSimilarity / comparisonCount;
    memory.analytics.averageSimilarity.push(avgSimilarity);
    if (memory.analytics.averageSimilarity.length > 10) {
      memory.analytics.averageSimilarity.shift();
    }
  }
  
  // Calculate overall statistics
  memory.analytics.averageRetryRate = memory.analytics.retryRates.length > 0 
    ? memory.analytics.retryRates.reduce((sum, val) => sum + val, 0) / memory.analytics.retryRates.length 
    : 0;
    
  memory.analytics.averageSimilarityScore = memory.analytics.averageSimilarity.length > 0
    ? memory.analytics.averageSimilarity.reduce((sum, val) => sum + val, 0) / memory.analytics.averageSimilarity.length
    : 0;
  
  items[0].json.memory = memory;
  return items;
}

 

Step 15: Adaptive Temperature Adjustment

 

Implement a system that automatically adjusts the temperature based on repetition patterns:


function adaptTemperature(items) {
  const memory = items[0].json.memory || {};
  memory.temperatureSettings = memory.temperatureSettings || {
    currentTemperature: 0.7,
    minTemperature: 0.5,
    maxTemperature: 1.0,
    adjustmentStep: 0.05
  };
  
  // If we've been having repetition issues, increase temperature
  if (memory.analytics && memory.analytics.averageRetryRate > 1) {
    memory.temperatureSettings.currentTemperature += memory.temperatureSettings.adjustmentStep;
    if (memory.temperatureSettings.currentTemperature > memory.temperatureSettings.maxTemperature) {
      memory.temperatureSettings.currentTemperature = memory.temperatureSettings.maxTemperature;
    }
  }
  // If responses have been very diverse, possibly decrease temperature
  else if (memory.analytics && memory.analytics.averageSimilarityScore < 0.3) {
    memory.temperatureSettings.currentTemperature -= memory.temperatureSettings.adjustmentStep;
    if (memory.temperatureSettings.currentTemperature < memory.temperatureSettings.minTemperature) {
      memory.temperatureSettings.currentTemperature = memory.temperatureSettings.minTemperature;
    }
  }
  
  items[0].json.temperatureToUse = memory.temperatureSettings.currentTemperature;
  items[0].json.memory = memory;
  
  return items;
}

 

Step 16: Implementing Custom Response Validators

 

Create specialized validators for specific content types:


function validateResponse(items) {
  const response = items[0].json.aiResponse;
  const responseType = items[0].json.expectedResponseType || 'general';
  let isValid = true;
  let validationMessage = '';
  
  // Different validation based on response type
  switch (responseType) {
    case 'list':
      // Check if list items are unique
      const listItems = response.match(/\d+.\s+(.\*?)(?=\n\d+.|\n\n|$)/gs) || [];
      const uniqueItems = new Set(listItems.map(item => item.trim()));
      
      if (uniqueItems.size < listItems.length) {
        isValid = false;
        validationMessage = 'List contains duplicate items';
      }
      break;
      
    case 'code':
      // Check if code blocks are repetitive
      const codeBlocks = response.match(/`[\s\S]*?`/g) || [];
      for (let i = 0; i < codeBlocks.length; i++) {
        for (let j = i + 1; j < codeBlocks.length; j++) {
          if (calculateSimilarity(codeBlocks[i], codeBlocks[j]) > 0.7) {
            isValid = false;
            validationMessage = 'Code examples are too similar';
            break;
          }
        }
        if (!isValid) break;
      }
      break;
      
    default:
      // General validation - check for paragraph repetition
      const paragraphs = response.split('\n\n');
      for (let i = 0; i < paragraphs.length; i++) {
        for (let j = i + 1; j < paragraphs.length; j++) {
          if (paragraphs[i].length > 50 && calculateSimilarity(paragraphs[i], paragraphs[j]) > 0.7) {
            isValid = false;
            validationMessage = 'Contains repeated paragraphs';
            break;
          }
        }
        if (!isValid) break;
      }
  }
  
  items[0].json.isValid = isValid;
  items[0].json.validationMessage = validationMessage;
  
  return items;
}

 

Conclusion: Creating a Robust Anti-Repetition System

 

By implementing these techniques in your n8n workflows, you can significantly reduce repetitive responses from language models. The key points to remember are:

  • Adjust temperature and other diversity parameters
  • Maintain memory of previous responses
  • Include context about previous responses in your prompts
  • Implement similarity detection for filtering repetitive content
  • Create retry mechanisms with specialized prompts
  • Use persistent storage for long-term memory across workflow runs
  • Monitor and adapt your system over time

While complete elimination of repetition is challenging, these approaches will help maintain diverse, fresh responses from language models in your n8n automations. As language models evolve, you may need to adjust your strategies, but the fundamental principles of memory, context, and diversity parameters will remain effective.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022