Spaces:
Sleeping
Sleeping
| import os | |
| import re | |
| from importlib import import_module, util | |
| from threading import Lock | |
| from typing import Any, cast | |
| import gradio as gr | |
| from huggingface_hub import hf_hub_download | |
| from llama_cpp import Llama | |
| # Prevent thread thrashing on standard/shared 2-vCPU hardware architectures | |
| # NOTE: We removed torch entirely! It's no longer needed for CPU GGUF inference. | |
| if util.find_spec("spaces"): | |
| spaces = import_module("spaces") | |
| else: | |
| class _SpacesFallback: | |
| def GPU(duration=60): | |
| def _decorator(func): | |
| return func | |
| return _decorator | |
| spaces = _SpacesFallback() | |
| HF_SECRET_TOKEN = os.environ.get("HF_TOKEN") | |
| PRIVATE_MODEL_ID = "rotextechnologiesuk/nuclear-sqep" | |
| GGUF_FILE = "rotex_nuclear_sqep_Q4_K_M.gguf" | |
| GPU_DECORATOR_DURATION = int(os.environ.get("GPU_DECORATOR_DURATION", "60")) | |
| MAX_CONTEXT_TURNS = int(os.environ.get("MAX_CONTEXT_TURNS", "8")) | |
| MAX_INPUT_CHARS = int(os.environ.get("MAX_INPUT_CHARS", "512")) | |
| DEFAULT_MAX_NEW_TOKENS = int(os.environ.get("DEFAULT_MAX_NEW_TOKENS", "768")) | |
| MAX_NEW_TOKENS_LIMIT = int(os.environ.get("MAX_NEW_TOKENS_LIMIT", "2048")) | |
| try: | |
| GRADIO_MAJOR_VERSION = int(gr.__version__.split(".")[0]) | |
| except (AttributeError, ValueError, IndexError): | |
| GRADIO_MAJOR_VERSION = 5 | |
| DEFAULT_SYSTEM_PROMPT = ( | |
| "You are a Nuclear SQEP specialist assistant. " | |
| "Provide precise, practical, and safety-conscious answers. " | |
| ) | |
| THINK_BLOCK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE) | |
| THINK_OPEN_RE = re.compile(r"<think>.*$", re.DOTALL | re.IGNORECASE) | |
| DETAILS_BLOCK_RE = re.compile(r"<details>.*?</details>", re.DOTALL | re.IGNORECASE) | |
| THINKING_PREFIX_RE = re.compile( | |
| r"^\s*(?:[^\w<\n]+\s*)?(?:thinking(?:\s+process)?\s*:?)", | |
| re.IGNORECASE, | |
| ) | |
| ASSISTANT_PREFIX_RE = re.compile(r"^\s*assistant\s*:\s*", re.IGNORECASE) | |
| print("Downloading GGUF file from Hugging Face Hub...") | |
| # Safely download the specific target GGUF binary from the repository | |
| model_path = hf_hub_download( | |
| repo_id=PRIVATE_MODEL_ID, | |
| filename=GGUF_FILE, | |
| token=HF_SECRET_TOKEN | |
| ) | |
| print("Loading GGUF model via lightning-fast native llama_cpp core...") | |
| # Initialize the high-speed C++ engine optimized to utilize exactly 2 CPU threads | |
| model = Llama( | |
| model_path=model_path, | |
| n_ctx=4096, # Set healthy context allocation window | |
| n_threads=2, # Match hardware cores exactly to maximize processing throughput | |
| n_batch=512, # Batch processing limits | |
| verbose=False # Keeps terminal logs clean | |
| ) | |
| MODEL_LOCK = Lock() | |
| def _remove_hidden_blocks(text): | |
| cleaned = DETAILS_BLOCK_RE.sub("", text or "") | |
| cleaned = THINK_BLOCK_RE.sub("", cleaned) | |
| cleaned = THINK_OPEN_RE.sub("", cleaned) | |
| return cleaned.strip() | |
| def _extract_visible_answer(raw_response): | |
| response_text = (raw_response or "").strip() | |
| hidden_parts = [] | |
| for match in re.finditer(r"<think>(.*?)</think>", response_text, re.DOTALL | re.IGNORECASE): | |
| candidate = match.group(1).strip() | |
| if candidate: | |
| hidden_parts.append(candidate) | |
| cleaned = THINK_BLOCK_RE.sub("", response_text) | |
| cleaned = THINK_OPEN_RE.sub("", cleaned).strip() | |
| if THINKING_PREFIX_RE.match(cleaned): | |
| parts = re.split(r"\n\s*\n", cleaned, maxsplit=1) | |
| if len(parts) == 2: | |
| hidden_candidate = THINKING_PREFIX_RE.sub("", parts[0]).strip() | |
| if hidden_candidate: | |
| hidden_parts.append(hidden_candidate) | |
| cleaned = parts[1].strip() | |
| else: | |
| lines = cleaned.splitlines() | |
| if lines: | |
| hidden_candidate = THINKING_PREFIX_RE.sub("", lines[0]).strip() | |
| if hidden_candidate: | |
| hidden_parts.append(hidden_candidate) | |
| cleaned = "\n".join(lines[1:]).strip() | |
| final_answer = ASSISTANT_PREFIX_RE.sub("", cleaned).strip() or "I am ready for your next request." | |
| hidden_trace = "\n\n".join(hidden_parts).strip() | |
| return final_answer, hidden_trace | |
| def _build_messages(message, history, system_prompt): | |
| prompt = (system_prompt or "").strip() or DEFAULT_SYSTEM_PROMPT | |
| messages = [{"role": "system", "content": prompt}] | |
| history_slice = (history or [])[-MAX_CONTEXT_TURNS:] | |
| if history_slice and isinstance(history_slice[0], dict): | |
| for item in history_slice: | |
| role = str(item.get("role", "")).strip().lower() | |
| content = "" if item.get("content") is None else str(item.get("content")).strip() | |
| if not role or not content: | |
| continue | |
| if role == "assistant": | |
| content = _remove_hidden_blocks(content) | |
| if role in {"user", "assistant", "system"}: | |
| messages.append({"role": role, "content": content}) | |
| else: | |
| for item in history_slice: | |
| if not isinstance(item, (list, tuple)) or len(item) < 2: | |
| continue | |
| user_content = "" if item[0] is None else str(item[0]).strip() | |
| assistant_content = "" if item[1] is None else str(item[1]).strip() | |
| if user_content: | |
| messages.append({"role": "user", "content": user_content}) | |
| if assistant_content: | |
| messages.append( | |
| { | |
| "role": "assistant", | |
| "content": _remove_hidden_blocks(assistant_content), | |
| } | |
| ) | |
| truncated_message = (message or "")[:MAX_INPUT_CHARS].strip() | |
| messages.append({"role": "user", "content": truncated_message}) | |
| return messages | |
| def generate_response(message, history, system_prompt, temperature, top_p, max_new_tokens): | |
| user_message = (message or "")[:MAX_INPUT_CHARS].strip() | |
| if not user_message: | |
| return "", "" | |
| normalized_temperature = max(0.0, min(float(temperature), 1.0)) | |
| normalized_top_p = max(0.1, min(float(top_p), 1.0)) | |
| bounded_max_new_tokens = max(32, min(int(max_new_tokens), MAX_NEW_TOKENS_LIMIT)) | |
| with MODEL_LOCK: | |
| try: | |
| messages = _build_messages(user_message, history, system_prompt) | |
| do_sample = normalized_temperature > 0.0 | |
| # Execute ultra-fast, native C++ chat inference via llama_cpp | |
| response_obj = model.create_chat_completion( | |
| messages=messages, | |
| max_tokens=bounded_max_new_tokens, | |
| temperature=normalized_temperature if do_sample else 0.0, | |
| top_p=normalized_top_p, | |
| repeat_penalty=1.03, | |
| ) | |
| raw_response = response_obj["choices"][0]["message"]["content"].strip() | |
| return _extract_visible_answer(raw_response) | |
| except Exception as exc: | |
| print(f"Inference Engine Exception encountered: {str(exc)}") | |
| return "An engine error occurred while processing this request. Please clear and try again.", "" | |
| def respond(message, history, system_prompt, temperature, top_p, max_new_tokens): | |
| history = history or [] | |
| user_message = (message or "")[:MAX_INPUT_CHARS].strip() | |
| if not user_message: | |
| return history, "" | |
| final_answer, _hidden_trace = generate_response( | |
| user_message, | |
| history, | |
| system_prompt, | |
| temperature, | |
| top_p, | |
| max_new_tokens, | |
| ) | |
| normalized_history = [] | |
| if history and isinstance(history[0], dict): | |
| for item in history: | |
| role = str(item.get("role", "")).strip().lower() | |
| content = "" if item.get("content") is None else str(item.get("content")).strip() | |
| if role in {"user", "assistant"} and content: | |
| normalized_history.append({"role": role, "content": content}) | |
| else: | |
| for item in history: | |
| if not isinstance(item, (list, tuple)) or len(item) < 2: | |
| continue | |
| user_turn = "" if item[0] is None else str(item[0]).strip() | |
| assistant_turn = "" if item[1] is None else str(item[1]).strip() | |
| if user_turn: | |
| normalized_history.append({"role": "user", "content": user_turn}) | |
| if assistant_turn: | |
| normalized_history.append({"role": "assistant", "content": assistant_turn}) | |
| updated_history = normalized_history + [ | |
| {"role": "user", "content": user_message}, | |
| {"role": "assistant", "content": final_answer}, | |
| ] | |
| return updated_history, "" | |
| def clear_chat(): | |
| return [], "" | |
| APP_CSS = """:root { | |
| --lm-bg: #0c1117; | |
| --lm-panel: #121924; | |
| --lm-panel-2: #182232; | |
| --lm-border: #2a3548; | |
| --lm-text: #edf2fb; | |
| --lm-muted: #9fb0cb; | |
| --lm-accent: #58c5ff; | |
| --lm-accent-soft: #18384a; | |
| } | |
| .gradio-container { | |
| min-height: 100vh; | |
| color: var(--lm-text); | |
| background: | |
| radial-gradient(1200px 520px at 12% -20%, #1d334a 0%, transparent 55%), | |
| radial-gradient(980px 620px at 95% 120%, #21384e 0%, transparent 60%), | |
| var(--lm-bg); | |
| font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; | |
| } | |
| #lm-shell-header { | |
| margin-bottom: 14px; | |
| padding: 18px 20px; | |
| border: 1px solid var(--lm-border); | |
| border-radius: 14px; | |
| background: linear-gradient(140deg, rgba(16, 24, 37, 0.92), rgba(20, 32, 48, 0.92)); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| backdrop-filter: blur(6px); | |
| } | |
| .lm-title { | |
| font-size: 1.05rem; | |
| font-weight: 700; | |
| letter-spacing: 0.04em; | |
| text-transform: uppercase; | |
| } | |
| .lm-subtitle { | |
| margin-top: 4px; | |
| color: var(--lm-muted); | |
| font-size: 0.9rem; | |
| } | |
| .lm-badge { | |
| border: 1px solid #3d4f6b; | |
| border-radius: 999px; | |
| padding: 8px 14px; | |
| background: rgba(19, 33, 50, 0.8); | |
| color: #c9d8ef; | |
| font-family: "IBM Plex Mono", "Consolas", monospace; | |
| font-size: 0.75rem; | |
| } | |
| #lm-shell-body { | |
| gap: 14px; | |
| } | |
| #lm-sidebar { | |
| border: 1px solid var(--lm-border); | |
| border-radius: 14px; | |
| padding: 16px; | |
| background: linear-gradient(180deg, rgba(18, 26, 38, 0.95), rgba(14, 20, 30, 0.95)); | |
| } | |
| #lm-main { | |
| border: 1px solid var(--lm-border); | |
| border-radius: 14px; | |
| padding: 16px; | |
| background: linear-gradient(180deg, rgba(17, 23, 34, 0.95), rgba(14, 19, 28, 0.95)); | |
| } | |
| #lm-chat-window { | |
| border: 1px solid var(--lm-border); | |
| border-radius: 12px; | |
| background: radial-gradient(circle at 50% -40%, rgba(32, 54, 74, 0.5), rgba(16, 22, 32, 0.96)); | |
| } | |
| #lm-chat-window .message.user { | |
| background: #1f3046; | |
| border: 1px solid #3a4e68;} | |
| #lm-chat-window .message.bot { | |
| background: #16202d; | |
| border: 1px solid #2c3b52;} | |
| #lm-input-row { | |
| margin-top: 12px; | |
| align-items: stretch; | |
| gap: 10px;} | |
| #lm-input-box textarea { | |
| border: 1px solid var(--lm-border); | |
| border-radius: 10px; | |
| background: rgba(15, 22, 32, 0.9); | |
| color: var(--lm-text); | |
| font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;} | |
| #lm-send-button button,.lm-primary-btn button { | |
| border: 1px solid #4a7b96; | |
| border-radius: 10px; | |
| background: linear-gradient(120deg, var(--lm-accent-soft), #21465f); | |
| color: #eaf6ff; | |
| font-weight: 600;} | |
| #lm-send-button button:hover,.lm-primary-btn button:hover { | |
| border-color: var(--lm-accent); | |
| box-shadow: 0 0 0 1px rgba(88, 197, 255, 0.35) inset;} | |
| #lm-sidebar .gr-markdown,#lm-sidebar .gradio-markdown { | |
| color: var(--lm-text);} | |
| #lm-sidebar .gr-markdown p,#lm-sidebar .gradio-markdown p { | |
| color: var(--lm-muted);} | |
| @keyframes rise-in { | |
| from { | |
| opacity: 0; | |
| transform: translateY(8px); | |
| } to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| }}#lm-shell-header,#lm-sidebar,#lm-main { | |
| animation: rise-in 0.45s ease both;} | |
| #lm-main { | |
| animation-delay: 0.08s;} | |
| @media (max-width: 960px) { | |
| #lm-shell-header { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } .lm-badge { | |
| width: 100%; | |
| text-align: center; | |
| }}""" | |
| blocks_kwargs = { | |
| "title": "Rotex LLM Nuclear SQEP Shell", | |
| "fill_height": True, | |
| } | |
| if GRADIO_MAJOR_VERSION < 6: | |
| blocks_kwargs["css"] = APP_CSS | |
| with gr.Blocks(**blocks_kwargs) as demo: | |
| gr.HTML( | |
| """ | |
| <div id="lm-shell-header"> | |
| <div> | |
| <div class="lm-title">Rotex Nuclear SQEP Assistant</div> | |
| <div class="lm-subtitle">LLM model for Nuclear SQEP response.</div> | |
| </div> | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(elem_id="lm-shell-body"): | |
| with gr.Column(scale=1, min_width=310, elem_id="lm-sidebar"): | |
| gr.Markdown("## Session Controls") | |
| clear_button = gr.Button("New Chat", elem_classes=["lm-primary-btn"]) | |
| system_prompt = gr.Textbox( | |
| label="System prompt", | |
| value=DEFAULT_SYSTEM_PROMPT, | |
| lines=7, | |
| ) | |
| gr.Markdown("## Generation Settings") | |
| temperature = gr.Slider( | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=0.6, | |
| step=0.05, | |
| label="Temperature", | |
| ) | |
| top_p = gr.Slider( | |
| minimum=0.1, | |
| maximum=1.0, | |
| value=0.95, | |
| step=0.01, | |
| label="Top-p", | |
| ) | |
| max_new_tokens = gr.Slider( | |
| minimum=64, | |
| maximum=MAX_NEW_TOKENS_LIMIT, | |
| value=DEFAULT_MAX_NEW_TOKENS, | |
| step=32, | |
| label="Max new tokens", | |
| ) | |
| with gr.Column(scale=3, elem_id="lm-main"): | |
| chatbot = gr.Chatbot( | |
| label="", | |
| height=640, | |
| elem_id="lm-chat-window", | |
| ) | |
| with gr.Row(elem_id="lm-input-row"): | |
| user_input = gr.Textbox( | |
| show_label=False, | |
| placeholder="Message the assistant...", | |
| lines=3, | |
| max_lines=8, | |
| elem_id="lm-input-box", | |
| ) | |
| send_button = gr.Button("Send", variant="primary", elem_id="lm-send-button", min_width=60, scale=0) | |
| send_inputs = [ | |
| user_input, | |
| chatbot, | |
| system_prompt, | |
| temperature, | |
| top_p, | |
| max_new_tokens, | |
| ] | |
| send_button.click( | |
| fn=respond, | |
| inputs=send_inputs, | |
| outputs=[chatbot, user_input], | |
| ) | |
| user_input.submit( | |
| fn=respond, | |
| inputs=send_inputs, | |
| outputs=[chatbot, user_input], | |
| ) | |
| clear_button.click( | |
| fn=clear_chat, | |
| outputs=[chatbot, user_input], | |
| queue=False, | |
| ) | |
| if __name__ == "__main__": | |
| queued_demo = demo.queue(default_concurrency_limit=1) | |
| if GRADIO_MAJOR_VERSION >= 6: | |
| cast(Any, queued_demo).launch(css=APP_CSS) | |
| else: | |
| queued_demo.launch() |