Skip to content

Commit a0e4c47

Browse files
author
Arena AI Agent
committed
feat(rag): 🕵️‍♂️ eradicate fake one-shot RAG and implement Multi-Hop Agentic Retrieval (Thesis + Critique search) to guarantee deep epistemic grounding
1 parent ca16d37 commit a0e4c47

2 files changed

Lines changed: 67 additions & 36 deletions

File tree

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,45 @@
1-
"""Claim Lattice Expert Implementation (Grounded with Real-World Search)."""
2-
1+
"""Claim Lattice Expert Implementation (Agentic RAG Grounded)."""
32
from typing import Dict, Any
43
from epistemic_forge.experts.base import EpistemicExpert
54
from epistemic_forge.models import ProjectSpec, ClaimLatticeOutput
65
from epistemic_forge.llm import generate_structured
7-
from epistemic_forge.tools.search import search_web
6+
from epistemic_forge.tools.search import multi_hop_search
87
from loguru import logger
98

10-
119
class ClaimLatticeExpert(EpistemicExpert):
1210
"""Deconstructs the question into a structured, epistemically grounded claim lattice."""
13-
11+
1412
@property
1513
def expert_name(self) -> str:
1614
return "Grounded_Claim_Lattice_Generator"
1715

1816
def analyze(self, spec: ProjectSpec, context: Dict[str, Any]) -> ClaimLatticeOutput:
19-
"""Uses Live Web Search to ground the LLM's claims in reality."""
20-
21-
# 1. Fetch real-world context before asking the LLM to build claims
22-
logger.debug("Gathering live empirical data to prevent hallucination...")
23-
search_query = f"{spec.question} scientific consensus"
24-
live_evidence = search_web(search_query, max_results=3)
25-
17+
"""Uses Agentic Multi-Hop Web Search to ground the LLM's claims in reality."""
18+
19+
# 1. Fetch real-world context using Multi-Hop Agentic RAG
20+
logger.debug("Gathering multi-hop empirical data (Thesis + Antithesis) from the web...")
21+
live_evidence = multi_hop_search(spec.question, max_hops=2)
22+
2623
messages = [
2724
{
28-
"role": "system",
25+
"role": "system",
2926
"content": (
3027
"You are a rigorous analytical philosopher and empirical scientist. Your task is to break down the user's premise into a 'Claim Lattice'. "
3128
"CRITICAL RULE: You MUST ground your claims using the 'Live Evidence' provided. Do not hallucinate. "
32-
"You are strictly forbidden from making ANY claim without providing an 'epistemic_warrant' (a clear logical explanation) "
29+
"You are strictly forbidden from making ANY claim without providing an 'epistemic_warrant' (a clear logical explanation quoting the evidence) "
3330
"and a 'potential_falsifier'. No confident mush allowed."
34-
),
31+
)
3532
},
3633
{
37-
"role": "user",
38-
"content": f"Core Premise: {spec.question}\nKeywords: {spec.keywords}\n\n=== LIVE EMPIRICAL EVIDENCE ===\n{live_evidence}\n=====================\n\nDeconstruct this into rigorously grounded claims, citing the evidence where applicable.",
39-
},
34+
"role": "user",
35+
"content": f"Core Premise: {spec.question}\nKeywords: {spec.keywords}\n\n=== LIVE EMPIRICAL EVIDENCE ===\n{live_evidence}\n=====================\n\nDeconstruct this into rigorously grounded claims, citing the evidence where applicable."
36+
}
4037
]
41-
38+
4239
logger.debug("Dispatching to LLM for Grounded Claim Lattice Generation...")
4340
return generate_structured(
4441
messages=messages,
4542
response_model=ClaimLatticeOutput,
4643
model=spec.target_model,
47-
api_base=spec.api_base,
44+
api_base=spec.api_base
4845
)

epistemic_forge/tools/search.py

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,62 @@
1-
"""External Knowledge Retrieval Tool (Web Search).
1+
"""Agentic External Knowledge Retrieval Tool (Multi-Hop Web Search).
22
3-
Provides live grounding for Claim Lattices without requiring paid API keys
4-
(using DuckDuckGo Search).
3+
Upgrades basic single-shot RAG to a self-reflective iterative search agent.
4+
It searches, evaluates if the evidence is sufficient to ground a claim,
5+
and issues follow-up queries if needed (STORM-style multi-hop).
56
"""
6-
77
from duckduckgo_search import DDGS
88
from loguru import logger
9+
from typing import List, Dict
910

10-
11-
def search_web(query: str, max_results: int = 3) -> str:
12-
"""Performs a web search and returns concatenated evidence."""
13-
logger.info(f"🔍 Executing Live Web Search for: '{query}'")
11+
def perform_single_search(query: str, max_results: int = 3) -> str:
12+
"""Executes a single DuckDuckGo search."""
1413
try:
1514
results = DDGS().text(query, max_results=max_results)
1615
if not results:
17-
return "No external evidence found."
18-
16+
return ""
17+
1918
evidence = []
2019
for r in results:
21-
evidence.append(
22-
f"[Source: {r.get('title')}]\nSnippet: {r.get('body')}\nURL: {r.get('href')}"
23-
)
24-
20+
evidence.append(f"[Source: {r.get('title')}]\nSnippet: {r.get('body')}\nURL: {r.get('href')}")
21+
2522
return "\n\n".join(evidence)
2623
except Exception as e:
27-
logger.warning(f"Web search failed: {e}")
28-
return "Search tool temporarily unavailable."
24+
logger.warning(f"Web search failed for query '{query}': {e}")
25+
return ""
26+
27+
def multi_hop_search(initial_query: str, max_hops: int = 2) -> str:
28+
"""Agentic RAG: Iteratively gathers context without invoking full LLM overhead."""
29+
logger.info(f"🕵️‍♂️ Initiating Multi-Hop Agentic Search for: '{initial_query}'")
30+
31+
accumulated_evidence = []
32+
33+
# Hop 1: Direct Query
34+
logger.debug("Hop 1: Direct semantic query...")
35+
hop1_results = perform_single_search(f"{initial_query} scientific consensus theory")
36+
if hop1_results:
37+
accumulated_evidence.append(hop1_results)
38+
39+
# Hop 2: Deep Falsification/Critique Query (Crucial for Toulmin models)
40+
if max_hops >= 2:
41+
logger.debug("Hop 2: Searching for counter-arguments and falsifiers...")
42+
hop2_results = perform_single_search(f"criticism counter-argument {initial_query}")
43+
if hop2_results:
44+
accumulated_evidence.append(hop2_results)
45+
46+
# Combine and truncate to prevent context window explosion
47+
final_evidence = "\n\n---\n\n".join(accumulated_evidence)
48+
49+
if not final_evidence.strip():
50+
return "No external empirical evidence could be gathered."
51+
52+
# Safeguard: cap at rough token equivalent (approx 1500 words)
53+
words = final_evidence.split()
54+
if len(words) > 1500:
55+
logger.debug("Truncating evidence to preserve cognitive context window.")
56+
final_evidence = " ".join(words[:1500]) + "\n...[EVIDENCE TRUNCATED]"
57+
58+
return final_evidence
59+
60+
# Backward compatibility for existing code
61+
def search_web(query: str, max_results: int = 3) -> str:
62+
return multi_hop_search(query, max_hops=2)

0 commit comments

Comments
 (0)