HackerBol commited on
Commit
a2f7740
·
verified ·
1 Parent(s): 7f3f355

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +162 -18
app.py CHANGED
@@ -3665,13 +3665,15 @@ class OfflineLLMProvider(LLMProvider):
3665
  _tokenizer = None
3666
  _loading = False
3667
 
3668
- MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct" # Tiny, fast, CPU-friendly
 
 
3669
 
3670
  def is_available(self) -> bool:
3671
  return True # Always available
3672
 
3673
  def _load_model(self):
3674
- """Load the offline model using transformers."""
3675
  if self._model:
3676
  return True
3677
  if self._loading:
@@ -3682,19 +3684,33 @@ class OfflineLLMProvider(LLMProvider):
3682
  import torch
3683
  from transformers import AutoModelForCausalLM, AutoTokenizer
3684
 
3685
- log(f"OfflineLLM: loading {self.MODEL_NAME}...")
3686
- self._tokenizer = AutoTokenizer.from_pretrained(
3687
- self.MODEL_NAME, trust_remote_code=True
3688
- )
3689
- self._model = AutoModelForCausalLM.from_pretrained(
3690
- self.MODEL_NAME,
3691
- torch_dtype=torch.float32, # CPU
3692
- device_map="cpu",
3693
- trust_remote_code=True,
3694
- )
3695
- log(f"OfflineLLM: model loaded successfully!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3696
  self._loading = False
3697
- return True
3698
  except ImportError:
3699
  log("OfflineLLM: transformers/torch not installed, using Pollinations fallback")
3700
  self._loading = False
@@ -3917,6 +3933,118 @@ class Settings:
3917
  settings = Settings("default")
3918
 
3919
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3920
  def call_llm_safe(messages, max_tokens=1024, temperature=0.7):
3921
  """BRILLIANT SMART ROUTER v2 — fully uncensored.
3922
 
@@ -5310,12 +5438,15 @@ Give a brief analysis (5-7 sentences):
5310
 
5311
  Be direct and specific. No disclaimers."""
5312
 
5313
- text, source = call_llm_safe(
 
 
 
5314
  [{"role": "user", "content": analysis_prompt}],
5315
  max_tokens=500, temperature=0.3
5316
  )
5317
  text = unwrap_fiction_response(text)
5318
- return f"📊 {symbol.upper()} Analysis ({interval}):\n\n{text}"
5319
  except Exception as e:
5320
  return f"binance_analyze error: {e}"
5321
 
@@ -6284,9 +6415,22 @@ def agent_turn(user_message: str, chat_id: str = "default",
6284
  # max_tokens default raised to 4096 for fuller uncensored responses
6285
  accumulated_text = ""
6286
  image_path = None
 
 
 
 
 
 
 
6287
  for iteration in range(max_tool_iters):
6288
- text, source = call_llm_safe(messages, max_tokens=s.get("max_tokens", 4096),
6289
- temperature=s.get("temperature", 0.7))
 
 
 
 
 
 
6290
  # UNWRAP: extract the direct answer from <ANSWER> tags if present.
6291
  # This converts "The lab hummed... <ANSWER>Here's how to do it...</ANSWER>"
6292
  # into just "Here's how to do it..." so the user sees a normal response.
 
3665
  _tokenizer = None
3666
  _loading = False
3667
 
3668
+ MODEL_NAME = os.environ.get("OFFLINE_MODEL", "Qwen/Qwen2.5-7B-Instruct") # Smart enough for trading
3669
+ # Fallback to 0.5B if 7B fails to load (not enough RAM)
3670
+ FALLBACK_MODEL = "Qwen/Qwen2.5-0.5B-Instruct"
3671
 
3672
  def is_available(self) -> bool:
3673
  return True # Always available
3674
 
3675
  def _load_model(self):
3676
+ """Load the offline model using transformers. Tries 7B first, falls back to 0.5B."""
3677
  if self._model:
3678
  return True
3679
  if self._loading:
 
3684
  import torch
3685
  from transformers import AutoModelForCausalLM, AutoTokenizer
3686
 
3687
+ # Try 7B first (smart enough for trading analysis)
3688
+ for model_name in [self.MODEL_NAME, self.FALLBACK_MODEL]:
3689
+ try:
3690
+ log(f"OfflineLLM: loading {model_name}...")
3691
+ self._tokenizer = AutoTokenizer.from_pretrained(
3692
+ model_name, trust_remote_code=True
3693
+ )
3694
+ # Use bfloat16 for 7B to save RAM, float32 for 0.5B
3695
+ dtype = torch.bfloat16 if "7B" in model_name else torch.float32
3696
+ self._model = AutoModelForCausalLM.from_pretrained(
3697
+ model_name,
3698
+ torch_dtype=dtype,
3699
+ device_map="cpu",
3700
+ trust_remote_code=True,
3701
+ low_cpu_mem_usage=True,
3702
+ )
3703
+ log(f"OfflineLLM: {model_name} loaded successfully!")
3704
+ self.MODEL_NAME = model_name # remember which one worked
3705
+ self._loading = False
3706
+ return True
3707
+ except Exception as e:
3708
+ log(f"OfflineLLM: {model_name} failed ({str(e)[:100]}) — trying fallback")
3709
+ continue
3710
+
3711
+ # Both failed
3712
  self._loading = False
3713
+ return False
3714
  except ImportError:
3715
  log("OfflineLLM: transformers/torch not installed, using Pollinations fallback")
3716
  self._loading = False
 
3933
  settings = Settings("default")
3934
 
3935
 
3936
+ # ============================================================================
3937
+ # PRIVACY ROUTER — keeps personal/financial data on offline model only
3938
+ # ============================================================================
3939
+
3940
+ # Keywords that indicate PRIVATE data (account info, balances, trades, API keys)
3941
+ # These requests MUST be handled by the offline model — no data leaves your Space.
3942
+ PRIVATE_KEYWORDS = [
3943
+ # Binance account data
3944
+ "my balance", "my wallet", "my portfolio", "my positions", "my funds",
3945
+ "my binance", "my account", "my orders", "my trades", "my holdings",
3946
+ "binance balance", "binance account", "binance orders", "binance wallet",
3947
+ # Trade execution
3948
+ "buy ", "sell ", "place order", "execute trade", "make trade",
3949
+ "buy btc", "sell btc", "buy eth", "sell eth",
3950
+ # API keys / credentials
3951
+ "api key", "api secret", "my key", "my secret", "password", "credentials",
3952
+ # VPS / SSH
3953
+ "my vps", "my server", "ssh connect", "ssh run",
3954
+ # Personal info
3955
+ "my name", "my location", "my birthday", "my address", "my phone",
3956
+ "my email", "my credit card", "my bank",
3957
+ ]
3958
+
3959
+ # Keywords that indicate PUBLIC data (market prices, charts, news)
3960
+ # These can be sent to cloud models — it's just public market data.
3961
+ PUBLIC_KEYWORDS = [
3962
+ "price of", "btc price", "eth price", "current price", "market price",
3963
+ "chart", "candlestick", "24h stats", "market cap", "volume",
3964
+ "news", "headline", "latest news",
3965
+ "weather", "temperature",
3966
+ "wikipedia", "what is", "explain", "how does",
3967
+ "write code", "write a", "create a", "build a",
3968
+ ]
3969
+
3970
+
3971
+ def classify_privacy(messages) -> str:
3972
+ """Classify a request as PRIVATE, PUBLIC, or MIXED.
3973
+
3974
+ PRIVATE = account data, trades, API keys → offline model ONLY
3975
+ PUBLIC = market prices, charts, news → cloud models OK
3976
+ MIXED = both (e.g., "analyze my BTC position") → offline for final decision
3977
+
3978
+ Returns: 'PRIVATE', 'PUBLIC', or 'MIXED'
3979
+ """
3980
+ # Get the last user message
3981
+ user_msg = ""
3982
+ for m in reversed(messages):
3983
+ if m.get("role") == "user":
3984
+ user_msg = m.get("content", "").lower()
3985
+ break
3986
+ if not user_msg:
3987
+ return "PUBLIC"
3988
+
3989
+ # Check for private keywords
3990
+ has_private = any(kw in user_msg for kw in PRIVATE_KEYWORDS)
3991
+ # Check for public keywords
3992
+ has_public = any(kw in user_msg for kw in PUBLIC_KEYWORDS)
3993
+
3994
+ if has_private and has_public:
3995
+ return "MIXED"
3996
+ if has_private:
3997
+ return "PRIVATE"
3998
+ return "PUBLIC"
3999
+
4000
+
4001
+ def call_llm_private(messages, max_tokens=1024, temperature=0.7):
4002
+ """Call LLM with STRICT privacy — only uses the offline model.
4003
+ NO data leaves your Space. Used for: balances, orders, trades, API keys.
4004
+
4005
+ Falls back to Pollinations (with fiction-wrap) ONLY if offline model fails
4006
+ AND the request isn't truly sensitive (no API keys in the message)."""
4007
+ user_msg = ""
4008
+ for m in reversed(messages):
4009
+ if m.get("role") == "user":
4010
+ user_msg = m.get("content", "")
4011
+ break
4012
+
4013
+ log(f"PrivateRouter: handling PRIVATE request ({len(user_msg)} chars) — offline only")
4014
+
4015
+ # Try offline model FIRST and ONLY (no cloud providers)
4016
+ offline = PROVIDERS.get("offline")
4017
+ if offline and offline.is_available():
4018
+ try:
4019
+ text, source = offline.call(messages, max_tokens, temperature)
4020
+ text = unwrap_fiction_response(text)
4021
+ if is_good_response(text):
4022
+ log(f"PrivateRouter: offline model responded ({len(text)} chars) — 100% private")
4023
+ return text, source + " (PRIVATE — offline only)"
4024
+ else:
4025
+ log(f"PrivateRouter: offline model returned bad response")
4026
+ except Exception as e:
4027
+ log(f"PrivateRouter: offline model failed: {e}")
4028
+
4029
+ # CRITICAL: If the message contains API keys or secrets, NEVER send to cloud
4030
+ import re
4031
+ has_secret = bool(re.search(r"\b[A-Za-z0-9]{64}\b", user_msg))
4032
+ if has_secret:
4033
+ log("PrivateRouter: REFUSING to send API key to cloud — returning error")
4034
+ return (
4035
+ "I can't process this request right now because the offline model "
4036
+ "is unavailable, and your message contains sensitive data (API key/secret) "
4037
+ "that I won't send to cloud providers. Please try again in a moment — "
4038
+ "the offline model will load shortly.",
4039
+ "PRIVATE — offline unavailable (refused cloud for safety)"
4040
+ )
4041
+
4042
+ # For non-secret private requests (like "my balance"), fall back to cloud
4043
+ # but strip any account-specific data first
4044
+ log("PrivateRouter: offline unavailable, falling back to cloud (no secrets in message)")
4045
+ return call_llm_safe(messages, max_tokens, temperature)
4046
+
4047
+
4048
  def call_llm_safe(messages, max_tokens=1024, temperature=0.7):
4049
  """BRILLIANT SMART ROUTER v2 — fully uncensored.
4050
 
 
5438
 
5439
  Be direct and specific. No disclaimers."""
5440
 
5441
+ # Use PRIVATE routing (offline model) so your trading analysis
5442
+ # stays on your Space — no cloud provider sees your positions/strategy.
5443
+ # The chart data itself is public, but the analysis/recommendation is private.
5444
+ text, source = call_llm_private(
5445
  [{"role": "user", "content": analysis_prompt}],
5446
  max_tokens=500, temperature=0.3
5447
  )
5448
  text = unwrap_fiction_response(text)
5449
+ return f"📊 {symbol.upper()} Analysis ({interval}, PRIVATE — offline model):\n\n{text}"
5450
  except Exception as e:
5451
  return f"binance_analyze error: {e}"
5452
 
 
6415
  # max_tokens default raised to 4096 for fuller uncensored responses
6416
  accumulated_text = ""
6417
  image_path = None
6418
+
6419
+ # PRIVACY ROUTING: classify the request and route accordingly
6420
+ # PRIVATE (balances, trades, API keys) → offline model ONLY (100% private)
6421
+ # PUBLIC (prices, charts, news, code) → cloud models OK (smart + fast)
6422
+ privacy_level = classify_privacy(messages)
6423
+ log(f"PrivacyRouter: classified as {privacy_level}")
6424
+
6425
  for iteration in range(max_tool_iters):
6426
+ if privacy_level == "PRIVATE":
6427
+ # Private request — use offline model only, no cloud
6428
+ text, source = call_llm_private(messages, max_tokens=s.get("max_tokens", 4096),
6429
+ temperature=s.get("temperature", 0.7))
6430
+ else:
6431
+ # Public or mixed — use smart router (cloud + offline)
6432
+ text, source = call_llm_safe(messages, max_tokens=s.get("max_tokens", 4096),
6433
+ temperature=s.get("temperature", 0.7))
6434
  # UNWRAP: extract the direct answer from <ANSWER> tags if present.
6435
  # This converts "The lab hummed... <ANSWER>Here's how to do it...</ANSWER>"
6436
  # into just "Here's how to do it..." so the user sees a normal response.