Spaces:
Sleeping
Sleeping
| """Ask a question against the indexed RL corpus. | |
| Usage: | |
| uv run python scripts/ask.py "What is the main idea of PPO?" | |
| uv run python scripts/ask.py --k 8 "How does Rainbow combine Double DQN and PER?" | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| from rich.console import Console | |
| from rich.markdown import Markdown | |
| from rich.panel import Panel | |
| from researchpath.embeddings import Embedder | |
| from researchpath.index import load_index, search | |
| from researchpath.rag import answer | |
| from researchpath.retrieval import HybridRetriever, Reranker | |
| console = Console() | |
| ROOT = Path(__file__).resolve().parents[1] | |
| INDEX_PATH = ROOT / "data" / "index.faiss" | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description="Ask the RL corpus a question.") | |
| parser.add_argument("question", help="Question to ask.") | |
| parser.add_argument("--k", type=int, default=5, help="Top-k chunks to retrieve (default: 5).") | |
| parser.add_argument("--hybrid", action="store_true", help="Use BM25+FAISS hybrid retrieval.") | |
| parser.add_argument("--rerank", action="store_true", help="Apply cross-encoder reranking on retrieved candidates.") | |
| parser.add_argument( | |
| "--show-sources", | |
| action="store_true", | |
| help="Print the retrieved chunks before the answer.", | |
| ) | |
| args = parser.parse_args() | |
| if not INDEX_PATH.exists(): | |
| console.print(f"[red]No index at {INDEX_PATH}. Run scripts/build_index.py first.[/red]") | |
| return 1 | |
| if args.rerank: | |
| mode = "hybrid+rerank (BM25+FAISS+CrossEncoder)" if args.hybrid else "dense+rerank (FAISS+CrossEncoder)" | |
| else: | |
| mode = "hybrid (BM25+FAISS)" if args.hybrid else "dense (FAISS)" | |
| console.print(f"[bold cyan]Q:[/bold cyan] {args.question} [dim][retrieval: {mode}][/dim]\n") | |
| index, chunks = load_index(INDEX_PATH) | |
| embedder = Embedder() | |
| if args.rerank: | |
| base = HybridRetriever(index, chunks, embedder) if args.hybrid else None | |
| if base is None: | |
| # Wrap dense FAISS in a hybrid-compatible interface for the reranker | |
| base = HybridRetriever(index, chunks, embedder) | |
| reranker = Reranker(base) | |
| hits = reranker.search(args.question, k=args.k) | |
| elif args.hybrid: | |
| retriever = HybridRetriever(index, chunks, embedder) | |
| hits = retriever.search(args.question, k=args.k) | |
| else: | |
| hits = search(index, chunks, embedder, args.question, k=args.k) | |
| if args.show_sources: | |
| for i, h in enumerate(hits, 1): | |
| console.print( | |
| Panel( | |
| h.text, | |
| title=f"#{i} [{h.arxiv_id}, p{h.page}] score={h.score:.3f}", | |
| border_style="dim", | |
| ) | |
| ) | |
| console.print() | |
| result = answer(args.question, hits) | |
| console.print(Panel(Markdown(result.answer), title="Answer", border_style="green")) | |
| console.print( | |
| f"\n[dim]Retrieved {len(hits)} chunks | " | |
| f"model: {result.llm.model} | " | |
| f"tokens: {result.llm.input_tokens} in / {result.llm.output_tokens} out[/dim]" | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |