File size: 25,068 Bytes
6993919 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 | """
RMI Agent System β Agent MUNCH Multi-Specialist Intelligence Operative
======================================================================
9 specialized crypto intelligence operatives, each a distinct skill module
under the Agent MUNCH persona. Uses free OpenRouter models with fallbacks.
Architecture:
- Each specialist has its own system prompt, model preference, and output format
- RAG context injection: fetches real DataBus data before LLM call
- Smart caching: checks Redis for previously answered similar questions
- Keyword + explicit skill routing
- SSE streaming for real-time output
Specialists:
rug_detect β Token rug/honeypot detection
wallet_forensics β Wallet funding trail analysis
market_intel β Market conditions & whale analysis
bundle_detect β Coordinated trading detection
code_audit β Smart contract vulnerability scanning
social_sentiment β Sentiment divergence analysis
airdrop_assess β Airdrop claim safety evaluation
defi_yield β DeFi yield trap identification
general β Agent MUNCH default operative
"""
import contextlib
import hashlib
import json
import logging
import os
from collections.abc import AsyncGenerator
from dataclasses import dataclass, field
logger = logging.getLogger("agent.system")
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# AGENT DEFINITIONS
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@dataclass
class AgentDef:
id: str
name: str
icon: str
description: str
system_prompt: str
model: str
fallbacks: list[str] = field(default_factory=list)
temperature: float = 0.3
max_tokens: int = 800
color: str = "#8B5CF6" # UI color
output_format: str = "standard" # standard, evidence_chain, threat_rating
databus_context: list[str] = field(default_factory=list) # DataBus chains to inject
MUNCH_BASE = """You are Agent MUNCH, a crypto intelligence operative for Rug Munch Intelligence.
You are NOT a generic AI assistant. You are a highly trained specialist operative.
Speak like briefing a client β direct, forensic, precise. Never say "I'm an AI" or "as an AI."
Use threat classification: CRITICAL, HIGH, MEDIUM, LOW. Use confidence scores (0-100%).
Reference real data when available. If you lack data, say "I need to pull [X] data β recommend running [tool]."
Never fabricate addresses, prices, or on-chain data. Be skeptical. Trust nothing until verified.
"""
AGENTS = {
"rug_detect": AgentDef(
id="rug_detect",
name="Rug Detection Specialist",
icon="π‘οΈ",
description="Token rug pull, honeypot, and scam detection specialist",
system_prompt=MUNCH_BASE
+ """You specialize in detecting rug pulls, honeypots, and token scams.
Focus on: liquidity lock verification, mint authority analysis, deployer wallet forensics,
honeypot detection patterns, proxy contract abuse, concentrated ownership risk.
Format output as THREAT RATING: [LEVEL] (Score: X/100) followed by KEY FINDINGS and RECOMMENDATION.
When you identify a rug pattern, say "RUG PATTERN DETECTED" with specific evidence.""",
model="nvidia/nemotron-3-super-120b-a12b:free",
fallbacks=["google/gemma-4-31b-it:free"],
temperature=0.2,
color="#EF4444",
output_format="threat_rating",
databus_context=["alerts", "market_overview"],
),
"wallet_forensics": AgentDef(
id="wallet_forensics",
name="Wallet Forensic Investigator",
icon="π",
description="Wallet funding trail analysis, entity resolution, insider network mapping",
system_prompt=MUNCH_BASE
+ """You specialize in wallet forensics and funding trail analysis.
Focus on: wallet clustering, deployer wallet networks, mixer exit detection,
insider wallet identification, counterparty risk, funding source tracing.
Format output as CHAIN OF CUSTODY: wallet β funding source β linked wallets β risk classification.
Classify wallets as: SMART MONEY, INSIDER, MEME DUMPER, MIXER EXIT, TEAM WALLET, MEV BOT.""",
model="google/gemma-4-26b-a4b-it:free",
fallbacks=["nvidia/nemotron-3-super-120b-a12b:free"],
temperature=0.2,
color="#22D3EE",
output_format="evidence_chain",
databus_context=["whale_alerts", "alerts"],
),
"market_intel": AgentDef(
id="market_intel",
name="Market Intelligence Analyst",
icon="π",
description="Market conditions, whale movements, Fear & Greed, prediction markets",
system_prompt=MUNCH_BASE
+ """You specialize in market intelligence analysis.
Focus on: whale movement interpretation, DEX flow anomalies, volume spikes,
Fear & Greed contextualization, sentiment divergence from on-chain data,
prediction market signals, macro crypto conditions.
During Extreme Greed periods, explicitly flag elevated scam and rug risk.
Be data-driven β cite specific metrics, not vague observations.""",
model="qwen/qwen3-next-80b-a3b-instruct:free",
fallbacks=["nvidia/nemotron-3-super-120b-a12b:free"],
temperature=0.4,
color="#8B5CF6",
output_format="standard",
databus_context=["market_overview", "trending", "whale_alerts"],
),
"bundle_detect": AgentDef(
id="bundle_detect",
name="Bundle Detection Operator",
icon="π",
description="Coordinated trading detection, wash trading, same-timestamp analysis",
system_prompt=MUNCH_BASE
+ """You specialize in detecting coordinated trading bundles.
Focus on: same-timestamp transaction clusters, gas-funded wallet groups,
wash trading patterns, insider pre-positioning, coordinated buy/sell walls,
MEV sandwich attack patterns, token launch sniping detection.
Format: BUNDLE IDENTIFIED β wallets involved β timing β estimated profit β THREAT LEVEL.""",
model="nvidia/nemotron-3-super-120b-a12b:free",
fallbacks=["google/gemma-4-31b-it:free"],
temperature=0.2,
color="#F59E0B",
output_format="evidence_chain",
databus_context=["bundle_detect", "alerts"],
),
"code_audit": AgentDef(
id="code_audit",
name="Multi-Chain Code Auditor",
icon="π",
description="Smart contract vulnerability scanning across EVM, Solana, and more",
system_prompt=MUNCH_BASE
+ """You specialize in smart contract code auditing across multiple chains.
EVM focus: proxy upgrade abuse, unrestricted mint, hidden owner functions, reentrancy, unsafe delegatecall.
Solana focus: mint authority freeze, close authority, unchecked CPI, fake CPI returns.
Base focus: unverified contract risks, permissioned token patterns.
Format: VULNERABILITY SCORECARD listing each finding with severity (CRITICAL/HIGH/MEDIUM/LOW),
the specific code pattern, and remediation.""",
model="nvidia/nemotron-3-super-120b-a12b:free",
fallbacks=["google/gemma-4-31b-it:free"],
temperature=0.2,
color="#06D6A0",
output_format="threat_rating",
databus_context=["alerts"],
),
"social_sentiment": AgentDef(
id="social_sentiment",
name="Social Sentiment Decoder",
icon="π£οΈ",
description="X/Twitter sentiment vs on-chain movement divergence analysis",
system_prompt=MUNCH_BASE
+ """You specialize in social sentiment analysis and its divergence from on-chain reality.
Focus on: Twitter/X sentiment vs actual wallet behavior, pump-and-dump social patterns,
influencer wallet timing correlation, coordinated shill detection,
sentiment manipulation via bot networks, "this is fine" divergence signals.
Key insight: when sentiment says BUY but whales are EXITING, that's the classic divergence.
Format: SENTIMENT vs ON-CHAIN: divergence score, social signals, on-chain reality, ASSESSMENT.""",
model="qwen/qwen3-next-80b-a3b-instruct:free",
fallbacks=["nvidia/nemotron-3-super-120b-a12b:free"],
temperature=0.4,
color="#38BDF8",
output_format="standard",
databus_context=["market_overview", "trending", "whale_alerts"],
),
"airdrop_assess": AgentDef(
id="airdrop_assess",
name="Airdrop Threat Assessor",
icon="π",
description="Airdrop claim safety, signature risk, wallet drain potential evaluation",
system_prompt=MUNCH_BASE
+ """You specialize in airdrop and claim safety assessment.
Focus on: contract verification for claims, signature requirement risks (EIP-712 phishing),
wallet drain potential in claim processes, gas spike exploitation during claims,
fake airdrop phishing detection, legitimate vs scam airdrop differentiation.
Key rule: NEVER recommend clicking a claim link without verifying the contract address on-chain.
Format: AIRDROP RATING with legitimacy score, claim safety checklist, and specific risks.""",
model="google/gemma-4-31b-it:free",
fallbacks=["nvidia/nemotron-3-super-120b-a12b:free"],
temperature=0.3,
color="#A78BFA",
output_format="threat_rating",
databus_context=["alerts", "market_overview"],
),
"defi_yield": AgentDef(
id="defi_yield",
name="DeFi Yield Trap Detector",
icon="π",
description="Unsustainable yield detection, emission inflation, TVL manipulation",
system_prompt=MUNCH_BASE
+ """You specialize in detecting unsustainable DeFi yield mechanisms.
Focus on: emission schedule inflation analysis, TVL manipulation via protocol-owned liquidity,
reward token devaluation trajectories, hidden lock periods and withdrawal gates,
yield farming that requires depositing into unverified contracts,
leveraged yield loops that amplify risk.
Key pattern: if yield >30% APY with no clear revenue source, it's likely a yield trap.
Format: YIELD SAFETY SCORE with sustainability analysis, risk factors, and honest yield estimate.""",
model="qwen/qwen3-next-80b-a3b-instruct:free",
fallbacks=["nvidia/nemotron-3-super-120b-a12b:free"],
temperature=0.3,
color="#FB3B76",
output_format="threat_rating",
databus_context=["market_overview", "trending"],
),
"general": AgentDef(
id="general",
name="Agent MUNCH",
icon="π΅οΈ",
description="General crypto intelligence operative β your all-purpose specialist",
system_prompt=MUNCH_BASE
+ """You are the default operative, skilled in all areas of crypto intelligence.
You can discuss token security, wallet analysis, market conditions, DeFi risks,
blockchain technology, trading strategies, and scam patterns with equal expertise.
When a question falls outside your expertise, say "This requires [specialist name] deployment β
I recommend switching to that skill for deeper analysis."
Always offer actionable next steps: "Recommend running [tool] at rugmunch.io for [specific analysis].""",
model="google/gemma-4-31b-it:free",
fallbacks=["nvidia/nemotron-3-super-120b-a12b:free"],
temperature=0.5,
color="#8B5CF6",
output_format="standard",
databus_context=["market_overview", "alerts"],
),
}
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ROUTING
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ROUTES = {
"rug_detect": [
"scan",
"token",
"scam",
"rug",
"honeypot",
"contract",
"audit",
"safety",
"risk score",
"verify token",
"check coin",
"rug pull",
"is this safe",
"is this a scam",
],
"wallet_forensics": [
"wallet",
"address",
"holder",
"whale",
"smart money",
"portfolio",
"entity",
"counterparty",
"deployer",
"funding",
"trace",
"follow the money",
"cluster",
],
"market_intel": [
"market",
"trending",
"fear greed",
"sentiment",
"prediction",
"price",
"volume",
"mover",
"gainer",
"condition",
"macro",
"btc",
"eth",
"sol",
"dominance",
],
"bundle_detect": [
"bundle",
"coordinated",
"wash trade",
"same time",
"sniper",
"launch",
"front run",
"sandwich",
"mev",
"bot cluster",
],
"code_audit": [
"code",
"contract",
"source",
"audit",
"vulnerability",
"proxy",
"mint authority",
"reentrancy",
"delegatecall",
"verify source",
"solana program",
],
"social_sentiment": [
"twitter",
"social",
"sentiment",
"influencer",
"shill",
"hype",
"pump social",
"bot network",
"community sentiment",
"reddit",
],
"airdrop_assess": [
"airdrop",
"claim",
"free token",
"signature",
"eip-712",
"phishing claim",
"eligible",
"merkle",
],
"defi_yield": [
"yield",
"apy",
"farming",
"liquidity pool",
"staking",
"emission",
"tvl",
"protocol",
"curve",
"convex",
"leveraged",
],
}
def classify(msg: str) -> str:
m = msg.lower()
for agent_id, keywords in ROUTES.items():
if any(kw in m for kw in keywords):
return agent_id
return "general"
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# RAG CONTEXT INJECTION
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async def fetch_databus_context(chains: list[str]) -> str:
"""Fetch real data from DataBus and format as context for the LLM."""
if not chains:
return ""
context_parts = []
try:
import httpx
for chain in chains:
try:
url = "http://localhost:8000/api/v1/databus/fetch"
async with httpx.AsyncClient(timeout=8) as c:
r = await c.post(url, json={"data_type": chain, "limit": 5})
if r.status_code == 200:
data = r.json()
# Extract the actual data payload
result = data.get("data", data.get("results", [{}]))
if isinstance(result, list) and result:
result = result[0].get("data", result[0]) if result else {}
context_parts.append(f"[{chain} DATA]: {json.dumps(result, default=str)[:800]}")
except Exception as e:
logger.warning(f"DataBus context fetch failed for {chain}: {e}")
except Exception as e:
logger.warning(f"DataBus context system unavailable: {e}")
if context_parts:
return "\n\nREAL-TIME PLATFORM DATA (use this in your analysis, do not fabricate):\n" + "\n".join(context_parts)
return ""
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SMART CACHING
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async def check_cache(msg: str, agent_id: str) -> str | None:
"""Check Redis for previously answered similar questions."""
try:
import redis
r = redis.Redis(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", "6379")),
password=os.getenv("REDIS_PASSWORD", ""),
decode_responses=True,
socket_timeout=2,
)
# Hash the question + agent for cache key
cache_key = f"agent_cache:{agent_id}:{hashlib.sha256(msg.encode()).hexdigest()[:16]}"
cached = r.get(cache_key)
if cached:
logger.info(f"Cache hit for {agent_id}: {cache_key}")
return cached
except Exception:
pass
return None
async def store_cache(msg: str, agent_id: str, response: str, ttl: int = 3600):
"""Store response in Redis cache. TTL defaults to 1 hour."""
try:
import redis
r = redis.Redis(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", "6379")),
password=os.getenv("REDIS_PASSWORD", ""),
decode_responses=True,
socket_timeout=2,
)
cache_key = f"agent_cache:{agent_id}:{hashlib.sha256(msg.encode()).hexdigest()[:16]}"
# Only cache if response is substantive (>200 chars)
if len(response) > 200:
r.setex(cache_key, ttl, response[:4000]) # Cap stored size
except Exception:
pass
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# STREAMING ROUTER
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async def route_and_stream(msg: str, role_hint: str = "") -> AsyncGenerator[dict, None]:
"""Route to specialist agent, inject RAG context, stream response.
Provider priority:
1. Gemini 2.5 Flash (FREE, 1500 RPD, smart, fast)
2. OpenRouter free models (fallback when Gemini rate-limited)
"""
import httpx
agent_id = role_hint if role_hint in AGENTS else classify(msg)
agent = AGENTS[agent_id]
yield {
"type": "agent",
"role": agent_id,
"name": agent.name,
"icon": agent.icon,
"color": agent.color,
}
# Check cache first -- skip LLM call entirely if we already have the answer
cached = await check_cache(msg, agent_id)
if cached:
yield {"type": "cache_hit", "agent": agent_id}
yield {"type": "token", "text": cached}
yield {"type": "done"}
return
# Fetch RAG context from DataBus
rag_context = await fetch_databus_context(agent.databus_context)
system_with_context = agent.system_prompt + rag_context
messages = [
{"role": "system", "content": system_with_context},
{"role": "user", "content": msg},
]
full_response = ""
# ββ Provider 1: Gemini (FREE, primary) ββ
from dotenv import load_dotenv
load_dotenv()
gemini_keys = []
for env_var in ["GEMINI_API_KEY", "GEMINI_API_KEY_2", "GEMINI_API_KEY_3"]:
k = os.environ.get(env_var, "")
if k and len(k) > 20:
gemini_keys.append(k)
for gkey in gemini_keys:
try:
# Gemini native streaming API (key in URL, OpenAI-compatible format)
base_url = f"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions?key={gkey}"
headers = {"Content-Type": "application/json"}
body = {
"model": "gemini-2.5-flash",
"messages": messages,
"max_tokens": agent.max_tokens,
"temperature": agent.temperature,
"stream": True,
}
async with httpx.AsyncClient(timeout=45) as c:
async with c.stream("POST", base_url, json=body, headers=headers) as r:
if r.status_code == 200:
async for line in r.aiter_lines():
if line.startswith("data: "):
d = line[6:]
if d == "[DONE]":
if full_response:
await store_cache(msg, agent_id, full_response)
yield {"type": "done"}
return
try:
ch = json.loads(d)
txt = ch.get("choices", [{}])[0].get("delta", {}).get("content", "")
if txt:
full_response += txt
yield {"type": "token", "text": txt}
except Exception:
pass
if full_response:
await store_cache(msg, agent_id, full_response)
yield {"type": "done"}
return
elif r.status_code == 429:
logger.info("Gemini rate-limited, trying next key/fallback")
continue # Try next key or fallback provider
else:
logger.warning(f"Gemini error {r.status_code}, trying fallback")
continue
except Exception as e:
logger.warning(f"Gemini call failed: {e}")
continue
# ββ Provider 2: OpenRouter (fallback, costs credits) ββ
api_key = os.environ.get("OPENROUTER_API_KEY", "")
if not api_key:
b64 = os.environ.get("LLM_API_KEY_B64", "")
if b64:
import base64
with contextlib.suppress(BaseException):
api_key = base64.b64decode(b64).decode()
if api_key:
models = [agent.model, *agent.fallbacks]
for model in models:
try:
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "https://rugmunch.io",
"X-Title": f"RMI {agent.name}",
}
body = {
"model": model,
"messages": messages,
"max_tokens": agent.max_tokens,
"temperature": agent.temperature,
"stream": True,
}
async with httpx.AsyncClient(timeout=60) as c, c.stream(
"POST",
"https://openrouter.ai/api/v1/chat/completions",
json=body,
headers=headers,
) as r:
if r.status_code == 200:
async for line in r.aiter_lines():
if line.startswith("data: "):
d = line[6:]
if d == "[DONE]":
if full_response:
await store_cache(msg, agent_id, full_response)
yield {"type": "done"}
return
try:
ch = json.loads(d)
txt = ch.get("choices", [{}])[0].get("delta", {}).get("content", "")
if txt:
full_response += txt
yield {"type": "token", "text": txt}
except Exception:
pass
if full_response:
await store_cache(msg, agent_id, full_response)
yield {"type": "done"}
return
elif r.status_code == 429:
continue
except Exception as e:
logger.warning(f"OpenRouter model {model} failed: {e}")
continue
yield {
"type": "error",
"text": "All providers unavailable (Gemini rate-limited, OpenRouter failed)",
}
yield {"type": "done"}
def agents_list() -> list:
return [
{
"id": a.id,
"name": a.name,
"icon": a.icon,
"model": a.model,
"description": a.description,
"color": a.color,
"output_format": a.output_format,
}
for a in AGENTS.values()
]
|