{"id":50428819,"url":"https://github.com/mralaminahamed/wp-support-rag","last_synced_at":"2026-05-31T12:30:30.859Z","repository":{"id":361127687,"uuid":"1253035518","full_name":"mralaminahamed/wp-support-rag","owner":"mralaminahamed","description":"Self-hosted RAG support desk for WordPress plugins — grounded, cited answers from GitHub + WordPress.org docs. FastAPI + pgvector + Celery API, React/Tailwind admin, embeddable widget. pnpm/Turbo monorepo.","archived":false,"fork":false,"pushed_at":"2026-05-29T09:27:21.000Z","size":620,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T10:22:09.087Z","etag":null,"topics":["anthropic","celery","fastapi","llm","monorepo","openai","pgvector","postgresql","python","rag","react","redis","retrieval-augmented-generation","tailwindcss","turborepo","typescript","vite","wordpress","wordpress-plugin"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mralaminahamed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-29T05:15:13.000Z","updated_at":"2026-05-29T09:27:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mralaminahamed/wp-support-rag","commit_stats":null,"previous_names":["mralaminahamed/wp-support-rag"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mralaminahamed/wp-support-rag","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mralaminahamed%2Fwp-support-rag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mralaminahamed%2Fwp-support-rag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mralaminahamed%2Fwp-support-rag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mralaminahamed%2Fwp-support-rag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mralaminahamed","download_url":"https://codeload.github.com/mralaminahamed/wp-support-rag/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mralaminahamed%2Fwp-support-rag/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33731998,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anthropic","celery","fastapi","llm","monorepo","openai","pgvector","postgresql","python","rag","react","redis","retrieval-augmented-generation","tailwindcss","turborepo","typescript","vite","wordpress","wordpress-plugin"],"created_at":"2026-05-31T12:30:29.820Z","updated_at":"2026-05-31T12:30:30.846Z","avatar_url":"https://github.com/mralaminahamed.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WP Plugin Support Desk RAG\n\n[![CI](https://github.com/mralaminahamed/wp-support-rag/actions/workflows/ci.yml/badge.svg)](https://github.com/mralaminahamed/wp-support-rag/actions/workflows/ci.yml)\n[![Frontend](https://github.com/mralaminahamed/wp-support-rag/actions/workflows/frontend.yml/badge.svg)](https://github.com/mralaminahamed/wp-support-rag/actions/workflows/frontend.yml)\n\n**Author:** Al Amin Ahamed ([@mralaminahamed](https://github.com/mralaminahamed))\n\nA self-hosted Retrieval-Augmented Generation service that answers WordPress\nplugin support questions from a grounded corpus of the author's own documentation\n(GitHub READMEs/CHANGELOGs/docs/issues and WordPress.org FAQ/changelog/support\nthreads). It deflects repetitive support tickets with instant, **cited** answers,\nand fails open to retrieved links when the LLM is unavailable.\n\n## How it works\n\n```\nwidget → POST /api/v1/query\n  → route (plugin slug or centroid routing)\n  → hybrid retrieve (HNSW cosine + Postgres FTS, merged by RRF)\n  → generate (cache → cost breaker → provider → citation validation → cache)\n  → cited answer  (or degraded links / decline)\n```\n\n- **Frameworkless** pgvector RAG — no LangChain/LlamaIndex in the hot path.\n- **Embeddings**: OpenAI `text-embedding-3-large` as `halfvec(3072)` with an HNSW\n  index, or **fully-local Ollama** (e.g. `nomic-embed-text`, 768-dim) — selected by\n  config. The vector width is bound to the column + index, so switching providers\n  needs a migration and a re-embed (not a runtime toggle).\n- **Hybrid retrieval**: vector + lexical fused with Reciprocal Rank Fusion.\n- **Multi-provider generation**: Claude, OpenAI, or Ollama, interchangeable by config\n  and switchable at runtime from the admin Settings page.\n- **Runs fully local**: point generation *and* embeddings at Ollama and the whole\n  pipeline needs no external API.\n- **Grounded \u0026 cited**: only source URLs of supplied chunks may be cited.\n- **Resilient**: fail-open on provider outage (degraded links); a clear 503 when the\n  embeddings provider is unconfigured; per-request cost circuit breaker.\n\nSee `docs/` for the full SRS, architecture, implementation plan, and ADRs.\n\n## Repository layout\n\nA monorepo: a pnpm + Turborepo workspace for the JS apps, with the Python service\nself-contained under `apps/api`.\n\n```\napps/\n  api/    # Python backend — FastAPI + Celery (package `app`, eval/, tests/, scripts/, own pyproject + uv.lock)\n  web/    # embeddable support widget (single-file, no build)\n  admin/  # admin console — Vite + React + TypeScript\nconfig/plugins/   # declarative plugin registrations (FR-PM-5; see config/README.md)\ndocker-compose*.yml  pnpm-workspace.yaml  turbo.json\n```\n\nPython commands run from `apps/api`; JS commands (`pnpm dev/build`) from the root.\n\n## Quickstart (local)\n\n```bash\ncd apps/api \u0026\u0026 uv sync                    # install (Python lives here)\ndocker compose up -d                      # postgres+pgvector, redis, app, worker, beat\ncd apps/api \u0026\u0026 uv run alembic upgrade head\ncurl localhost:8000/health                # {\"status\":\"ok\",...}\n```\n\n`docker compose up` runs all services: **api** (`:8000`), worker, beat, Postgres,\nRedis, the **widget** (`web`, `:8080`), and the **admin** console (`admin`, `:8081`).\nIn production (`docker-compose.prod.yml`) Caddy serves the API + widget on\n`$DOMAIN` and the admin console on `admin.$DOMAIN`, all with automatic TLS.\n\nSet provider keys / selection in `.env` (see `.env.example`):\n\n```\nWPRAG_OPENAI_API_KEY=...        # embeddings (OpenAI mode) + OpenAI generation\nWPRAG_ANTHROPIC_API_KEY=...     # Claude provider\nWPRAG_DEFAULT_PROVIDER=ollama   # generation provider: anthropic | openai | ollama\nWPRAG_EMBEDDING_PROVIDER=ollama # embeddings backend: openai (default) | ollama\nWPRAG_OLLAMA_BASE_URL=http://host.docker.internal:11434  # reach a host Ollama from Docker\nWPRAG_GITHUB_TOKEN=...          # raises the GitHub rate limit + enables private-repo ingestion\nWPRAG_ADMIN_BEARER_TOKEN=...    # admin endpoints\n```\n\nFor a fully-local setup, run [Ollama](https://ollama.com) on the host\n(`ollama pull llama3.2 \u0026\u0026 ollama pull nomic-embed-text`), keep the defaults above,\nthen `alembic upgrade head` and re-ingest so the embedding column matches the\nlocal model's width. No OpenAI/Anthropic key is then required.\n\nThe admin token is an opaque, high-entropy secret you generate (no fixed format);\nthe API compares the `Authorization` header to `Bearer \u003ctoken\u003e` exactly:\n\n```bash\npython -c \"import secrets; print(secrets.token_urlsafe(32))\"   # or: openssl rand -base64 32\n```\n\nKeep it out of version control (`.env` is git-ignored) and supply it via the\nenvironment in production. Without it, every `/api/v1/admin/*` endpoint returns 401.\n\n## Embed the widget\n\nOne script tag on any external page (no build step):\n\n```html\n\u003cscript src=\"https://your-host/widget.js\"\n        data-plugin-slug=\"swift-menu-duplicator\"\n        data-api-base=\"https://your-api-host\"\u003e\u003c/script\u003e\n```\n\nIt posts to `/api/v1/query`, renders the cited answer, and offers a\nhelpful/not-helpful control posting to `/api/v1/feedback`. See `apps/web/index.html`\nfor a working external-page demo.\n\n## Admin console\n\nThe `admin` app (`:8081`, `apps/admin`) is a React console for operating the service:\n\n- **Dashboard** — service health, query metrics, corpus coverage, and a recent-activity feed.\n- **Plugins** — searchable/sortable registry; expand a plugin to see its sources and trigger ingestion.\n- **Playground** — a chat-style interface for grounded, cited Q\u0026A (each turn is an independent RAG query, streamed).\n- **Settings** — switch the generation and embedding provider/model at runtime (with an Ollama model picker), test the API connection, and set your profile (name + email → Gravatar avatar). Light/dark theme.\n\n## API\n\n| Method | Path | Auth | Purpose |\n|---|---|---|---|\n| GET | `/health` | — | Liveness + DB/Redis probes |\n| POST | `/api/v1/query` | per-IP rate limit | Ask a question; returns a cited answer + `query_id` |\n| POST | `/api/v1/query/stream` | per-IP rate limit | Same, streamed as SSE: `token` events then a `done` event |\n| POST | `/api/v1/feedback` | per-IP rate limit | Bind `helpful`/`not_helpful` to a `query_id` |\n| POST | `/api/v1/admin/plugins` | bearer | Register a plugin and its sources |\n| GET | `/api/v1/admin/plugins` | bearer | List registered plugins with source counts |\n| GET | `/api/v1/admin/plugins/{slug}/sources` | bearer | List a plugin's sources and ingestion state |\n| POST | `/api/v1/admin/ingest` | bearer | Trigger ingestion for every plugin's sources |\n| POST | `/api/v1/admin/ingest/{slug}` | bearer | Trigger ingestion (one Celery task per source) |\n| GET | `/api/v1/admin/metrics` | bearer | Deflection, helpful, cache-hit, degraded rates, mean cost, p95 latency (optional `?plugin_slug=`) |\n| GET | `/api/v1/admin/queries` | bearer | Recent queries for the activity feed (`?limit=`) |\n| GET·PUT·DELETE | `/api/v1/admin/llm` | bearer | Read / override / reset the active generation provider+model |\n| PUT·DELETE | `/api/v1/admin/llm/embedding` | bearer | Override / reset the embedding provider+model (same vector width only) |\n| GET | `/api/v1/admin/ollama/models` | bearer | List models available on the configured Ollama server |\n\nThe widget streams from `/api/v1/query/stream` where available and falls back to\n`/api/v1/query`. Streamed tokens are provisional; the closing `done` event carries\nthe citation-validated answer.\n\n## Production deployment\n\n```bash\nDOMAIN=support.example.com POSTGRES_PASSWORD=… WPRAG_ADMIN_BEARER_TOKEN=… \\\nWPRAG_OPENAI_API_KEY=… WPRAG_ANTHROPIC_API_KEY=… \\\ndocker compose -f docker-compose.prod.yml up -d\n```\n\nCaddy terminates TLS automatically for `$DOMAIN` and reverse-proxies the API.\nAll secrets are environment-only. See `RUNBOOK.md` for day-two operations.\n\n## Quality gates\n\n```bash\n# Backend (from apps/api)\nruff check . \u0026\u0026 ruff format --check .       # lint + format\nmypy --strict app eval                      # types\npytest                                       # tests (external calls mocked/VCR-replayed)\npython -m eval.harness                       # offline eval gate\n\n# Admin console (from repo root)\npnpm --filter @wp-support-rag/admin type-check\npnpm --filter @wp-support-rag/admin lint\npnpm --filter @wp-support-rag/admin build\npnpm --filter @wp-support-rag/admin e2e      # Playwright (API mocked)\n```\n\nCI runs backend lint/typecheck/test and the admin build + e2e on every push; the\neval gate runs on changes under `apps/api/app/prompts/`, `apps/api/app/rag/`, or\n`apps/api/eval/dataset/` and blocks regressions.\n\n\u003e Note: the embedding dimension is bound to the DB column + HNSW index, so the\n\u003e backend integration tests must run against a database at the configured width.\n\u003e See `RUNBOOK.md` §5 for running tests against a local Ollama (768-dim) dev DB.\n\n## Plugin registry\n\nPlugins are declared in `config/plugins/*.yaml` and synced into the database:\n\n```bash\ncd apps/api\nWPRAG_DATABASE_DSN=postgresql+asyncpg://wprag:wprag@localhost:5432/wprag \\\n  python -m scripts.sync_plugins          # add/update; --prune drops undeclared plugins\n```\n\nSee `config/README.md` for the file schema and source types.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmralaminahamed%2Fwp-support-rag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmralaminahamed%2Fwp-support-rag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmralaminahamed%2Fwp-support-rag/lists"}