88 lines
5.2 KiB
Python
88 lines
5.2 KiB
Python
"""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':
|
|
from tools.answer_evaluation.question_intent import graph_needs_text
|
|
if graph_needs_text(question):
|
|
return {'status':'ambiguous','reason':'text_evidence_required','accepted_for_delivery':True,
|
|
'answer':'Táto otázka vyžaduje textové podklady o zadaní, priebehu alebo výsledkoch práce. '
|
|
'Použi /rag s rovnakou otázkou; graf samotný tieto výňatky neposkytuje.',
|
|
'claims':[],'source_urls':[],'engine':engine,'index_version':None,
|
|
'complete':False,'truncated':False,'truncation_reasons':[]}
|
|
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()
|