rmi-backend/app/_archive/legacy_2026_07/rag_tool.py
cryptorugmunch 628c1d2a10
Some checks failed
CI / build (push) Failing after 3s
refactor(rmi-backend,audit): mount Wave 3 + archive 136 dead-code files (P2.3)
PHASE 2.3 (AUDIT-2026-Q3.md):

Task 1 — Wire-in Wave 3 (1 router mounted, 2 deferred):
  - app.routers.unified_scanner_router mounted at /api/v2/scanner/* (2 routes:
    POST /api/v2/scanner/token/scan, POST /api/v2/scanner/wallet/scan).
    Refactored prefix from /api/v2 -> /api/v2/scanner to avoid future conflicts
    with the v1 /api/v1/scanner/ stub.
  - app.routers.unified_wallet_scanner DEFERRED (no router APIRouter attribute;
    library module consumed by unified_scanner_router via get_wallet_scanner()).
  - app.routers.admin_extensions DEFERRED (DORMANT per audit; 25 routes at
    /api/v1/admin/* would shadow /api/v1/admin/alerts_webhook).

Task 2 — Archive 136 dead-code files to app/_archive/legacy_2026_07/:
  - 73 routers in app/routers/ (reach graph showed zero reach into mount.py).
  - 63 flat app/*.py (domain modules never imported by live code).
  - 1 file RESTORED post-archive: app/routers/x402_bridge_health.py (caught by
    tests/unit/test_bridge_health.py which directly imports it; reach graph
    considered tests/ only as transitive reach — to be patched in next cycle).

Forced-LIVE (NOT archived per user directive):
  - app/ai_pipeline_v3.py  (3 importers in audit window, importers themselves DEAD)
  - app/splade_bm25.py       (LIVE via app.rag_service)
  - app/wallet_manager_v2.py (LIVE via x402_enforcement, x402_tools, sweep_all, sweep_now)
  - app/crypto_embeddings.py (NOT in audit ARCHIVE list; heavy import graph)

Verification (forward-import closure from mount.py + main.py + factory.py + lifespan.py):
  - imports = 348 app.* modules
  - reached = 194 files reachable from roots
  - archive set = audit_dead (186) - reached - forced_live (4) - test_live (1) = 136
  - Net delta: 136 files moved, 44,932 LOC reduction, 293->295 active routes (+2 from Wave 3)

pyproject.toml updates:
  - setuptools.packages.find: added exclude for app._archive*
  - ruff.extend-exclude: added "app/_archive/"
  - mypy.exclude: added "app/_archive/"

Smoke test: pytest tests/ — 817 passed, 3 pre-existing failures unchanged
(0 new failures; 0 routes lost; all 4 forced-LIVE files still importable).

Restoration: git mv app/_archive/legacy_2026_07/<name>.py <original-path>
and add the import to app/mount.py ROUTER_MODULES.

Refs: AUDIT-2026-Q3.md /home/dev/pry/rmi-final-deadcode-2026-07-06.md
2026-07-06 20:52:31 +02:00

253 lines
8.9 KiB
Python

#!/usr/bin/env python3
"""
RAG Tool Interface for OpenClaw Agents (2026 Agentic RAG Approach)
- Exposes RAG as a tool callable via Hermes subagents
- Supports reflection loops and multi-hop retrieval
- Integrates with OpenClaw tool schema
"""
import json
import sys
from pathlib import Path
from typing import Any
# Add RAG lib to path
sys.path.insert(0, str(Path.home() / ".hermes" / "scripts"))
from rag_system_v2 import RMIRAGSystem
class RagToolInterface:
"""2026 Agentic RAG tool wrapper."""
def __init__(self):
self.rag = RMIRAGSystem()
self.max_iterations = 3 # Prevent infinite loops
def query(
self,
query: str,
namespace: str | None = None,
n_results: int = 5,
min_confidence: float = 0.7,
) -> dict[str, Any]:
"""
Query RAG and return results with reflection.
Implements 2026 Agentic RAG pattern:
1. Initial retrieval
2. Self-evaluation (confidence check)
3. Refinement if needed
4. Evidence-weighted synthesis
"""
# Step 1: Initial retrieval
results = self.rag.query(query, n=n_results, source_filter=namespace)
# Convert ChromaDB distance to confidence score
def distance_to_confidence(distance: float) -> float:
"""ChromaDB distance → confidence (lower distance = higher confidence)."""
if distance is None:
return 0.5
return max(0.0, 1.0 - distance)
# Convert results to standard format
formatted_results = []
for r in results:
conf = distance_to_confidence(r.get("distance"))
if conf >= min_confidence:
formatted_results.append(
{
"text": r["text"],
"metadata": r["metadata"],
"confidence": round(conf, 3),
"source": r["metadata"].get("source", "unknown"),
}
)
# Step 2: Self-evaluation
confidence_score = len(formatted_results) / n_results # heuristic
output = {
"query": query,
"results": formatted_results,
"confidence": round(confidence_score, 3),
"n_retrieved": len(formatted_results),
"metadata": self.rag.get_stats(),
}
# Step 3: Reflection loop if confidence low
if confidence_score < 0.6:
output["reflection"] = {
"step": 1,
"original_confidence": confidence_score,
"threshold": 0.6,
"refined_query": f"additional context for: {query}",
"action": "refining",
}
# Try refinement (single additional query)
refined_results = self.rag.query(output["reflection"]["refined_query"], n=n_results)
for r in refined_results:
conf = distance_to_confidence(r.get("distance"))
if conf >= min_confidence:
formatted_results.append(
{
"text": r["text"],
"metadata": r["metadata"],
"confidence": round(conf, 3),
"source": r["metadata"].get("source", "unknown"),
}
)
output["results"] = formatted_results
output["n_refined"] = len(refined_results)
return output
def multi_hop_query(self, question: str, hops: list[dict[str, str]]) -> dict[str, Any]:
"""
Multi-hop retrieval: chain multiple queries.
Example:
hops = [
{"purpose": "finding token patterns", "query": "rug pull token patterns"},
{"purpose": "scam indicators", "query": " scam indicators from recent rugs"}
]
"""
all_results = []
for hop in hops:
hop_results = self.query(hop["query"])
hop_result_list = [
{"text": r["text"], "source": r["source"], "hop": hop["purpose"]} for r in hop_results["results"]
]
all_results.extend(hop_result_list)
return {
"question": question,
"hops": len(hops),
"results": all_results,
"total_results": len(all_results),
}
def reflection_loop(self, original_query: str, max_iterations: int = 3) -> dict[str, Any]:
"""
Self-correcting retrieval with iteration budgets.
Stops when:
- Confidence threshold reached (0.8)
- Max iterations exceeded
- No new results found
"""
iterations = []
current_query = original_query
all_results = []
for i in range(max_iterations):
result = self.query(current_query, n_results=5)
iterations.append(
{
"step": i,
"query": current_query,
"results_count": len(result["results"]),
"confidence": result["confidence"],
}
)
# Add new results
all_results.extend(result["results"])
# Check stop conditions
if result["confidence"] >= 0.8:
iterations[-1]["reason"] = "confidence_threshold_reached"
break
if len(result["results"]) == 0:
iterations[-1]["reason"] = "no_new_results"
break
# Refine query for next iteration
current_query = f"details about: {current_query}"
return {
"original_query": original_query,
"iterations": iterations,
"final_results": all_results,
}
def get_tool_schema(self) -> dict[str, Any]:
"""Return OpenAI-compatible tool schema for agents."""
return {
"type": "function",
"function": {
"name": "rag_query",
"description": (
"Query RugMunch Intelligence RAG for crypto security patterns, "
"rug pull indicators, wallet risk scores, and market intelligence."
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": 'Search query for RAG (e.g., "rug pull patterns", "wallet risk indicators")',
},
"namespace": {
"type": "string",
"enum": [
"scan_results",
"alerts",
"content",
"market_data",
"onchain",
"social_feed",
"news",
],
"default": None,
"description": "Optional source filter to narrow search",
},
"n_results": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"default": 5,
"description": "Number of results to retrieve",
},
},
"required": ["query"],
},
},
}
# ============================================================
# CLI INTERFACE
# ============================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="RAG Tool Interface for OpenClaw Agents")
parser.add_argument("command", choices=["query", "reflection", "multi-hop", "schema"])
parser.add_argument("--query", type=str, help="Query text")
parser.add_argument("--namespace", type=str, help="Source namespace filter")
parser.add_argument("--n", type=int, default=5, help="Number of results")
parser.add_argument("--hops", type=str, help="Multi-hop config (JSON)")
parser.add_argument("--max-iter", type=int, default=3, help="Max reflection iterations")
args = parser.parse_args()
rag = RagToolInterface()
if args.command == "query":
result = rag.query(args.query, namespace=args.namespace, n_results=args.n)
print(json.dumps(result, indent=2))
elif args.command == "reflection":
result = rag.reflection_loop(args.query, max_iterations=args.max_iter)
print(json.dumps(result, indent=2))
elif args.command == "multi-hop":
hops = json.loads(args.hops)
result = rag.multi_hop_query(args.query, hops)
print(json.dumps(result, indent=2))
elif args.command == "schema":
print(json.dumps(rag.get_tool_schema(), indent=2))