Skip to content

Repository files navigation

DSN × BCT LLM Agent Challenge

Submission for the Data & AI Summit Hackathon 3.0 — DSN × BCT LLM Agent Challenge.

Two containerized FastAPI services that model user behaviour from Yelp review history and generate personalized reviews and recommendations.

Live Demo

Service Base URL Swagger UI
Task A — Review Generation https://bct-task-a-52ol.onrender.com /docs
Task B — Recommendations https://bct-task-b.onrender.com /docs

Free tier may spin down after inactivity — first request takes ~30s to wake up.


Architecture

Yelp Dataset (JSONL)
      │
      ▼
data_loader.py ──► businesses_subset.jsonl
                   reviews_subset.jsonl
                   users_subset.jsonl
                        │
                        ▼
                 ingest_data.py ──► ChromaDB (text-embedding-3-small)
                                         │
                              ┌──────────┴──────────┐
                              ▼                     ▼
                     ReviewAgent               RecommendationAgent
                  (LangGraph 3-node)          (LangGraph 4-node)
                              │                     │
                              ▼                     ▼
                     Task A API :8000       Task B API :8001

Stack: Python · FastAPI · LangGraph · LangChain · OpenAI GPT-4o-mini · ChromaDB · Docker


Quick Start

1. Clone and configure

git clone <repo-url>
cd bct-hackathon
cp .env.example .env
# Edit .env and add your OPENAI_API_KEY

2. Prepare data

Download the Yelp Academic Dataset and place the JSON files in:

data/raw/Yelp-JSON/Yelp JSON/yelp_dataset/

Then run the preprocessing pipeline:

python -m venv venv && source venv/bin/activate
pip install -r requirements.txt

# Sample ~100 users from the full dataset
python src/utils/data_loader.py

# Embed businesses into ChromaDB
python -m src.utils.ingest_data

3. Start services

docker compose up --build

Both services start automatically:

Service Base URL Swagger UI Health
Task A — Review Generation http://localhost:8000 /docs /health
Task B — Recommendations http://localhost:8001 /docs /health

Cloud Deploy (Render + Pinecone)

For live judging without local setup:

1. Create Pinecone index

Sign up at pinecone.io (free tier). Grab your API key — the index is created automatically on first startup.

2. Ingest data into Pinecone

# Set PINECONE_API_KEY in .env, then run the same ingest script
PINECONE_API_KEY=your_key python -m src.utils.ingest_data

3. Deploy to Render

# Push repo to GitHub, then connect it in Render dashboard
# render.yaml is already configured for both services

Set these environment variables in the Render dashboard for each service:

Variable Value
OPENAI_API_KEY Your OpenAI key
PINECONE_API_KEY Your Pinecone key
PINECONE_INDEX_NAME bct-hackathon

The vector backend switches automatically — PINECONE_API_KEY present → Pinecone, absent → ChromaDB. Docker and local workflows are unchanged.


API Reference

Task A — Generate Review

POST /generate-review

curl -X POST http://localhost:8000/generate-review \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_123",
    "business_id": "biz_456",
    "user_history": [
      {
        "business_name": "Suya Spot",
        "category": "Nigerian, BBQ",
        "stars_given": 5,
        "review_text": "Omo the suya here is too good. The pepper is on point."
      }
    ],
    "business_metadata": {
      "name": "Jollof Kitchen",
      "category": "Nigerian, West African",
      "avg_stars": 4.2,
      "price_range": "$$"
    },
    "nigerian_mode": true
  }'

Response:

{
  "predicted_stars": 5,
  "generated_review": "Omo, Jollof Kitchen is where it's at! ...",
  "confidence": 0.95,
  "cold_start": false
}

nigerian_mode: true activates Nigerian English slang and cultural framing in the generated review.


Task B — Recommendations

POST /recommend

curl -X POST http://localhost:8001/recommend \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_123",
    "user_history": [
      {"business_name": "Mama Cass", "category": "Nigerian", "stars_given": 5}
    ],
    "context": {
      "time_of_day": "evening",
      "mood": "casual dining with friends",
      "location_preference": "nearby"
    },
    "num_recommendations": 5
  }'

Response:

{
  "recommendations": [
    {
      "business_id": "abc123",
      "business_name": "Kumo Sushi",
      "category": "Japanese, Sushi Bars",
      "predicted_rating": 4.5,
      "reason": "Matches user preference for casual group dining",
      "confidence": 0.95
    }
  ],
  "cold_start": false,
  "reasoning_trace": "Profile built from history. -> Retrieved 10 candidates. -> Scored and ranked. -> Final recommendations generated."
}

Cold-Start Handling

Both endpoints handle users with no review history. Pass an empty user_history array:

{ "user_history": [] }

The system generates a balanced baseline persona and proceeds. The response includes "cold_start": true so callers can adjust downstream.


Evaluation

Run evaluation scripts against the processed subset:

# Rating accuracy (RMSE, MAE, ±1 star accuracy)
python eval/eval_rmse.py --max-users 20

# Review quality (ROUGE-1, ROUGE-2, ROUGE-L)
python eval/eval_rouge.py --max-users 20 --save-examples 5

# With Nigerian mode
python eval/eval_rouge.py --max-users 20 --nigerian-mode --save-examples 5

Results (n=20, leave-one-out evaluation):

Metric Score
RMSE 1.75
MAE 1.15
Within ±1 star 75%
ROUGE-1 F1 0.262
ROUGE-2 F1 0.051
ROUGE-L F1 0.144

Project Structure

├── data/
│   ├── raw/            # Yelp JSON files (not committed)
│   ├── processed/      # Sampled subsets (JSONL)
│   └── chroma_db/      # ChromaDB vector store
├── eval/
│   ├── eval_rmse.py    # Rating accuracy evaluation
│   └── eval_rouge.py   # Review quality evaluation
├── src/
│   ├── agents/
│   │   ├── review_agent.py          # Task A: LangGraph 3-node agent
│   │   └── recommendation_agent.py  # Task B: LangGraph 4-node agent
│   ├── models/
│   │   └── user_profiler.py         # Persona extraction via GPT-4o-mini
│   ├── api/
│   │   ├── task_a_api.py            # FastAPI app, port 8000
│   │   └── task_b_api.py            # FastAPI app, port 8001
│   └── utils/
│       ├── data_loader.py           # Streaming JSONL loader + sampler
│       ├── vector_store.py          # ChromaDB manager
│       └── ingest_data.py           # Ingestion script
├── task_a/Dockerfile
├── task_b/Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example

Environment Variables

Variable Description
OPENAI_API_KEY OpenAI API key (required)
CHROMA_DB_PATH Path to ChromaDB storage (default: ./data/chroma_db)
PROCESSED_DATA_DIR Path to processed JSONL files (default: ./data/processed)
RAW_DATA_DIR Path to raw Yelp JSON files
LOG_LEVEL Logging level (default: INFO)

Key Design Decisions

  • Streaming data loading — 5GB review file read line-by-line, never loaded into memory
  • Cold-start as first class — 85% of Yelp users have one review; the system never fails on empty history
  • Index-based LLM ranking — Task B sends numbered candidates to the LLM instead of raw IDs, preventing ID corruption in LLM output
  • Nigerian cultural layernigerian_mode flag adds authentic Nigerian English (Omo, Abeg, Chai) to review generation via targeted system prompt injection
  • Reasoning trace — Task B exposes the agent's step-by-step reasoning for interpretability

About

Two containerized FastAPI services that model user behaviour from Yelp review history and generate personalized reviews and recommendations.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages