Naveen-2007 commited on
Commit
4351cdd
·
1 Parent(s): dc84587

Deploy to Hugging Face Spaces - Remove Render, enable full features

Browse files
Files changed (14) hide show
  1. .dockerignore +5 -1
  2. .env +3 -0
  3. Dockerfile +2 -2
  4. Dockerfile.backend +0 -29
  5. Dockerfile.frontend +0 -26
  6. Dockerfile.render +0 -29
  7. README.md +46 -0
  8. app/api.py +36 -69
  9. config/config.py +2 -5
  10. render.yaml +0 -20
  11. requirements.txt +4 -5
  12. start.sh +0 -19
  13. startup.sh +3 -3
  14. supervisord.conf +0 -29
.dockerignore CHANGED
@@ -1,6 +1,7 @@
1
  # Git
2
  .git
3
  .gitignore
 
4
 
5
  # Python
6
  __pycache__
@@ -8,11 +9,11 @@ __pycache__
8
  *$py.class
9
  *.so
10
  .Python
11
- .env
12
  .venv
13
  env/
14
  venv/
15
  ENV/
 
16
 
17
  # IDE
18
  .vscode
@@ -28,4 +29,7 @@ Thumbs.db
28
 
29
  # Docker
30
  docker-compose*.yml
 
 
 
31
  .dockerignore
 
1
  # Git
2
  .git
3
  .gitignore
4
+ .gitattributes
5
 
6
  # Python
7
  __pycache__
 
9
  *$py.class
10
  *.so
11
  .Python
 
12
  .venv
13
  env/
14
  venv/
15
  ENV/
16
+ .python-version
17
 
18
  # IDE
19
  .vscode
 
29
 
30
  # Docker
31
  docker-compose*.yml
32
+
33
+ # UV
34
+ uv.lock
35
  .dockerignore
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Environment variables
2
+ GROQ_API_KEY=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
3
+ TAVILY_API_KEY=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
Dockerfile CHANGED
@@ -22,8 +22,8 @@ COPY . .
22
  # Create directories for data persistence
23
  RUN mkdir -p /app/workspace_data /app/chroma_db
24
 
25
- # Expose ports
26
- EXPOSE 8000 8501
27
 
28
  # Copy startup script
29
  COPY startup.sh /app/startup.sh
 
22
  # Create directories for data persistence
23
  RUN mkdir -p /app/workspace_data /app/chroma_db
24
 
25
+ # Hugging Face Spaces uses port 7860
26
+ EXPOSE 7860
27
 
28
  # Copy startup script
29
  COPY startup.sh /app/startup.sh
