S SmartDocs
Chuỗi bài: Ricky python 63 dòng · Cập nhật 2026-04-07

extractive.py

Ricky/RAG_project/retrieval/extractive.py

"""
Extractive answer builder: picks the most relevant sentences directly from
retrieved documents without involving an LLM.

Used for procedural questions (how to / set / connect / alarm) where
step-by-step instructions are better extracted verbatim.
"""
import re
import logging
from langchain_core.documents import Document

from retrieval.query import normalize_for_match

logger = logging.getLogger(__name__)

_ACTION_KEYWORDS = ["press", "hold", "tap", "set", "button", "connect", "pair", "sync"]


def build_extractive_answer(question: str, docs: list[Document]) -> str:
    """
    Return the 1–3 most relevant sentences from the top docs, scored by
    token overlap with the question, with a bonus for action-instruction words.

    Returns an empty string when no useful sentences are found.
    """
    q_tokens = set(normalize_for_match(question).split())
    if not q_tokens:
        return ""

    candidates: list[tuple[int, str, str]] = []
    for doc in docs[:4]:
        source = doc.metadata.get("source", "unknown")
        text = " ".join(doc.page_content.split())
        parts = re.split(r"(?<=[\.\!\?;])\s+|\s{2,}", text)
        for sent in parts:
            sent = sent.strip()
            if len(sent) < 20:
                continue
            s_tokens = set(normalize_for_match(sent).split())
            overlap = len(q_tokens.intersection(s_tokens))
            if overlap == 0:
                continue
            action_bonus = 2 if any(k in sent.lower() for k in _ACTION_KEYWORDS) else 0
            candidates.append((overlap * 3 + action_bonus, source, sent))

    if not candidates:
        return ""

    candidates.sort(key=lambda x: x[0], reverse=True)
    lines: list[str] = []
    seen: set[str] = set()
    for _, source, sent in candidates:
        key = normalize_for_match(sent)[:120]
        if key in seen:
            continue
        seen.add(key)
        lines.append(f"- {sent} (source: {source})")
        if len(lines) >= 3:
            break

    if not lines:
        return ""
    return "Based on the documents, here are the relevant steps/clues:\n" + "\n".join(lines)

Bài viết liên quan