Download app.py from Angshul/BRAG: direct link, hf CLI and curl.
- Browser
- Download file 8.21 kB
-
https://huggingface.co/spaces/Angshul/BRAG/resolve/main/app.py
- Command line
-
hf download hf://spaces/Angshul/BRAG/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Angshul/BRAG/resolve/main/app.py
8.21 kB
| """BRAG - Bayesian RAG: Gradio Space (live mode) with a programmatic API.""" | |
| import json, os, hashlib, threading | |
| from functools import lru_cache | |
| import gradio as gr | |
| from brag import BRAG, Corpus, hf_llm | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| CORPORA = {'HotpotQA': 'hotpotqa_corpus.json', '2WikiMultihopQA': '2wikimultihopqa_corpus.json', | |
| 'MuSiQue': 'musique_corpus.json'} | |
| DEFAULT_MODEL = os.getenv('BRAG_MODEL', 'Qwen/Qwen2.5-7B-Instruct') | |
| CAL_PATH = os.path.join(HERE, 'data', 'calibration.json') | |
| DEFAULT_CAL = json.load(open(CAL_PATH)) if os.path.exists(CAL_PATH) else None | |
| _lock = threading.Lock() | |
| def builtin_corpus(name): | |
| return Corpus.from_json(os.path.join(HERE, 'data', CORPORA[name])) | |
| def custom_corpus(digest, text): | |
| text = text.strip() | |
| if text.startswith('['): | |
| rows = json.loads(text) | |
| ps = [r if isinstance(r, str) else f"{r.get('title', '')}: {r.get('text', '')}" for r in rows] | |
| else: | |
| ps = [l.strip() for l in text.splitlines() if l.strip()] | |
| return Corpus(ps) | |
| def get_corpus(corpus, documents): | |
| if documents and documents.strip(): | |
| return custom_corpus(hashlib.sha1(documents.encode()).hexdigest(), documents) | |
| if corpus not in CORPORA: | |
| raise gr.Error(f'corpus must be one of {list(CORPORA)} or documents must be given') | |
| with _lock: | |
| return builtin_corpus(corpus) | |
| def get_llm(model, hf_token, provider): | |
| if os.getenv('BRAG_MOCK') == '1': # local testing only | |
| from mock_llm import mock_llm | |
| return mock_llm | |
| token = (hf_token or '').strip() or os.getenv('HF_TOKEN') | |
| if not token: | |
| raise gr.Error('No Hugging Face token: pass hf_token, or set the HF_TOKEN secret on the Space.') | |
| return hf_llm((model or DEFAULT_MODEL).strip(), token, (provider or 'auto').strip()) | |
| def parse_cal(calibration): | |
| if calibration and str(calibration).strip(): | |
| try: | |
| return json.loads(calibration) if isinstance(calibration, str) else calibration | |
| except json.JSONDecodeError as e: | |
| raise gr.Error(f'calibration is not valid JSON: {e}') | |
| return DEFAULT_CAL | |
| def brag(question: str, corpus: str = 'HotpotQA', documents: str = '', lam: float = 0.05, | |
| model: str = DEFAULT_MODEL, hf_token: str = '', provider: str = 'auto', calibration: str = ''): | |
| """Answer a question with BRAG (Bayesian RAG). | |
| Args: | |
| question: the question to answer. | |
| corpus: built-in corpus to search: HotpotQA, 2WikiMultihopQA or MuSiQue (ignored if documents given). | |
| documents: optional custom corpus: one passage per line, or a JSON list of strings / {title, text}. | |
| lam: cost of one LLM call in units of answer error (higher = cheaper, earlier stopping). | |
| model: Hugging Face model id, or URL of an OpenAI-compatible endpoint (dedicated Inference Endpoint, TGI, vLLM). | |
| hf_token: optional Hugging Face token (defaults to the Space's HF_TOKEN secret). | |
| provider: Hugging Face inference provider ('auto' picks one that serves the model). | |
| calibration: optional JSON calibration (output of the calibrate endpoint) for this model. | |
| Returns: | |
| answer, and a JSON report with the per-step posterior, information rates, actions and costs. | |
| """ | |
| if not question or not question.strip(): | |
| raise gr.Error('question is empty') | |
| engine = BRAG(get_llm(model, hf_token, provider), parse_cal(calibration), lam=float(lam)) | |
| try: | |
| res = engine.run(question.strip(), get_corpus(corpus, documents)) | |
| except gr.Error: | |
| raise | |
| except Exception as e: | |
| raise gr.Error(f'LLM/provider error: {type(e).__name__}: {e}') | |
| rep = res.to_dict(); rep['model'] = model; rep['lambda'] = float(lam) | |
| return res.answer or '', rep | |
| def calibrate(labelled_json: str, corpus: str = 'HotpotQA', documents: str = '', | |
| model: str = DEFAULT_MODEL, hf_token: str = '', provider: str = 'auto'): | |
| """Calibrate BRAG's hyperpriors for a model/corpus from labelled questions. | |
| Args: | |
| labelled_json: JSON list of {"question": ..., "gold": [aliases]} (30+ recommended, max 200). | |
| corpus / documents / model / hf_token / provider: as in the brag endpoint. | |
| Returns: | |
| calibration JSON to pass as `calibration` to the brag endpoint. | |
| """ | |
| try: | |
| rows = json.loads(labelled_json) | |
| assert isinstance(rows, list) and rows and all('question' in r and 'gold' in r for r in rows) | |
| except Exception: | |
| raise gr.Error('labelled_json must be a JSON list of {"question": ..., "gold": [...]}') | |
| if len(rows) > 200: | |
| raise gr.Error('at most 200 labelled questions per call') | |
| engine = BRAG(get_llm(model, hf_token, provider), DEFAULT_CAL) | |
| try: | |
| return engine.calibrate(rows, get_corpus(corpus, documents)) | |
| except Exception as e: | |
| raise gr.Error(f'LLM/provider error: {type(e).__name__}: {e}') | |
| API_DOC = """ | |
| ### Programmatic use | |
| ```python | |
| from gradio_client import Client | |
| c = Client("Angshul/BRAG") # or Client("Angshul/BRAG", hf_token="hf_...") | |
| answer, report = c.predict( | |
| question="Friedrich Ratzel coined the phrase Lebensraum; which political movement made it notorious?", | |
| corpus="HotpotQA", documents="", lam=0.05, | |
| model="Qwen/Qwen2.5-7B-Instruct", hf_token="", provider="auto", calibration="", | |
| api_name="/brag") | |
| ``` | |
| Own documents: pass `documents` (one passage per line, or a JSON list). Different model: run | |
| `api_name="/calibrate"` once on 30+ labelled questions and pass the returned JSON as `calibration`. | |
| REST: `POST /gradio_api/call/brag` with `{"data": [question, corpus, documents, lam, model, hf_token, provider, calibration]}`. | |
| """ | |
| with gr.Blocks(title='BRAG - Bayesian RAG') as demo: | |
| gr.Markdown('# BRAG — Bayesian RAG\nClosed-form Bayesian orchestration of retrieval-augmented QA. ' | |
| 'The orchestrator makes **no LLM calls**: each decision is arithmetic on conjugate posteriors ' | |
| '(microseconds). It chooses between reading retrieved passages, a second retrieval hop and ' | |
| 'closed-book sampling, and stops when the expected error reduction per call falls below λ.') | |
| with gr.Tab('Ask'): | |
| q = gr.Textbox(label='Question', lines=2) | |
| with gr.Row(): | |
| corpus = gr.Dropdown(list(CORPORA), value='HotpotQA', label='Corpus') | |
| lam = gr.Slider(0.005, 0.3, value=0.05, step=0.005, label='λ (cost per LLM call)') | |
| docs = gr.Textbox(label='Own documents (optional): one passage per line, or JSON list', lines=3) | |
| with gr.Accordion('Model', open=False): | |
| model = gr.Textbox(value=DEFAULT_MODEL, label='HF model id or OpenAI-compatible endpoint URL') | |
| provider = gr.Textbox(value='auto', label='Inference provider') | |
| tok = gr.Textbox(label='HF token (optional; default: Space secret)', type='password') | |
| cal = gr.Textbox(label='Calibration JSON (optional)', lines=2) | |
| go = gr.Button('Answer', variant='primary') | |
| ans = gr.Textbox(label='Answer'); rep = gr.JSON(label='Trace: posterior, information rates, actions, costs') | |
| go.click(brag, [q, corpus, docs, lam, model, tok, provider, cal], [ans, rep], api_name='brag') | |
| with gr.Tab('Calibrate'): | |
| lab = gr.Textbox(label='Labelled questions JSON: [{"question": ..., "gold": [...]}, ...]', lines=6) | |
| with gr.Row(): | |
| corpus2 = gr.Dropdown(list(CORPORA), value='HotpotQA', label='Corpus') | |
| model2 = gr.Textbox(value=DEFAULT_MODEL, label='HF model id') | |
| docs2 = gr.Textbox(label='Own documents (optional)', lines=2) | |
| tok2 = gr.Textbox(label='HF token (optional)', type='password'); prov2 = gr.Textbox(value='auto', label='Provider') | |
| go2 = gr.Button('Calibrate'); out2 = gr.JSON(label='Calibration (pass to the brag endpoint)') | |
| go2.click(calibrate, [lab, corpus2, docs2, model2, tok2, prov2], out2, api_name='calibrate') | |
| gr.Markdown(API_DOC) | |
| demo.queue(default_concurrency_limit=int(os.getenv('BRAG_CONCURRENCY', '16')), max_size=256) | |
| if __name__ == '__main__': | |
| demo.launch() | |