Spaces:
Running
Running
| from flask import Flask, request, render_template_string, Response, stream_with_context | |
| import os | |
| import time | |
| import json | |
| import subprocess | |
| import atexit | |
| import requests | |
| from huggingface_hub import hf_hub_download | |
| app = Flask(__name__) | |
| # --- ১. আসল ১-বিট Bonsai চালানোর জন্য PrismML-এর llama.cpp ফর্ক বিল্ড করা --- | |
| # stock llama-cpp-python-এ Q1_0-এর দ্রুত কার্নেল নেই, তাই আসল স্পিড পেতে | |
| # PrismML-এর নিজস্ব ফর্ক সোর্স থেকে বিল্ড করতে হয়। HF Spaces-এ pip install | |
| # দিয়ে এটা সম্ভব না, তাই অ্যাপ স্টার্টআপের সময় নিজে থেকে git clone + cmake | |
| # build করা হচ্ছে (একবারই হবে, বিল্ড হয়ে গেলে পরের রিস্টার্টে স্কিপ হবে)। | |
| LLAMA_CPP_DIR = os.path.join(os.getcwd(), "llama.cpp") | |
| LLAMA_SERVER_BIN = os.path.join(LLAMA_CPP_DIR, "build", "bin", "llama-server") | |
| LLAMA_SERVER_PORT = 8081 | |
| LLAMA_SERVER_URL = f"http://127.0.0.1:{LLAMA_SERVER_PORT}" | |
| MODEL_REPO = "prism-ml/Bonsai-1.7B-gguf" | |
| MODEL_FILE = "Bonsai-1.7B-Q1_0.gguf" | |
| server_process = None | |
| llm_ready = False | |
| def build_llama_cpp_fork(): | |
| """PrismML-এর llama.cpp ফর্ক ক্লোন ও বিল্ড করে (Q1_0 কার্নেলসহ)।""" | |
| if os.path.exists(LLAMA_SERVER_BIN): | |
| print("✅ llama-server আগে থেকেই বিল্ড করা আছে, বিল্ড স্কিপ করা হলো") | |
| return | |
| print("⏳ PrismML-এর llama.cpp ফর্ক ক্লোন করা হচ্ছে...") | |
| subprocess.run( | |
| ["git", "clone", "--depth", "1", | |
| "https://github.com/PrismML-Eng/llama.cpp", LLAMA_CPP_DIR], | |
| check=True | |
| ) | |
| print("⏳ CMake কনফিগার করা হচ্ছে...") | |
| subprocess.run(["cmake", "-B", "build"], cwd=LLAMA_CPP_DIR, check=True) | |
| cpu_count = os.cpu_count() or 2 | |
| print(f"⏳ বিল্ড হচ্ছে ({cpu_count} থ্রেড দিয়ে)... এতে কয়েক মিনিট লাগতে পারে") | |
| subprocess.run( | |
| ["cmake", "--build", "build", "-j", str(cpu_count), "--target", "llama-server"], | |
| cwd=LLAMA_CPP_DIR, check=True | |
| ) | |
| print("✅ বিল্ড সম্পূর্ণ!") | |
| def start_llama_server(model_path): | |
| """বিল্ড হওয়া llama-server বাইনারি ব্যাকগ্রাউন্ডে চালু করে।""" | |
| global server_process | |
| cpu_count = os.cpu_count() or 2 | |
| print("⏳ llama-server চালু করা হচ্ছে...") | |
| server_process = subprocess.Popen([ | |
| LLAMA_SERVER_BIN, | |
| "-m", model_path, | |
| "--host", "127.0.0.1", | |
| "--port", str(LLAMA_SERVER_PORT), | |
| "-t", str(cpu_count), | |
| "-c", "2048" | |
| ]) | |
| atexit.register(server_process.terminate) | |
| # সার্ভার রেডি হওয়া পর্যন্ত অপেক্ষা (হেলথ চেক) | |
| for _ in range(90): | |
| try: | |
| r = requests.get(f"{LLAMA_SERVER_URL}/health", timeout=2) | |
| if r.status_code == 200: | |
| print("✅ llama-server প্রস্তুত!") | |
| return True | |
| except Exception: | |
| pass | |
| time.sleep(2) | |
| print("⚠️ নির্ধারিত সময়ে llama-server রেডি হয়নি") | |
| return False | |
| try: | |
| print("⏳ মডেল ডাউনলোড হচ্ছে...") | |
| model_path = hf_hub_download( | |
| repo_id=MODEL_REPO, | |
| filename=MODEL_FILE, | |
| local_dir="./models", | |
| token=None | |
| ) | |
| print(f"✅ মডেল ডাউনলোড সম্পূর্ণ: {model_path}") | |
| build_llama_cpp_fork() | |
| llm_ready = start_llama_server(model_path) | |
| except Exception as e: | |
| print(f"⚠️ সেটআপে সমস্যা হয়েছে: {e}") | |
| llm_ready = False | |
| # --- ২. HTML টেমপ্লেট --- | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Bonsai 8B চ্যাট (Hugging Face Space)</title> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <style> | |
| * { box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, sans-serif; | |
| max-width: 900px; | |
| margin: 20px auto; | |
| padding: 15px; | |
| background: #f0f2f5; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 16px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| overflow: hidden; | |
| padding: 20px; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 15px 20px; | |
| margin: -20px -20px 20px -20px; | |
| border-radius: 16px 16px 0 0; | |
| } | |
| .header h2 { margin: 0; font-weight: 400; } | |
| .header small { opacity: 0.8; font-size: 14px; } | |
| .chat-box { | |
| background: #f8f9fa; | |
| border-radius: 12px; | |
| padding: 15px; | |
| height: 400px; | |
| overflow-y: auto; | |
| margin-bottom: 15px; | |
| border: 1px solid #e0e0e0; | |
| } | |
| .message { | |
| margin: 8px 0; | |
| padding: 10px 15px; | |
| border-radius: 18px; | |
| max-width: 80%; | |
| word-wrap: break-word; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .user-msg { | |
| background: #007bff; | |
| color: white; | |
| margin-left: auto; | |
| text-align: right; | |
| border-bottom-right-radius: 4px; | |
| } | |
| .bot-msg { | |
| background: #e9ecef; | |
| color: #333; | |
| margin-right: auto; | |
| border-bottom-left-radius: 4px; | |
| white-space: pre-wrap; | |
| } | |
| .bot-msg.thinking { | |
| background: #e9ecef; | |
| color: #666; | |
| font-style: italic; | |
| } | |
| .time-info { | |
| font-size: 11px; | |
| color: #a0aec0; | |
| margin: -6px 4px 10px 4px; | |
| text-align: left; | |
| } | |
| .input-area { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| #userInput { | |
| flex: 1; | |
| padding: 12px 18px; | |
| border: 2px solid #ddd; | |
| border-radius: 25px; | |
| font-size: 15px; | |
| outline: none; | |
| transition: 0.2s; | |
| } | |
| #userInput:focus { | |
| border-color: #667eea; | |
| } | |
| button { | |
| padding: 12px 28px; | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| border-radius: 25px; | |
| font-size: 15px; | |
| cursor: pointer; | |
| transition: 0.2s; | |
| white-space: nowrap; | |
| } | |
| button:hover { | |
| background: #5a67d8; | |
| transform: scale(1.02); | |
| } | |
| button:disabled { | |
| background: #a0aec0; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .status { | |
| color: #718096; | |
| font-size: 13px; | |
| margin-top: 10px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .loader { | |
| display: inline-block; | |
| width: 14px; | |
| height: 14px; | |
| border: 2px solid #e2e8f0; | |
| border-top: 2px solid #667eea; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .clear-btn { | |
| background: #e53e3e; | |
| padding: 8px 16px; | |
| font-size: 13px; | |
| } | |
| .clear-btn:hover { | |
| background: #c53030; | |
| } | |
| .footer { | |
| margin-top: 15px; | |
| text-align: center; | |
| color: #a0aec0; | |
| font-size: 12px; | |
| } | |
| .settings-btn { | |
| background: #718096; | |
| padding: 12px 18px; | |
| } | |
| .settings-btn:hover { | |
| background: #5a6373; | |
| } | |
| .settings-overlay { | |
| display: none; | |
| position: fixed; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| background: rgba(0,0,0,0.5); | |
| z-index: 1000; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 20px; | |
| } | |
| .settings-panel { | |
| background: white; | |
| border-radius: 16px; | |
| padding: 25px; | |
| width: 100%; | |
| max-width: 420px; | |
| max-height: 85vh; | |
| overflow-y: auto; | |
| } | |
| .settings-panel h3 { | |
| margin-top: 0; | |
| margin-bottom: 20px; | |
| } | |
| .settings-panel label { | |
| display: block; | |
| font-weight: 600; | |
| margin: 16px 0 8px; | |
| color: #2d3748; | |
| } | |
| .settings-panel .row-label { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .settings-panel .row-label span.value { | |
| color: #2f855a; | |
| font-weight: 700; | |
| } | |
| .settings-panel input[type="number"] { | |
| width: 100%; | |
| padding: 10px 14px; | |
| border: 2px solid #ddd; | |
| border-radius: 10px; | |
| font-size: 15px; | |
| box-sizing: border-box; | |
| } | |
| .settings-panel input[type="range"] { | |
| width: 100%; | |
| } | |
| .settings-panel select { | |
| width: 100%; | |
| padding: 10px 14px; | |
| border: 2px solid #ddd; | |
| border-radius: 10px; | |
| font-size: 15px; | |
| background: #f7f7f7; | |
| } | |
| .settings-buttons { | |
| display: flex; | |
| gap: 10px; | |
| margin-top: 25px; | |
| } | |
| .settings-buttons button { | |
| flex: 1; | |
| padding: 12px; | |
| font-size: 14px; | |
| } | |
| .btn-cancel { | |
| background: #e2e8f0 !important; | |
| color: #2d3748 !important; | |
| } | |
| .btn-cancel:hover { | |
| background: #cbd5e0 !important; | |
| } | |
| .btn-save { | |
| background: #2f855a !important; | |
| } | |
| .btn-save:hover { | |
| background: #276749 !important; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h2>🌳 Bonsai 8B (১-বিট) চ্যাট</h2> | |
| <small>Hugging Face Space • CPU Inference • মডেল সাইজ: ~১.১৫ GB (Q1_0, 1-bit)</small> | |
| </div> | |
| <div class="chat-box" id="chatBox"> | |
| <div class="message bot-msg">👋 হ্যালো! আমি Bonsai 8B। আপনি কী জানতে চান?</div> | |
| </div> | |
| <div class="input-area"> | |
| <input type="text" id="userInput" placeholder="এখানে প্রশ্ন লিখুন..." /> | |
| <button id="sendBtn">পাঠান</button> | |
| <button id="settingsBtn" class="settings-btn">⚙️</button> | |
| <button id="clearBtn" class="clear-btn">🗑️</button> | |
| </div> | |
| <div class="status" id="status"> | |
| <span>✅</span> প্রস্তুত | |
| </div> | |
| <div class="footer"> | |
| Powered by PrismML llama.cpp fork (llama-server) • Bonsai 1.7B (Q1_0, 1-bit) | |
| </div> | |
| </div> | |
| <!-- সেটিংস প্যানেল --> | |
| <div class="settings-overlay" id="settingsOverlay"> | |
| <div class="settings-panel"> | |
| <h3>⚙️ প্যারামিটার সেটিংস</h3> | |
| <label class="row-label">🌡️ Temperature <span class="value" id="tempValue">0.1</span></label> | |
| <input type="range" id="tempSlider" min="0" max="1.5" step="0.1" value="0.1"> | |
| <label>📝 Max Tokens</label> | |
| <input type="number" id="maxTokensInput" value="64"> | |
| <label>🎯 Top P</label> | |
| <input type="number" step="0.1" min="0" max="1" id="topPInput" value="0.7"> | |
| <label>⬆️ Top K</label> | |
| <input type="number" id="topKInput" value="20"> | |
| <label>🔁 Repetition Penalty</label> | |
| <input type="number" step="0.1" id="repPenaltyInput" value="1.4"> | |
| <label>🎲 Do Sample</label> | |
| <select id="doSampleSelect"> | |
| <option value="false" selected>False</option> | |
| <option value="true">True</option> | |
| </select> | |
| <label>🧠 থিংক মোড (Think Mode)</label> | |
| <select id="thinkModeSelect"> | |
| <option value="false" selected>বন্ধ (দ্রুত রেসপন্স)</option> | |
| <option value="true">চালু (ধীর, কিন্তু বেশি চিন্তা করে উত্তর দেয়)</option> | |
| </select> | |
| <label>⚡ Use Cache</label> | |
| <select id="useCacheSelect"> | |
| <option value="true" selected>True</option> | |
| <option value="false">False</option> | |
| </select> | |
| <label>🌊 Stream</label> | |
| <select id="streamSelect"> | |
| <option value="true" selected>True (স্ট্রিমিং)</option> | |
| <option value="false">False</option> | |
| </select> | |
| <div class="settings-buttons"> | |
| <button id="settingsCancelBtn" class="btn-cancel">বাতিল</button> | |
| <button id="settingsSaveBtn" class="btn-save">সংরক্ষণ করুন</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const chatBox = document.getElementById('chatBox'); | |
| const input = document.getElementById('userInput'); | |
| const sendBtn = document.getElementById('sendBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const status = document.getElementById('status'); | |
| // --- সেটিংস প্যানেল --- | |
| const settingsBtn = document.getElementById('settingsBtn'); | |
| const settingsOverlay = document.getElementById('settingsOverlay'); | |
| const settingsCancelBtn = document.getElementById('settingsCancelBtn'); | |
| const settingsSaveBtn = document.getElementById('settingsSaveBtn'); | |
| const tempSlider = document.getElementById('tempSlider'); | |
| const tempValue = document.getElementById('tempValue'); | |
| let settings = { | |
| temperature: 0.1, | |
| max_tokens: 64, | |
| top_p: 0.7, | |
| top_k: 20, | |
| repeat_penalty: 1.4, | |
| do_sample: false, | |
| think_mode: false, | |
| use_cache: true, | |
| stream: true | |
| }; | |
| function openSettings() { | |
| tempSlider.value = settings.temperature; | |
| tempValue.textContent = settings.temperature; | |
| document.getElementById('maxTokensInput').value = settings.max_tokens; | |
| document.getElementById('topPInput').value = settings.top_p; | |
| document.getElementById('topKInput').value = settings.top_k; | |
| document.getElementById('repPenaltyInput').value = settings.repeat_penalty; | |
| document.getElementById('doSampleSelect').value = String(settings.do_sample); | |
| document.getElementById('thinkModeSelect').value = String(settings.think_mode); | |
| document.getElementById('useCacheSelect').value = String(settings.use_cache); | |
| document.getElementById('streamSelect').value = String(settings.stream); | |
| settingsOverlay.style.display = 'flex'; | |
| } | |
| function closeSettings() { | |
| settingsOverlay.style.display = 'none'; | |
| } | |
| function saveSettings() { | |
| settings.temperature = parseFloat(tempSlider.value); | |
| settings.max_tokens = parseInt(document.getElementById('maxTokensInput').value, 10); | |
| settings.top_p = parseFloat(document.getElementById('topPInput').value); | |
| settings.top_k = parseInt(document.getElementById('topKInput').value, 10); | |
| settings.repeat_penalty = parseFloat(document.getElementById('repPenaltyInput').value); | |
| settings.do_sample = document.getElementById('doSampleSelect').value === 'true'; | |
| settings.think_mode = document.getElementById('thinkModeSelect').value === 'true'; | |
| settings.use_cache = document.getElementById('useCacheSelect').value === 'true'; | |
| settings.stream = document.getElementById('streamSelect').value === 'true'; | |
| closeSettings(); | |
| } | |
| settingsBtn.addEventListener('click', openSettings); | |
| settingsCancelBtn.addEventListener('click', closeSettings); | |
| settingsSaveBtn.addEventListener('click', saveSettings); | |
| tempSlider.addEventListener('input', () => { tempValue.textContent = tempSlider.value; }); | |
| function addMessage(text, isUser = false, isThinking = false) { | |
| const div = document.createElement('div'); | |
| div.className = `message ${isUser ? 'user-msg' : 'bot-msg'}${isThinking ? ' thinking' : ''}`; | |
| div.textContent = text; | |
| chatBox.appendChild(div); | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| return div; | |
| } | |
| async function sendMessage() { | |
| const text = input.value.trim(); | |
| if (!text) return; | |
| addMessage(text, true); | |
| input.value = ''; | |
| sendBtn.disabled = true; | |
| status.innerHTML = '<span class="loader"></span> চিন্তা করছি...'; | |
| // Thinking indicator | |
| const thinkingDiv = addMessage('⏳ চিন্তা করছি...', false, true); | |
| // সেন্ড বাটনে ক্লিক করার মুহূর্ত থেকেই সময় গোনা শুরু | |
| const clickTime = performance.now(); | |
| let firstTokenTime = null; | |
| try { | |
| const response = await fetch('/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ prompt: text, ...settings }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error('সার্ভার এরর'); | |
| } | |
| thinkingDiv.remove(); | |
| const botDiv = addMessage('', false); | |
| if (settings.stream && response.body) { | |
| // --- স্ট্রিমিং মোড: টোকেন আসার সাথে সাথে দেখানো হয় --- | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let fullText = ''; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| const chunkText = decoder.decode(value, { stream: true }); | |
| if (chunkText) { | |
| if (firstTokenTime === null) { | |
| firstTokenTime = performance.now(); // প্রথম টোকেন আসার মুহূর্ত | |
| } | |
| fullText += chunkText; | |
| botDiv.textContent = fullText; | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| } | |
| } | |
| } else { | |
| // --- নন-স্ট্রিমিং মোড: পুরো রেসপন্স একসাথে আসে --- | |
| const data = await response.json(); | |
| firstTokenTime = performance.now(); | |
| botDiv.textContent = data.error ? `⚠️ ${data.error}` : data.response; | |
| } | |
| const endTime = performance.now(); | |
| const timeToFirst = ((firstTokenTime ?? endTime) - clickTime) / 1000; | |
| const totalTime = (endTime - clickTime) / 1000; | |
| // প্রতিটা রেসপন্সের নিচে ছোট করে টাইমিং দেখানো হচ্ছে | |
| const timeDiv = document.createElement('div'); | |
| timeDiv.className = 'time-info'; | |
| timeDiv.textContent = | |
| `⏱️ রেসপন্স শুরু হতে: ${timeToFirst.toFixed(2)}s • সম্পূর্ণ হতে: ${totalTime.toFixed(2)}s`; | |
| chatBox.appendChild(timeDiv); | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| status.innerHTML = '✅ প্রস্তুত'; | |
| } catch (error) { | |
| thinkingDiv.remove(); | |
| addMessage('⚠️ সার্ভার ত্রুটি! আবার চেষ্টা করুন।', false); | |
| status.innerHTML = '❌ সংযোগ ত্রুটি'; | |
| } | |
| sendBtn.disabled = false; | |
| input.focus(); | |
| } | |
| function clearChat() { | |
| chatBox.innerHTML = ''; | |
| addMessage('🗑️ চ্যাট ক্লিয়ার করা হয়েছে।', false); | |
| addMessage('👋 নতুন করে শুরু করুন!', false); | |
| status.innerHTML = '✅ প্রস্তুত'; | |
| } | |
| sendBtn.addEventListener('click', sendMessage); | |
| clearBtn.addEventListener('click', clearChat); | |
| input.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') sendMessage(); | |
| }); | |
| // Auto-focus on page load | |
| input.focus(); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # --- ৩. Flask রাউট --- | |
| def index(): | |
| return render_template_string(HTML_TEMPLATE) | |
| def chat(): | |
| data = request.get_json() | |
| user_prompt = data.get('prompt', '') | |
| if not user_prompt: | |
| return {"error": "কোনো প্রশ্ন নেই"} | |
| if not llm_ready: | |
| return {"error": "মডেল/সার্ভার প্রস্তুত না। সার্ভার লগ চেক করুন।"} | |
| # --- ফ্রন্টএন্ড সেটিংস প্যানেল থেকে পাঠানো প্যারামিটার --- | |
| max_tokens = int(data.get('max_tokens', 64)) | |
| temperature = float(data.get('temperature', 0.1)) | |
| top_p = float(data.get('top_p', 0.7)) | |
| top_k = int(data.get('top_k', 20)) | |
| repeat_penalty = float(data.get('repeat_penalty', 1.4)) | |
| do_sample = bool(data.get('do_sample', False)) | |
| think_mode = bool(data.get('think_mode', False)) | |
| use_stream = bool(data.get('stream', True)) | |
| use_cache = bool(data.get('use_cache', True)) # এখন সত্যিই llama-server-এর prompt cache নিয়ন্ত্রণ করে | |
| # থিংক মোড বন্ধ থাকলে "/no_think" যোগ করা হচ্ছে | |
| think_suffix = "" if think_mode else " /no_think" | |
| full_prompt = f"<|im_start|>user\n{user_prompt}{think_suffix}<|im_end|>\n<|im_start|>assistant\n" | |
| payload = { | |
| "prompt": full_prompt, | |
| "n_predict": max_tokens, | |
| "temperature": temperature if do_sample else 0.0, | |
| "top_p": top_p, | |
| "top_k": top_k, | |
| "repeat_penalty": repeat_penalty, | |
| "cache_prompt": use_cache, | |
| "stream": use_stream, | |
| "stop": ["<|im_end|>", "user:", "User:"] | |
| } | |
| if use_stream: | |
| def generate(): | |
| try: | |
| with requests.post(f"{LLAMA_SERVER_URL}/completion", json=payload, stream=True, timeout=300) as r: | |
| for line in r.iter_lines(): | |
| if not line: | |
| continue | |
| line = line.decode('utf-8') | |
| if line.startswith("data: "): | |
| try: | |
| chunk = json.loads(line[len("data: "):]) | |
| except json.JSONDecodeError: | |
| continue | |
| content = chunk.get("content", "") | |
| if content: | |
| yield content | |
| if chunk.get("stop"): | |
| break | |
| except Exception as e: | |
| print(f"❌ জেনারেশন ত্রুটি: {e}") | |
| yield f"\n⚠️ মডেল ত্রুটি: {str(e)}" | |
| return Response(stream_with_context(generate()), mimetype='text/plain') | |
| else: | |
| try: | |
| r = requests.post(f"{LLAMA_SERVER_URL}/completion", json=payload, timeout=300) | |
| result = r.json() | |
| output = result.get("content", "").strip() | |
| return {"response": output} | |
| except Exception as e: | |
| print(f"❌ জেনারেশন ত্রুটি: {e}") | |
| return {"error": f"মডেল ত্রুটি: {str(e)}"} | |
| # --- ৪. সার্ভার চালানো --- | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860) | |