Wire RRF merge into bot.py — replace keyword+vector concatenation

bot.py now calls orchestrate_retrieval() from retrieval.py instead of
doing keyword retrieval and vector search separately then concatenating.
Claims found by both systems get RRF-boosted to the top. Query
decomposition (multi-part → sub-queries) is now active.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
m3taversal 2026-04-01 15:27:13 +01:00
parent 71b62e6d03
commit b766176259

View file

@ -42,7 +42,8 @@ from telegram.ext import (
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import json as _json import json as _json
from kb_retrieval import KBIndex, format_context_for_prompt, retrieve_context from kb_retrieval import KBIndex, retrieve_context, retrieve_vector_context
from retrieval import orchestrate_retrieval
from market_data import get_token_price, format_price_context from market_data import get_token_price, format_price_context
from worktree_lock import main_worktree_lock from worktree_lock import main_worktree_lock
from x_client import search_tweets, fetch_from_url, check_research_rate_limit, record_research_usage, get_research_remaining from x_client import search_tweets, fetch_from_url, check_research_rate_limit, record_research_usage, get_research_remaining
@ -994,41 +995,24 @@ async def handle_tagged(update: Update, context: ContextTypes.DEFAULT_TYPE):
logger.warning("Query reformulation failed: %s", e) logger.warning("Query reformulation failed: %s", e)
# Fall through — use raw text # Fall through — use raw text
# Retrieve full KB context (entity resolution + claim search + agent positions) # Unified retrieval: keyword → decompose → vector → RRF merge
t_kb = time.monotonic() # Both systems search independently; RRF boosts claims found by both.
kb_ctx = retrieve_context(search_query_text, KB_READ_DIR, index=kb_index) def _vector_fn(q):
kb_context_text = format_context_for_prompt(kb_ctx) return retrieve_vector_context(q) # no keyword_paths exclusion — RRF deduplicates
kb_duration = int((time.monotonic() - t_kb) * 1000) retrieval_result = await orchestrate_retrieval(
retrieval_layers = ["keyword"] if (kb_ctx and (kb_ctx.entities or kb_ctx.claims)) else [] text=text,
tool_calls.append({ search_query=search_query_text,
"tool": "retrieve_context", kb_read_dir=KB_READ_DIR,
"input": {"query": search_query_text[:200], "original_query": text[:200] if search_query_text != text else None}, kb_index=kb_index,
"output": {"entities": len(kb_ctx.entities) if kb_ctx else 0, llm_fn=call_openrouter,
"claims": len(kb_ctx.claims) if kb_ctx else 0}, triage_model=TRIAGE_MODEL,
"duration_ms": kb_duration, retrieve_context_fn=retrieve_context,
}) retrieve_vector_fn=_vector_fn,
)
# Layer 1+2: Qdrant vector search + graph expansion (semantic, complements keyword) kb_context_text = retrieval_result["kb_context_text"]
# Pass keyword-matched paths to exclude duplicates at Qdrant query level kb_ctx = retrieval_result["kb_ctx"]
# Normalize: KBIndex stores absolute paths, Qdrant stores repo-relative paths retrieval_layers = retrieval_result["retrieval_layers"]
keyword_paths = [] tool_calls.extend(retrieval_result["tool_calls"])
if kb_ctx and kb_ctx.claims:
for c in kb_ctx.claims:
p = c.path
if KB_READ_DIR and p.startswith(KB_READ_DIR):
p = p[len(KB_READ_DIR):].lstrip("/")
keyword_paths.append(p)
from kb_retrieval import retrieve_vector_context
vector_context, vector_meta = retrieve_vector_context(search_query_text, keyword_paths=keyword_paths)
if vector_context:
kb_context_text = kb_context_text + "\n\n" + vector_context
retrieval_layers.extend(vector_meta.get("layers_hit", []))
tool_calls.append({
"tool": "retrieve_qdrant_context", "input": {"query": text[:200]},
"output": {"direct_hits": len(vector_meta.get("direct_results", [])),
"expanded": len(vector_meta.get("expanded_results", []))},
"duration_ms": vector_meta.get("duration_ms", 0),
})
stats = get_db_stats() stats = get_db_stats()