complete restructure

This commit is contained in:
2026-08-19 13:16:43 -04:00
parent d7b6f28cbd
commit f87e1d51ef
60 changed files with 8176 additions and 5143 deletions
+18 -12
View File
@@ -1,34 +1,40 @@
# AGENTS.md
Single Python package `vibe_bot`: a Discord bot (discord.py, `!` prefix) with SQLite RAG chat history, Kokoro TTS, and image gen/edit via OpenAI-compatible APIs. Python 3.13, managed with uv. Everything lives in `vibe_bot/`.
Single Python package `vibe_bot`: a Discord bot (discord.py, `!` prefix) with SQLite RAG chat history, Kokoro TTS, and image gen/edit via OpenAI-compatible APIs. Python 3.13, managed with uv. Everything lives in `vibe_bot/` (plus the standalone benchmark `scripts/bench_rag.py`, which is never imported by the package).
## Commands
- Setup: `uv sync --extra dev` — plain `uv sync` already installs the `dev` dependency group (ruff, pyright); the `dev` extra adds black/debugpy/mypy
- Setup: `uv sync` — installs the project plus the `dev` dependency group (ruff, mypy, pyright, black, pytest); there are no extras
- Run bot: `uv run python -m vibe_bot.main` — this logs the real bot into Discord with the token from `.env`; don't run it as a smoke test
- Tests: `uv run pytest vibe_bot/tests/ -v`; single test: `uv run pytest vibe_bot/tests/test_main.py::test_name`
- Tests: `uv run pytest vibe_bot/tests/ -v` — hermetic by default (`addopts = "-m 'not live'"`); `uv run pytest -m live` runs the network-dependent live tests; single test: `uv run pytest vibe_bot/tests/test_app.py::test_name`
- Checks: `uv run ruff check vibe_bot/`, `uv run mypy vibe_bot/` (strict), `uv run pyright vibe_bot/` (strict), `uv run black --check vibe_bot/`
- Container: `./build.sh` (podman). CI (`.gitea/workflows/build-push.yml`, Gitea) only builds/pushes the image on main/release — lint and tests are not gated; run them locally
- RAG benchmark: `uv run python scripts/bench_rag.py` — hermetic (embeddings monkeypatched, temp DBs); prints p95 latency of `get_conversation_context` at 1k and 5k rows
- Container: `./build.sh` (podman). CI (`.gitea/workflows/build-push.yml`, Gitea) runs a `test` job (all four checks + hermetic pytest) on pushes/PRs/releases, and `build-and-push` (image build/push) only on main/release, gated on `test`
## Setup requirements
- A repo-root `.env` is required even to run tests: `config.py` calls `load_dotenv()` and raises `RuntimeError` at import time if any required var is missing. Placeholder values suffice for the mocked suite. Never commit it.
- PortAudio is a required system library (kokoro-tts → sounddevice). Without it, ~60 tests in `test_main`/`test_tts` error at import time. On this host: `sudo dnf install portaudio` (the Containerfile installs `portaudio19-dev`).
- PortAudio is a required system library (kokoro-tts → sounddevice). Without it, the test suite errors at import time (every module loads `conftest.py`, which imports `vibe_bot.app` → `vibe_bot.tts` → `sounddevice`). On this host: `sudo dnf install portaudio` (the Containerfile installs `portaudio19-dev`).
- TTS needs `kokoro-v1.0.onnx` and `voices-v1.0.bin` in the repo root (baked into the container image). The bot runs without them; only `!speak` degrades.
## Test suite gotchas
- Baseline: everything passes except `test_llama_wrapper.py::test_chat_completion_think` and `::test_chat_completion_instruct`. Those two are unmocked live calls to the real `CHAT_ENDPOINT` from `.env` and fail without network access to that API.
- `test_config.py` hardcodes `sys.path.insert(0, "/var/home/ducoterra/Projects/vibe_discord_bots")` (a stale repo path). The test only passes because pytest's cwd fallback finds the package — run tests from the repo root.
- If `uv run <tool>` suddenly fails with `ModuleNotFoundError` or "bad interpreter", the `.venv` shebangs are stale from a repo move: `rm -rf .venv && uv sync --extra dev`.
- The network-dependent tests are marked `live` (in `test_llm_client.py`): unmocked calls to the real `CHAT_ENDPOINT` from `.env`. They are deselected by default and only run via `uv run pytest -m live`, which needs network access to that API.
- If `uv run <tool>` suddenly fails with `ModuleNotFoundError` or "bad interpreter", the `.venv` shebangs are stale from a repo move: `rm -rf .venv && uv sync`.
## Code map
- `main.py` — entrypoint. The bot is created at module import (module-level `commands.Bot(...)`), `bot.run()` only under `__main__`. Custom-bot "commands" (`!<bot_name> ...`) are matched in `on_message` against the database, not registered with `bot.command`.
- `main.py` — entrypoint: `validate_config()`, `configure_logging()`, then `build_bot(app).run(token)`.
- `app.py` — composition root: `configure_logging()` (the only `basicConfig`), `@dataclass App` (flat fields: db/manager/registry/tts/chat/image/speech/conversation/bot_cache), `create_app()`, `build_bot(app)` (intents; event handlers; `commands.register_all(bot, app)`). Custom-bot "commands" (`!<bot_name> ...`) are matched in `on_message` against `app.bot_cache`, not registered with `bot.command`.
- `commands/` — command groups registered by `register_all`: `custom_bots` (custom-bot, list-custom-bots, delete-custom-bot), `speech` (speak, voices), `images` (doodlebob, retcon), `conversation` (talkforme), `admin` (lobotomize, debug, history), `chat` (docstring only — custom-bot chat flows through `on_message`); `_state.py` holds the shared App.
- `services/` — the LLM-backed logic: `ChatService`, `ImageService`, `SpeechService`, `ConversationService`; the Discord command handlers are thin wrappers.
- `db/` — `connection` (shared connect), `schema` (init + migrations log at INFO; backfills the `norm` column on existing DBs), `messages` (`ChatDatabase`: embeddings as float32 blobs with a stored L2 `norm`, cleanup, RAG context), `bots` (`CustomBotManager`), `search` (single-JOIN RAG retrieval scored by one matrix multiply over the stored norms), `timing` (image-gen ETA stats), `vectors` (vector math). `database.py` is a thin facade re-exporting `ChatDatabase`/`CustomBotManager`.
- `llm/` — `chat.py` (completion clients: instruct / with-history / with-tools), `images.py` (gen + edit clients), `registry.py` (tool registry: schema + dispatch). `llm_client.py` is the public facade over `llm/` plus the embedding HTTP plumbing. (`llama_wrapper.py` is gone.)
- `config.py` — env loading + import-time validation, voice catalog.
- `database.py` — `ChatDatabase` (embeddings stored as float32 blobs, cosine-similarity RAG, schema auto-migrates on startup) and `CustomBotManager`.
- `llama_wrapper.py` — thin OpenAI-compatible clients for chat / image gen / image edit / embeddings, each with its own endpoint, key, and model.
- `tools.py` — `get_channel_members` is a no-op LangChain `@tool` stub used only for name/description/schema; the real implementation is `get_channel_members_impl(channel)`, wired into the tool executor in `main.py`.
- `prompts.py` — LLM prompt constants (image layout/prompt/verify, length hint, `build_system_prompt`).
- `textutil.py` — `split_message`, `get_user_info`.
- `tools.py` — `get_channel_members` is a no-op LangChain `@tool` stub used only for name/description/schema; the real implementation is `get_channel_members_impl(channel)`, wired into the tool executor in `app.py`.
- `tts.py` — `TTSEngine` (Kokoro wrapper; `AudioResult.partial` signals failed chunks); voice/speed defaults are aliases of `config.TTS_VOICE`/`TTS_SPEED` (single source: config).
## Style