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

Аўдыт пошуку ў RAG: састарэлыя фрагменты, дрэйф і шумны кантэкст

Аўдыт пошуку RAG на Python: эталонныя запыты, парогі касінуснай рэлевантнасці, праверка дрэйфу тэстам Калмагорава–Смірнова, эфектыўнасць кантэксту і як чытаць справаздачу.

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

Крыніцы: I Built a RAG System That Audits Itself. Here’s How (With Code).

Тыхія аспекты неяксамоці RAG

Уявіце бота падтрымкі, які паведамляе кліенту, што тэрмін вярнення — 30 дзён, хоча на самай працэ ён станавіць 14 дзён. Дакумент з правіламі быў зменены ў студзені, а індэкс укладкі — у жаўтні. Ніхто не пастрадаў, і судоўых практык не было, але трывожнае тое, што ніхто не можа сказаць, як давно прадаюцца некарэктныя адпаведзі. Тыдзень? Тры месцы? Без можлівасці пераканацца немагчыма сказаць.

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

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

Сконцентруйцеся на адзыскванні інфармацыі, а не на модэлі

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

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

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

Супетнай частка крыўці лежыць у звычным методзе ацэнкі, які ўжываецца ад пачатку да канца: прыходзіць запіт, а потым ацэнюецца адпаведны адказ. RAGAS, як адны з наіболей шырока адымлёвых методаў ацэнкі, быў створаны на аснове цэлага працэсу апрацоўкі і ацэнюе тачнасць кантэксту, адпаведнасць і рэлевантнасць адказа (Es і іншыя, 2023, EACL 2024). Цэль гэты метод – стварыць надзеяны фрэймворк. Тым не менш, ён ацэнюе сістэму в цэлым, а не сам слой адзысквання інфармацыі, таму пасяжны рэзультат ацэнкі ад пачатку да канца можа маскаваць спад карыстнасці адзысквання, калі мадэль часам мае успех і можа гэта компенсаваць.

Ешча адзін проблема крыўцюе пад гэтым. У 2024 годзе Лю і саавтары паўтарылі, што мадэлі мовы паспелі значна гірша, калі важлівая інфармацыя знаходзіцца сярод дужа дзьяўнага кантэксту, нават у мадэлях, трэнаваных спецыяльна для дзьяўных вхідных дадзенняў. Такім чынам, выявленне пяці фрагментаў, з якіх тры маюць лягкія асоцыяцыі з запітам, актываўна шкодзіць сістэме, а токены, якія на іх вітрачаюцца, пагаршуюць якасць адпаведзі. Ніякія з гэтых проблем не выяўляюцца, як толькі не будзе адразу пераглянута складовая выявлення дадзенняў, што і ёсць планам.

Тры спосабы абыякавасці, якія не фіксуюць стандартныя метрыкі

Заражэнне фрагментамі

Назва „заражэнне чанкаў“ не выйшла з жаднага нараднага працы. Яна апісвае чанкі, якія здаюцца рэлевантнымі, таму што ўжытакі слоў у іх перакрываюцца з запытам, таму яны займаюць высокія месцы ў вектарнам пошуку, але не маюць адпаведзення. Адна з распашчастых прычын — раздзелэнне, якое залішае ключавыя словы ў аднам чанку, а справжній контэнт — у іншым. Чанк, багаты на ключавыя словы, атрымвае высокія балы за косай схожасць, а корыстны чанк не потрапляе ў топ-рэзультаты, і модэль складае правдопадобную, але некорэктную адпаведзь.

Стары контэнт

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

Зміна эмбеддынга

Гэты працэс вялі і поступовы, і ён ёсць найбольш апакойлівым з трох. Мадэль для вбудоввання фіксуе корпус у тым стане, які ён меў у час андэксавання. Кожная зміна дакумента, кожны новы тэрмін, які прадаецца ў домэне, і кожная зміна у тым, як корыстувальнікі формулююць запыткі, часткова паслабляе точнасць гэтага «снэпшота». Серэдняя косайная схожасць можа знізіцца з 0,82 да 0,74 за шасць месцаў. Нічага не ламаецца, жадных апавешчэнняў не выходзіць, а паслабленне стае зрозумелым толькі тады, калі адпаведзі значна пагіршаюцца, а ў гэты момент сістэма ужо застаецца на месцы месяцямі.

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

Што робіць практычная перапрацоўка

