findEthics commited on
Commit
7d886a8
·
1 Parent(s): 4b34d35

Initial commit

Browse files
Files changed (4) hide show
  1. Dockerfile +30 -0
  2. README.md +1 -0
  3. app.py +177 -0
  4. requirements.txt +10 -0
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.9 slim image for efficiency
2
+ FROM python:3.9-slim
3
+
4
+ # Create a non-root user for security
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+
8
+ # Set environment variables
9
+ ENV PATH="/home/user/.local/bin:$PATH"
10
+ ENV PYTHONPATH="/app:$PYTHONPATH"
11
+
12
+ # Set working directory
13
+ WORKDIR /app
14
+
15
+ # Copy requirements file with proper ownership
16
+ COPY --chown=user requirements.txt requirements.txt
17
+
18
+ # Install Python dependencies
19
+ RUN pip install --no-cache-dir --upgrade pip && \
20
+ pip install --no-cache-dir --upgrade -r requirements.txt
21
+
22
+ # Copy application code
23
+ COPY --chown=user app.py /app/
24
+ COPY --chown=user . /app/
25
+
26
+ # Expose port 7860 for Hugging Face Spaces
27
+ EXPOSE 7860
28
+
29
+ # Command to run the application
30
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -6,6 +6,7 @@ colorTo: pink
6
  sdk: docker
7
  pinned: false
8
  license: mit
 
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
+ app_port: 7860
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import FastAPI, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel
5
+ import torch
6
+ from transformers import pipeline
7
+ from duckduckgo_search import DDGS
8
+ from typing import Optional, List, Dict, Any
9
+ import logging
10
+
11
+ # Configure logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Initialize FastAPI app
16
+ app = FastAPI(
17
+ title="Open Source Chat API",
18
+ description="A fully open source alternative to HF API using local models",
19
+ version="1.0.0"
20
+ )
21
+
22
+ # Configure CORS for Android app access
23
+ app.add_middleware(
24
+ CORSMiddleware,
25
+ allow_origins=["*"],
26
+ allow_methods=["POST", "GET"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ # Request/Response models
31
+ class ChatRequest(BaseModel):
32
+ prompt: str
33
+ max_new_tokens: int = 500
34
+ use_search: bool = False
35
+ temperature: float = 0.7
36
+
37
+ class ChatResponse(BaseModel):
38
+ response: str
39
+ search_results: Optional[List[Dict[str, Any]]] = None # Contains web search results if use_search is enabled
40
+
41
+ class SearchRequest(BaseModel):
42
+ query: str
43
+ max_results: int = 5
44
+
45
+ qa_pipeline = None
46
+
47
+ # Function to load the local language model
48
+ def load_model():
49
+ """Load the local language model"""
50
+ global qa_pipeline
51
+
52
+ try:
53
+
54
+ # Check if GPU is available
55
+ device = "cuda" if torch.cuda.is_available() else "cpu"
56
+ logger.info(f"Using device: {device}")
57
+
58
+ qa_pipeline = pipeline(
59
+ "question-answering",
60
+ model="distilbert-base-uncased-distilled-squad",
61
+ device=0 if device == "cuda" else -1
62
+ )
63
+
64
+ logger.info("Model loaded successfully!")
65
+
66
+ except Exception as e:
67
+ logger.error(f"Error loading model: {e}")
68
+
69
+ def search_web(query: str, max_results: int = 5) -> List[Dict[str, Any]]:
70
+ """Search the web using DuckDuckGo"""
71
+ try:
72
+ ddgs = DDGS()
73
+ results = []
74
+
75
+ for result in ddgs.text(query,safesearch='off',max_results=max_results):
76
+ results.append({
77
+ "title": result.get("title", ""),
78
+ "body": result.get("body", ""),
79
+ "href": result.get("href", "")
80
+ })
81
+
82
+ return results
83
+
84
+ except Exception as e:
85
+ logger.error(f"Search error: {e}")
86
+ return []
87
+
88
+ def generate_response(prompt: str,search_context: str) -> str:
89
+ """Generate response using local model"""
90
+ try:
91
+ if qa_pipeline is None:
92
+ return "Model not loaded properly. Please try again."
93
+
94
+ # Validate that qa_pipeline is a question-answering pipeline
95
+ if not hasattr(qa_pipeline, "task") or qa_pipeline.task != "question-answering":
96
+ return "Invalid pipeline type. Expected a question-answering pipeline."
97
+
98
+ result = qa_pipeline(question=prompt, context=search_context)['answer']
99
+ return result
100
+
101
+ except Exception as e:
102
+ logger.error(f"Generation error: {e}")
103
+ return f"Sorry, I encountered an error: {str(e)}"
104
+
105
+ @app.on_event("startup")
106
+ async def startup_event():
107
+ """Load model on startup"""
108
+ load_model()
109
+
110
+ @app.get("/")
111
+ async def root():
112
+ """Health check endpoint"""
113
+ return {
114
+ "message": "Open Source Chat API is running!",
115
+ "model_loaded": qa_pipeline is not None,
116
+ "endpoints": {
117
+ "chat": "/chat",
118
+ "search": "/search",
119
+ "docs": "/docs"
120
+ }
121
+ }
122
+
123
+ @app.post("/chat", response_model=ChatResponse)
124
+ async def chat(request: ChatRequest):
125
+ """Main chat endpoint"""
126
+ try:
127
+ search_results = None
128
+
129
+ # Perform web search if requested
130
+ if request.use_search:
131
+ search_results = search_web(request.prompt)
132
+ search_context = None
133
+ # Enhance prompt with search results
134
+ if search_results:
135
+ num_results = min(5, len(search_results))
136
+ search_context = "\n".join([
137
+ f"- {result['title']}: {result['body'][:200]}..."
138
+ for result in search_results[:num_results]
139
+ ])
140
+ else:
141
+ search_context = request.prompt
142
+
143
+ if search_context:
144
+ # Generate response
145
+ response = generate_response(
146
+ request.prompt,
147
+ search_context
148
+ )
149
+ else:
150
+ response = "No context available to generate a response."
151
+
152
+ return ChatResponse(
153
+ response=response,
154
+ search_results=search_results if request.use_search else None
155
+ )
156
+
157
+ except Exception as e:
158
+ logger.error(f"Chat endpoint error: {e}")
159
+ raise HTTPException(status_code=500, detail=str(e))
160
+
161
+ @app.post("/search")
162
+ async def search(request: SearchRequest):
163
+ """Web search endpoint"""
164
+ try:
165
+ results = search_web(request.query, request.max_results)
166
+ return {"results": results}
167
+
168
+ except Exception as e:
169
+ logger.error(f"Search endpoint error: {e}")
170
+ raise HTTPException(status_code=500, detail=str(e))
171
+
172
+ if __name__ == "__main__":
173
+ import uvicorn
174
+ import os
175
+ host = os.getenv("HOST", "0.0.0.0")
176
+ port = int(os.getenv("PORT", 7860)) # Changed from 8000 to 7860
177
+ uvicorn.run(app, host=host, port=port)
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ pydantic==2.5.0
4
+ torch==2.6.0
5
+ transformers==4.35.0
6
+ accelerate==0.24.0
7
+ duckduckgo-search
8
+ requests==2.31.0
9
+ python-multipart==0.0.6
10
+ keras