Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from ..ingest.embedder import Embedder | |
| from .citations import Citation, extract_citations, render_sources | |
| from .llm import LLMClient | |
| from .loader import RetrievalIndex | |
| from .prompt import build_context, detect_language, system_prompt | |
| from .retrieve import retrieve | |
| class AnswerResult: | |
| answer: str | |
| sources: list[Citation] | |
| sources_md: str | |
| used_ids: list[str] = field(default_factory=list) | |
| THREAD_RULE_EN = ("This is a continuing conversation. Cite ONLY [S#] ids that appear in the " | |
| "CURRENT sources below; ids mentioned in earlier answers are not valid.") | |
| THREAD_RULE_IT = ("Questa è una conversazione in corso. Cita SOLO gli id [S#] presenti nelle " | |
| "fonti ATTUALI qui sotto; gli id citati nelle risposte precedenti non sono validi.") | |
| _MAX_TURNS = 3 # turns of context shown to the model | |
| _CLIP_CHARS = 600 # per-answer clip so old answers can't crowd out sources | |
| def _conversation_block(prior: list[dict]) -> str: | |
| lines = [] | |
| for t in prior[-_MAX_TURNS:]: | |
| a = str(t.get("a", "")) | |
| if len(a) > _CLIP_CHARS: | |
| a = a[:_CLIP_CHARS] + " …" | |
| lines.append(f"USER: {t.get('q', '')}\nASSISTANT: {a}") | |
| return "CONVERSATION SO FAR:\n" + "\n\n".join(lines) | |
| def generate_from(query: str, scored, llm: LLMClient, token_budget: int = 4000) -> AnswerResult: | |
| """Build the grounded prompt from already-retrieved chunks, generate, attach citations.""" | |
| context, id_map = build_context(scored, token_budget) | |
| if not id_map: | |
| return AnswerResult("I couldn't find anything about that in the materials.", [], "") | |
| text = llm.complete(system_prompt(detect_language(query)), f"SOURCES:\n{context}\n\nQUESTION: {query}") | |
| cites = extract_citations(text, id_map) | |
| return AnswerResult(answer=text, sources=cites, sources_md=render_sources(cites), | |
| used_ids=[c.sid for c in cites]) | |
| def answer(query: str, index: RetrievalIndex, embedder: Embedder, llm: LLMClient, | |
| k: int = 6, token_budget: int = 4000, reranker=None) -> AnswerResult: | |
| return generate_from(query, retrieve(query, index, embedder, k=k, reranker=reranker), llm, token_budget) | |
| def generate_in_thread(query: str, prior: list[dict], scored, llm: LLMClient, | |
| token_budget: int = 4000) -> AnswerResult: | |
| """generate_from for an ongoing thread: same grounding, plus a clipped transcript of the | |
| last turns so follow-ups can resolve references; citations come only from current sources.""" | |
| context, id_map = build_context(scored, token_budget) | |
| if not id_map: | |
| return AnswerResult("I couldn't find anything about that in the materials.", [], "") | |
| lang = detect_language(query) | |
| system = system_prompt(lang) + "\n" + (THREAD_RULE_IT if lang == "it" else THREAD_RULE_EN) | |
| convo = _conversation_block(prior) + "\n\n" if prior else "" | |
| text = llm.complete(system, f"{convo}SOURCES:\n{context}\n\nQUESTION: {query}") | |
| cites = extract_citations(text, id_map) | |
| return AnswerResult(answer=text, sources=cites, sources_md=render_sources(cites), | |
| used_ids=[c.sid for c in cites]) | |
| def answer_in_thread(query: str, prior: list[dict], index: RetrievalIndex, embedder: Embedder, | |
| llm: LLMClient, k: int = 6, token_budget: int = 4000, reranker=None) -> AnswerResult: | |
| """answer() with conversation context: retrieval still runs on the current query alone.""" | |
| return generate_in_thread(query, prior, retrieve(query, index, embedder, k=k, reranker=reranker), | |
| llm, token_budget) | |