Skip to content

Latest commit

 

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🛒 ShopBuddy — Agentic RAG E-commerce Assistant

ShopBuddy answers shopping questions about product prices, reviews, comparisons, and buying guidance. It combines a local AstraDB product catalog with a multi-engine web-search fallback, coordinated through a LangGraph workflow and MCP tools.

The repository includes a Flipkart ETL pipeline, FastAPI backend, browser chat interface, Streamlit scraper UI, RAGAS evaluation helpers, Docker packaging, and AWS EKS deployment automation.

Python FastAPI LangGraph Docker AWS EKS

Project preview

ShopBuddy Chat UI

How it works

ShopBuddy classifies each message into one of three routes:

  • Product: retrieve catalog context, grade its relevance, and generate shopping guidance. Missing or irrelevant local context triggers web search.
  • Smalltalk: respond briefly to greetings, questions about the assistant, complaints, and feedback.
  • Out of domain: return a fixed shopping-scope response without an answer-generation call.

Common greetings use a deterministic shortcut. Other messages use structured LLM classification, with a keyword fallback if classification fails. For mixed shopping and unrelated questions, the classifier extracts the shopping portion.

If neither local retrieval nor web search returns context, ShopBuddy returns a fixed no-results response instead of asking the model to invent an answer.

System architecture

The application has an offline data pipeline and an online query pipeline.

flowchart TD
    FK["Flipkart"] --> SC["Selenium scraper"]
    SC --> CSV["product_reviews.csv"]
    CSV --> ING["Document transformation and embedding"]
    ING --> DB["AstraDB vector collection"]

    USER["User"] --> UI["Browser chat UI"]
    UI --> API["FastAPI POST /get"]
    API --> CL["LangGraph classifier"]
    CL -->|Product| RET["MCP get_product_info"]
    CL -->|Smalltalk| ST["Short LLM response"]
    CL -->|Out of domain| RF["Fixed refusal"]
    RET --> MMR["Numeric filters and MMR retrieval"]
    MMR --> CMP["LLM contextual compression"]
    CMP --> GR["Relevance grader"]
    GR -->|Relevant| GEN["Answer generator"]
    GR -->|Missing or irrelevant| RW["Query rewriter"]
    RW --> WEB["MCP multi-engine web search"]
    WEB --> GEN
    GEN --> API
    ST --> API
    RF --> API
Loading

Agentic workflow

The active graph is defined in prod_assistant/workflow/agentic_workflow_with_mcp_websearch.py.

