"""Production answer path: retrieval -> model selection -> guard -> rendered text.""" from __future__ import annotations import threading from pathlib import Path from tools.rag.common import DB_FILE from tools.rag.rag_utils import build_rag_context from tools.rag.llm_context import compact_rag_payload from tools.graphrag.graph_search import search_graph from tools.answer_evaluation.graph_context import graph_context from tools.answer_evaluation.checked_generation import generate_checked_answer from tools.answer_evaluation.verified_rag import generate_verified_rag from tools.answer_evaluation.rag_evidence import expand_comparison_sources from tools.answer_evaluation.rag_preparation import prepare_named_content from tools.retrieval.question_constraints import unsupported_constraint, constraint_message from tools.sync.index_state import index_snapshot from tools.answers.model_client import complete_with_configured_model from tools.answers.answer_errors import public_failure _capacity = threading.BoundedSemaphore(2) def retrieval_failure(engine: str) -> dict: return public_failure({'reason':'retrieval_unavailable','engine':engine,'index_version':None, 'complete':False,'truncated':False,'truncation_reasons':[]}) def deliver_answer(question: str, *, engine: str = 'rag', limit: int = 5, complete=None, db_file: Path = DB_FILE, include_diagnostics: bool = False) -> dict: if not _capacity.acquire(blocking=False): return {'status': 'error', 'reason': 'answer_capacity_exceeded', 'accepted_for_delivery': False, 'answer': 'Agent práve spracúva iné odpovede. Skúste to o chvíľu.'} try: model = complete or complete_with_configured_model unsupported=unsupported_constraint(question) if unsupported: return {'status':'ambiguous','reason':'unsupported_constraint','accepted_for_delivery':True, 'answer':constraint_message(unsupported),'claims':[],'source_urls':[], 'index_version':None,'complete':False,'truncated':False,'truncation_reasons':[], 'engine':engine} if engine == 'rag': try: with index_snapshot(db_file.parent, db_file=db_file): retrieved = build_rag_context(db_file, question, limit=limit) retrieved = expand_comparison_sources(db_file, question, retrieved) retrieved = prepare_named_content(db_file, question, retrieved) retrieved = compact_rag_payload(retrieved) except Exception: return retrieval_failure(engine) result = generate_verified_rag(question, retrieved, model) context = retrieved elif engine == 'graphrag': try: context = graph_context(search_graph(question)) except Exception: return retrieval_failure(engine) if context.get('clarification'): return {'status':'ambiguous','reason':context.get('reason'),'accepted_for_delivery':True, 'answer':context['clarification'],'claims':[],'source_urls':[], 'engine':engine, 'complete':False,'truncated':False,'truncation_reasons':[], 'index_version':context.get('index_version')} prompt = (Path(__file__).resolve().parents[1] / 'answer_evaluation/graph_answer_prompt.txt').read_text() generated = generate_checked_answer(question, context, prompt, model) if generated.get('accepted_for_delivery'): result = {**generated['answer'], 'accepted_for_delivery': True, 'reason': None, 'index_version': context.get('index_version'), 'complete': context.get('complete'), 'truncated': context.get('truncated'), 'truncation_reasons': context.get('truncation_reasons'), 'attempts': generated['attempts']} else: reason=generated.get('reason','answer_rejected') result = {'status': 'error', 'reason': reason, 'accepted_for_delivery': False, 'answer': ('Jazykový model nie je dostupný. Skús otázku neskôr.' if reason=='model_unavailable' else 'Odpoveď neprešla kontrolou podkladov; neoverené údaje sa nezobrazia.'), 'index_version': context.get('index_version'), 'attempts': generated['attempts']} else: raise ValueError('Unsupported answer engine') return {**result, 'engine': engine, **({'retrieval_context': context} if include_diagnostics else {})} finally: _capacity.release()