Галоўная / Артыкулы

Адладка ціхіх збояў RAG: пошук, разбіўка, фільтры і ацэнкі

Адказы RAG могуць гучаць правільна і ўсё роўна быць памылковымі. Інструкцыя для Python-каманд па трасіроўцы пошуку, разбіўкі, фільтраў метаданых, ацэнак, гібрыднага пошуку і абгрунтавання.

Выдавец: REACTAPP.TOP8 хв чытання1774 слоў

Крыніцы: RAG Is Failing Quietly: A Debugging Playbook for Python Teams

Нявідомыя неудачы

Найбольш адразнівальныя багі RAG — гэта не тые, які ствараюць шум. Нявыразныя неудачы простыя: бот паўтарае, што нічога не знайшоў, база вектарных дадзеных не працуе, задача на стварэнне эмбедынга зламалася, або адпаведзь прыходзіць без цітатаў.

Тыхія неудачы каштуюць больш. Дакументы атрымліваюцца, адпаведзь чытаецца гладка, цітаты выглядаюць правамітнымі, а панель керування паведамляе пра успешную атрымку. Аднак эксперт у галіні прачытае адпаведзь і скажае, што ён некарэктны. Самэ гэтам часам каманда усвядомляе, што дыбагавала не тыяй складавай.

RAG – гэта не адна функцыя. Гэта серыя выбораў, якія нарабляюцца адна за іншай. Кто-небудзь адлучае, як чытаць і перапісваць запіт, дзе розрываць дакументы, які механізм пошуку выклікваць, якія метаданы скаржаюць кандыдатаў, як перэранжаваць короткі спіс, што ў канцы канцоў падае ў запрошэнне, як мадэль паводзіцца, калі падставы скудныя, і чы рэштка, напісаная у адказе, дзейсна вынікае з таго, што было падана. Адзінолькі аналіз заканчальнага запрошэння значыць дасягнуць рэзультата пасля таго, як шкода вяліка.

Працэс, які дыбагуецца

Рэалістычная сістэма больш нагадвае наступнае, чым апрантаваныя схемы на слайдзе:

flowchart LR
    A[User question] --> B[Query rewrite]
    B --> C[Retriever]
    C --> D[Reranker]
    D --> E[Evidence pack]
    E --> F[Answer generator]
    F --> G[Verifier]
    G --> H[Final answer]
    C --> I[Trace log]
    D --> I
    E --> I
    F --> I
    G --> I

Будзь-якая частка сістэмы можа выклікваць аберанціі. Генератор прызначаецца за ўсе, каліколькі ён ёсць найболей відчутным элементам, але багато проблем пачынаюцца ў поперадніх етапах. Сістэма адзыскві ўмоžжыць можа выбраць неправі перыод часу. Сістэма пераранкінгу можа падняць у прыорытэт некалькі неканэйскарыя фрагменты тэксту. Комплект доказаў можа не адначыць адзін корыстны параграф, каліколькі ліміт токенав быў занадта малы. Якісны адлагоджэнне значыць зробіць кожны етап прозрачным.

Аберанція 1: схожы тэкст не ёсць корыстным доказам

Вектарны пошук выявляе семантычную схожасць, якая не завжды ёсць рэлевантной для задання. Вядзімо прыклад, калі корыстнік спытаецца, чы можу кліянты-падпрыемцы атрымаць вярненне грошаў пасля адкорэктавання правіл у другім квартале. Сістэма адзыскві ўмоžжыць можа вернуць фрагмент пра правіла вярнення грошаў проста таму, што ў ём упомінаюцца падпрыемцы, вярненне грошаў і правілы. Аднак такі фрагмент можа быць застарэлым, непачатковым або несувязаным з другім кварталам.

Корыстныя пазначкі паказваюць ID адзёванага дакумента, яго версію, дату чыннасці, рэйтынг сэроднечы, рэйтынг пераранкавання, фільтры метаданых, якія былі застосаваны, і чы гэты дакумент быў выкарыстоўаны у фінальнай адпаведзі. Калі ў логах паказана толькі ціфра, напрыклад 0.83, то немагчыма дыяганастыкаваць адказку.

Нядача 2: разбіўка на часткі знішчыла значэнне

Разбіўка на часткі — гэта не проста дэталь хранення. Яна вялікая частка для кантролю якосці адпаведзі. Палітыку вярнення грошаў можна структураваць як крэтыяры адпаведнасці, аўтаназіі, дадатак для падпрыемстваў, даты чыннасці і рэгіональныя разлікі. Калі прыстрой для разбівкі аддзеляе дату чыннасці ад аўтаназіі, адпаведзь можа цітаваць параграф з крэтыярамі адпаведнасці, але працісціць умову, якая змінюе рэзультат.

Лепшыя фрагменты маюць стабільны ID дакумента, заголовак раздзела, версію, дату, достатню колькасць суседніх тэкстаў, каб захаваць значэнне, і паказчык на аднаго ўсходнага дакумента. Нічыга з гэтага не спрабоўвае аргументаваць неабходнасць вялічзінных фрагментаў, якія толькі дадаюць зайвага шуму. Мета — фрагменты, якія захоўваюць момент, калі вырашаецца якаясь задача.

Недастатак 3: відсутнае фільтраўванне метаданых

Багатыя сістэмы RAG спачатку ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўж

from dataclasses import dataclass
from datetime import date

@dataclass(frozen=True)
class SearchFilters:
    product: str | None
    customer_tier: str | None
    region: str | None
    as_of: date
    permission_group: str

def build_filters(user_context: dict) -> SearchFilters:
    return SearchFilters(
        product=user_context.get("product"),
        customer_tier=user_context.get("tier"),
        region=user_context.get("region"),
        as_of=date.today(),
        permission_group=user_context["permission_group"],
    )

Парадраграма мае значэнне. Заставіце працаваць фільтры дазволу і свежасці прытымкі, перш чым прасіце мадэль пра аналіз. Атрыманне прыватнага або застарэлага кантэксту і надзея, што запіт сам усё выправіць, — гэта паскудны падход.

Працоўная сітуацыя 4: набор для ацэнкі „шчаслівых“ сцэнарыёў

Найшырэйшы спосаб удосконаліць сістэму RAG — гэта маленькі, намерна створаны набор для ацэнкі. Не вікорыстоўвайце тыя тысячы прыкладоў. Пачніце з адносна трыдцяці прыкладоў, якія відображаюць рэальныя способы, калі сістэма не працуе належна:

  • запытанні, да якіх адпаведзення няма ў корпусе
  • запытанні, якім трэба два джэрэлы
  • запытанні, дзе старая і новая правілы суперсучаснаць
  • запытанні з аднаковым формулюваннем пра разныя продукты
  • запытанні, якія залежаць ад табелі
  • запытанні, дзе правільны адпаведзенні — перадаць ў рукі чалавека

Мінімальны механізм атрымання даных мога выглядаць так:

from dataclasses import dataclass

@dataclass(frozen=True)
class RagCase:
    question: str
    required_doc_ids: set[str]
    forbidden_doc_ids: set[str]

def evaluate_retrieval(cases: list[RagCase], retrieve) -> dict:
    total = len(cases)
    hit = 0
    leaked_forbidden = 0

    for case in cases:
        results = retrieve(case.question)
        retrieved_ids = {item["doc_id"] for item in results}

        if case.required_doc_ids & retrieved_ids:
            hit += 1

        if case.forbidden_doc_ids & retrieved_ids:
            leaked_forbidden += 1

    return {
        "cases": total,
        "required_hit_rate": hit / total,
        "forbidden_leak_rate": leaked_forbidden / total,
    }

Ён спецыяльна просты. Ён не кантролюе якасць адказа, але выяўляе багато неудач у пошуку дахоўкі дахоўкі прычынна, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прычымам, прыч

@dataclass(frozen=True)
class AnswerEval:
    question: str
    answer: str
    evidence_doc_ids: set[str]
    expected_claims: set[str]

def simple_claim_check(eval_case: AnswerEval) -> dict:
    answer_lower = eval_case.answer.lower()
    missing = [
        claim
        for claim in eval_case.expected_claims
        if claim.lower() not in answer_lower
    ]

    return {
        "passed": len(missing) == 0,
        "missing_claims": missing,
        "evidence_count": len(eval_case.evidence_doc_ids),
    }

У наладках адміністрування можа быць прыўязана людская перагледка або суддзя на базе LLM для болей сложных тверджэнняў, але нават простая перапачатка тверджэнняў лепшая, чым расплывчатая згода.

Лепшы след

Кожна адпаведзь должна за сабой заставіць структураваны след:

import time
import uuid
from dataclasses import dataclass, field

@dataclass
class RagTrace:
    run_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    started_at: float = field(default_factory=time.time)
    query: str = ""
    rewritten_query: str | None = None
    filters: dict = field(default_factory=dict)
    retrieved: list[dict] = field(default_factory=list)
    evidence_doc_ids: list[str] = field(default_factory=list)
    prompt_tokens: int = 0
    completion_tokens: int = 0
    verifier_result: str | None = None
    latency_ms: int | None = None

def finish_trace(trace: RagTrace) -> RagTrace:
    trace.latency_ms = int((time.time() - trace.started_at) * 1000)
    return trace

За дапамою яго практычныя запытанні падаюць у адпаведныя адпаведныя. Чы была вышукана правільная колекцыя? Чы фільтры адключылі правільныя дакументы? Чы сістэма аднаходжэння паказала апэктывны выхад? Чы пакет доказаў містыў занадто многа кантэксту? Чы апрацоўвальнік звернуў увагу на неяўныя цітаты? Чы затрымка выйшла з-за аднаходжэння, стварэння чы апрацоўкі? Без адпаведных адпаведзей яшчэ няма сістэмы RAG, толькі запыт з прыўязаным полем для пошуку.