Тры пераканання адпавядаюць гэтым трох способам неудачы.

  1. Адэкватнасць і колькасць рэзультатаў. Для кожнага запіту ў наборы тэста скрыпт запошчае топ-К фрагментаў і вылічае косінусавую схожасць между эмбеддынгам запіту і эмбеддынгам кожнага фрагмента. Ён таксама пераканальваецца, чы не вернуліся фрагменты, якія, як вядома, знаходзяць адпаведныя адпаведзі. Калі яны не вернуліся, запіт пазначаецца як неудачны.
  2. Змены па адносу да базовага стану. Кожны запуск зберагае свой распад балав схожасці, а пазнейшыя запускі паўставляюць новы распад з гэтым зберажаным базовым станам за дапамой тэста Колмагораўа-Смірнава. Калі гэтыя распады сутэйна разніцяцца, выдаецца апавешчэнне пра змену. Не трэба аналізаваць матэматыку: п-значэнне менш за 0,05 проста значыць, што ў чымсь змянілася і трэба гэта пераканальвацца.
  • Эфектыўнасць у кантэксце. Скрыпт вылічае, які частака з адзначаных токэнаў з’являецца ў адказе модэля. Высокі частака азначае точнае і рэлевантнае адзначэння, тады як нізкі частака указвае на наявнасць шуму, які запалюе кантэкст. Чысла, якія застаюцца ніжэй 40%, ўскладнююць аналіз і вымагаюць дальнейшага выяснення.
  • Аўдыт стварае структураваны адчынны-кодовы файл у фармаце JSON. Дастатнека ўсьміхнутага кожны тыдзень кран-задання, пры чым неабходна старанна сочыць за канцэнтрацыяй флагаў і аналізам адхылэнняў. Большасць сістэм RAG ніколі не мелі такага слоя манітарынгу.

    Золаты набор запытанняў

    Перад выкарыстоўваннем любога коду неабходны золатыя наборы запытанняў: адчынны-кодовы файл у фармаце JSON, які містыць рэпрэзентатыўныя запытанні, кожна з якіх супакоўвана з ID-амі частак, якія павінны з’явіцца ў яе рэзультатах.

    [
      {
        "query": "What is the refund policy for digital products?",
        "expected_chunk_ids": ["faq_doc_chunk_12", "faq_doc_chunk_13"],
        "notes": "Customer FAQ, policy updated 2024-Q1"
      },
      {
        "query": "How do I reset my API key?",
        "expected_chunk_ids": ["api_docs_chunk_07"],
        "notes": "API documentation, stable"
      }
    ]
    

    Размах мае значэнне. Спачатку 20 запыткаў здаваліся достатнімі, але гэтага было недастатнь, каліхо тэст на зміну патэрнаў трэбуе больш пунктавых дадзеных, а 20 пунктав дае такую розпыленую структуру, якой нельга даваць доверы. Лічыце 50 як мінімум, а 100 або больш — як пункт, пры якому сігналы зміны станаюць значамымі.

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

    • Размясціце запыткі па разныя катэгорыі тэм у замест на тое, каб агрупаваць іх навакол аднаго дакумента.
    • Дадзіце калькі запыткаў, якія не должны вярнуць жадных рэзультатаў, каліхо негатыўныя прыклады дапамагаюць выявіць хыбна паазначаныя рэзультаты.
  • Апдэйтуйце «золатны» набор, калі корпус сур’ёзна змініцца, а не пастойча. Бн являе сабо стабільны апавядак і не ўжо жывы набор дадзеных.
  • Структура праекта і галоўны скрыпт

    Рэпазітарый арганізаваны так:

    rag-retrieval-audit/
    ├── audit/
    │   ├── __init__.py           # Package exports
    │   ├── rag_audit.py          # Main audit logic — all three checks
    │   └── config.py             # All thresholds and model settings
    ├── examples/
    │   ├── golden_queries.json   # Sample golden set (10 queries)
    │   └── run_audit.py          # End-to-end demo — runs without an existing collection
    ├── tests/
    │   └── test_audit.py         # Unit tests for all scoring functions
    ├── requirements.txt          # sentence-transformers, chromadb, scipy, numpy
    └── README.md                 # Setup, usage, how to read the report
    

    А ўсё гэта — структура файла rag_audit.py, які містыць усі тры пераконтроўкі. Гэта толькі праграматычны план для адразування, а не повна рэалізацыя.

    # rag_audit.py — structure overview
    # Full implementation: https://github.com/satyam671/rag-retrieval-audit
    
    # ── CONFIGURATION (tune to your pipeline)
    EMBEDDING_MODEL      = "all-MiniLM-L6-v2"  # must match your index
    TOP_K                = 5
    RELEVANCE_THRESHOLD  = 0.70
    DRIFT_P_THRESHOLD    = 0.05
    EFFICIENCY_THRESHOLD = 0.40
    
    # ── CHECK 1: Are the right chunks coming back?
    def score_relevance(query_embedding, chunk_embeddings, expected_ids, retrieved_ids):
        """Cosine similarity per chunk + expected chunk hit/miss against golden set."""
        ...
    # ── CHECK 2: Has retrieval quality shifted over time?
    def detect_drift(current_sims, baseline_path=None):
        """Two-sample KS test comparing current similarity distribution to baseline."""
        ...
    # ── CHECK 3: How much of the context window is signal?
    def score_efficiency(retrieved_docs, answer):
        """Token overlap between retrieved chunks and the LLM answer."""
        ...
    # ── RUNNER
    def run_audit(golden_set_path, collection_name, chroma_persist_dir,
                  baseline_path=None, answers_path=None, output_path="rag_audit_report.json"):
        """Runs all three checks, writes a structured JSON report, saves the baseline."""
        ...
    

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

    Прагляд кожнай пераканальнай перацэўкі

    Ацэнка рэлевантнасці частак

    Пераканальная перацэўка выконвае два крокі, якія людзі часта працінаюць, калі ствараюць яе самі.

    Па-першае, яна вырахоўвае косінусавую схожасць безпасцерожна з эмбедынгаў і не выкарыстоўвае значэнні адстані, якія вяртае ChromaDB. Стандартны метрык ChromaDB — гэта L2, а не косінусавая схожасць, і гэтыя два показнікі не можна заменіць аднам другым. Іх сумешчанне дае рэзультаты, якія здаюцца разумнымі, але на самай працэ памераюць не тое.

    Паўторна перакананне — гэта падтверджэнне таго, што прызначаныя ID-ы частак золатага набора з’явіліся ў топ-K, і самэ гэта выявляе проблему заражэння частак. Серэдняе значэння схожасці 0.75 здаецца нормальным, пакуль вы не дакладзеце, што частка, якая мяла адпаведаць на запіт, павёла ся з рэйтынгам 0.62 і зайняла шостае месца, тое ўсьмо за межамі. Мадэль так і не отрымала правільнай адпаведзі, хоча показнікі схожасці все ўсё здаваліся прыемлімымі.

    Практычная разліка между вікорыстаннем механізма гэтынгу і без яго чыста відчуваецца ў кодзе.

    # WITHOUT the relevance gate — standard approach most teams use
    def build_context_naive(query, collection, top_k=5):
        results = collection.query(
            query_embeddings=[embed(query)],
            n_results=top_k,
            include=["documents"]
        )
        # Pass everything back, no quality check
        return "\n\n".join(results["documents"][0])
    
    # WITH the relevance gate - what the audit tells you to build
    def build_context_gated(query, collection, model, top_k=5, threshold=0.70):
        q_emb   = model.encode([query], normalize_embeddings=True)[0]
        results = collection.query(
            query_embeddings=[q_emb.tolist()],
            n_results=top_k,
            include=["documents", "embeddings", "ids"]
        )
        passed_chunks = []
        for i, chunk_emb in enumerate(results["embeddings"][0]):
            sim = cosine_sim(q_emb, np.array(chunk_emb))
            if sim >= threshold:
                passed_chunks.append(results["documents"][0][i])
        # Empty context is better than wrong context
        return "\n\n".join(passed_chunks)
    

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

    Адказ на змяну эмбедынгаў

    Перакананне ў змяне базуецца на тэсце Колмагораўа-Смірнаўа. Яны паўпоручваюць форму ныякшага распаду сэродзелівасці з захаваным базовым станам і выяўляюць значныя адхіленьні. Чаму саме распады, а не сярэжныя балы? Таму што адна цыфра масківае занадта многа. Сярэжны бал можа застацца на роўні 0,78, тады як распад пад яным стане плоскім і розпыліцца, што значыць, што дзеяныя запытанні стануць набліжэнні гіршымі, а іншыя будуць компенсавацыя. Тэст КС выяўляе змяны распаду, якія застаюцца непазіранымі пад час вычыслення сярэжных значэнняў.

    У прыкладным графіку паказана змяненасць уместкавання інфармацыі ў працоўнай сістэме RAG чырых месяцаў пасля адчынення карпуса без паўтарнага індэксавання: статыстыка KS складае 0,21, а значэнне p — 0,003.

    Ёсць адна прычына аберагання: тэсту для таго, каб быў надзеяны, патрэбны достатнія даныя. Пры 15 запытаннях значэнні p мяняюцца і ствараюць хыбную паверу, тады як 50 і больш запытання даюць стабільны сігнал. Гэта ўжо адна з прычын, чаму размах «золатага» набора мае большое значэнне, чым можа здавацца.

    Эфектыўнасць вікна кантэксту

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

    Усё-такі гэты спосаб выяўляе сістэматычныя проблемы. Калі показнік эфектыўнасці стаўляецца ніжыя 40% ўсё час, значыць, ў чымусь на пачатковым етапе ёсць прычына: фрагменты занадта вялікія, параметр top-K занадта высокі, або працэс выкарыстоўвання даступа прыносіць тэксты, супарадныя за тэматыкай, але не стосунечныя да запіту. Гэтыя перакананні не ўсё такі точныя, але яны правільна вказуюць направленне, і гэтага дастаткова, каб запачаткаць расследаванне.

    Чытанне атласа

    Рэзультат — гэта файл у фармаце JSON, і наступныя поля заслуговуюць на увагу:

    • flag_rate — гэта частка запытанняў, якія спрычынілі працэс выкарыстоўвання даступа. Значэнне вышэй 20% означае, што працэс выкарыстоўвання даступа трэба пераглянуць, а рост з 8% за адну недзелю да 22% за наступную гаворыць пра тое, што ў чымусь змінілася сітуацыя.
    • drift_analysis.drift_detected — гэта логічнае значэнне, на якое трэба звернуць увагу. Калі яго значэнне true, трэба паглядзець на mean_delta, каб з’ясаваць направленне змян. Ад’ютнае значэнне delta означае, што ступень супараднення падае, і гэта ёсць сігналам да перэіндэксавання.
    • drift_analysis.ks_statistic зростае ў залежнасці ад велічыны схылу распаду. Значэння ніжэй 0.10, верагатываецца, ўскладнены шумамі. Значэння вышэй 0.20 ўскладнюе сітуацыю, незалежна ад значэння p.
    • query_results[i].relevance.expected_chunk_hit — гэта правдзівы параметр для кожнага запіту. Значэння false означае, што фрагмент, які павінен быў адпаведаць на запіт, не патрапіў у топ-K. Калі такое выходзіць у болейш чым 15% елементаў «золатага» набора, трэба перэструктураваць індэкс.

    Прыклад запускаецца за дапамою адной команды, якая паказана тут.

    python -m examples.run_audit
    

    Шпаргалка сумярняе аўдыт у ўсіх аспектах.

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

    Урок пра розмер вялікі

    Выкананне аналізу на адзінай групе з 15 запытанняў даў значэнні p, якія не мелі сэнсу. Тэст тэхнічна выкананы, але не надзеяны, і было лёгка запыталіцца пра статыстычную рэалізацыю два дні, прычым не разумеючы, што даных проста занадта мало.

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

    Меры гэтага падходу

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

    Гэтыя пытанні маюць значэнне. Бенчмаркі NVIDIA на пяці наборах дадзеных, якія былі падсумаваны ў аналізе FloTorch, паказалі, што оптымальная ужоўвышка фрагмента разніцяецца залежна ад тыпа запытання: запытанні на факты маюць максімум 256–512 токенав, тады як аналітычныя запытанні выкарыстоўваюць 1024 або больш токенав. Будзь-якая адна ужоўвышка фрагмента ёсць компромісам, а ідэальны набор, які ўключае оба тыпы запытанняў, у сярэднем выклікае базовую неадаптацыю. Аддзіяльным задачэм ёсць безпаследкова тэставанне стратэгіі фрагментавання і паўраненне семантычнага фрагментавання з розбівкай на фіксаванай ужоўвышцы за дапамогою рэальных цыфраў.

    Справы

    • Артыкул RAGAS пра аўтаматызаваную ацэнку генеравання з падтрымкай атрымання інфармацыі (arXiv 2309.15217, 2023; EACL 2024).
    • Даследжэнне «загубленага ў серэдзіне» па адзінкавай працэйскусці мовных модэляў у дужаўгіх кантэкстах, опублікованае ў 12-м тыме TACL у 2024 годзе.
    • Практычныя даследжэнні NVIDIA па стратэгіях фрагментавання дакументаў разных тыпаў, з 2024 годзе.