Upload app.py
Browse files
app.py
CHANGED
|
@@ -9,9 +9,10 @@ from huggingface_hub import hf_hub_download
|
|
| 9 |
app = Flask(__name__)
|
| 10 |
CORS(app)
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
| 14 |
-
|
|
|
|
| 15 |
|
| 16 |
model = None
|
| 17 |
loading_error = None
|
|
@@ -22,7 +23,7 @@ def load_model():
|
|
| 22 |
# Use HF_TOKEN if your space is private
|
| 23 |
token = os.environ.get("HF_TOKEN")
|
| 24 |
|
| 25 |
-
print(f"📥 Downloading model from: {MODEL_REPO}/{MODEL_FILE}...")
|
| 26 |
|
| 27 |
# Download from HuggingFace model repository
|
| 28 |
model_path = hf_hub_download(
|
|
@@ -34,23 +35,18 @@ def load_model():
|
|
| 34 |
print(f"✅ Model downloaded to: {model_path}")
|
| 35 |
print("🏗️ Initializing model engine (llama-cpp)...")
|
| 36 |
|
| 37 |
-
# Use
|
| 38 |
-
# The model is crashing due to memory/batch issues
|
| 39 |
model = Llama(
|
| 40 |
model_path=model_path,
|
| 41 |
-
n_ctx=
|
| 42 |
-
n_threads=
|
| 43 |
-
n_batch=
|
| 44 |
-
|
| 45 |
-
use_mmap=True,
|
| 46 |
-
use_mlock=False,
|
| 47 |
-
verbose=False,
|
| 48 |
-
seed=42 # Fixed seed for reproducibility
|
| 49 |
)
|
| 50 |
-
print("✅
|
| 51 |
except Exception as e:
|
| 52 |
loading_error = str(e)
|
| 53 |
-
print(f"❌ Error loading model: {e}")
|
| 54 |
|
| 55 |
# Start loading in background
|
| 56 |
threading.Thread(target=load_model, daemon=True).start()
|
|
@@ -62,7 +58,7 @@ def index():
|
|
| 62 |
<!DOCTYPE html>
|
| 63 |
<html>
|
| 64 |
<head>
|
| 65 |
-
<title>OpenGSSTEC AI API</title>
|
| 66 |
<style>
|
| 67 |
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
| 68 |
h1 { color: #333; }
|
|
@@ -71,10 +67,14 @@ def index():
|
|
| 71 |
.status { padding: 10px; border-radius: 5px; margin: 20px 0; }
|
| 72 |
.online { background: #d4edda; color: #155724; }
|
| 73 |
.loading { background: #fff3cd; color: #856404; }
|
|
|
|
| 74 |
</style>
|
| 75 |
</head>
|
| 76 |
<body>
|
| 77 |
-
<h1>🤖 OpenGSSTEC AI API -
|
|
|
|
|
|
|
|
|
|
| 78 |
<div class="status """ + ("online" if model else "loading") + """">
|
| 79 |
Status: """ + ("✅ Online and Ready" if model else "⏳ Loading Model...") + """
|
| 80 |
</div>
|
|
@@ -84,31 +84,21 @@ def index():
|
|
| 84 |
<div class="endpoint">
|
| 85 |
<h3>GET /health</h3>
|
| 86 |
<p>Check API health and model status</p>
|
| 87 |
-
<code>curl https://gsstec-open.hf.space/health</code>
|
| 88 |
</div>
|
| 89 |
|
| 90 |
<div class="endpoint">
|
| 91 |
<h3>POST /chat</h3>
|
| 92 |
<p>Send chat messages to the AI model</p>
|
| 93 |
-
<pre><code>curl -X POST https://gsstec-open.hf.space/chat \\
|
| 94 |
-
-H "Content-Type: application/json" \\
|
| 95 |
-
-d '{
|
| 96 |
-
"messages": [
|
| 97 |
-
{"role": "user", "content": "Hello!"}
|
| 98 |
-
]
|
| 99 |
-
}'</code></pre>
|
| 100 |
</div>
|
| 101 |
|
| 102 |
<h2>Model Information</h2>
|
| 103 |
<ul>
|
| 104 |
-
<li><strong>Model:</strong>
|
| 105 |
<li><strong>Repository:</strong> """ + MODEL_REPO + """</li>
|
| 106 |
-
<li><strong>Parameters:</strong>
|
| 107 |
-
<li><strong>Context Length:</strong>
|
| 108 |
-
<li><strong>
|
| 109 |
</ul>
|
| 110 |
-
|
| 111 |
-
<p><a href="/health">Check Health Status →</a></p>
|
| 112 |
</body>
|
| 113 |
</html>
|
| 114 |
"""
|
|
@@ -120,102 +110,46 @@ def health():
|
|
| 120 |
"status": "online" if model else "loading",
|
| 121 |
"repo": MODEL_REPO,
|
| 122 |
"file": MODEL_FILE,
|
| 123 |
-
"
|
| 124 |
"error": loading_error
|
| 125 |
})
|
| 126 |
|
| 127 |
@app.route('/chat', methods=['POST'])
|
| 128 |
def chat():
|
| 129 |
-
print("=" * 60)
|
| 130 |
-
print("📨 Received /chat request")
|
| 131 |
-
|
| 132 |
if not model:
|
| 133 |
-
print("❌ Model not loaded yet")
|
| 134 |
return jsonify({"error": "Model still loading"}), 503
|
| 135 |
|
| 136 |
try:
|
| 137 |
-
# Log raw request
|
| 138 |
-
print(f"📦 Content-Type: {request.content_type}")
|
| 139 |
-
print(f"📦 Raw data (first 500 bytes): {request.data[:500]}")
|
| 140 |
-
|
| 141 |
data = request.json
|
| 142 |
-
print(f"📊 Parsed JSON keys: {list(data.keys()) if data else 'None'}")
|
| 143 |
-
|
| 144 |
messages = data.get('messages', [])
|
| 145 |
-
print(f"💬 Message count: {len(messages)}")
|
| 146 |
-
|
| 147 |
-
# Calculate approximate token count (rough estimate: 1 token ≈ 4 chars)
|
| 148 |
-
total_chars = sum(len(str(msg.get('content', ''))) for msg in messages)
|
| 149 |
-
estimated_tokens = total_chars // 4
|
| 150 |
-
print(f"📊 Estimated input tokens: {estimated_tokens}")
|
| 151 |
|
| 152 |
-
#
|
| 153 |
-
MAX_INPUT_TOKENS = 500 # Much smaller for 14B model stability
|
| 154 |
-
if estimated_tokens > MAX_INPUT_TOKENS:
|
| 155 |
-
print(f"⚠️ Input too large, truncating conversation history...")
|
| 156 |
-
# Keep system message and recent messages only
|
| 157 |
-
system_msg = messages[0] if messages and messages[0].get('role') == 'system' else None
|
| 158 |
-
user_messages = [m for m in messages if m.get('role') != 'system']
|
| 159 |
-
|
| 160 |
-
# Keep only last message for maximum stability
|
| 161 |
-
keep_count = 1 # Only keep the most recent message
|
| 162 |
-
truncated_messages = []
|
| 163 |
-
if system_msg:
|
| 164 |
-
# Truncate system message if it's too long
|
| 165 |
-
system_content = system_msg.get('content', '')
|
| 166 |
-
if len(system_content) > 1000: # ~250 tokens
|
| 167 |
-
system_content = system_content[:1000] + "\n\n[System prompt truncated for context limit]"
|
| 168 |
-
print(f"⚠️ System prompt truncated from {len(system_msg.get('content', ''))} to {len(system_content)} chars")
|
| 169 |
-
truncated_messages.append({'role': 'system', 'content': system_content})
|
| 170 |
-
|
| 171 |
-
truncated_messages.extend(user_messages[-keep_count:])
|
| 172 |
-
messages = truncated_messages
|
| 173 |
-
print(f"✂️ Truncated to {len(messages)} messages")
|
| 174 |
-
|
| 175 |
-
# Format for Qwen2.5 Instruct (ChatML format)
|
| 176 |
prompt = ""
|
| 177 |
for msg in messages:
|
| 178 |
role = msg.get('role', 'user')
|
| 179 |
content = msg.get('content', '')
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
prompt = prompt[
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
max_tokens=200, # Much smaller for stability
|
| 201 |
-
temperature=0.3, # Lower temperature for more stable output
|
| 202 |
-
top_p=0.8,
|
| 203 |
-
stop=["<|im_end|>", "<|endoftext|>"],
|
| 204 |
-
echo=False
|
| 205 |
-
)
|
| 206 |
-
except Exception as model_error:
|
| 207 |
-
print(f"❌ Model inference failed: {model_error}")
|
| 208 |
-
# Return a fallback response
|
| 209 |
-
return jsonify({
|
| 210 |
-
"choices": [{
|
| 211 |
-
"message": {"role": "assistant", "content": "I apologize, but I'm experiencing technical difficulties. The model is currently unstable. Please try a shorter message or try again later."},
|
| 212 |
-
"finish_reason": "error"
|
| 213 |
-
}]
|
| 214 |
-
}), 503
|
| 215 |
|
| 216 |
-
print("✅ Model completed")
|
| 217 |
response_text = output["choices"][0]["text"].strip()
|
| 218 |
-
print(f"💬 Response length: {len(response_text)} chars")
|
| 219 |
|
| 220 |
return jsonify({
|
| 221 |
"choices": [{
|
|
@@ -224,11 +158,7 @@ def chat():
|
|
| 224 |
}]
|
| 225 |
})
|
| 226 |
except Exception as e:
|
| 227 |
-
|
| 228 |
-
print(f"❌ Type: {type(e).__name__}")
|
| 229 |
-
import traceback
|
| 230 |
-
traceback.print_exc()
|
| 231 |
-
return jsonify({"error": str(e), "type": type(e).__name__}), 500
|
| 232 |
|
| 233 |
if __name__ == '__main__':
|
| 234 |
port = int(os.environ.get('PORT', 7860))
|
|
|
|
| 9 |
app = Flask(__name__)
|
| 10 |
CORS(app)
|
| 11 |
|
| 12 |
+
# Fallback to a smaller, more stable model if the 14B model fails
|
| 13 |
+
# This is a proven stable configuration
|
| 14 |
+
MODEL_REPO = "microsoft/Phi-3-mini-4k-instruct-gguf"
|
| 15 |
+
MODEL_FILE = "Phi-3-mini-4k-instruct-q4.gguf"
|
| 16 |
|
| 17 |
model = None
|
| 18 |
loading_error = None
|
|
|
|
| 23 |
# Use HF_TOKEN if your space is private
|
| 24 |
token = os.environ.get("HF_TOKEN")
|
| 25 |
|
| 26 |
+
print(f"📥 Downloading fallback model from: {MODEL_REPO}/{MODEL_FILE}...")
|
| 27 |
|
| 28 |
# Download from HuggingFace model repository
|
| 29 |
model_path = hf_hub_download(
|
|
|
|
| 35 |
print(f"✅ Model downloaded to: {model_path}")
|
| 36 |
print("🏗️ Initializing model engine (llama-cpp)...")
|
| 37 |
|
| 38 |
+
# Use very conservative settings for maximum stability
|
|
|
|
| 39 |
model = Llama(
|
| 40 |
model_path=model_path,
|
| 41 |
+
n_ctx=2048, # Phi-3 mini works well with 2K context
|
| 42 |
+
n_threads=2,
|
| 43 |
+
n_batch=128,
|
| 44 |
+
verbose=False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
)
|
| 46 |
+
print("✅ Fallback model loaded successfully!")
|
| 47 |
except Exception as e:
|
| 48 |
loading_error = str(e)
|
| 49 |
+
print(f"❌ Error loading fallback model: {e}")
|
| 50 |
|
| 51 |
# Start loading in background
|
| 52 |
threading.Thread(target=load_model, daemon=True).start()
|
|
|
|
| 58 |
<!DOCTYPE html>
|
| 59 |
<html>
|
| 60 |
<head>
|
| 61 |
+
<title>OpenGSSTEC AI API - Fallback Mode</title>
|
| 62 |
<style>
|
| 63 |
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
| 64 |
h1 { color: #333; }
|
|
|
|
| 67 |
.status { padding: 10px; border-radius: 5px; margin: 20px 0; }
|
| 68 |
.online { background: #d4edda; color: #155724; }
|
| 69 |
.loading { background: #fff3cd; color: #856404; }
|
| 70 |
+
.warning { background: #f8d7da; color: #721c24; }
|
| 71 |
</style>
|
| 72 |
</head>
|
| 73 |
<body>
|
| 74 |
+
<h1>🤖 OpenGSSTEC AI API - Fallback Mode</h1>
|
| 75 |
+
<div class="status warning">
|
| 76 |
+
⚠️ Running in fallback mode with Phi-3-mini due to 14B model instability
|
| 77 |
+
</div>
|
| 78 |
<div class="status """ + ("online" if model else "loading") + """">
|
| 79 |
Status: """ + ("✅ Online and Ready" if model else "⏳ Loading Model...") + """
|
| 80 |
</div>
|
|
|
|
| 84 |
<div class="endpoint">
|
| 85 |
<h3>GET /health</h3>
|
| 86 |
<p>Check API health and model status</p>
|
|
|
|
| 87 |
</div>
|
| 88 |
|
| 89 |
<div class="endpoint">
|
| 90 |
<h3>POST /chat</h3>
|
| 91 |
<p>Send chat messages to the AI model</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
</div>
|
| 93 |
|
| 94 |
<h2>Model Information</h2>
|
| 95 |
<ul>
|
| 96 |
+
<li><strong>Model:</strong> Phi-3-mini-4k-instruct (Q4)</li>
|
| 97 |
<li><strong>Repository:</strong> """ + MODEL_REPO + """</li>
|
| 98 |
+
<li><strong>Parameters:</strong> 3.8B</li>
|
| 99 |
+
<li><strong>Context Length:</strong> 2048 tokens</li>
|
| 100 |
+
<li><strong>Status:</strong> Fallback mode - stable and reliable</li>
|
| 101 |
</ul>
|
|
|
|
|
|
|
| 102 |
</body>
|
| 103 |
</html>
|
| 104 |
"""
|
|
|
|
| 110 |
"status": "online" if model else "loading",
|
| 111 |
"repo": MODEL_REPO,
|
| 112 |
"file": MODEL_FILE,
|
| 113 |
+
"mode": "fallback",
|
| 114 |
"error": loading_error
|
| 115 |
})
|
| 116 |
|
| 117 |
@app.route('/chat', methods=['POST'])
|
| 118 |
def chat():
|
|
|
|
|
|
|
|
|
|
| 119 |
if not model:
|
|
|
|
| 120 |
return jsonify({"error": "Model still loading"}), 503
|
| 121 |
|
| 122 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
data = request.json
|
|
|
|
|
|
|
| 124 |
messages = data.get('messages', [])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
# Simple prompt formatting for Phi-3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
prompt = ""
|
| 128 |
for msg in messages:
|
| 129 |
role = msg.get('role', 'user')
|
| 130 |
content = msg.get('content', '')
|
| 131 |
+
if role == 'system':
|
| 132 |
+
prompt += f"System: {content}\n"
|
| 133 |
+
elif role == 'user':
|
| 134 |
+
prompt += f"User: {content}\n"
|
| 135 |
+
elif role == 'assistant':
|
| 136 |
+
prompt += f"Assistant: {content}\n"
|
| 137 |
+
prompt += "Assistant: "
|
| 138 |
+
|
| 139 |
+
# Keep prompt reasonable
|
| 140 |
+
if len(prompt) > 1500:
|
| 141 |
+
prompt = prompt[-1500:]
|
| 142 |
+
|
| 143 |
+
output = model(
|
| 144 |
+
prompt,
|
| 145 |
+
max_tokens=300,
|
| 146 |
+
temperature=0.7,
|
| 147 |
+
top_p=0.9,
|
| 148 |
+
stop=["User:", "System:"],
|
| 149 |
+
echo=False
|
| 150 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
|
|
|
| 152 |
response_text = output["choices"][0]["text"].strip()
|
|
|
|
| 153 |
|
| 154 |
return jsonify({
|
| 155 |
"choices": [{
|
|
|
|
| 158 |
}]
|
| 159 |
})
|
| 160 |
except Exception as e:
|
| 161 |
+
return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
if __name__ == '__main__':
|
| 164 |
port = int(os.environ.get('PORT', 7860))
|