Component Responsibility
Classifier Select product, smalltalk, or out_of_domain using structured output.
Retriever Call the MCP get_product_info tool.
Smalltalk Generate a short conversational response.
Refusal Return a fixed response for unrelated subjects.
Grader edge Check whether local context is relevant; inspect at most 4,000 characters.
Rewriter Turn an unsuccessful shopping question into a short web-search query.
WebSearch Call the MCP web_search tool.
Generator Produce a concise plain-text answer from available context.
START -> Classifier
Classifier -> Retriever -> Grader -> Generator -> END
                              `-> Rewriter -> WebSearch -> Generator -> END
Classifier -> Smalltalk -> END
Classifier -> Refusal -> END

Graph state keeps the current question, route, context, rewritten query, rewrite count, and messages as separate fields. FastAPI builds the agent once at startup per worker. MCP tool discovery is retried on demand, with a ten-second cooldown, if startup discovers no tools.

The answer prompt prefers local data, requires INR for prices, asks for approximate price ranges when using web listings, and limits shopping responses to 3–4 plain-text sentences.

Retrieval

Local retrieval has three stages:

  1. Parse price and rating constraints into numeric metadata filters.
  2. Run AstraDB maximum marginal relevance (MMR) search for relevant, diverse candidates.
  3. Optionally use LLMChainFilter to remove irrelevant documents.
Example Parsed constraint
under 20k price_value <= 20000
above 1.5 lakh price_value >= 150000
between 15000 and 25000 15000 <= price_value <= 25000
rating above 4.2 rating_value >= 4.2

Defaults are k=4, fetch_k=25, and lambda_mult=0.6. There is no configured similarity-score threshold in the active implementation. If a filter fails or matches nothing, retrieval retries without the filter. If compression rejects every candidate, the workflow falls back to web search.

Project structure

ecomm-prod-assistant/
├── .github/workflows/
│   ├── deploy.yml                   # Build -> ECR -> EKS rollout
│   └── infra.yml                    # Provision AWS infrastructure
├── infra/eks-with-ecr.yaml           # VPC, ECR, EKS, IAM, node group
├── k8/                              # Deployment and LoadBalancer Service
├── prod_assistant/
│   ├── config/config.yaml           # Model and retrieval configuration
│   ├── etl/
│   │   ├── data_scraper.py          # Flipkart product/review scraping
│   │   └── data_ingestion.py        # CSV -> Documents -> AstraDB
│   ├── evaluation/ragas_eval.py     # RAGAS metric helpers
│   ├── exception/                   # Custom exception formatting
│   ├── logger/                      # JSON logging with structlog
│   ├── mcp_servers/
│   │   ├── client.py                # Standalone client example
│   │   └── product_search_saver.py  # Product retrieval and web-search tools
│   ├── prompt_library/prompts.py    # Shopping answer prompt
│   ├── retriever/retrieval.py       # Constraint parsing, MMR, compression
│   ├── router/main.py               # FastAPI app and lifespan
│   ├── utils/                       # Configuration and model loaders
│   └── workflow/
│       └── agentic_workflow_with_mcp_websearch.py
├── data/product_reviews.csv
├── templates/chat.html
├── static/style.css
├── docs/images/shopbuddy-ui.png
├── diagnose.py                      # Layer-by-layer diagnostics
├── dump_reviews.py                  # Capture HTML for selector debugging
├── scrape_catalog.py                # Resumable multi-category scraper
├── scrapper_ui.py                   # Streamlit scraping and ingestion UI
├── main.py                          # Placeholder greeting script
├── Dockerfile
├── pyproject.toml
├── requirements.txt
└── uv.lock

Archived workflows and pre-fix backups are historical snapshots and are not used by the active application. The test/ directory currently contains only an empty initializer.

Technology stack

Area Technology
Runtime Python 3.11 in Docker; package metadata allows Python 3.10+
API FastAPI 0.116.1, Uvicorn 0.35.0
Orchestration LangGraph 0.6.7, LangChain 0.3.27
Default LLM Groq openai/gpt-oss-120b
Other model configurations Google gemini-2.0-flash, OpenAI gpt-4o
Embeddings HuggingFace sentence-transformers/all-MiniLM-L6-v2
Vector database AstraDB collection ecommercedata
Tools MCP with streamable HTTP
Web search ddgs: Bing, Brave, Mojeek, DuckDuckGo
Scraping Selenium, undetected-chromedriver, BeautifulSoup
Evaluation RAGAS 0.3.4
Interfaces Jinja2, HTML/CSS, jQuery, Bootstrap, Streamlit
Deployment Docker, Kubernetes, AWS EKS/ECR, GitHub Actions

Environment variables

Create .env in the repository root using this format:

GROQ_API_KEY=your_groq_api_key
GOOGLE_API_KEY=your_google_api_key
OPENAI_API_KEY=your_openai_api_key
ASTRA_DB_API_ENDPOINT=https://your-database-endpoint
ASTRA_DB_APPLICATION_TOKEN=AstraCS:your_token
ASTRA_DB_KEYSPACE=default_keyspace

# Optional runtime overrides
LLM_PROVIDER=groq
MCP_SERVER_URL=http://127.0.0.1:8001/mcp
MCP_HOST=127.0.0.1
MCP_PORT=8001
SEARCH_BACKENDS=bing,brave,mojeek,duckduckgo
SEARCH_REGION=in-en
SEARCH_MAX_RESULTS=5
SEARCH_RETRIES=3
RETRIEVER_FETCH_K=25
RETRIEVER_LAMBDA=0.6
RETRIEVER_COMPRESSION=1
  • GROQ_API_KEY is required for the default LLM.
  • GOOGLE_API_KEY is used by the Google provider; current ingestion validation also expects it.
  • OPENAI_API_KEY is intended for the optional OpenAI provider; see its current limitation below.
  • ASTRA_DB_* variables configure local vector retrieval and ingestion.
  • LLM_PROVIDER selects a configured provider and defaults to groq.
  • MCP_SERVER_URL configures the application client; MCP_HOST and MCP_PORT configure the server.
  • SEARCH_* variables configure search engines, locale, result count, and retries.
  • RETRIEVER_COMPRESSION=0 disables LLM filtering for latency/quality comparisons.
  • CONFIG_PATH optionally selects another YAML configuration file.

Optional LangSmith tracing uses LANGSMITH_TRACING, LANGSMITH_API_KEY, and LANGSMITH_PROJECT. The catalog scraper disables tracing for its own process.

Local .env files are ignored by Git and excluded from Docker builds. The checked-in .env.copy lists credential names; use the assignment format above when creating .env.

Local setup

Prerequisites

  • Python 3.11 recommended
  • Chrome for Selenium scraping
  • AstraDB database credentials
  • Groq API key for the default model

Install dependencies

With uv:

uv sync
source .venv/bin/activate

Or with pip:

python3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Create .env, then run commands from the repository root. Use the module commands below rather than the currently broken ecomm-assistant console entry point.

Start services

Terminal 1 — MCP on port 8001:

python -m prod_assistant.mcp_servers.product_search_saver

Terminal 2 — FastAPI on port 8000:

uvicorn prod_assistant.router.main:app --host 0.0.0.0 --port 8000 --reload

Open http://localhost:8000 for the chat UI or http://localhost:8000/docs for API documentation. The MCP endpoint defaults to http://127.0.0.1:8001/mcp.

Data pipeline

Scrape products

The catalog script spreads queries across phones, laptops, headphones, earbuds, smartwatches, and TVs. It skips existing product IDs and appends each completed query to the CSV.

python scrape_catalog.py
python scrape_catalog.py --target 60
python scrape_catalog.py --no-reviews
python scrape_catalog.py --category phones

Defaults are 250 total products, 12 products per query, two pages per query, and three reviews per product. Collection depends on the results available on Flipkart.

For interactive scraping and ingestion:

streamlit run scrapper_ui.py

CSV columns are product_id, product_title, rating, total_reviews, price, top_reviews, and category.

The scraper reuses one Chrome instance, supports multiple review layouts, filters page noise, and can save a debug HTML capture. The Streamlit interface overwrites the CSV; the catalog CLI appends and resumes.

Ingest into AstraDB

python -m prod_assistant.etl.data_ingestion

Each CSV row becomes a LangChain Document containing title, price, rating, review count, and review text. Metadata includes numeric price_value and rating_value fields so retrieval can apply real numeric constraints.

The product ID is used as the AstraDB document ID. Re-running ingestion upserts products rather than inserting new random-ID copies. Re-ingest older catalog documents to add the numeric metadata required by filtered retrieval.

MCP tools

Tool Behavior
get_product_info(query) Retrieve catalog documents with constraints, MMR, and optional LLM compression.
web_search(query) Search configured engines with retries and format titles, snippets, and source URLs.

The vector retriever is built lazily on the first product request. If its initialization fails, the MCP server remains available for web search. Client tool discovery also tolerates startup-order failures and retries on later product requests.

API endpoints

Method Endpoint Description
GET / Render the browser chat UI.
GET /status Return static application/deployment information.
GET /health Lightweight process health check.
GET /ready Report agent initialization and loaded tool names.
POST /get Accept msg and optional session_id form fields; return plain text.
curl -X POST http://localhost:8000/get \
  -F "msg=What is the best phone under 30000?" \
  -F "session_id=demo-user-1"

Reusing session_id selects the same LangGraph checkpoint thread. Current prompts use the current question rather than passing the complete stored conversation history to the model.

Diagnostics

Start MCP, then run:

python diagnose.py
python diagnose.py "iphone 16 price"

The script checks environment variables, CSV size, AstraDB collection/count, retrieval, direct web search, and MCP connectivity. It contacts external services and may invoke the configured LLM through contextual compression.

For review-selector debugging:

python dump_reviews.py "laptop under 60000"

This captures data/review_page_dump.html for offline inspection.

RAGAS evaluation

prod_assistant/evaluation/ragas_eval.py exposes context-precision and response-relevancy helpers. They are not called automatically by the production request pipeline.

Supply a real query, generated response, and list of retrieved context strings from the run being evaluated:

from prod_assistant.evaluation.ragas_eval import (
    evaluate_context_precision,
    evaluate_response_relevancy,
)

# query, response, and contexts come from a recorded application run.
print(evaluate_context_precision(query, response, contexts))
print(evaluate_response_relevancy(query, response, contexts))

Run these synchronous helpers outside an existing async event loop. They invoke model APIs and can incur provider usage. Running python -m prod_assistant.retriever.retrieval performs sample retrievals, not RAGAS evaluation.

Docker

docker build -t shopbuddy:latest .
docker run --env-file .env -p 8000:8000 shopbuddy:latest

The container starts one MCP server on internal port 8001 and Uvicorn on port 8000 with two workers. Only port 8000 needs to be published because FastAPI and MCP share the container network namespace.

CI/CD and AWS EKS

.github/workflows/infra.yml is manually triggered. It deploys the CloudFormation template in infra/eks-with-ecr.yaml, which creates a VPC, two public subnets, an internet gateway, ECR repository, EKS cluster, IAM roles, and managed node group.

.github/workflows/deploy.yml runs on pushes to main or manual dispatch:

  1. Verify the configured EKS cluster exists.
  2. Authenticate to ECR and validate the repository.
  3. Build and push timestamped and latest image tags.
  4. Configure kubectl for EKS.
  5. Create/update product-assistant-secrets from GitHub secrets.
  6. Apply Deployment and LoadBalancer Service manifests.
  7. Patch the Deployment to the timestamped image and verify rollout.

Required GitHub secrets include AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, EKS_CLUSTER_NAME, ECR_REGISTRY, ECR_REPOSITORY, and the API/database credentials used in .env.

The Deployment currently runs two replicas. Each container starts its own MCP server and two Uvicorn workers. The Service exposes port 80 and forwards to FastAPI on port 8000.

Current limitations

  • The browser does not persist or send session_id, so each UI message uses a separate checkpoint thread.
  • Stored message history is not included in the current model prompts, so follow-up questions do not yet gain conversational context.
  • User and model text are inserted as raw HTML in the chat UI and should be escaped.
  • Failed AstraDB retriever initialization is cached until the MCP process restarts.
  • Empty numeric-filter results trigger unfiltered retrieval, which can return products outside the requested constraints.
  • Web results are not graded again before generation.
  • MemorySaver is process-local, has no cleanup policy, and is not shared across workers or replicas.
  • The scraper depends on Flipkart markup and may require selector updates.
  • The Streamlit description field is used as a separate query, and its scraper overwrites the CSV.
  • The OpenAI branch references ChatOpenAI while its import is commented out.
  • Package discovery and the console entry point do not match the current prod_assistant package layout.
  • MCP tools call synchronous retrieval/search code inside async functions, which can limit concurrency.
  • Kubernetes manifests lack health/readiness probes and CPU/memory requests and limits.
  • The infrastructure security group allows broad inbound access and should be tightened for production.
  • Automated tests are not implemented; deployment currently runs without a test stage.
  • Python version and license declarations are inconsistent across repository metadata.

Suggested improvements

  • Add a persistent browser session ID and conversational context handling.
  • Escape chat content and add loading/error states.
  • Repair package discovery, the console entry point, and optional OpenAI loading.
  • Add routing, constraint-parsing, API, and mocked MCP integration tests.
  • Retry retriever initialization after a cooldown and preserve hard numeric constraints.
  • Grade web context before generation.
  • Use a shared checkpoint store with retention when memory is needed across replicas.
  • Add process supervision, Kubernetes probes, resource limits, and tighter network access.
  • Align Python version declarations and licensing documentation.

Author

Niraj Kumar

AI/ML Engineer Intern | B.Tech CSE, Sikkim Manipal Institute of Technology

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages