"""Grounded answer generation.""" from __future__ import annotations import logging from typing import TYPE_CHECKING from python.ebook_search.llm_interface import request_chat_completion if TYPE_CHECKING: from python.ebook_search.config import EbookSearchConfig from python.ebook_search.search import SearchResult logger = logging.getLogger(__name__) def answer_query(query: str, results: list[SearchResult], config: EbookSearchConfig) -> str: """Answer a question using only retrieved chunks.""" if not config.answer_enabled: logger.info("ebook_answer_skipped_disabled") return "Answer generation is disabled. Source chunks are shown below." if not results: logger.info("ebook_answer_skipped_no_results") return "No relevant sources were found." logger.info( "ebook_answer_request_start base_url=%s model=%s sources=%s query_length=%s", config.vllm_base_url, config.chat_model, len(results), len(query), ) context = "\n\n".join( f"[{index}] {result.source_title}{' - ' + result.chapter_title if result.chapter_title else ''}\n{result.text}" for index, result in enumerate(results, start=1) ) content = request_chat_completion( config, [ { "role": "system", "content": ( "Answer only from the provided context. Cite sources with bracketed numbers like [1]. " "If the context is insufficient, say so." ), }, {"role": "user", "content": f"Question:\n{query}\n\nContext:\n{context}"}, ], ) logger.info( "ebook_answer_request_complete model=%s answer_length=%s", config.chat_model, len(content), ) return content or "The model returned an empty answer."