Spaces:
Sleeping
Sleeping
| # System Design | |
| ## Runtime topology | |
| ``` | |
| ββββββββββββββ HTTP ββββββββββββββββββββββββββββββββββββ | |
| client ββ€ uvicorn :8000 βββββββββββ€ FastAPI (app/main.py) β | |
| ββββββββββββββ β /simulate-review β | |
| β /recommend β | |
| β /persona /users /health β | |
| βββββββββββββββββ¬βββββββββββββββββββ | |
| β | |
| βββββββββββββββββββββββββββββββββββββββ΄ββββββββββββ | |
| β β | |
| βΌ βΌ | |
| Persona/Memory store Groq LLM API | |
| (filesystem JSON cache) (HTTPS, llama-3.1/3.3) | |
| data/personas/*.json ^ ^ | |
| data/memory/*.json β β | |
| β β β | |
| βΌ β β | |
| Processed parquet β β | |
| data/processed/reviews.parquet β β | |
| items.parquet β β | |
| users.parquet β β | |
| β β β | |
| βΌ β β | |
| Trained artifacts β β | |
| models_store/rating_lgbm.pkl (LightGBM regressor) ββββ β | |
| models_store/item_index.pkl (TF-IDF + items df) β | |
| models_store/item_sim.pkl (item-item cosine sparse) ββββββ | |
| ``` | |
| ## Layered components | |
| | Layer | File | Purpose | LLM-bound? | | |
| |---|---|---|---| | |
| | HTTP | `app/main.py` | FastAPI routes, request validation | no | | |
| | Orchestration | `app/recommender.py` | Task B pipeline coordinator | no (delegates) | | |
| | Reasoning Planner | `app/planner.py` | Per-request strategy + ranking weights | **yes** (1 call) | | |
| | Behavioral Analyzer | `app/recommender.py:analyze_behavior` | Decode current decision state | **yes** (1 call) | | |
| | Behavioral Simulator | `app/reasoner.py` | Per-candidate predicted reaction | **yes** (N calls) | | |
| | Review Generator | `app/generator.py` | Final user-facing review text | **yes** (1 call per Task A request) | | |
| | Persona builder | `app/persona/features.py` + `refine.py` + `store.py` | Stats + LLM refinement + cache | mixed (LLM once per user, then cached) | | |
| | Memory | `app/memory.py` | Long-term + short-term + tagged experiences | no | | |
| | Context | `app/context.py` | Time bucket + auto NG flags | no | | |
| | Retrieval | `app/retrieval.py` | TF-IDF + item-item CF | no | | |
| | Rating model | `app/rating_model.py` | LightGBM cross-check | no | | |
| | LLM client | `app/llm.py` | Groq wrapper with JSON-mode | no | | |
| | Config | `app/config.py` | Paths + env loading | no | | |
| ## Data flow per request | |
| ### `POST /simulate-review` | |
| ``` | |
| request β persona_for(user_id, refine=True) # cached after first call | |
| β memory.get_or_build(user_id) # cached after first build | |
| β context.normalize(request.context) # auto NG flags | |
| β reasoner.reason(persona, memory, context, item) # LLM call | |
| β rating_model.predict(persona, item) # LightGBM | |
| β blend rating (0.6 LLM + 0.4 LGBM) | |
| β generator.generate(persona, item, reasoner_out, context) # LLM call | |
| β response | |
| ``` | |
| Net: 2 LLM calls + 1 LightGBM inference per request after warmup. | |
| ### `POST /recommend` | |
| ``` | |
| request β persona + memory + context (same as above) | |
| β analyze_behavior(...) # LLM call 1 | |
| β plan(...) # LLM call 2 β strategy | |
| β retrieve(user_id, top_k=12) # local TF-IDF + CF | |
| β for each candidate (12): | |
| reason(persona, memory, context, candidate) # LLM call 3..14 | |
| rating_model.predict(persona, candidate) | |
| β rerank | |
| β response (top_n) | |
| ``` | |
| Net: 2 + N LLM calls per request (N = candidates_k, default 12). | |
| ## Caching | |
| - **Persona JSON**: written to `data/personas/{user_id}.json` after first | |
| build. The LLM-refinement call is the expensive part; once written, all | |
| subsequent requests for that user use the cache. | |
| - **Memory JSON**: written to `data/memory/{user_id}.json` after first | |
| build. Currently NOT auto-rebuilt; see "Cache invalidation" below. | |
| - **TF-IDF + CF**: built once via `python -m app.retrieval` and pickled | |
| to `models_store/`. The `@lru_cache` on `_bundle()` loads it once per | |
| process. | |
| - **LightGBM model**: pickled to `models_store/rating_lgbm.pkl` and | |
| loaded on first `predict()` call. | |
| ## Cache invalidation | |
| Today: filesystem caches are not auto-invalidated. To rebuild: | |
| ```powershell | |
| # Persona/memory after a schema change | |
| Remove-Item -Recurse data/personas/*.json, data/memory/*.json | |
| # TF-IDF + CF after the dataset changes | |
| Remove-Item models_store/item_index.pkl, models_store/item_sim.pkl | |
| python -m app.retrieval | |
| # LightGBM after feature changes | |
| Remove-Item models_store/rating_lgbm.pkl | |
| python -m app.rating_model | |
| ``` | |
| Production version should hash the persona schema + git commit and invalidate | |
| on mismatch. | |
| ## Scalability | |
| | Concern | Current | Path to scale | | |
| |---|---|---| | |
| | Per-candidate LLM cost | 12 calls per /recommend | Async fan-out to Groq (groq SDK supports asyncio); or distill into a small local reasoner | | |
| | Persona LLM cost | 1 per cold user | Already cached on disk; for >100k users move to Redis/Postgres | | |
| | TF-IDF + CF index size | ~250 MB combined | Move to a vector DB (Qdrant/Milvus) for `>1M` items | | |
| | Rating model retraining | Manual (`python -m app.rating_model`) | Schedule weekly via cron; track RMSE drift | | |
| | LLM rate limits (Groq) | Single API key | Pool keys + retry-with-backoff in `app/llm.py` | | |
| ## Failure modes & current handling | |
| | Failure | Behavior | | |
| |---|---| | |
| | Groq returns invalid JSON | `chat_json` salvages by stripping code fences; on second failure raises (caller's `except` returns heuristic fallback) | | |
| | User not in dataset | 404 from `/persona`, `/simulate-review`, `/recommend` | | |
| | Parquet missing | 503 with instruction to run `python -m app.data.ingest` | | |
| | LightGBM model missing | rating model returns `None`; LLM rating is used alone | | |
| | LLM rate limit | Currently surfaces as HTTP 5xx; add retry in `app/llm.py` for prod | | |
| ## Deployment | |
| `docker-compose.yml` ships a single-service image. The first container start | |
| runs `app/data/ingest.py` + `app/retrieval.py` + `app/rating_model.py` as an | |
| init job, then starts uvicorn. Data and model artifacts are mounted as | |
| volumes so subsequent boots skip the build. | |
| Resource budget for an 8-core dev box: | |
| - RAM: ~1.5 GB peak during ingest, ~600 MB serving | |
| - Disk: ~700 MB (raw CSV + parquet + indexes + models) | |
| - CPU: ingest is ~3 min; serving is LLM-bound (CPU near-idle) | |