rmi-backend/rmi_langchain.py
cryptorugmunch cfd75fd1a0 fix(lint): drop ruff errors from 1470 to 0 across app/ and tests/
- Exclude generated SDK (sdks/python) and operational scripts from ruff lint
- Add targeted per-file ignores for ASYNC*, S310, S603, S607, S108, S314,
  S102, PIE810, SIM102 in scripts/
- Auto-fix safe categories (I001, F401, W292, F841, PIE790, RUF100, etc.)
- Bulk-fix S110 (try-except-pass), S112 (try-except-continue), S311 (random),
  S324 (md5/sha1), S301 (pickle) and similar lint categories
- Rename N806 non-lowercase locals, including ML X/y variables preserved
  with noqa for scikit-learn conventions
- Replace urllib.request calls with httpx.AsyncClient / httpx.Client (S310)
- Wrap blocking os.path/os calls in asyncio.get_running_loop().run_in_executor
- Replace subprocess.run with asyncio.create_subprocess_exec in async contexts
- Store asyncio.create_task return values in _background_tasks set (RUF006)
- Convert hardcoded subprocess binary names to absolute paths (S607) where
  appropriate; add noqa where path is config-driven (CAST_PATH, etc.)
- Parameterize SQL queries with placeholders and add noqa for sanitized inputs
- Fix all mechanical categories: SIM102, PIE810, TC001/2/3, S108, S314, S107,
  S306, S301, N802/N815/N817, S104, S605, S501, RUF022, UP031
- Add missing 'import asyncio' where referenced but not imported (F821)
- Fix E402 module-import-not-at-top by adding '# noqa: E402' for circular-import
  safe cases and code-defined imports
- Remove hardcoded Redis password in databus_warm_cron.py; use env vars

Tests:
- Add tests/unit/core/test_ai_router.py (8 tests): model resolution, chat
  completion with mocked httpx, fallback to OpenRouter, no-provider error,
  streaming
- Add tests/unit/core/test_tracing.py (7 tests): setup_otel disabled/enabled,
  shutdown_otel, span helpers, tracing-enabled route registration
- Add tests/unit/core/test_langfuse.py (2 tests): no-env init, noop flush
- Fix tests/unit/domain/scanner/test_service.py to import from the moved
  app.domains.scanners.core.service

Result: 'ruff check .' passes with 0 errors (was 1470).
Pytest: 808 passed, 1 skipped (no regressions).
2026-07-08 03:36:58 +07:00

298 lines
10 KiB
Python

