Skip to main content

【Left Side Escalator】An AI Matchmaking Bot That Turns One Sentence into a Community Match

Project Overview

Left Side Escalator AI matchmaking bot

Left Side Escalator (電扶梯走左邊) is a Taiwanese personal-growth podcast focused on helping listeners step outside their comfort zones and become better versions of themselves. Its Discord community brings together listeners who want to exchange ideas, take action, and connect with people who share similar values.

After noticing that members had no easy way to find people with similar interests, I proactively proposed, designed, and built an AI matchmaking bot for the community.

A member simply tags @AI Matchmaker and writes a request such as:

“I’m looking for friends who like yoga.”

The bot interprets the request, searches the community’s member data, and returns relevant matches within seconds.

Built independently with Discord.js, OpenAI GPT-4o, Node.js, and Express, the bot went from proposal in December 2024 to public launch in February 2025.

chatbot

What Problem Did I Solve?

As the community grew, finding people with similar interests required members to search introduction posts manually or ask an administrator for help.

I turned this manual process into a self-service experience: members can describe who they want to meet in natural language and receive relevant matches within seconds.

Who Uses It?

The bot launched publicly in the Left Side Escalator (電扶梯走左邊) Discord community on February 20, 2025. Members can tag @AI Matchmaker and type a natural-language request to find people with shared interests or birthdays. Usage logs show that members continued to use the bot for matchmaking through at least June 2025, with queries ranging from yoga and hiking to psychology.

Results

  • Replaced manual searching and admin-assisted matching with a one-sentence query.
  • Reduced the barrier to starting conversations with like-minded members.
  • Continued receiving real community usage for months after launch.

What key decisions did you own?

  1. Splitting the architecture into three layers. I deliberately split the system into a Discord bot (interaction layer), an Express API (data layer), and OpenAI (natural-language-to-query translation layer), instead of putting all logic inside the bot — so the data service could be deployed and verified independently.
  2. Using the LLM as a query compiler, not a semantic search engine. I skipped embeddings and a vector database, and instead wrote a strict system prompt (openaiSystemMessage.js) that constrains GPT-4o to translate user input into exactly two fixed API path formats (/find?birthday= or /find?hobbies=). Few-shot examples in the prompt hard-code date-format handling (Chinese/English) and interest-synonym expansion (e.g. "爬山" expands to a dozen-plus synonyms like "登山, 百岳, 爬百岳...") to keep match rates high.
  3. Choosing CSV over a database for storage. Given the member count and low update frequency, I used a CSV file as the MVP data source (csvLoader.js loads it into memory once on startup) — deliberately skipping a database and an admin UI, to put engineering effort into query quality instead.
  4. API security. The Express server has a Bearer-token authMiddleware, so member data can't be accessed without authorization or scraped externally.
  5. Discord embed pagination. I wrote generateEmbeds.js to handle Discord's three hard embed limits (field length, total embed length, field count), so a result set with many matches automatically splits across embeds instead of erroring out (full discussion below).
  6. Operational trade-offs. Since the bot was expected to run on a memory-constrained free tier (Render, 512MB), I added scheduled GC cleanup and a /healthz health-check endpoint for UptimeRobot to hit, so the bot wouldn't get marked offline due to idling or cold starts.

What constraints did you face, and how did you trade off?

  • Match accuracy depends entirely on prompt quality. Because interest matching on the Express side is a simple string.includes(), not semantic search, all the "fuzzy matching" capability comes from the synonym lists hand-written into the OpenAI system prompt. That means adding a new interest category requires manually extending the synonym list — a known, accepted maintenance cost, since the community's interest categories are limited and this is far cheaper than adding vector search.

  • Data updates are a manual process. When a new member joins, their data has to be manually added to data.csv and the service restarted. This is called out explicitly as a known limitation in the proposal deck ("Update database (CSV) manually if there are new friends"). The trade-off: the community doesn't grow fast enough to justify building an admin CMS for this.

  • Discord's message-format limits (not fully solved). Discord embeds stack four hard limits — a single field's content ≤1024 characters, a single embed's total content ≤6000 characters, a max of 25 fields per embed, and a max of 10 embeds per message. generateEmbeds.js handles the first three (content over 1024 characters auto-splits into a new field marked (cont.); content over 6000 characters or more than 25 fields opens a new EmbedBuilder) — but not the fourth. The current implementation sends every embed transformDiscordEmbed() produces in a single message.edit({ embeds: transformDiscordEmbedResult }) call (see index.js). There's a commented-out earlier implementation still in the code that sent each embed as its own message (message.channel.send({ content: '<@...>', embeds: [embed] })), which would have sidestepped the 10-embed limit — but it flooded the channel and hurt the experience, so I switched to a single combined reply. This is a known, unresolved trade-off: it buys a clean single reply, at the cost that a query matching enough people (roughly 10+ embeds' worth) would theoretically get rejected outright by the Discord API — there's currently no guard or graceful degradation for that case.

  • Free/low-cost deployment trade-offs. The proposal stage compared Vercel (cold starts), Zeabur (starting at $5/month), and AWS, and ultimately went with a free/low-cost tier constrained to 512MB of memory — which is why memory monitoring and manual GC exist, in exchange for zero or near-zero hosting cost.

  • Deliberately narrow query scope. The bot only supports two query dimensions — birthday and interests — rather than open-ended full-text search, so that OpenAI's output format stays constrained and verifiable, avoiding natural-language translations that don't fit the API format.

  • Cost is controlled but not built for future scale. GPT-4o usage cost ($2.50 / $10 per 1M tokens) is acceptable at the community's current size, so there's no caching or local embedding model. If usage grows significantly, the current architecture would need to be re-evaluated.

Other Notes

This project went from an architecture proposal deck on 2024-12-29, to a public launch for the full community on 2025-02-20, to real, stable usage logs at least through 2025-06-02 — spanning roughly six months of continuous production operation, which demonstrates a full design-build-deploy-operate delivery cycle, not just a POC. The code also defends against failure cases (if OpenAI or the API call fails, the bot replies "Error generating response, please try again later" instead of crashing), showing production robustness was considered as part of delivery.