29 lines
5.1 KiB
Markdown
29 lines
5.1 KiB
Markdown
# Decisions (Architecture Decision Record)
|
|
|
|
| # | Decision | Chosen | Alternatives rejected | Why |
|
|
|---|---|---|---|---|
|
|
| 1 | Language/runtime | TypeScript + Node.js (LTS) | Python (aiogram), Go | Single-language team, grammY ecosystem, shared types across adapter/core |
|
|
| 2 | Telegram framework | grammY | Telegraf, raw Bot API | Actively maintained, first-class webhook + middleware, TS-native |
|
|
| 3 | AI orchestration | Minimal orchestration (no LangGraph/LangChain yet) | LangGraph.js, LangChain.js | Phase 1 needs a single LLM call; the spec allows "smallest useful surface". Introduced later only when agent/tools/memory justify it. |
|
|
| 4 | LLM access | Existing LiteLLM gateway (`litellm:4000/v1`), model alias `mem0-openai` (upstream OpenAI `gpt-5-mini` primary + OpenRouter fallback, load-balanced inside LiteLLM) | Direct OpenAI + OpenRouter dual-provider in app code | Reuses the mem0 stack's existing failover; one OpenAI-compatible endpoint. NOTE: LiteLLM serves ONLY declared aliases — `gpt-5-mini` is NOT valid, must use `mem0-openai` (caused a 400 Invalid-model bug). |
|
|
| 5 | Database | Reuse `mem0-postgres` (pgvector/pgvector:pg17) in an isolated `bot` database | Project-specific postgres container, NocoDB | Avoids a second Postgres; pgvector already provisioned for Phase 3; `bot` db isolates from mem0 tables. |
|
|
| 6 | DB provisioning | Self-migration on startup (`CREATE DATABASE` + `schema_migrations`) | Manual psql one-time | Reproducible, spec §14 requires migration/startup checks. |
|
|
| 7 | Chat state | Postgres `bot_user`/`conversation`/`message` | In-process memory, Redis | Spec §8.2: Postgres is the durable system of record. |
|
|
| 8 | RAG / product facts | **IMPLEMENTED 2026-08-31**: `CatalogRetriever` (keyword, not vector) replaces `NoopRetriever` — SKU-token + name/category substring + CJK-particle-stripped segments over the generated `catalog.ts`; top-5 facts injected into the system prompt. Live in prod wiring (`server.ts`). Paid SKU hits also surface their FREE trial twin (CZH01 → FREECZH01) so free-trial questions answer from facts. | Vector DB in Phase 1 | Spec §8.3: no corpus justifies it yet; keyword search is enough at 254 SKUs; pgvector first later. |
|
|
| 9 | n8n role | Automation/tool layer via constrained HTTP client | n8n as the app backend | Spec §9. Tool inputs validated; no arbitrary workflow execution exposed to the LLM. |
|
|
| 10 | Multi-channel | Shared core + per-channel adapters (`Channel`/`IncomingMessage`/`OutgoingMessage` + capability flags) | Per-channel monoliths | Spec §7. Future Shopee/Lazada adapters implement the contract without touching the AI core. |
|
|
| 11 | Deployment | Docker multi-stage (dev bind-mount + tsx watch; prod immutable image) | Host-installed Node | Spec §4/§5: reproducible prod, hot-reload dev. |
|
|
| 12 | Secrets | Env vars via `.env` (gitignored); source-of-truth in repo `../SECRETS.md` | Hard-coded, dotenv-in-repo | Spec §12/§15; repo convention: single credential store. |
|
|
| 13 | Webhook ingress | CF tunnel `bot.digikedai.com` → **directly to `http://digikedai-bot:8080`** (container name on `bridge_hoelee`), no Traefik hop | Traefik labels, DSM nginx | cloudflared and the bot share `bridge_hoelee`; the bot's own Hono server answers `/health` + `/<secret>/webhook`, so no proxy middleware is needed. Simplest path that requires no shared-proxy changes. |
|
|
| 14 | Webhook timeout | grammY `onTimeout:"return"` + 50s; LLM client timeout 45s | grammY default `throw`@10s | LLM (GPT-5-mini w/ reasoning) replies exceed 10s → grammY never returns 200 → Telegram re-delivers the same update forever ("bot keeps replying" loop). |
|
|
| 15 | Host port | `5247:8080` | 8080 (taken by DSM nginx), 5244-5246 (alist stack) | 5247 was the first free port. |
|
|
| 16 | Rate limiting | In-memory fixed-window `RateLimiter` keyed by `channel:userId`, enforced in the shared `MessageService` (after normalize, before persist/LLM); deny replies once per window, then silence; webhook still answers 200 | Redis, `@grammyjs/ratelimiter`, per-IP limits | Single replica today (no Redis needed); channel-agnostic so Shopee/Lazada inherit it; never lets Telegram re-deliver a throttled update (same principle as D14). |
|
|
| 17 | User long-term memory | Postgres `user_memory` KV table (keyed by `bot_user.id`, not conversation) + cheap LLM async summary via `SUMMARY_MODEL` (default = main model); trigger = new facts only (lang change / new SKU / identity keywords / ≥10 turns fallback); every memory failure degrades silently, never blocks the reply | mem0 HTTP API, pgvector, per-turn summarization, rule-only extraction | Reuses existing Postgres + LiteLLM with zero new infra; cross-session/cross-channel personalization with bounded cost (docs/MEMORY_FEATURE.md) |
|
|
|
|
## Open decisions (Phase 2)
|
|
|
|
- **D1** — Mint a dedicated LiteLLM virtual key for the bot (rate-limit + independent revocation) instead of sharing the master key.
|
|
- **D2** — ~~Traefik route~~ **RESOLVED 2026-08-29**: CF tunnel targets `http://digikedai-bot:8080` directly (D13), no Traefik/nginx hop needed.
|
|
- **D3** — Exact free-account n8n workflow path + input schema (currently a placeholder).
|
|
- **D4** — User allowlist: enabled-by-default for production (currently open in development).
|