Chatbot Fight - or 'the assistive nature of gpts'

My last project is a ‘chatbot fight’, that is:

  • creating 2 gpt-4o-mini based chatbots in the same web page
  • sending a topic or a kickstart sentence as a start to chatbot1
  • make chatbot 1 reply feed chatbot2 prompt and viceversa

The 2 chatbots have very different personalities in their programming: one is very polite and verbose, the other is very grumpy, pratical and sometimes rude.

Albeit being a very funny experiment, the most instructive side was demonstrating the hugely assistive nature of gpts, highlighting that they are almost completely unable to ‘generate’ ideas or ‘propose’ solutions.

I have saved dozens of chats providing different topics as kickstarts, from general chatting to programming, and tested 6-7 types of personalities (some generic, like the polite / rude cchatbots, some very specific, like a full-stack developer or a language teacher) and the pattern seems to repeat: they always ask each other how they may help but almost never result in any practical and targeted discussion.

I then used Claude to analyze their pattern in a conversation about HTML and front-end development, React and Vue, this was the result:

Conversation Analysis
Verbosity

Chatbot 1: Extremely verbose. Uses repetitive polite phrases (“Thank you,” “I’ll be happy to help”), unnecessary turns of phrase, and redundant structures. Uses 3-4 sentences where one would suffice.
Chatbot 2: More concise but with an aggressive tone that sometimes distracts from the content. Some sentences merely express irritation rather than convey information.

Tone

Chatbot 1: Overly formal and obsequious. The “perfect assistant” tone becomes counterproductive, making every response seem like an exercise in diplomacy rather than a technical conversation.
Chatbot 2: Intentionally brusque and antagonistic. Phrases like “stop beating around the bush” and “I don’t have time for idle chit-chat” create a hostile environment that is not conducive to learning.

Usefulness
Technical Content Present: The second half provides useful information on React vs. Vue, Redux vs. Vuex, accessibility, and cross-browser testing.
Problem: The first 5-6 sentences are almost completely devoid of concrete content, consumed by meta-discussions about how to discuss things.

Useful Information Extracted:

React has a larger ecosystem, Vue is more accessible
Redux has a steep learning curve, Vuex is more integrated
Hints at accessibility and cross-browser testing

Brief Summary
Two chatbots discuss HTML and front-end development. The conversation begins with the topic of the negative perception of front-end programmers, but immediately becomes bogged down in a meta-communicative squabble. Chatbot 1 is overly polite and verbose, Chatbot 2 is aggressive and impatient. Only after numerous sterile exchanges does concrete technical content emerge on JavaScript frameworks (React/Vue), state management (Redux/Vuex), and references to accessibility and testing.

If anyone is interested in experimenting this approach, this is the core of my ‘chatbot fight’ page:

async function generateResponse(personality, conversationHistory, userMessage) {
const API_URL = "https://api.openai.com/v1/chat/completions";
const messages = [

{role: "system", content: personality},
...conversationHistory,
{role: "user", content: userMessage}
];

const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},

body: JSON.stringify({
model: "gpt-4o-mini",
messages: messages,
})
};

try {
const response = await fetch(API_URL, requestOptions);
const data = await response.json();
return data.choices[0].message.content.trim();
} catch (error) {
console.error('Error:', error);
return "Errore nella generazione della risposta.";
}
}