Гібрыдны пошук часта ёсць простаю альтэрнатываў

Команды часта занадта быстра пераходзяць ад звычайнага векторнага пошуку да агентнай сістэмы RAG. Спачатку варта спробаваць гібрыдны спосаб аднаходжэння. Семантычны пошук становіцца эфектывным, калі корыстнік і дакумент вжываюць разныя словы. Пошук за ключоўымі словамі ёсць фаварытам, калі важна тачнасць у буквальных выразах, такіх як ідэнтыфікатор, код каштоўкі, назва поля чы клаза юрыдычнага тэксту. Рэальныя сістэмы часта вынужданы вжываць оба падходы.

def hybrid_rank(vector_results: list[dict], keyword_results: list[dict]) -> list[dict]:
    scores: dict[str, float] = {}
    items: dict[str, dict] = {}

    for rank, item in enumerate(vector_results, start=1):
        doc_id = item["doc_id"]
        scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (rank + 10)
        items[doc_id] = item

    for rank, item in enumerate(keyword_results, start=1):
        doc_id = item["doc_id"]
        scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (rank + 10)
        items[doc_id] = item

    return sorted(
        items.values(),
        key=lambda item: scores[item["doc_id"]],
        reverse=True,
    )

Гэты спосаб з’еднання дакументаў на аднойчыннай пазіцыі не ўражае розкішнасцю, але ён корыстны. Ён надае прызнакі дакументам, якія з’являюцца ў обох спісках рэзультатаў, адночасна залішаючы можлівасць кожнаму з алгорытмав аднаходжэння дакументаў выявіць чыясьці важлівага.

Калі агентскі спосаб аднаходжэння дакументаў ефектыўны

Агентскі падход RAG падходзіць для задач, якія трэба расследаваць. Добрыя прыклады – сітуацыі, калі пачатковы анаіз часта не мае достатнько доказаў, запытанні, якія стосуюцца кальколькі дакументаў, рабочыя працэсы, якія выбіраюць межы SQL, вектарнага пошуку і вызоваў API, мадэлі, якія павінны пераканацца, чы маюць яны достатнько доказаў, а таксама працэсы, якія выгадваюць ад прабавы з іншым запытам.

Некальколькі прыклады – простыя адпаведзі на часта задаваемыя запытанні, автодаполненне з нізкім часам адпаведзення, пошук у стабільной дакументацыі і ўсё таму, дзе кожны дадатковы вызов мадэлі коштуе занадта дарога. Задача павінна праверыць, чы агентскі спосаб аднаходжэння дакументаў ёсць неабходны. Калі для ўсунення проблемы падходзіць фільтр метададаў, выкорыстоўваеце гэты фільтр.

Чэрніця для практычнага выкарыстоўвання

Перш чым звынувачваць мадэль, пераканацеся, што выпало ўважыць гэта:

  1. Чы ёсць дакументы з версіямі?
  2. Чы правыя на выкарыстоўванне дакументаў апэльнуюцца перад ўзяццем іх?
  3. Чы за замовчаннем адключаюцца застарэлыя дакументы?
  4. Чы фрагменты дакументаў формуюцца на адной паўазе на значэнне, а не толькі на колькасці сімвалаў?
  5. Чы точныя тэрміны падлеглі пошуку па ключоўых словах?
  6. Чы табелі і зображэнні обрабоўваюцца окрэма?
  7. Чы у ацэнках урахоўваныя варыянты без адпаведнай адпаведзі?
  8. Чы ідэнтыфікаторы выкарыстоўваных дакументаў фіксуюцца?
  9. Чы джэрэлы, якія цітуюцца, перакананыя?
  10. Чы якасць адпаведзі ацэнюецца окрэма ад якасці выкарыстоўвання дакументаў?

Большасць неудач у RAG не ёсць таінственнымі. Яны проста не спазірваюцца.

Дзе рываець ланцуг доказаў

RAG дапамагае сістэмам LLM чувацца абазаванымі, але такое абазаванне не выконваецца аўтаматычна. Мадэль можа выкарыстоўваць толькі тыя доказы, якія яна атрымляе. Апраналізавальны механізм можа знайсці толькі тое, што было правільна індэксавана. Індэкс можа супрацоўваць толькі з метаданнымі, якія былі захаваны. Атрыбуты непаслуху можа выявіць толькі тыя неудачы, якія хтось падкропіў назвамі. Гэтая работа менш прыгожая, чым дэманстрацыя, але самэ ў яй і будуецца паўтараць доверлівасць.

Таму, калі сістэма RAG тыха паспяшае, запыт не ёсць пунктом выйску. Лепшы вопыт: дзе зламалася ланцоўка доказаў?