Anti-Chatbot: a chat that answers with real strangers
- Role: Interface, search and ranking pipeline, backend
- When: Fall 2025
- Exhibited: ITP Winter Show 2025
- Stack: Python, Flask, OpenAI API (GPT-4o-mini), Serper (Google search), sentence-transformers, JavaScript, Vite, marked

The idea
Chatbots answer everything in the same calm, helpful voice: the statistical average of human speech, safe and sanded down. The internet does not talk like that, it is specific, messy, sometimes useful and often rude.
The pitch was a chat interface that refuses to generate a single word. Ask it something and it answers with a sentence a real person already wrote, maybe five minutes ago, maybe five years ago, to someone else entirely.
There is something absurd, and oddly intimate, about being answered by a stranger who was never talking to you.

The interface
The frontend is plain JavaScript on Vite, and replies are rendered as Markdown with marked, since Reddit comments are written in it. A long reply is split at paragraph breaks into several bubbles that appear 150 ms apart, so the answer arrives in bursts, like a person typing. The author, subreddit or board, and permalink sit under the last bubble. Images and GIFs appear as their own bubble.
Turning a message into a search
People do not type messages the way threads are titled (eg. “I feel empty” is a feeling, not a search query) , so GPT-4o-mini (yes, I still used llms) rewrites each message into three short thread titles that someone might have posted, and the original message is added as a fourth query.
The searching process has one hard rule, which is to keep the tone. A serious message should find serious threads. A meme should find meme threads. A question should find a thread that answers it.
However, I never stated that responses should be “pro user”.

Each query goes to Google through the Serper API. To heat it up, each query also gets a second search restricted to 4chan alone, so 4chan always has a chance to answer.
Every comment is a candidate
A search result is a whole thread, but a good reply is usually one comment deep inside it. So the backend fetches full threads (up to 10 per message) through Reddit’s .json endpoints and 4chan’s read-only JSON API, and every original post and comment becomes a candidate with its author, board or subreddit, timestamp and permalink.
Some data cleaning is also done. E.g., 4chan posts need cleaning first. Reply links like >>12345678 and quote lines that point at other posts are removed. Image and GIF links are pulled out of each post as media, so a reply can be a picture.

Picking the best reply
A single message can pull in a lot of candidates. Comparing each one carefully against the message is slow, and comparing them all loosely gives weak matches. So ranking happens in two stages:
| Stage | Model | How it works | Job |
|---|---|---|---|
| 1. Filter | all-MiniLM-L6-v2 (bi-encoder) | Embeds the message and every candidate separately, compares by cosine similarity | Cheap: narrow everything down to the top 20 |
| 2. Rerank | ms-marco-MiniLM-L-6-v2 (cross-encoder) | Reads the message and one candidate together as a pair | Precise: score how well it works as a reply |
The top cross-encoder score wins.
When nothing comes back
Sometimes the search finds nothing usable, and a chatbot would apologize. But this one doesn’t, to make it even more “human”, we added a meme fallback. Because we all send reaction pics or emojis when we really don’t know what to reply.
- A copypasta from r/copypasta’s top posts of the week.
- If that fails, a random meme from one of several meme subreddits.
- If even that fails:
¯\_(ツ)_/¯.

That’s it.
And my favorite response is actually this one, while testing:
