Files
jarvis-memory/skills/qdrant-memory/scripts/monitor_ollama_models.py

208 lines
7.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Monitor Ollama model library for 100B+ parameter models
Only outputs/announces when there are significant new large models.
Always exits with code 0 to prevent "exec failed" logs.
Usage: monitor_ollama_models.py [--json]
"""
import argparse
import sys
import json
import urllib.request
import re
import hashlib
from datetime import datetime
QDRANT_URL = "http://10.0.0.40:6333"
KB_COLLECTION = "knowledge_base"
OLLAMA_LIBRARY_URL = "https://ollama.com/library"
LARGE_MODEL_TAGS = ["100b", "120b", "200b", "400b", "70b", "8x7b", "8x22b"]
GOOD_FOR_OPENCLAW = ["code", "coding", "instruct", "chat", "reasoning", "llama", "qwen", "mistral", "deepseek", "gemma", "mixtral"]
def fetch_library():
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
req = urllib.request.Request(OLLAMA_LIBRARY_URL, headers=headers)
try:
with urllib.request.urlopen(req, timeout=20) as response:
return response.read().decode('utf-8', errors='ignore')
except:
return None
def extract_models(html):
models = []
model_blocks = re.findall(r'<a[^>]*href="/library/([^"]+)"[^>]*>(.*?)</a>', html, re.DOTALL)
for model_name, block in model_blocks[:50]:
model_info = {
"name": model_name, "url": f"https://ollama.com/library/{model_name}",
"is_large": False, "is_new": False, "tags": [], "description": ""
}
tag_matches = re.findall(r'<span[^>]*>([^<]+(?:b|B))</span>', block)
model_info["tags"] = [t.lower() for t in tag_matches]
for tag in model_info["tags"]:
if any(large_tag in tag for large_tag in LARGE_MODEL_TAGS):
if "70b" in tag and not ("8x" in model_name.lower() or "mixtral" in model_name.lower()):
continue
model_info["is_large"] = True
break
desc_match = re.search(r'<p[^>]*>([^<]+)</p>', block)
if desc_match:
model_info["description"] = desc_match.group(1).strip()
updated_match = re.search(r'(\d+)\s+(hours?|days?)\s+ago', block, re.IGNORECASE)
if updated_match:
num = int(updated_match.group(1))
unit = updated_match.group(2).lower()
if (unit.startswith("hour") and num <= 24) or (unit.startswith("day") and num <= 2):
model_info["is_new"] = True
desc_lower = model_info["description"].lower()
name_lower = model_name.lower()
model_info["good_for_openclaw"] = any(kw in desc_lower or kw in name_lower for kw in GOOD_FOR_OPENCLAW)
models.append(model_info)
return models
def get_embedding(text):
data = {"model": "nomic-embed-text", "input": text[:500]}
req = urllib.request.Request("http://localhost:11434/api/embed",
data=json.dumps(data).encode(),
headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=30) as response:
result = json.loads(response.read().decode())
return result.get("embeddings", [None])[0]
except:
return None
def search_kb_for_model(model_name):
url = f"{QDRANT_URL}/collections/{KB_COLLECTION}/points/scroll"
data = {"limit": 100, "with_payload": True, "filter": {"must": [
{"key": "domain", "match": {"value": "AI/LLM"}},
{"key": "path", "match": {"text": model_name}}
]}}
req = urllib.request.Request(url, data=json.dumps(data).encode(),
headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=10) as response:
result = json.loads(response.read().decode())
return result.get("result", {}).get("points", [])
except:
return []
def store_model(model_info):
import uuid
text = f"{model_info['name']}: {model_info['description']}\nTags: {', '.join(model_info['tags'])}"
embedding = get_embedding(text)
if not embedding:
return False
metadata = {
"domain": "AI/LLM", "path": f"AI/LLM/Ollama/Models/{model_info['name']}",
"subjects": ["ollama", "models", "llm", "100b+"] + model_info['tags'],
"category": "reference", "content_type": "web_page",
"title": f"Ollama Model: {model_info['name']}", "source_url": model_info['url'],
"date_added": datetime.now().strftime("%Y-%m-%d"), "date_scraped": datetime.now().isoformat(),
"model_tags": model_info['tags'], "is_large": model_info['is_large'], "is_new": model_info['is_new'],
"text_preview": text[:300]
}
point = {"id": str(uuid.uuid4()), "vector": embedding, "payload": metadata}
url = f"{QDRANT_URL}/collections/{KB_COLLECTION}/points"
req = urllib.request.Request(url, data=json.dumps({"points": [point]}).encode(),
headers={"Content-Type": "application/json"}, method="PUT")
try:
with urllib.request.urlopen(req, timeout=10) as response:
result = json.loads(response.read().decode())
return result.get("status") == "ok"
except:
return False
def evaluate_candidate(model_info):
score = 0
reasons = []
if not model_info["is_large"]:
return {"is_candidate": False, "score": 0, "reasons": []}
score += 5
reasons.append("🦣 100B+ parameters")
if model_info.get("good_for_openclaw"):
score += 2
reasons.append("✨ Good for OpenClaw")
if model_info["is_new"]:
score += 2
reasons.append("🆕 Recently updated")
return {"is_candidate": score >= 5, "score": score, "reasons": reasons}
def format_notification(candidates):
lines = ["🤖 New Large Model Alert (100B+)", f"📅 {datetime.now().strftime('%Y-%m-%d')}", ""]
lines.append(f"📊 {len(candidates)} new large model(s) found:")
lines.append("")
for model in candidates[:5]:
eval_info = model["evaluation"]
lines.append(f"{model['name']}")
lines.append(f" {model['description'][:60]}...")
lines.append(f" Tags: {', '.join(model['tags'][:3])}")
for reason in eval_info["reasons"]:
lines.append(f" {reason}")
lines.append(f" 🔗 {model['url']}")
lines.append("")
lines.append("💡 Potential gpt-oss:120b replacement")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
html = fetch_library()
if not html:
if args.json:
print("{}")
sys.exit(0) # Silent fail with exit 0
models = extract_models(html)
large_models = [m for m in models if m["is_large"]]
candidates = []
for model in large_models:
existing = search_kb_for_model(model["name"])
is_new_to_kb = len(existing) == 0
evaluation = evaluate_candidate(model)
model["evaluation"] = evaluation
if is_new_to_kb:
store_model(model)
if evaluation["is_candidate"] and is_new_to_kb:
candidates.append(model)
# Output results
if args.json:
if candidates:
print(json.dumps({"candidates": candidates, "notification": format_notification(candidates)}))
else:
print("{}")
elif candidates:
print(format_notification(candidates))
# No output if no candidates (silent)
# Always exit 0 to prevent "exec failed" logs
sys.exit(0)
if __name__ == "__main__":
main()