Spaces:
Sleeping
Sleeping
NeonClary Cursor commited on
Commit ·
fddd8f4
1
Parent(s): 7a97213
Switch LLM provider from Gemini to OpenAI GPT-5.4-mini
Browse filesThe live app ran the Gemini provider with no fallback, so any key/quota
issue made chat silently fail with no response. Switch the orchestrator
and personas to OpenAI gpt-5.4-mini:
- bootstrap.py: add an explicit "openai" provider branch so the OpenAI
client is used as the primary LLM (not just a vLLM fallback).
- muscle_growth_config.yaml: llm.provider=openai, openai.model=gpt-5.4-mini.
- .env.example / README: document OPENAI_API_KEY as the primary secret;
keep GEMINI_API_KEY as an optional fallback.
Key is supplied via the OPENAI_API_KEY env var / HF Space secret and is
never committed. Verified locally end-to-end (signup + chat-stream) with
all personas returning responses.
Co-authored-by: Cursor <cursoragent@cursor.com>
- README.md +85 -19
- multi_llm_chatbot_backend/.env.example +19 -11
- multi_llm_chatbot_backend/app/core/bootstrap.py +6 -1
- muscle_growth_config.yaml +3 -2
README.md
CHANGED
|
@@ -8,36 +8,102 @@ pinned: false
|
|
| 8 |
app_port: 7860
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# MuscleGrowthAI
|
| 12 |
|
| 13 |
-
An AI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
## Hugging Face Spaces deployment
|
| 16 |
|
| 17 |
-
This Space ships as a single Docker image built from the repository
|
|
|
|
| 18 |
|
| 19 |
-
1. Builds the React frontend at image-build time with `REACT_APP_API_URL=""`
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
### Required Space secrets
|
| 24 |
|
| 25 |
| Secret | Purpose |
|
| 26 |
|--------|---------|
|
| 27 |
-
| `JWT_SECRET_KEY` | Signs auth tokens. Set to a long random string. |
|
| 28 |
-
| `
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|--------|---------|
|
| 34 |
-
| `OPENAI_API_KEY` | Only if switching to the OpenAI provider. |
|
| 35 |
-
| `VLLM_API_KEY` | Only if pointing at a Neon vLLM endpoint. |
|
| 36 |
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
app_port: 7860
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# MuscleGrowthAI Panel
|
| 12 |
|
| 13 |
+
An AI personalized bodybuilding assistant built on Neon AI's Collaborative
|
| 14 |
+
Conversational AI (CCAI) framework. Ask about hypertrophy programming,
|
| 15 |
+
nutrition, recovery, form, and progress tracking and get diverse perspectives
|
| 16 |
+
from a panel of five fitness AI advisors.
|
| 17 |
+
|
| 18 |
+
This repository is a **complete, deployable application** — the CCAI
|
| 19 |
+
multi-advisor stack (FastAPI backend + React frontend) wired to the
|
| 20 |
+
MuscleGrowthAI configuration in [`muscle_growth_config.yaml`](muscle_growth_config.yaml)
|
| 21 |
+
and the personas in [`personas/fitness_advisors/`](personas/fitness_advisors).
|
| 22 |
+
|
| 23 |
+
## Advisors
|
| 24 |
+
|
| 25 |
+
1. **Hypertrophy Coach** — splits, sets/reps, muscle-group programming
|
| 26 |
+
2. **Nutrition Strategist** — protein, macros, meal timing
|
| 27 |
+
3. **Recovery Specialist** — rest, stretching, soreness management
|
| 28 |
+
4. **Form & Safety Coach** — technique, breathing, injury prevention
|
| 29 |
+
5. **Program Planner** — scheduling, tracking, progression
|
| 30 |
|
| 31 |
## Hugging Face Spaces deployment
|
| 32 |
|
| 33 |
+
This Space ships as a single Docker image built from the repository-root
|
| 34 |
+
[`Dockerfile`](Dockerfile). The container:
|
| 35 |
|
| 36 |
+
1. Builds the React frontend (CRA) at image-build time with `REACT_APP_API_URL=""`
|
| 37 |
+
so every `fetch` issues a relative URL.
|
| 38 |
+
2. Serves the bundled SPA from FastAPI at `/`, with the API on `/api/...`,
|
| 39 |
+
`/auth/...`, etc. — all on the same `:7860` origin.
|
| 40 |
+
3. Persists user data (auth, profiles, chat sessions) in **SQLite via
|
| 41 |
+
`aiosqlite`** at `${DATA_DIR}/muscle_growth_panel.db`. Mount a Hugging Face
|
| 42 |
+
Storage Bucket at `/data` to make the database survive Space rebuilds.
|
| 43 |
+
There is **no MongoDB** and no third-party data plane.
|
| 44 |
|
| 45 |
### Required Space secrets
|
| 46 |
|
| 47 |
| Secret | Purpose |
|
| 48 |
|--------|---------|
|
| 49 |
+
| `JWT_SECRET_KEY` | Signs auth tokens. Set this to a long random string. |
|
| 50 |
+
| `OPENAI_API_KEY` | Powers the default OpenAI provider (`gpt-5.4-mini`). Get a key from Clary, or use your own OpenAI key. |
|
| 51 |
+
| `GEMINI_API_KEY` | Optional — only if you switch `llm.provider` back to `gemini` (`gemini-2.5-flash`). |
|
| 52 |
|
| 53 |
+
Set these under **Settings → Variables and secrets** on the Space.
|
| 54 |
|
| 55 |
+
## Local deployment
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
**Do you need Docker?** No — Docker is optional. There are two supported paths,
|
| 58 |
+
and neither requires MongoDB (persistence is SQLite):
|
| 59 |
|
| 60 |
+
### Option A — Docker (simplest, mirrors the Space exactly)
|
| 61 |
+
|
| 62 |
+
Requires **Docker Desktop** only.
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
# From the repo root, create a .env with at least:
|
| 66 |
+
# JWT_SECRET_KEY=some-long-random-string
|
| 67 |
+
# OPENAI_API_KEY=your-openai-key
|
| 68 |
+
docker compose up --build
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
Open <http://localhost:7860>. Override the host port with `MUSCLE_HOST_PORT` if
|
| 72 |
+
7860 is taken.
|
| 73 |
+
|
| 74 |
+
### Option B — Native (no Docker)
|
| 75 |
+
|
| 76 |
+
Requires **Python 3.12** and **Node.js 20+** (no Docker, no MongoDB).
|
| 77 |
+
|
| 78 |
+
**Backend** (terminal 1):
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
cd multi_llm_chatbot_backend
|
| 82 |
+
python -m venv venv
|
| 83 |
+
# Windows: venv\Scripts\activate • macOS/Linux: source venv/bin/activate
|
| 84 |
+
pip install -r requirements.txt
|
| 85 |
+
cp .env.example .env # then edit JWT_SECRET_KEY + OPENAI_API_KEY
|
| 86 |
+
uvicorn app.main:app --reload --port 8000
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
**Frontend** (terminal 2):
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
cd phd-advisor-frontend
|
| 93 |
+
npm install
|
| 94 |
+
# point the SPA at the backend from step above:
|
| 95 |
+
# Windows PowerShell: $env:REACT_APP_API_URL="http://localhost:8000"; npm start
|
| 96 |
+
# macOS/Linux: REACT_APP_API_URL=http://localhost:8000 npm start
|
| 97 |
+
npm start
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
Open <http://localhost:3000> — you should see **AI Personalized Bodybuilding
|
| 101 |
+
Plan** with the five fitness advisors.
|
| 102 |
+
|
| 103 |
+
## Configuration
|
| 104 |
+
|
| 105 |
+
All branding, login fields, chat examples, orchestrator keywords, and LLM/RAG
|
| 106 |
+
settings live in [`muscle_growth_config.yaml`](muscle_growth_config.yaml). The
|
| 107 |
+
backend resolves `personas.personas_dir` relative to that file, so the advisor
|
| 108 |
+
YAMLs in `personas/fitness_advisors/` load automatically. Point the app at a
|
| 109 |
+
different config with the `CONFIG_PATH` environment variable.
|
multi_llm_chatbot_backend/.env.example
CHANGED
|
@@ -1,16 +1,24 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
|
| 7 |
-
#
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
OPENAI_API_KEY=
|
| 10 |
-
GEMINI_API_KEY=
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
|
|
|
| 14 |
|
| 15 |
-
#
|
| 16 |
-
#
|
|
|
|
|
|
| 1 |
+
# Copy this file to `.env` (same directory) for a native, non-Docker local run.
|
| 2 |
+
# The backend auto-loads it on startup.
|
| 3 |
|
| 4 |
+
# Path to the app config, relative to this backend directory.
|
| 5 |
+
CONFIG_PATH=../muscle_growth_config.yaml
|
| 6 |
|
| 7 |
+
# Where the SQLite database + uploaded docs live. Any writable folder works;
|
| 8 |
+
# on a native run this replaces the container's /data mount.
|
| 9 |
+
DATA_DIR=./local_data
|
| 10 |
+
|
| 11 |
+
# Signs auth tokens — set to a long random string.
|
| 12 |
+
JWT_SECRET_KEY=change-me-to-a-long-random-string
|
| 13 |
+
|
| 14 |
+
# Default provider is OpenAI GPT-5.4-mini (see muscle_growth_config.yaml -> llm.provider).
|
| 15 |
+
# Set your OpenAI API key here (get one from Clary, or use your own).
|
| 16 |
OPENAI_API_KEY=
|
|
|
|
| 17 |
|
| 18 |
+
# Optional alternatives:
|
| 19 |
+
# GEMINI_API_KEY= # only if you switch llm.provider back to "gemini"
|
| 20 |
+
VLLM_API_KEY=
|
| 21 |
|
| 22 |
+
# Frontend origins allowed to call this backend during native dev
|
| 23 |
+
# (CRA dev server defaults to port 3000).
|
| 24 |
+
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
multi_llm_chatbot_backend/app/core/bootstrap.py
CHANGED
|
@@ -18,7 +18,7 @@ from app.models.default_personas import get_default_personas
|
|
| 18 |
settings = get_settings()
|
| 19 |
|
| 20 |
current_provider = settings.llm.provider or "vllm"
|
| 21 |
-
available_providers = ["ollama", "gemini", "vllm"]
|
| 22 |
|
| 23 |
|
| 24 |
def _load_shared_env_var(name: str) -> str:
|
|
@@ -114,6 +114,11 @@ def _wrap_resilient(primary: ImprovedVllmClient, fallback: OpenAIFallbackClient,
|
|
| 114 |
def create_llm_client(provider=None):
|
| 115 |
if provider is None:
|
| 116 |
provider = current_provider
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
if provider == "gemini":
|
| 118 |
return ImprovedGeminiClient(model_name=settings.llm.gemini.model)
|
| 119 |
if provider == "vllm":
|
|
|
|
| 18 |
settings = get_settings()
|
| 19 |
|
| 20 |
current_provider = settings.llm.provider or "vllm"
|
| 21 |
+
available_providers = ["ollama", "gemini", "vllm", "openai"]
|
| 22 |
|
| 23 |
|
| 24 |
def _load_shared_env_var(name: str) -> str:
|
|
|
|
| 114 |
def create_llm_client(provider=None):
|
| 115 |
if provider is None:
|
| 116 |
provider = current_provider
|
| 117 |
+
if provider == "openai":
|
| 118 |
+
# Standalone OpenAI (GPT) client. OpenAIFallbackClient implements the same
|
| 119 |
+
# LLMClient interface as the Gemini client, so it works as the primary
|
| 120 |
+
# orchestrator/persona LLM, not just a vLLM fallback.
|
| 121 |
+
return _build_openai(settings.llm.openai.orchestrator_reasoning_effort)
|
| 122 |
if provider == "gemini":
|
| 123 |
return ImprovedGeminiClient(model_name=settings.llm.gemini.model)
|
| 124 |
if provider == "vllm":
|
muscle_growth_config.yaml
CHANGED
|
@@ -169,7 +169,7 @@ mongodb:
|
|
| 169 |
database_name: "muscle_growth_advisor"
|
| 170 |
|
| 171 |
llm:
|
| 172 |
-
provider: "
|
| 173 |
gemini:
|
| 174 |
model: "gemini-2.5-flash"
|
| 175 |
ollama:
|
|
@@ -182,8 +182,9 @@ llm:
|
|
| 182 |
neon_persona_orchestrator: "vanilla"
|
| 183 |
neon_persona_advisors: "vanilla"
|
| 184 |
openai:
|
|
|
|
| 185 |
api_key: ""
|
| 186 |
-
model: "gpt-5.4"
|
| 187 |
orchestrator_reasoning_effort: "low"
|
| 188 |
persona_reasoning_effort: "none"
|
| 189 |
resilient:
|
|
|
|
| 169 |
database_name: "muscle_growth_advisor"
|
| 170 |
|
| 171 |
llm:
|
| 172 |
+
provider: "openai"
|
| 173 |
gemini:
|
| 174 |
model: "gemini-2.5-flash"
|
| 175 |
ollama:
|
|
|
|
| 182 |
neon_persona_orchestrator: "vanilla"
|
| 183 |
neon_persona_advisors: "vanilla"
|
| 184 |
openai:
|
| 185 |
+
# Key comes from the OPENAI_API_KEY env var / HF Space secret (never committed).
|
| 186 |
api_key: ""
|
| 187 |
+
model: "gpt-5.4-mini"
|
| 188 |
orchestrator_reasoning_effort: "low"
|
| 189 |
persona_reasoning_effort: "none"
|
| 190 |
resilient:
|