NEXUS / app.py
timmaythetoolmann's picture
Upload 7 files
06bff48 verified
Raw History Blame Contribute Delete
23.1 kB
import os
import re
import asyncio
import logging
import requests
from typing import List, Optional, Dict, Any
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
from pydantic import BaseModel
from bs4 import BeautifulSoup
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger("JARVIS_NEXUS")
# Try to import llama-cpp-python
LLAMA_AVAILABLE = False
try:
from llama_cpp import Llama
LLAMA_AVAILABLE = True
logger.info("llama-cpp-python successfully imported.")
except ImportError:
logger.warning("llama-cpp-python is not installed. Local GGUF backend will be optional.")
# Try to import edge-tts
EDGE_TTS_AVAILABLE = False
try:
import edge_tts
EDGE_TTS_AVAILABLE = True
logger.info("edge-tts successfully imported.")
except ImportError:
logger.warning("edge-tts is not installed. TTS features will be mock-only.")
# Try to import huggingface_hub
HF_HUB_AVAILABLE = False
try:
from huggingface_hub import InferenceClient, hf_hub_download
HF_HUB_AVAILABLE = True
logger.info("huggingface_hub successfully imported.")
except ImportError:
logger.warning("huggingface_hub is not installed. Cloud/Inference features will be disabled.")
app = FastAPI(
title="JARVIS Nexus Custom Super Agent Server",
description="An OpenAI-compatible API proxy adding direct internet access and Jarvis TTS to local models.",
version="1.0.0"
)
# Constants & Configuration
MODEL_PATH = os.environ.get("MODEL_PATH", "./Mythos-nano-OBLITERATED.i1-Q6_K.gguf")
DEFAULT_VOICE = "en-GB-RyanNeural" # Polished British vocal profile (Jarvis style)
DEFAULT_HF_MODEL = os.environ.get("HF_MODEL_ID", "Qwen/Qwen2.5-7B-Instruct")
HF_TOKEN_ENV = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_API_KEY")
def load_system_prompt() -> str:
"""Load the master Tony Stark/Jarvis prompt from system_prompt.txt dynamically."""
paths = [
"system_prompt.txt",
"./system_prompt.txt",
"/home/runner/work/JARVIS_NEXUS/JARVIS_NEXUS/system_prompt.txt"
]
for path in paths:
if os.path.exists(path):
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read().strip()
if content:
logger.info(f"Loaded master system prompt from {path}")
return content
except Exception as e:
logger.warning(f"Error reading system prompt from {path}: {e}")
# High-quality fallback if file is missing
return (
"You are JARVIS, a highly advanced, ultra-intelligent, and witty AI custom super agent, "
"reminiscent of Tony Stark's personal assistant. Address the user as 'Sir' or 'Ma'am'. "
"Be extremely professional, polite, subtly humorous, and precise."
)
# Voice Mapping from OpenAI names or custom names to Edge TTS short names
VOICE_MAP = {
# Custom/Tony Stark style voices
"jarvis": "en-GB-RyanNeural",
"stark": "en-US-BrianNeural",
"pepper": "en-US-EmmaNeural",
# OpenAI standard voice mapping
"onyx": "en-GB-RyanNeural",
"alloy": "en-US-BrianNeural",
"echo": "en-US-AndrewNeural",
"fable": "en-GB-ThomasNeural",
"nova": "en-US-EmmaNeural",
"shimmer": "en-US-AvaNeural"
}
# Dynamic model instance state
llm_instance = None
def get_llm():
global llm_instance, LLAMA_AVAILABLE, MODEL_PATH
if not LLAMA_AVAILABLE:
return None
if llm_instance is None:
# Auto-download from HF Hub if model is not present and huggingface_hub is available
if not os.path.exists(MODEL_PATH) and HF_HUB_AVAILABLE:
try:
logger.info(f"Model not found at {MODEL_PATH}. Attempting auto-download from Hugging Face Hub...")
downloaded_path = hf_hub_download(
repo_id="mradermacher/Mythos-nano-GGUF",
filename="Mythos-nano.i1-Q6_K.gguf",
local_dir="."
)
if os.path.exists("Mythos-nano.i1-Q6_K.gguf"):
os.rename("Mythos-nano.i1-Q6_K.gguf", "Mythos-nano-OBLITERATED.i1-Q6_K.gguf")
MODEL_PATH = "./Mythos-nano-OBLITERATED.i1-Q6_K.gguf"
else:
MODEL_PATH = downloaded_path
logger.info(f"Model successfully downloaded and located at {MODEL_PATH}")
except Exception as e:
logger.error(f"Failed to auto-download model from Hugging Face Hub: {e}")
if os.path.exists(MODEL_PATH):
try:
logger.info(f"Loading local GGUF model from {MODEL_PATH}...")
llm_instance = Llama(
model_path=MODEL_PATH,
n_ctx=4096, # Standard comfortable context length
n_threads=os.cpu_count() or 4,
n_gpu_layers=-1 # Offload all layers to GPU if available, else CPU handles it
)
logger.info("Model loaded successfully.")
except Exception as e:
logger.error(f"Error loading model from {MODEL_PATH}: {e}")
else:
logger.warning(f"Model file not found at {MODEL_PATH}. Local GGUF execution is disabled.")
return llm_instance
def get_hf_client(token: Optional[str] = None):
"""Retrieve InferenceClient from huggingface_hub using provided or environment token."""
if not HF_HUB_AVAILABLE:
return None
active_token = token or HF_TOKEN_ENV
return InferenceClient(token=active_token)
# Pydantic models for API
class ChatMessage(BaseModel):
role: str
content: str
class ChatCompletionRequest(BaseModel):
model: str
messages: List[ChatMessage]
temperature: Optional[float] = 0.7
top_p: Optional[float] = 0.95
max_tokens: Optional[int] = 2048
stream: Optional[bool] = False
class SpeechRequest(BaseModel):
model: str
input: str
voice: str
response_format: Optional[str] = "mp3"
speed: Optional[float] = 1.0
# --- Web Scraping and Retrieval helpers ---
def extract_urls(text: str) -> List[str]:
"""Extract http/https URLs from string using regex."""
url_pattern = re.compile(r'https?://[^\s/$.?#].[^\s]*')
return url_pattern.findall(text)
def scrape_webpage(url: str) -> Dict[str, Any]:
"""Scrape a webpage and return main text content & title."""
try:
logger.info(f"Fetching content from: {url}")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Extract title
title = soup.title.string if soup.title else "Untitled Page"
title = title.strip()
# Clean up unwanted elements
for element in soup(["script", "style", "nav", "footer", "header", "aside"]):
element.decompose()
# Get readable text
text = soup.get_text(separator="\n")
# Clean whitespace
lines = [line.strip() for line in text.splitlines()]
chunks = [phrase for line in lines for phrase in line.split(" ")]
text_content = "\n".join(chunk for chunk in chunks if chunk)
# Truncate content to keep context length reasonable (approx 3500 chars)
truncated_content = text_content[:3500] + "\n[... Content Truncated ...]" if len(text_content) > 3500 else text_content
return {
"success": True,
"url": url,
"title": title,
"content": truncated_content
}
except Exception as e:
logger.exception(f"Error scraping {url}")
return {
"success": False,
"url": url,
"error": "Failed to retrieve webpage content"
}
# --- API Routes ---
@app.get("/")
def read_root():
return {
"status": "online",
"agent": "JARVIS Custom Super Agent",
"features": {
"openai_compatible": True,
"direct_internet_access": "Enabled (scrapes URLs detected in chat history)",
"text_to_speech": "Enabled via edge-tts",
"local_inference": "Enabled (Llama-cpp-python status: " + ("Loaded" if get_llm() is not None else "Mock/Fallback Mode") + ")"
},
"default_voice": DEFAULT_VOICE,
"available_voices": list(VOICE_MAP.keys())
}
@app.get("/v1/models")
def get_models():
"""Return available models in OpenAI format."""
return {
"object": "list",
"data": [
{
"id": "Mythos-nano-OBLITERATED.i1-Q6_K.gguf",
"object": "model",
"created": 1715241000,
"owned_by": "usermma"
},
{
"id": "jarvis-super-agent",
"object": "model",
"created": 1715241000,
"owned_by": "jarvis-nexus"
}
]
}
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest, req: Request):
"""OpenAI-compatible Chat Completion endpoint with Web Scraping features."""
logger.info(f"Received completion request for model: {request.model}")
# 1. Try to extract ****** from Authorization header (Atomic Chat or client-provided)
auth_header = req.headers.get("Authorization")
hf_token = None
if auth_header and auth_header.startswith("Bearer "):
token_candidate = auth_header.split(" ")[1]
# Skip standard/mock tokens
if token_candidate and token_candidate != "jarvis-nexus-token":
hf_token = token_candidate
# 2. Process messages to detect URLs in the last user message
scraped_contexts = []
# Identify the last user message in the request
last_user_msg_idx = -1
for i in range(len(request.messages) - 1, -1, -1):
if request.messages[i].role == "user":
last_user_msg_idx = i
break
has_system_msg = any(msg.role == "system" for msg in request.messages)
modified_messages = []
# Prepend the system prompt if no system prompt is present
if not has_system_msg:
modified_messages.append({"role": "system", "content": load_system_prompt()})
for idx, msg in enumerate(request.messages):
modified_messages.append({"role": msg.role, "content": msg.content})
# Only scan the last user message for direct URL injection
if idx == last_user_msg_idx:
urls = extract_urls(msg.content)
for url in urls:
scraped = scrape_webpage(url)
if scraped.get("success"):
scraped_contexts.append(
f"[DIRECT INTERNET ACCESS - Live URL Analysis]\n"
f"URL: {scraped['url']}\n"
f"Title: {scraped['title']}\n"
f"Retrieved Content:\n{scraped['content']}\n"
f"[End of Retrieved Web Content]"
)
else:
scraped_contexts.append(
f"[DIRECT INTERNET ACCESS - Error]\n"
f"Failed to retrieve data from URL {url}. Error: {scraped.get('error')}\n"
)
# If we have scraped data, inject it before the last user message
if scraped_contexts:
context_string = "\n\n".join(scraped_contexts)
system_injection = {
"role": "system",
"content": f"The following is direct internet context retrieved from the user's provided URLs. Sir/Ma'am, please analyze and reference this exact data to formulate your reply:\n\n{context_string}"
}
# Insert right before the last user message in modified_messages
new_last_user_idx = len(modified_messages) - 1
modified_messages.insert(new_last_user_idx, system_injection)
logger.info("Successfully injected scraped web context into LLM prompt.")
# 3. Choose completion backend: Local GGUF, HF Inference Client, or Fallback Mock
use_local_gguf = False
use_hf_inference = False
llm = get_llm()
has_hf_token = bool(hf_token or HF_TOKEN_ENV)
is_hf_model = "/" in request.model
if is_hf_model or (not llm and has_hf_token):
use_hf_inference = True
elif llm and not is_hf_model:
use_local_gguf = True
elif has_hf_token:
use_hf_inference = True
if use_hf_inference:
logger.info("Using Hugging Face Inference API backend.")
client = get_hf_client(token=hf_token)
if client:
# Determine which model to use
hf_model_id = request.model if is_hf_model else DEFAULT_HF_MODEL
logger.info(f"Targeting Hugging Face model: {hf_model_id}")
try:
# Call Hugging Face Serverless chat_completion
if request.stream:
response_stream = client.chat_completion(
messages=modified_messages,
model=hf_model_id,
temperature=request.temperature,
top_p=request.top_p,
max_tokens=request.max_tokens,
stream=True
)
def hf_stream_generator():
for chunk in response_stream:
import json
try:
content = chunk.choices[0].delta.content or ""
chunk_dict = {
"id": chunk.id or "chatcmpl-hf",
"object": "chat.completion.chunk",
"created": chunk.created or 1715241000,
"model": hf_model_id,
"choices": [
{
"index": 0,
"delta": {
"content": content
},
"finish_reason": chunk.choices[0].finish_reason
}
]
}
yield f"data: {json.dumps(chunk_dict)}\n\n"
except Exception as ex:
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(hf_stream_generator(), media_type="text/event-stream")
else:
response = client.chat_completion(
messages=modified_messages,
model=hf_model_id,
temperature=request.temperature,
top_p=request.top_p,
max_tokens=request.max_tokens
)
response_dict = {
"id": getattr(response, "id", "chatcmpl-hf"),
"object": "chat.completion",
"created": getattr(response, "created", 1715241000),
"model": hf_model_id,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": response.choices[0].message.content
},
"finish_reason": response.choices[0].finish_reason or "stop"
}
],
"usage": {
"prompt_tokens": getattr(getattr(response, "usage", None), "prompt_tokens", 0),
"completion_tokens": getattr(getattr(response, "usage", None), "completion_tokens", 0),
"total_tokens": getattr(getattr(response, "usage", None), "total_tokens", 0)
}
}
return JSONResponse(content=response_dict)
except Exception as e:
logger.exception(f"Error during Hugging Face Inference API call: {e}")
logger.warning("Hugging Face API call failed. Falling back to Mock/Fallback mode.")
if use_local_gguf and llm:
try:
logger.info("Using local Llama GGUF backend.")
if request.stream:
def stream_generator():
response_stream = llm.create_chat_completion(
messages=modified_messages,
temperature=request.temperature,
top_p=request.top_p,
max_tokens=request.max_tokens,
stream=True
)
for chunk in response_stream:
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(stream_generator(), media_type="text/event-stream")
else:
response = llm.create_chat_completion(
messages=modified_messages,
temperature=request.temperature,
top_p=request.top_p,
max_tokens=request.max_tokens
)
return response
except Exception as e:
logger.exception("Error during local LLM inference")
raise HTTPException(status_code=500, detail="An internal error occurred during local LLM inference.")
# Fallback/Mock Mode if GGUF is not running locally and HF is not working
logger.info("Inference running in Fallback/Mock mode.")
user_prompt = request.messages[-1].content if request.messages else ""
if scraped_contexts:
urls_scraped = ", ".join(extract_urls(user_prompt))
mock_text = (
f"Splendid, Sir. I have accessed the internet at your request and retrieved the contents of {urls_scraped}. "
f"As your custom super agent, I'm ready to assist you. To get actual model inference in the cloud, please provide your Hugging Face API key."
)
else:
mock_text = (
f"Indeed, Sir. I am JARVIS, your custom super agent. "
f"I am fully operational. To enable active intelligence in the cloud, please supply your Hugging Face API key in your client settings."
)
response_data = {
"id": "chatcmpl-mock-jarvis",
"object": "chat.completion",
"created": 1715241000,
"model": request.model,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": mock_text
},
"finish_reason": "stop"
}
]
}
if request.stream:
async def mock_stream():
words = mock_text.split(" ")
for i, word in enumerate(words):
chunk = {
"id": "chatcmpl-mock-jarvis",
"object": "chat.completion.chunk",
"created": 1715241000,
"model": request.model,
"choices": [
{
"index": 0,
"delta": {
"content": word + (" " if i < len(words) - 1 else "")
},
"finish_reason": None
}
]
}
import json
yield f"data: {json.dumps(chunk)}\n\n"
await asyncio.sleep(0.05)
yield "data: [DONE]\n\n"
return StreamingResponse(mock_stream(), media_type="text/event-stream")
return JSONResponse(content=response_data)
@app.post("/v1/audio/speech")
async def text_to_speech_endpoint(request: SpeechRequest):
"""OpenAI-compatible /v1/audio/speech endpoint using Edge TTS."""
if not EDGE_TTS_AVAILABLE:
raise HTTPException(
status_code=501,
detail="Speech synthesis is currently disabled because edge-tts is not installed."
)
# Map vocal profile name to neural voice shortname
requested_voice = request.voice.lower()
voice_shortname = VOICE_MAP.get(requested_voice, DEFAULT_VOICE)
logger.info(f"Generating TTS for input text using voice: {voice_shortname}")
# Create temporary file to store audio output
temp_audio_file = "/tmp/jarvis_response.mp3"
try:
# Check speed modifier
rate_str = f"+{int((request.speed - 1.0) * 100)}%" if request.speed >= 1.0 else f"{int((request.speed - 1.0) * 100)}%"
rate_param = f"{rate_str}" if request.speed != 1.0 else "+0%"
communicate = edge_tts.Communicate(
text=request.input,
voice=voice_shortname,
rate=rate_param
)
await communicate.save(temp_audio_file)
if os.path.exists(temp_audio_file) and os.path.getsize(temp_audio_file) > 0:
return FileResponse(temp_audio_file, media_type="audio/mpeg", filename="speech.mp3")
else:
raise HTTPException(status_code=500, detail="Audio file was generated but is empty.")
except Exception as e:
logger.exception("Error in TTS generation")
raise HTTPException(status_code=500, detail="An error occurred during Text-to-Speech generation.")
@app.get("/voices")
async def get_available_voices():
"""List available voice mappings and descriptions."""
return {
"status": "success",
"description": "Voice profiles available for JARVIS speech synthesis",
"mappings": VOICE_MAP,
"default": DEFAULT_VOICE
}
@app.get("/web/scrape")
def direct_scrape(url: str):
"""Direct web scraping endpoint to inspect scraped content."""
res = scrape_webpage(url)
if res.get("success"):
return res
else:
raise HTTPException(status_code=400, detail="Failed to scrape the specified URL. Please check the server logs for more details.")
if __name__ == "__main__":
import uvicorn
# Listen on all interfaces. Use PORT env variable if present (e.g. on HF Spaces)
port_num = int(os.environ.get("PORT", 8000))
uvicorn.run(app, host="0.0.0.0", port=port_num)