Dockerfile.backend DELETED
@@ -1,29 +0,0 @@
1
- FROM python:3.11-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- gcc \
9
- curl \
10
- && rm -rf /var/lib/apt/lists/*
11
-
12
- # Copy requirements first for caching
13
- COPY requirements.txt .
14
-
15
- # Install Python dependencies
16
- RUN pip install --no-cache-dir --upgrade pip && \
17
- pip install --no-cache-dir -r requirements.txt
18
-
19
- # Copy application code
20
- COPY . .
21
-
22
- # Create directories for data persistence
23
- RUN mkdir -p /app/workspace_data /app/chroma_db
24
-
25
- # Expose port
26
- EXPOSE 8000
27
-
28
- # Run FastAPI
29
- CMD ["uvicorn", "app.api:app", "--host", "0.0.0.0", "--port", "8000"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile.frontend DELETED
@@ -1,26 +0,0 @@
1
- FROM python:3.11-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- gcc \
9
- curl \
10
- && rm -rf /var/lib/apt/lists/*
11
-
12
- # Copy requirements first for caching
13
- COPY requirements.txt .
14
-
15
- # Install Python dependencies
16
- RUN pip install --no-cache-dir --upgrade pip && \
17
- pip install --no-cache-dir -r requirements.txt
18
-
19
- # Copy application code
20
- COPY . .
21
-
22
- # Expose port
23
- EXPOSE 8501
24
-
25
- # Run Streamlit
26
- CMD ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0", "--server.headless=true"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile.render DELETED
@@ -1,29 +0,0 @@
1
- FROM python:3.11-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y --no-install-recommends \
7
- curl \
8
- dos2unix \
9
- && rm -rf /var/lib/apt/lists/* \
10
- && apt-get clean
11
-
12
- # Copy and install Python dependencies (cached layer)
13
- COPY requirements.txt .
14
- RUN pip install --no-cache-dir --upgrade pip && \
15
- pip install --no-cache-dir -r requirements.txt
16
-
17
- # Copy ALL application code
18
- COPY . .
19
-
20
- # Create workspace directories
21
- RUN mkdir -p /app/workspace_data /app/chroma_db
22
-
23
- # Fix line endings and make startup script executable
24
- RUN dos2unix /app/start.sh && chmod +x /app/start.sh
25
-
26
- # Render requires port 10000
27
- EXPOSE 10000
28
-
29
- CMD ["/bin/bash", "/app/start.sh"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Perplexity AI Clone
3
+ emoji: 🔍
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # Perplexity AI Clone
12
+
13
+ An AI-powered search and research assistant with multiple modes:
14
+
15
+ - **Automatic**: Auto-routes to best mode based on query
16
+ - **Web Search**: Real-time web search with citations
17
+ - **RAG**: Search uploaded documents
18
+ - **Deep Research**: Multi-step research with synthesis
19
+ - **Agentic**: Multi-agent RAG with planning
20
+ - **Analysis**: Data analysis and insights
21
+ - **Summarize**: Summarize content and documents
22
+
23
+ ## Features
24
+
25
+ - 🔍 Real-time web search with Tavily
26
+ - 📚 Document upload and RAG
27
+ - 🤖 LangGraph-powered pipelines
28
+ - 💡 Follow-up question suggestions
29
+ - 🖼️ Image search integration
30
+ - 📊 Knowledge panels
31
+
32
+ ## Environment Variables
33
+
34
+ Set these secrets in your Hugging Face Space:
35
+
36
+ - `GROQ_API_KEY`: Your Groq API key
37
+ - `TAVILY_API_KEY`: Your Tavily API key
38
+
39
+ ## Tech Stack
40
+
41
+ - FastAPI backend
42
+ - Streamlit frontend
43
+ - LangChain + LangGraph
44
+ - Groq LLM (llama3-70b-8192)
45
+ - FAISS vector store
46
+ - Sentence Transformers embeddings
app/api.py CHANGED
@@ -54,12 +54,12 @@ app.add_middleware(
54
 
55
 
56
  # =======================================================
57
- # Health Check Endpoint (for Azure Container Apps)
58
  # =======================================================
59
  @app.get("/health")
60
  async def health_check():
61
  """Health check endpoint for container orchestration"""
62
- return {"status": "healthy", "service": "perplexity-clone-api", "lite_mode": Config.LITE_MODE}
63
 
64
 
65
  # =======================================================
@@ -76,47 +76,35 @@ browse_tool = BrowseTool()
76
  image_search = TavilyImageSearch()
77
  summarizer = SummarizerTool()
78
 
79
- # Only load heavy components if not in LITE_MODE
80
- if not Config.LITE_MODE:
81
- reranker = Reranker()
82
- knowledge_panel = KnowledgePanel()
83
-
84
- # RAG demo vectorstore
85
- processor = DocumentProcessor(
86
- chunk_size=Config.CHUNK_SIZE,
87
- chunk_overlap=Config.CHUNK_OVERLAP,
88
- )
89
- demo_docs = processor.load_url("https://lilianweng.github.io/posts/2023-06-23-agent/")
90
- demo_splits = processor.split(demo_docs)
91
-
92
- vector = VectorStore()
93
- vector.create(demo_splits)
94
- else:
95
- reranker = None
96
- knowledge_panel = None
97
- vector = None
98
- print("⚡ LITE_MODE: Skipping heavy embeddings to save memory")
99
 
100
  # File manager for per-workspace document RAG
101
  file_manager = FileManager(base_dir="workspace_data")
102
 
103
  # =======================================================
104
- # Initialize All LangGraph Pipelines (only if not LITE_MODE)
105
  # =======================================================
106
- if not Config.LITE_MODE:
107
- deep_graph = DeepResearchGraph(vector)
108
- rag_graph = RAGOnlyGraph(file_manager)
109
- agentic_graph = AgenticRAGGraph(file_manager, vector, image_search)
110
- else:
111
- deep_graph = None
112
- rag_graph = None
113
- agentic_graph = None
114
-
115
  web_graph = WebSearchGraph()
116
  analysis_graph = AnalysisGraph()
117
  summarize_graph = SummarizeGraph()
118
 
119
- print("✅ All LangGraph pipelines initialized!" if not Config.LITE_MODE else "✅ LITE MODE: Core pipelines initialized!")
120
 
121
 
122
  # =======================================================
@@ -487,15 +475,9 @@ def deep_research(req: ChatRequest):
487
  memory.add(ws, "user", q)
488
 
489
  try:
490
- if deep_graph is None:
491
- # LITE_MODE fallback - use web search instead
492
- state = web_graph.run(q)
493
- answer = state.get("answer", "No answer generated.")
494
- sources = state.get("sources", [])
495
- else:
496
- state = deep_graph.run(q)
497
- answer = state.get("final_answer", "No answer generated.")
498
- sources = state.get("sources", [])
499
  except Exception as e:
500
  print(f"Deep research error: {e}")
501
  answer = f"Deep research encountered an error. Please try again."
@@ -971,17 +953,11 @@ def rag_mode(req: ModeRequest):
971
  memory.add(ws, "user", q)
972
 
973
  try:
974
- if rag_graph is None:
975
- # LITE_MODE fallback
976
- answer = "RAG mode requires document uploads. In lite mode, please use Web Search instead."
977
- sources = []
978
- follow = []
979
- else:
980
- # Run the RAGOnlyGraph pipeline
981
- state = rag_graph.run(q, ws)
982
- answer = state.get("answer", "No answer generated.")
983
- sources = state.get("sources", [])
984
- follow = state.get("followups", [])
985
  except Exception as e:
986
  print(f"RAG error: {e}")
987
  answer = f"RAG mode encountered an error: {str(e)[:100]}"
@@ -1014,22 +990,13 @@ def agentic_mode(req: ModeRequest):
1014
  print(f"\n🤖 AGENTIC MODE (LangGraph): {q}")
1015
 
1016
  try:
1017
- if agentic_graph is None:
1018
- # LITE_MODE fallback - use web search
1019
- state = web_graph.run(q)
1020
- answer = state.get("answer", "No answer generated.")
1021
- sources = state.get("sources", [])
1022
- links = state.get("links", [])
1023
- images = tavily_images_safe(q)
1024
- follow = state.get("followups", [])
1025
- else:
1026
- # Run the AgenticRAGGraph pipeline
1027
- state = agentic_graph.run(q, ws)
1028
- answer = state.get("answer", "No answer generated.")
1029
- sources = state.get("sources", [])
1030
- links = state.get("links", [])
1031
- images = state.get("images", [])
1032
- follow = state.get("followups", [])
1033
  except Exception as e:
1034
  print(f"Agentic error: {e}")
1035
  answer = f"Agentic mode encountered an error: {str(e)[:100]}"
 
54
 
55
 
56
  # =======================================================
57
+ # Health Check Endpoint
58
  # =======================================================
59
  @app.get("/health")
60
  async def health_check():
61
  """Health check endpoint for container orchestration"""
62
+ return {"status": "healthy", "service": "perplexity-clone-api"}
63
 
64
 
65
  # =======================================================
 
76
  image_search = TavilyImageSearch()
77
  summarizer = SummarizerTool()
78
 
79
+ # Load all components
80
+ reranker = Reranker()
81
+ knowledge_panel = KnowledgePanel()
82
+
83
+ # RAG demo vectorstore
84
+ processor = DocumentProcessor(
85
+ chunk_size=Config.CHUNK_SIZE,
86
+ chunk_overlap=Config.CHUNK_OVERLAP,
87
+ )
88
+ demo_docs = processor.load_url("https://lilianweng.github.io/posts/2023-06-23-agent/")
89
+ demo_splits = processor.split(demo_docs)
90
+
91
+ vector = VectorStore()
92
+ vector.create(demo_splits)
 
 
 
 
 
 
93
 
94
  # File manager for per-workspace document RAG
95
  file_manager = FileManager(base_dir="workspace_data")
96
 
97
  # =======================================================
98
+ # Initialize All LangGraph Pipelines
99
  # =======================================================
100
+ deep_graph = DeepResearchGraph(vector)
101
+ rag_graph = RAGOnlyGraph(file_manager)
102
+ agentic_graph = AgenticRAGGraph(file_manager, vector, image_search)
 
 
 
 
 
 
103
  web_graph = WebSearchGraph()
104
  analysis_graph = AnalysisGraph()
105
  summarize_graph = SummarizeGraph()
106
 
107
+ print("✅ All LangGraph pipelines initialized!")
108
 
109
 
110
  # =======================================================
 
475
  memory.add(ws, "user", q)
476
 
477
  try:
478
+ state = deep_graph.run(q)
479
+ answer = state.get("final_answer", "No answer generated.")
480
+ sources = state.get("sources", [])
 
 
 
 
 
 
481
  except Exception as e:
482
  print(f"Deep research error: {e}")
483
  answer = f"Deep research encountered an error. Please try again."
 
953
  memory.add(ws, "user", q)
954
 
955
  try:
956
+ # Run the RAGOnlyGraph pipeline
957
+ state = rag_graph.run(q, ws)
958
+ answer = state.get("answer", "No answer generated.")
959
+ sources = state.get("sources", [])
960
+ follow = state.get("followups", [])
 
 
 
 
 
 
961
  except Exception as e:
962
  print(f"RAG error: {e}")
963
  answer = f"RAG mode encountered an error: {str(e)[:100]}"
 
990
  print(f"\n🤖 AGENTIC MODE (LangGraph): {q}")
991
 
992
  try:
993
+ # Run the AgenticRAGGraph pipeline
994
+ state = agentic_graph.run(q, ws)
995
+ answer = state.get("answer", "No answer generated.")
996
+ sources = state.get("sources", [])
997
+ links = state.get("links", [])
998
+ images = state.get("images", [])
999
+ follow = state.get("followups", [])
 
 
 
 
 
 
 
 
 
1000
  except Exception as e:
1001
  print(f"Agentic error: {e}")
1002
  answer = f"Agentic mode encountered an error: {str(e)[:100]}"
config/config.py CHANGED
@@ -8,14 +8,11 @@ class Config:
8
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
9
  TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
10
 
11
- # Groq model
12
- LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-oss-20b")
13
 
14
  CHUNK_SIZE = 400
15
  CHUNK_OVERLAP = 80
16
-
17
- # Disable heavy features on free tier (512MB RAM limit)
18
- LITE_MODE = os.getenv("LITE_MODE", "true").lower() == "true"
19
 
20
  @classmethod
21
  def get_llm(cls):
 
8
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
9
  TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
10
 
11
+ # Groq model - using llama3-70b-8192 for best performance
12
+ LLM_MODEL = os.getenv("LLM_MODEL", "llama3-70b-8192")
13
 
14
  CHUNK_SIZE = 400
15
  CHUNK_OVERLAP = 80
 
 
 
16
 
17
  @classmethod
18
  def get_llm(cls):
render.yaml DELETED
@@ -1,20 +0,0 @@
1
- services:
2
- - type: web
3
- name: perplexity-clone
4
- env: docker
5
- dockerfilePath: ./Dockerfile.render
6
- dockerContext: .
7
- plan: free
8
- region: oregon
9
- healthCheckPath: /health
10
- envVars:
11
- - key: GROQ_API_KEY
12
- sync: false
13
- - key: TAVILY_API_KEY
14
- sync: false
15
- - key: BACKEND_URL
16
- value: http://localhost:8000
17
- - key: LITE_MODE
18
- value: "true"
19
- - key: LLM_MODEL
20
- value: "openai/gpt-oss-20b"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,5 +1,5 @@
1
  # =============================================
2
- # LIGHTWEIGHT REQUIREMENTS FOR AZURE APP SERVICE
3
  # =============================================
4
 
5
  # Core LangChain (compatible versions)
@@ -26,9 +26,8 @@ streamlit==1.31.1
26
  requests==2.31.0
27
  httpx==0.26.0
28
 
29
- # Embeddings - USE CPU-ONLY TORCH (smaller)
30
- --extra-index-url https://download.pytorch.org/whl/cpu
31
- torch==2.2.0+cpu
32
  sentence-transformers==2.3.1
33
 
34
  # Vector search
@@ -37,7 +36,7 @@ faiss-cpu==1.7.4
37
  # Web search
38
  tavily-python==0.3.3
39
 
40
- # Scraping (lightweight)
41
  beautifulsoup4==4.12.3
42
  lxml==5.1.0
43
 
 
1
  # =============================================
2
+ # REQUIREMENTS FOR HUGGING FACE SPACES
3
  # =============================================
4
 
5
  # Core LangChain (compatible versions)
 
26
  requests==2.31.0
27
  httpx==0.26.0
28
 
29
+ # Embeddings
30
+ torch
 
31
  sentence-transformers==2.3.1
32
 
33
  # Vector search
 
36
  # Web search
37
  tavily-python==0.3.3
38
 
39
+ # Scraping
40
  beautifulsoup4==4.12.3
41
  lxml==5.1.0
42
 
start.sh DELETED
@@ -1,19 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
-
4
- # Start FastAPI backend on port 8000 (internal only)
5
- echo "Starting FastAPI backend on port 8000..."
6
- uvicorn app.api:app --host 127.0.0.1 --port 8000 &
7
- BACKEND_PID=$!
8
-
9
- echo "Waiting for backend to initialize..."
10
- sleep 5
11
- echo "Backend started (PID: $BACKEND_PID)"
12
-
13
- # Start Streamlit on port 10000 (Render's expected port)
14
- echo "Starting Streamlit on port 10000..."
15
- exec streamlit run streamlit_app.py \
16
- --server.port=10000 \
17
- --server.address=0.0.0.0 \
18
- --server.headless=true \
19
- --browser.gatherUsageStats=false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
startup.sh CHANGED
@@ -16,7 +16,7 @@ if ! kill -0 $BACKEND_PID 2>/dev/null; then
16
  fi
17
 
18
  echo "Backend started successfully (PID: $BACKEND_PID)"
19
- echo "Starting Streamlit frontend on port 8501..."
20
 
21
- # Start Streamlit frontend (this will keep the container running)
22
- streamlit run streamlit_app.py --server.port 8501 --server.address 0.0.0.0 --server.headless true
 
16
  fi
17
 
18
  echo "Backend started successfully (PID: $BACKEND_PID)"
19
+ echo "Starting Streamlit frontend on port 7860..."
20
 
21
+ # Start Streamlit frontend on port 7860 (Hugging Face Spaces default)
22
+ streamlit run streamlit_app.py --server.port 7860 --server.address 0.0.0.0 --server.headless true
supervisord.conf DELETED
@@ -1,29 +0,0 @@
1
- [supervisord]
2
- nodaemon=true
3
- user=root
4
- logfile=/dev/stdout
5
- logfile_maxbytes=0
6
- loglevel=info
7
-
8
- [program:fastapi]
9
- command=uvicorn app.api:app --host 0.0.0.0 --port 8000 --workers 1
10
- directory=/app
11
- autostart=true
12
- autorestart=true
13
- stdout_logfile=/dev/stdout
14
- stdout_logfile_maxbytes=0
15
- stderr_logfile=/dev/stderr
16
- stderr_logfile_maxbytes=0
17
- priority=1
18
-
19
- [program:streamlit]
20
- command=streamlit run streamlit_app.py --server.port=10000 --server.address=0.0.0.0 --server.headless=true --browser.gatherUsageStats=false
21
- directory=/app
22
- autostart=true
23
- autorestart=true
24
- stdout_logfile=/dev/stdout
25
- stdout_logfile_maxbytes=0
26
- stderr_logfile=/dev/stderr
27
- stderr_logfile_maxbytes=0
28
- priority=2
29
- startsecs=10