13 KiB
Manual Testing Guide (Discord)
How to manually verify each bot command from a Discord server. The automated suite
(uv run pytest vibe_bot/tests/ -v) covers the internals; this guide is for
end-to-end behavior against a real Discord server and the real LLM/image/embedding
APIs. Every command below makes real API calls, so expect API usage and latency.
Before You Start
-
A working
.envat the repo root:DISCORD_TOKEN, the chat/image/edit/embedding endpoints with their keys and model names (see the Configuration section ofREADME.md). -
The bot running and online in your test server, either:
uv run python -m vibe_bot.mainor the container image (
./build.sh, then run with--env-file .env). Watch the console output while testing — the bot logs each command trigger. -
In the Discord Developer Portal, the bot must have Message Content Intent, Server Members Intent, and Presence Intent enabled (the bot requests all three at startup and will refuse to log in without them).
-
For
!speak:kokoro-v1.0.onnxandvoices-v1.0.binin the project root (baked into the container image). Without them the bot still runs; only!speakdegrades (it replies "TTS engine not initialized...").!voicesis a static list and works either way. -
Use a dedicated test channel. Start every clean pass by wiping stored memory:
!lobotomizeExpected: "All conversation history and memory has been cleared." (This deletes all chat messages and embeddings for all users and bots. It does not delete the custom bots themselves or the image ETA stats.)
-
Create the two standard test bots used throughout this guide. Use lowercase names: incoming chat text is lowercased before being matched against the stored bot name, so a lowercase name matches whatever capitalization you type.
!custom-bot alfred you are a proper british butler who speaks in short, polite sentences !custom-bot jarvis you are a dry, sarcastic AI assistant
Command Reference
!custom-bot <name> <personality> — create a custom bot
Happy path
- Send:
!custom-bot alfred you are a proper british butler - Expect:
Custom bot **'alfred'** has been created with personality: *you are a proper british butler*followed byYou can now use this bot with:!alfred `` !list-custom-botsnow includes alfred, and!alfred <msg>works immediately (the bot-name cache is invalidated on create).
Replacing an existing name — bot names are a global namespace, not per-user.
- Send the same command again with a different personality.
- Expect:
Custom bot **'alfred'** already existed and has been **replaced** with personality: *...*
Validation (names 2–50 chars, personality 10–1000 chars):
| Send | Expected reply |
|---|---|
!custom-bot a you are a test bot (1-char name) |
Invalid bot name. Name must be between 2 and 50 characters. |
| a 51+-character name | same |
!custom-bot bob short (personality < 10 chars) |
Invalid personality. Description must be at least 10 characters. |
| a 1001+-character personality | Personality too long. Max 1000 characters. |
!list-custom-bots — list custom bots
- Empty state (fresh database, or after deleting every bot):
No custom bots have been created yet. Use!custom-botto create one. - After creating bots:
Available Custom Bots:followed by one* <name>line per bot.
!delete-custom-bot <name> — delete a custom bot (owner only)
- As the creator:
!delete-custom-bot jarvisExpect:Custom bot 'jarvis' has been deleted.Afterwards!jarvis hiproduces no response and!list-custom-botsomits it (the bot-name cache is invalidated on delete). - Unknown name:
!delete-custom-bot nosuchbotExpect:Custom bot 'nosuchbot' not found. - Ownership: have a second Discord account create
tempbot, then run!delete-custom-bot tempbotfrom a third account. Expect:You can only delete your own custom bots.(the bot is not deleted). The creator's delete should then succeed.
Chatting with a custom bot — !<bot_name> <message>
This is not a registered command; messages starting with !<known bot name> are
matched in on_message and dispatched to the chat service.
Happy path
- Send:
!alfred Could you fetch me some tea? - Expect, in order:
alfred is searching its databanks for Could you fetch me some tea?...alfred response- The reply, in character, under 2–3 sentences.
Long replies — a reply longer than 1000 characters arrives as multiple consecutive messages (chunked at 1000 chars).
Missing the trailing text — !alfred alone (no message after the name)
matches nothing: the bot stays silent and no command error is raised.
Memory / RAG
!alfred My favorite color is teal. Remember that.- Later — different topic, ideally a different channel or day:
!alfred What is my favorite color? - Expect: it answers teal. RAG prepends your 10 most recent messages plus up to 5 semantically similar stored messages (similarity threshold 0.7) to the prompt.
- Cross-bot check (shared memory is by design — history is not siloed per bot):
!jarvis What is this user's favorite color?should also know.
Tool call
- Send:
!alfred Who is in this channel? - The bot may call the
get_channel_memberstool; expectalfred is looking at the channel members...before a reply that names server members. (Not guaranteed every time — the LLM decides whether to call it.)
Failure path — stop the LLM API (or point CHAT_ENDPOINT at a dead URL and
restart the bot), then send !alfred hi.
Expect: An error occurred while processing your request. and a traceback in the
bot console.
!speak <text> [--voice <name>] — plain text-to-speech
!speak hello worldExpect:Generating speech...then aspeech.mp3attachment. Play it.- Without
--voice, the configuredTTS_VOICE(defaultaf_sarah) is used. !speak hello world --voice af_bellaExpect: same flow, different voice. The--voiceflag is only recognized at the very end of the message;!speak the --voice flag is funspeaks the entire string verbatim.- Unknown voice:
!speak hi --voice af_nobodyExpect:Unknown voice 'af_nobody'. Use!voicesto see available voices. - No text:
!speak --voice af_bellaExpect:Please provide text to speak. - Over 5000 characters of text:
Expect:
Text too long to speak. Max 5000 characters. - Cooldown: 3 uses per 30 seconds per user. A 4th immediate call:
You're using that too quickly, try again in Ns. - Missing TTS model files (run without the onnx/bin files):
TTS engine not initialized. Make sure kokoro-v1.0.onnx and voices-v1.0.bin are present.
!speak <bot_name> <text> [--voice <name>] — custom bot responds and speaks
Requires an existing custom bot; the first word is compared exactly (case-sensitive) against stored bot names.
!speak alfred what time is itExpect, in order:**alfred** is thinking...**alfred**: <in-character reply>(the LLM reply, not your text)Generating speech for **alfred**...speech.mp3containing the bot's reply spoken aloud. The exchange is also stored in history (visible later via!history alfred).
!speak alfred what time is it --voice am_puck— same flow, am_puck voice.!speak alfred(no text after the name):Please provide text for the bot to respond to.!speak nonexistent hello:Custom bot 'nonexistent' not found.
!voices — list TTS voices
- No arguments. Expect the static catalog grouped by category:
en-US female (
af_alloy…af_sky), en-US male (am_adam…am_puck), en-GB (bf_*,bm_*), fr-FR (ff_siwis), Italian (if_sara,im_nicola), Japanese (jf_*,jm_kumo), Mandarin (zf_*,zm_*), ending withUse!speak --voice <voice_name>to choose a voice. - Spot-check one voice per language with
!speak <text in that language> --voice <name>and listen for the expected accent.
!doodlebob <prompt> — generate an image
!doodlebob a cat sitting on the moonExpect, in order:**Doodlebob shopping for a canvas...****Doodlebob selected <layout>**— the LLM pickssquare,portrait, orlandscapefrom the prompt (a tall subject should yield portrait, a wide scene landscape). The canvas size follows the layout.**Doodlebob calling drone strike on <first 100 chars of the final prompt>...**- From the second generation onward:
**Drone ETA: ~N seconds**(moving average of recorded generation times). - An
image.pngattachment that matches the prompt. **Strike complete. Image generated in N.N seconds.**
- Prompt over 2000 characters:
Prompt too long. Max 2000 characters. - Cooldown: 1 per 60 seconds per user.
- Image API failure:
Failed to generate image. The server may be busy.
!retcon <prompt> — edit an attached image
- Attach an image on the same message as the command (drag it into the
channel):
!retcon make it look like it is on fire [image]Expect:**Rewriting history to match make it look like it is on fire...**then an editedimage.pngattachment. - No attachment:
!retcon make it sunnyExpect:Please attach an image to edit.(Only real attachments count; pasted external URLs are ignored, and only Discord CDN hosts are downloaded, capped at 8 MB.) - Prompt over 2000 characters:
Prompt too long. Max 2000 characters. - Image edit API failure:
Failed to edit the image.
!talkforme <bot1> <bot2> <n> <topic> — bot-vs-bot conversation
Requires two existing custom bots.
!talkforme alfred jarvis 4 the meaning of lifeExpect:alfred is going to talk to jarvis about "the meaning of life" for 4 replies.then alternating messages headed## alfred/## jarvis, each reply in character. Total chat messages is n + 1 (bot1's opener plus n loop replies); n is capped at 20, so at most 21 messages.- Too few parts:
!talkforme alfred jarvisExpect:Usage: !talkforme bot1 bot2 <number> <topic> - Unknown bot:
!talkforme alfred nosuchbot 4 teaExpect:nosuchbot is not a real bot... - Non-integer count:
!talkforme alfred jarvis four teaExpect:Message limit must be an integer. - Topic over 500 characters:
Topic too long. Max 500 characters. - Cooldown: 1 per 30 seconds per user.
!history <bot_name> — view a bot's chat history
-
After chatting with alfred:
!history alfredExpect:Chat History for **alfred**:followed by the latest up-to-20 exchanges (oldest of the 20 first), each pair as:<your message> --- alfred: <its reply>Turns from
!speak alfred ...are included too (they are stored under the bot name). -
Unknown bot:
!history nosuchbotExpect:Custom bot 'nosuchbot' not found. -
A bot with no stored messages:
No chat history found for **alfred**. -
After
!lobotomize: same "No chat history found" reply.
!debug [subcommand] — debug menu
!debug— prints the menu listingmembers,whoami,tools.!debug members—Members in this channel (N total):followed by an alphabetized roster of server members (Discord has no per-channel membership; the channel only identifies the guild), each entry showing display name, nickname, global name, and[status]. In a DM (no guild):No members found in this channel.!debug whoami— a block about your own account: Global Name (if set), Nickname (if set), Top Role (if not @everyone), Activities (if any, custom status excluded), Joined date, Username, User ID, Account Created date. Verify each value against your account.!debug tools— theget_channel_memberstool's name, description, and parameter JSON schema.!debug banana—Unknown debug sub-command:banana`` plus a pointer to!debug.
!lobotomize — wipe all stored memory
- Send
!lobotomize. Expect:All conversation history and memory has been cleared. - Verify:
!history alfred→No chat history found for **alfred**.- The RAG recall test above no longer remembers the fact.
!list-custom-botsis unchanged (bots survive; onlychat_messagesandmessage_embeddingsare cleared).
Suggested End-to-End Pass
A full manual pass in order:
!lobotomize— clean slate!custom-bot alfred ...and!custom-bot jarvis ...!list-custom-bots— both listed!alfred ...— a few turns; tell it a fact to remember later!jarvis What did I tell you about ...?— cross-bot memory!speak hello world --voice af_bella, then!speak alfred tell me a joke!voices!doodlebob ...twice (second run should show the Drone ETA)!retcon ... [image]!talkforme alfred jarvis 4 ...!history alfred— contains the steps 4–6 exchanges!debug,!debug members,!debug whoami,!debug tools!delete-custom-bot jarvis— then confirm!jarvis hiis silent!lobotomize— confirm!history alfredis empty
Covered Only by the Automated Suite
Not practical to verify by hand (see uv run pytest vibe_bot/tests/ -v):
- embedding similarity thresholds and vector math
- database migrations and the
MAX_HISTORY_MESSAGEScleanup (needs 1000+ messages) - message chunking edge cases
- RAG benchmark behavior (
uv run python scripts/bench_rag.py)