"""
RMI LangChain Integration
==========================
Provides RMI tools as LangChain tools for use in LangChain agents, chains, and workflows.
Usage:
from rmi_langchain import RMIToolkit, create_rmi_agent
# Get all RMI tools as LangChain tools
toolkit = RMIToolkit(api_key="rmi_dev_...")
tools = toolkit.get_tools()
# Create an agent with RMI tools
agent = create_rmi_agent(api_key="rmi_dev_...")
result = agent.invoke({"input": "Scan this wallet for risks: 0xd8dA..."})
Author: RMI Development
Date: 2026-06-05
"""
import json
import os
from typing import Any
from pydantic import BaseModel, Field
try:
from langchain_core.callbacks import CallbackManagerForToolRun
from langchain_core.tools import BaseTool
LANGCHAIN_AVAILABLE = True
except ImportError:
LANGCHAIN_AVAILABLE = False
try:
from rmi_sdk import RMI, ToolResult # noqa: F401
SDK_AVAILABLE = True
except ImportError:
SDK_AVAILABLE = False
# ── RMI Tool Wrappers ─────────────────────────────────────────────
class RMIToolInput(BaseModel):
"""Base input schema for RMI tools."""
address: str = Field(description="Wallet or token address to analyze")
chain: str = Field(
default="solana", description="Blockchain: solana, base, ethereum, bsc, etc."
)
class ScanInput(RMIToolInput):
"""Input for wallet scan."""
class AuditInput(RMIToolInput):
"""Input for contract audit."""
source_code: str | None = Field(default=None, description="Optional contract source code")
class WhaleInput(RMIToolInput):
"""Input for whale analysis."""
class ReputationInput(BaseModel):
"""Input for reputation score."""
address: str = Field(description="Wallet or token address")
class HoneypotInput(RMIToolInput):
"""Input for honeypot check."""
class SentimentInput(BaseModel):
"""Input for sentiment analysis."""
query: str = Field(description="Search query for sentiment analysis")
class RugProbabilityInput(RMIToolInput):
"""Input for rug probability."""
class CompositeScoreInput(RMIToolInput):
"""Input for composite score."""
class SmartMoneyInput(BaseModel):
"""Input for smart money analysis."""
address: str = Field(description="Wallet address to find similar smart money wallets")
class MarketOverviewInput(BaseModel):
"""Input for market overview."""
class NarrativeInput(BaseModel):
"""Input for market narrative."""
class ForensicsInput(RMIToolInput):
"""Input for wallet forensics."""
if LANGCHAIN_AVAILABLE and SDK_AVAILABLE:
class RMITool(BaseTool):
"""Base class for RMI LangChain tools."""
rmi_client: RMI
def _run(
self,
run_manager: CallbackManagerForToolRun | None = None,
**kwargs: Any,
) -> str:
try:
result = self.rmi_client.call(self.name, kwargs)
return json.dumps(result.raw, indent=2, default=str)
except Exception as e:
return json.dumps({"error": str(e), "tool": self.name})
async def _arun(
self,
run_manager: CallbackManagerForToolRun | None = None,
**kwargs: Any,
) -> str:
# Use sync version for now - async SDK available separately
return self._run(run_manager=run_manager, **kwargs)
class RMIScanTool(RMITool):
name: str = "rmi_scan"
description: str = "Scan a wallet address for security risks, scam patterns, and suspicious activity. Returns risk assessment, labels, and recommendations."
args_schema: type[BaseModel] = ScanInput
class RMIAuditTool(RMITool):
name: str = "rmi_audit"
description: str = "Audit a smart contract for vulnerabilities, rug pull indicators, and malicious code patterns. Returns security assessment."
args_schema: type[BaseModel] = AuditInput
class RMIWhaleTool(RMITool):
name: str = "rmi_whale"
description: str = "Analyze whale wallet activity, holdings, and trading patterns. Returns portfolio analysis and behavioral insights."
args_schema: type[BaseModel] = WhaleInput
class RMIReputationTool(RMITool):
name: str = "rmi_reputation"
description: str = "Get a 0-100 reputation/trust score for a wallet or token address. Combines labels, scam databases, and on-chain behavior."
args_schema: type[BaseModel] = ReputationInput
class RMIHoneypotTool(RMITool):
name: str = "rmi_honeypot"
description: str = "Check if a token is a honeypot - tokens you can buy but cannot sell. Returns honeypot status and risk factors."
args_schema: type[BaseModel] = HoneypotInput
class RMISentimentTool(RMITool):
name: str = "rmi_sentiment"
description: str = "Get market sentiment analysis for a token, project, or keyword. Returns sentiment score and community mood."
args_schema: type[BaseModel] = SentimentInput
class RMIRugProbabilityTool(RMITool):
name: str = "rmi_rug_probability"
description: str = "Get rug pull probability score (0-100) for a token. Predicts likelihood of rug pull in next 24 hours."
args_schema: type[BaseModel] = RugProbabilityInput
class RMICompositeScoreTool(RMITool):
name: str = "rmi_composite_score"
description: str = "Get a composite buy/sell/avoid score for a token. Combines reputation, rug probability, market health, narrative sentiment, and MEV exposure."
args_schema: type[BaseModel] = CompositeScoreInput
class RMISmartMoneyTool(RMITool):
name: str = "rmi_smart_money"
description: str = "Find profitable traders similar to this wallet. Returns ranked list of smart money wallets with PnL and follow-worthiness scores."
args_schema: type[BaseModel] = SmartMoneyInput
class RMIMarketOverviewTool(RMITool):
name: str = "rmi_market_overview"
description: str = (
"Get current market overview - prices, volumes, trends across major chains and tokens."
)
args_schema: type[BaseModel] = MarketOverviewInput
class RMINarrativeTool(RMITool):
name: str = "rmi_narrative"
description: str = "Get current market narrative - what is the market saying RIGHT NOW? Returns trending narratives, sentiment, and community focus."
args_schema: type[BaseModel] = NarrativeInput
class RMIForensicsTool(RMITool):
name: str = "rmi_forensics"
description: str = "Run deep wallet forensics - transaction history, fund flows, connected addresses, and risk analysis."
args_schema: type[BaseModel] = ForensicsInput
# ── Toolkit ────────────────────────────────────────────────────
class RMIToolkit:
"""RMI tools as a LangChain toolkit."""
def __init__(self, api_key: str | None = None, **kwargs):
self.api_key = api_key or os.environ.get("RMI_API_KEY")
self.rmi = RMI(api_key=self.api_key, **kwargs)
def get_tools(self) -> list[BaseTool]:
"""Get all RMI tools as LangChain tools."""
return [
RMIScanTool(rmi_client=self.rmi),
RMIAuditTool(rmi_client=self.rmi),
RMIWhaleTool(rmi_client=self.rmi),
RMIReputationTool(rmi_client=self.rmi),
RMIHoneypotTool(rmi_client=self.rmi),
RMISentimentTool(rmi_client=self.rmi),
RMIRugProbabilityTool(rmi_client=self.rmi),
RMICompositeScoreTool(rmi_client=self.rmi),
RMISmartMoneyTool(rmi_client=self.rmi),
RMIMarketOverviewTool(rmi_client=self.rmi),
RMINarrativeTool(rmi_client=self.rmi),
RMIForensicsTool(rmi_client=self.rmi),
]
def create_rmi_agent(
api_key: str | None = None,
model: str = "gpt-4",
**kwargs,
):
"""Create a LangChain agent with RMI tools.
Args:
api_key: RMI API key.
model: LLM model to use.
**kwargs: Additional arguments for RMI client.
Returns:
Configured LangChain agent executor.
"""
try:
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
toolkit = RMIToolkit(api_key=api_key, **kwargs)
tools = toolkit.get_tools()
llm = ChatOpenAI(model=model, temperature=0)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""You are a crypto security analyst powered by Rug Munch Intelligence (RMI).
You have access to 230+ crypto intelligence tools for:
- Wallet scanning and forensics
- Smart contract auditing
- Whale tracking and analysis
- Rug pull prediction
- Market sentiment and narratives
- Reputation scoring
Always use the appropriate tool for the user's query. If a tool returns no data, try a different approach.
Provide clear, actionable recommendations based on the tool results.""",
),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
]
)
agent = create_tool_calling_agent(llm, tools, prompt)
return AgentExecutor(agent=agent, tools=tools, verbose=True)
except ImportError as e:
raise ImportError(
"langchain and langchain_openai are required: pip install langchain langchain-openai"
) from e
def create_crewai_rmi_tools(api_key: str | None = None, **kwargs):
"""Create CrewAI-compatible tools.
Usage:
from rmi_langchain import create_crewai_rmi_tools
from crewai import Agent, Task, Crew
tools = create_crewai_rmi_tools(api_key="rmi_dev_...")
analyst = Agent(
role="Crypto Security Analyst",
goal="Analyze crypto wallets and tokens for security risks",
backstory="Expert in detecting rugs, honeypots, and scams",
tools=tools,
verbose=True,
)
"""
toolkit = RMIToolkit(api_key=api_key, **kwargs)
return toolkit.get_tools()