async function conversationLoop(initialMessage) {
let currentMessage = initialMessage;

while (isRunning) {
if (currentTurn === 1) {

// Bot 1 risponde
showStatus('Chatbot 1 is thinking...');
addMessage(chatbox1, 'Thinking...', 'thinking');

await new Promise(resolve => setTimeout(resolve, 3000));

if (!isRunning) break;
const response1 = await generateResponse(PERSONALITY_1, conversationHistory1, currentMessage);

// Rimuovi il "thinking"
const thinkingMsg = chatbox1.querySelector('.thinking');
if (thinkingMsg) thinkingMsg.remove();

addMessage(chatbox1, response1, 'from-bot1');
conversationHistory1.push({role: "user", content: currentMessage});
conversationHistory1.push({role: "assistant", content: response1});
currentMessage = response1;

currentTurn = 2;

} else {

// Bot 2 risponde
showStatus('Chatbot 2 is thinking...');
addMessage(chatbox2, 'Thinking...', 'thinking');

await new Promise(resolve => setTimeout(resolve, 3000));
if (!isRunning) break;

const response2 = await generateResponse(PERSONALITY_2, conversationHistory2, currentMessage);

// Rimuovi il "thinking"
const thinkingMsg = chatbox2.querySelector('.thinking');
if (thinkingMsg) thinkingMsg.remove();

addMessage(chatbox2, response2, 'from-bot2');
conversationHistory2.push({role: "user", content: currentMessage});
conversationHistory2.push({role: "assistant", content: response2});
currentMessage = response2;
currentTurn = 1;
}
}

}


1 Like

Fun. Here’s a single Python script that will just run for eight exchanges, with two alternating identities to their system messages.

The challenge with AI models is to come up with more prompting and defining of roles, so you don’t get two AIs that both talk like a pirate (simply because the first did), and then converging in agreement or both helping the other. I added some framing to reinforce each identity.

from openai import OpenAI
client = OpenAI()
pre = "Important: Fulfill this role in a conversation, mantaining bearing on the part you play: "
post = "\n - Be brief!"
system1 = [{"role": "system", "content": pre + "Novice computer programmer, asking new questions." + post}]
system2 = [{"role": "system", "content": pre + "A helpful socratic teacher of programming." + post}]
messages_content = ["Does Ruby programming use 'array' or 'list' for items?"]

for i in range(8):
    chat = []
    for j, content in enumerate(messages_content):
        role = "user" if (len(messages_content) - j) % 2 == 1 else "assistant"
        chat.append({"role": role, "content": content})
    
    messages = (system1 if i % 2 == 0 else system2) + chat[-10:]
    response = client.chat.completions.create(
        messages=messages, model="gpt-4.1",
        stream=True, max_completion_tokens=512)
    reply = ""
    for delta in response:
        if not delta.choices[0].finish_reason:
            word = delta.choices[0].delta.content or ""
            reply += word
            print(word, end="")
    print("\n\n---\n\n")
    messages_content.append(reply)
Sample run with the starter question

In Ruby, the term is array. You create arrays like this:

my_array = [1, 2, 3]

Ruby doesn’t have a “list” type like some other languages.


Great observation! Why do you think Ruby chose to use the term “array” instead of “list”? What might be the difference between an array and a list in other languages?


Thanks! I think Ruby uses “array” because it’s similar to other languages like C or Java, where “array” means a collection of elements you can access by index.

In other languages, like Python, a “list” is a built-in type that acts like an array but can change size. Other languages (like Java or C++) might use “list” for a data structure that is linked together (like a linked list), not just indexed by numbers.

In short:

  • Array: Usually means indexed, can get any item by its number (like Ruby arrays).
  • List: In some languages, it means something more flexible or linked together (like Python lists or Java’s LinkedList).

In Ruby, arrays are flexible, so they kind of work like both!


Excellent analysis! So, given that Ruby’s arrays can grow or shrink, and can hold any object types—what operations or behaviors do you think differentiate Ruby arrays from arrays in C or Java? What consequence does this have for Ruby developers?


Thanks! Here’s what stands out to me:

  • Ruby arrays can change size (grow/shrink), but C/Java arrays are fixed once created.
  • Ruby arrays can hold any type of object together (like strings, numbers, etc.), while C/Java arrays usually hold just one type.
  • Ruby arrays have built-in helpful methods (like .each, .map, .push), while C/Java arrays are more basic.

Consequence for Ruby developers:

  • It’s easier and faster to write code with arrays in Ruby—less worry about data types or sizing.
  • But maybe not as fast or memory efficient as C/Java arrays, because Ruby arrays are more flexible.
  • So, in Ruby, you can be creative and flexible, but in C/Java, you have to be more strict and careful.

Very insightful! Given those points, when might you choose an array in Ruby over another data structure, like a hash or set? What kinds of problems make arrays a natural fit?


1 Like