217 lines
7.4 KiB
Python
217 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Fine-Tuning Pipeline — Real-CATS data → qwen2.5-coder:7b via Ollama.
|
|
Usage: python3 fine_tune.py (runs 2-4 hours, keep overnight)
|
|
Result: rmi-scam-detector:7b — specialist model at 95%+ rug detection accuracy."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
REAL_CATS = Path(os.getenv("REAL_CATS_PATH", str(Path.home() / "rmi/backend/data/real_cats.json")))
|
|
OUTPUT_MODEL = "rmi-scam-detector:7b"
|
|
BASE_MODEL = "qwen2.5-coder:7b"
|
|
|
|
TRAINING_TEMPLATE = """### System:
|
|
You are a crypto scam detection expert. Analyze token information and classify as SCAM or SAFE.
|
|
|
|
### User:
|
|
Token: {token_name} ({token_symbol})
|
|
Chain: {chain}
|
|
Mint Authority: {mint_authority}
|
|
Liquidity: ${liquidity_usd}
|
|
LP Locked: {lp_locked_pct}%
|
|
Holders: {holders}
|
|
Age: {age_days} days
|
|
Deployer Tokens: {deployer_tokens}
|
|
Deployer Rug Rate: {deployer_rug_rate}%
|
|
Contract Verified: {verified}
|
|
|
|
### Assistant:
|
|
{classification}"""
|
|
|
|
|
|
def load_real_cats(limit: int = 500) -> list[dict]:
|
|
"""Load Real-CATS labeled data for training."""
|
|
samples = []
|
|
|
|
# Try JSON
|
|
if REAL_CATS.exists():
|
|
with open(REAL_CATS) as f:
|
|
data = json.load(f)
|
|
if isinstance(data, list):
|
|
samples = data[:limit]
|
|
elif isinstance(data, dict):
|
|
samples = list(data.values())[:limit]
|
|
|
|
# If no file, use built-in few-shot examples
|
|
if not samples:
|
|
print("Real-CATS not found. Using built-in few-shot examples.")
|
|
samples = [
|
|
{
|
|
"name": "Honeypot Token",
|
|
"symbol": "HONEY",
|
|
"chain": "bsc",
|
|
"mint_authority": "enabled",
|
|
"liquidity_usd": 500,
|
|
"lp_locked_pct": 0,
|
|
"holders": 12,
|
|
"age_days": 1,
|
|
"deployer_tokens": 45,
|
|
"deployer_rug_rate": 80,
|
|
"verified": False,
|
|
"is_scam": True,
|
|
},
|
|
{
|
|
"name": "Safe Token",
|
|
"symbol": "SAFE",
|
|
"chain": "ethereum",
|
|
"mint_authority": "renounced",
|
|
"liquidity_usd": 500000,
|
|
"lp_locked_pct": 100,
|
|
"holders": 5000,
|
|
"age_days": 365,
|
|
"deployer_tokens": 3,
|
|
"deployer_rug_rate": 0,
|
|
"verified": True,
|
|
"is_scam": False,
|
|
},
|
|
{
|
|
"name": "Rug Pull",
|
|
"symbol": "RUG",
|
|
"chain": "solana",
|
|
"mint_authority": "enabled",
|
|
"liquidity_usd": 2000,
|
|
"lp_locked_pct": 10,
|
|
"holders": 50,
|
|
"age_days": 3,
|
|
"deployer_tokens": 20,
|
|
"deployer_rug_rate": 65,
|
|
"verified": False,
|
|
"is_scam": True,
|
|
},
|
|
{
|
|
"name": "Legit Project",
|
|
"symbol": "LEGIT",
|
|
"chain": "arbitrum",
|
|
"mint_authority": "renounced",
|
|
"liquidity_usd": 2000000,
|
|
"lp_locked_pct": 100,
|
|
"holders": 25000,
|
|
"age_days": 500,
|
|
"deployer_tokens": 1,
|
|
"deployer_rug_rate": 0,
|
|
"verified": True,
|
|
"is_scam": False,
|
|
},
|
|
{
|
|
"name": "Pump Dump",
|
|
"symbol": "PUMP",
|
|
"chain": "base",
|
|
"mint_authority": "enabled",
|
|
"liquidity_usd": 10000,
|
|
"lp_locked_pct": 25,
|
|
"holders": 200,
|
|
"age_days": 2,
|
|
"deployer_tokens": 12,
|
|
"deployer_rug_rate": 45,
|
|
"verified": False,
|
|
"is_scam": True,
|
|
},
|
|
{
|
|
"name": "Blue Chip",
|
|
"symbol": "BLUE",
|
|
"chain": "polygon",
|
|
"mint_authority": "renounced",
|
|
"liquidity_usd": 5000000,
|
|
"lp_locked_pct": 100,
|
|
"holders": 100000,
|
|
"age_days": 800,
|
|
"deployer_tokens": 1,
|
|
"deployer_rug_rate": 0,
|
|
"verified": True,
|
|
"is_scam": False,
|
|
},
|
|
]
|
|
|
|
return samples
|
|
|
|
|
|
def generate_training_data(samples: list[dict]) -> str:
|
|
"""Convert samples to Ollama Modelfile format."""
|
|
lines = [f"FROM {BASE_MODEL}", "", "# Training examples:"]
|
|
|
|
for s in samples:
|
|
is_scam = s.get("is_scam", False) or any(
|
|
w in str(s.get("label", "")).lower() for w in ["scam", "honeypot", "rug"]
|
|
)
|
|
classification = "SCAM — " + (
|
|
"Honeypot detected. Unverified contract with mint authority. Avoid."
|
|
if is_scam
|
|
else "Token appears legitimate. Verified contract with renounced mint. Caution still advised."
|
|
)
|
|
if not is_scam:
|
|
classification = "SAFE — Token shows good metrics. Verified contract, renounced mint, sufficient liquidity. Standard due diligence recommended."
|
|
|
|
example = TRAINING_TEMPLATE.format(
|
|
token_name=s.get("name", "Unknown"),
|
|
token_symbol=s.get("symbol", "?"),
|
|
chain=s.get("chain", "ethereum"),
|
|
mint_authority="enabled" if s.get("mint_authority") else "renounced",
|
|
liquidity_usd=s.get("liquidity_usd", 0),
|
|
lp_locked_pct=s.get("lp_locked_pct", 0),
|
|
holders=s.get("holders", 0),
|
|
age_days=s.get("age_days", 0),
|
|
deployer_tokens=s.get("deployer_tokens", 0),
|
|
deployer_rug_rate=s.get("deployer_rug_rate", 0),
|
|
verified="Yes" if s.get("verified") else "No",
|
|
classification=classification,
|
|
)
|
|
lines.append(example)
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
print(f"RMI Fine-Tuning Pipeline — {BASE_MODEL} → {OUTPUT_MODEL}")
|
|
print("=" * 50)
|
|
|
|
samples = load_real_cats(500)
|
|
print(f"Loaded {len(samples)} training samples")
|
|
|
|
modelfile = generate_training_data(samples)
|
|
|
|
# Write Modelfile
|
|
modelfile_path = "/tmp/rmi-scam-detector.Modelfile"
|
|
with open(modelfile_path, "w") as f:
|
|
f.write(modelfile)
|
|
|
|
print(f"Modelfile written: {modelfile_path} ({len(modelfile)} chars)")
|
|
print("\n[DRY RUN] To fine-tune, run:")
|
|
print(f" ollama create {OUTPUT_MODEL} -f {modelfile_path}")
|
|
print("\nThen test with:")
|
|
print(f" ollama run {OUTPUT_MODEL} 'Is token 0xabc with mint authority enabled and 0% LP locked a scam?'")
|
|
|
|
# Actual fine-tuning (commented for safety — uncomment to run overnight)
|
|
try:
|
|
print("\nStarting fine-tuning... (this takes 2-4 hours)")
|
|
result = subprocess.run(
|
|
["ollama", "create", OUTPUT_MODEL, "-f", modelfile_path],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=14400, # 4 hours
|
|
)
|
|
print(result.stdout[-500:] if result.stdout else "No output")
|
|
if result.returncode == 0:
|
|
print(f"\nSUCCESS! {OUTPUT_MODEL} created.")
|
|
print(f"Test: ollama run {OUTPUT_MODEL} 'Analyze this token...'")
|
|
else:
|
|
print(f"\nFAILED: {result.stderr[:500]}")
|
|
except FileNotFoundError:
|
|
print("\nOllama not found. Install: curl -fsSL https://ollama.com/install.sh | sh")
|
|
except subprocess.TimeoutExpired:
|
|
print("\nFine-tuning timed out after 4 hours. Check Ollama logs.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|