Адладка пошуку ў RAG: Recall@K, BM25, гібрыднае зліццё і пераранжаванне
Невялікі эксперымент з RAG-канвеерам, які ставіў патрэбны фрагмент чацвёртым: як разбіўка, BM25, зліццё рэйтынгаў і пераранжаванне мяняюць Recall@3 і MRR.
Крыніцы: How to Fix a RAG System That Keeps Retrieving the Wrong Context
Запытанне, на якое агент не змогаў даў правільную адпаведзь
Возьміце просты канвэйр RAG і запытайце яго пра тое, што як бы мала быць проста: як давго застаецца актываючым лінк для завантажэння CSV? У дасяглівайнах ёсць адпаведзь. Лінк для завантажэння готовага CSV застаецца актываўным семь дзён. Аднак агент пасунуў гэты абзац на чэтвёртае месца, і так як у мовнаму модэлю палягалі толькі тры найлепшыя фрагменты, ён так і не дасягнуў запытання.
Тое, што робіць гэтыя памылкі раздражаючымі, — гэта тое, што тры найлепшыя рэзультаты не выглядаюць некорэктнымі. Адны выявляе, як обрабоцуецца экспорт у фармаце CSV, другі — вялікі экспорт, а трэці — як анулюваць экспорт. На першы погляд здаецца, што агент працюе правільна. Але гэта не так. Ён знайшоў абзацы на правы тэматыку, але ніхто з іх не мяў неабходных фактов.
Звычным рэфлексам у разважных адказоў RAG ёст тое, што людзі працуюць «вадоўна»: перапішаюць запіт, дадаюць новыя інструкцыі, калянуюць мадэль. Корэктаванне запіта ёсць задовольняючым, таму што тры змененыя лініі адразу ж зміняюць адказ. У гэтым случае гэта нічога не дало б. Мадэль не выбрала тыя дакументы, іх выбраў механізм пошуку, і жадны запіт не можа вернуць фрагмент, які ніколі не прабраўся ў вікно кантэксту.
Гэта стае ўсё важлівей, калі сістэма дагадваліваецца. Пасля этапы дэманстрацыі паслаблены пошук стае прычыной неудачы, якую важка выявіць за адпаведным адказам. Мадэль можа здавацца впэўненай у сабе, кантэкст — адносна рэлевантным, а рэальная прычына лежыць кілька крокаў вышэй у ланцугу апрацоўкі. Лепшым прыемам ёсць раздзелэнне прычын неудачы: відсутнасць доказаў, слабкая структуразацыя даных, паслабленая класыфікацыя або проблемы на этапы генеравання. Тады парадак дыбаггінгу стае такім:
Няправільны адказ, пасля чаго трэба пераглянуць процэс выявлення, звярзаць яго з мерамі, пасля чаго выправіць яго, і толькі пасля чаго прыглядзецца да процэсу стварэння адказу.
Маленькі эксперымент паказвае, насколькі важлівы ранейшыя этапы.
Корпус, достатняя меры якога для чытання
У эксперыменце быў вымышлены набор дакументацыі пра ліміты частоты, рэзервныя копіі, экспорт у фармаце CSV, адказы API, аўтэнтыфікацыю, манітарынг і журналы аўдыту. Ён не быў вражаючым, і гэта было намеральна: достатняя меры маленькі, ўбліжчы да таго, каб чалавек мог чытаць кожны выявлены фрагмент, але настолькі заплутаны, што калькі фрагментаў моглі здавацца стосунковымі да адной запыткі. Кожны фрагмент атрымаў стабільны ID:
chunks = [
{
"id": "audit_03",
"source": "audit-logs",
"text": (
"Enterprise audit logs are retained for 365 days "
"before automatic deletion."
),
},
{
"id": "errors_07",
"source": "api-errors",
"text": (
"NX-204 means the requested resource exists but is not "
"available in the caller's current region."
),
},
{
"id": "exports_01",
"source": "csv-exports",
"text": (
"CSV exports run asynchronously and appear in the exports "
"panel when processing completes."
),
},
{
"id": "exports_04",
"source": "csv-exports",
"text": (
"A completed CSV download link remains active for seven days."
),
},
]
Шымбол гэтага выглядае як элемент бухгалтэрскага уліквідавання, але ён дапамагае ухіліцца ад небяспечных спрыткаў пазней. У комплекта таксама было 12 запыткаў для ацэнкі, кожная з яых пазначаная фрагментамі, якія былі практычна значныя:
eval_cases = [
{
"query": "How long are enterprise audit logs kept?",
"relevant": {"audit_03"},
},
{
"query": "What does error NX-204 mean?",
"relevant": {"errors_07"},
},
{
"query": "How long is a CSV export link usable?",
"relevant": {"exports_04"},
},
]
Дванаццац запыткаў быў бы смешным крэтарыем для парабянку, але гэта не была мета. Мета была стварыць достатнюю колькасць прыкладаў, каб выявіць, калі падтрымкай адной запыткі таямна ламаецца іншая. Нават маленькі набор для ацэнкі падрыхтавае падазры ў паверыльнасці, якая вынікае ад перапрацавкі тых сабе трох запыткаў вручную.
Свядома просты базовы рывень
У якосты базовага рывня былі выкарыстоўваны TF-IDF, скорачаны SVD і косайнае схожасць. У рэальным праекце, верагодна, быў бы выбраны сучасны мадэль амбеддынгу, але адным яго даданнем падышла бы ўжо ўсё новая змянная, якая не дапамагла б рашыць актуальную проблему. Сістэме для пошуку не была патрэбна складнасць — яй патрэбна была і, якую можна было бы зламаць, апрацаваць і вымерыць.
import numpy as np
from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import normalize
class LsaRetriever:
def __init__(self, chunks, dims=16):
self.chunks = chunks
self.tfidf = TfidfVectorizer(
stop_words="english",
ngram_range=(1, 2),
sublinear_tf=True,
)
term_matrix = self.tfidf.fit_transform(
chunk["text"] for chunk in chunks
)
# The corpus is tiny. SVD doesn't need dimensions it cannot use.
dims = min(
dims,
term_matrix.shape[0] - 1,
term_matrix.shape[1] - 1,
)
if dims < 1:
raise ValueError("Need more text to build the LSA index.")
self.svd = TruncatedSVD(
n_components=dims,
random_state=0,
)
self.index = normalize(
self.svd.fit_transform(term_matrix)
)
def search(self, query, limit=None):
query_vec = self.tfidf.transform([query])
query_vec = normalize(self.svd.transform(query_vec))
similarity = cosine_similarity(
query_vec,
self.index,
)[0]
ranked = np.argsort(similarity)[::-1]
if limit is not None:
ranked = ranked[:limit]
return [
(self.chunks[i], float(similarity[i]))
for i in ranked
]
Першы ўрадок раздзяліў дакументацыю на рэчэнні по рэчэнню. Задаўшы запыт для CSV, было атрымана гэта:
1. 0.879 exports_01
CSV exports run asynchronously and appear in the exports panel...
2. 0.843 exports_02
Large exports are split into multiple compressed files.
3. 0.830 exports_03
Users can cancel an export while it is still queued...
4. 0.772 exports_04
A completed CSV download link remains active for seven days.
Максымальныя 0,879 — гэта па-споглядку гэта працоўна смешна, адколі здаецца, што сістэма чытлаўа або ведае, што робіць. Але калі прачытаеш тыя даны, ён нічога не адпавядае. Самэй тады трэба перастаць спрыятаць рэштку схожасці як падтрымку.
Працоўныя проблемы выступаюць ўжо да стварэння рэзультата. Алгорытм выкарыстоўвае правдападобны, але бесполезны кантэкст, а большая мовная машына бачыць толькі тое, што застаецца пасля фільтрацыі па прынцыпе top-k.
Адразу выдрукуйце тое, што вернулася
Перш чым змяніце модулі, размеры частак аб стратэгіі пошуку, паводлівае дапаможнае прылада, якае выдрукуе тое, што вяртае алгорытм пошуку, ўжо прадае свою практычнасць.
def show_hits(retriever, query, limit=5):
print(f"\n{query}\n")
for position, (chunk, score) in enumerate(
retriever.search(query, limit),
start=1,
):
print(
f"{position:>2}. {score:.3f} "
f"{chunk['id']} ({chunk['source']})"
)
print(f" {chunk['text']}\n")
Са ўсіх элементаў эксперыменту гэты адпамогчык вікорыстоўваецца чымраз больш. Калі фрагменты з’являюцца на экране, неудачы класіфікуюцца па тыпах. Можа існаваць правы дакумент, але выграе неправы ўзьязд, або два-тры фрагменты можу быць майжэ ідэнтычныя. Іншыя разы запіт і узьязд перакрываюцца достатнькай колькасцю слоў, што дае высокі бал, хоча сам узьязд не ўжо ўжыццяпрактычны як доказ. Звычайныя цыфры маскуюць кожны з гэтых случаў, калі толькі хтось не прачытае тэкст.
Ён таксама адзначае тое, што лёгка праігнораваць: апрант выдае значныя рашэнні. Усё, што перазьяўляецца на гэтым этапе, стае версіяй рэальнасі для модэлю, і гэта заслуговае на болей рэтэльную пераглядку, ніж зазвычай.
Ацэнка па ID, а не па зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай зусімнай
if answer_hint in chunk["text"]:
...
Зручна, але ненадзеяна. Невялыя змяны ў межах фрагментаў, змяна пунктуацыі чы рэфразаванне можу зробіць правільны выкарыстоўвання дакумента некорэктным. Што горэ, гэта прыяўляе ацэнку да формуліравання сінтэтычнай адпаведзі, а не да суджэння пра рэлевантнасць, якае мае значэнне. Самэльчына фрагменты мелі ID з самага пачатку, ўбліжэнне, каб ацэнавальнік могаў іх безпасебна пераканацца:
def evaluate_retriever(retriever, cases, k=3):
recall_scores = []
reciprocal_ranks = []
for case in cases:
hits = retriever.search(case["query"])
relevant = case["relevant"]
relevant_positions = [
position
for position, (chunk, _) in enumerate(hits, start=1)
if chunk["id"] in relevant
]
found_in_top_k = sum(
position <= k
for position in relevant_positions
)
recall_scores.append(
found_in_top_k / len(relevant)
)
reciprocal_ranks.append(
1 / relevant_positions[0]
if relevant_positions
else 0.0
)
return {
f"recall@{k}": float(np.mean(recall_scores)),
"mrr": float(np.mean(reciprocal_ranks)),
}
Было выкарыстоўвана два показнікі. Recall@K паказвае, чы рэшанне патрапляе ў вікно кантэксту, а Mean Reciprocal Rank разлічае правільны фрагмент у парадзе 1 ад фрагмента, які знаходзіцца глыбэй у парадзе 8. Рэзультаты базовага тэставання: Recall@1 — 0,750, Recall@3 — 0,917, MRR — 0,854.
Занепакоюючым показнікам ёсць Recall@3. Калі плануецца вікно кантэксту з трохоў фрагментаў, дзеякія запытанні ужо втрачаюць неабходную інфармацыю для правільнага адпаведзення ўсё раней, чым пачынаецца генераванне. Корэктаванне настройкаў сістэмы ў такі момент было бы марным.
Размер фрагмента: частковы вінаватык
Фрагменты на рэвэлі былі галоўным падазроўнікам, і ў частцы ўсё ж былі вінаватыя. З фрагментаў з двума рэвэлімаў Recall@1 падняўся з 0,750 да 0,917, Recall@3 досяг 1,000, а MRR — да 0,958. Фрагменты з трыма рэвэлімаў далі практычна тыя ж рэзультаты. Фрагменты з цэлых староніц далі ідэальныя показнікі, што здаецца чудовым прыблізна на пяць секунд.
Проблема была ў тым, што сторанкі былі вельмі маленькія. Аднаўленне цэлай сторанкі робіла майжэ немагчымай працягнуць увагу ад адпаведнай адпаведзі, таму эфектыўнасць аднаўлення паднялася часткова таму, што кожны элемент містыў больш матэрыялу. Але за гэта плаціліся. Большыя фрагменты выкарыстоўвалі весь кантэкст, містылі некалякуючыя рэчэнні і залішалі менш прастору для окрэмых доказаў, а алгорытм, які завжды аднаўляў цэлыя інструкцыі, таксама добра паспрацаваў бы па праблеме аднаўлення.
Такім чынам, урок не быў у вялізный розмер фрагментаў. Урок быў у тым, што пачатковая падзелка на рэчэнні была занадта агрэсывная, што ёсць менш яскравы, але прыемны вывод. Гэта таксама вызывае сумневы ў падобных настройках:
chunk_size = 500
chunk_overlap = 50
У сабее тых цыфраў няма нічога парадзельнага. Ояны проста не павінны быць выкарыстоўваныя ў працэсе стварэння продукту, таму што яны адназначна былі выкарыстоўваныя ў аднам з навучальных матэрыялаў. Разбіўка на часткі ўскладнюе процэс выкарыстоўвання інфармацыі, і яй таксама патрэбны падтверджэнняя, як і для всіх іншых элементаў.
Точныя тэрміны несу значэнне
У прыкладзе CSV была ўсё жа ўсё адна падказка: кожны аб’ект з высокім рангам сэмантычна быў блізкі да даных, якія выкарыстоўваліся для экспорту, таму сістэма выкарыстоўвання інфармацыі не паспела неправацьна прачытаць даны. Яна проста не могла дастатньа розразліваць аб’екты ўнутры ціх даных. Тэхнічная дакументацыя паглыбляе гэта, таму што точныя строкі несу вельмі вялікі запас значэння. Код аднойчыны, такі як NX-204, не ўсё толькі сэмантычна супадае з суседнім кодам, і тое ж сама стосуецца customer_id, канца-тупа, номера версіі чы імені функцыі.
BM25 парадоксальна гэта мадэль, якая пасягнула 1,000 у показніках Recall@1, Recall@3 і MRR. Яна перамогла сэмантычны базовы фонд, пасягнула лепша, чым моглі спадзявацца большасць, і, што ўскладнюе справу, была бездоганна на гэтым маленькім наборэ дадзеных. Звучаць загалоўкі на кшталт «embeddings згінулі» было б прывабліва, але дадзеныя гэта не падтрымляюць. Яны паказваюць, што гэты набор ацэнкі ў вельмі вялікай меры паслужыў для точнага адпаведнення тэрмінаў. Колекцыя, заполненая перапісанымі запытаннямі, могла бы выбраць іншага пераможця. Галоўны вывод — гэта насколькі важлівае было лексычнае адпаведненне для гэтага корпусу, і гэта ўсё ж такі прычына стварэння набора ацэнкі.
Гібрыдны спосаб выкарыстоўвання дадзеных з аднаўленнем рангу
Адаптаванне семантычнага абшукаўніка яшчэ здавалася прытарпелым. Два методы не функцыонуюць аднакова, таму іх рангі былі сумаваны за дапамою методу Reciprocal Rank Fusion. Важны ўсё-такі аднаковы элемент айхтунага: кожны абшукаўнік дапамагае толькі пярэдставіць абмежаныя колькіцтва кандыдатаў. Ранжаванне вялікаг корпусу два разы і злучэнне всего ў Python было бы дзівным падходам для копіювання.
from collections import defaultdict
def fuse_rankings(vector_hits, bm25_hits, rrf_k=60):
fused = defaultdict(float)
chunks_by_id = {}
for hits in (vector_hits, bm25_hits):
for rank, (chunk, _) in enumerate(hits, start=1):
chunk_id = chunk["id"]
chunks_by_id[chunk_id] = chunk
fused[chunk_id] += 1 / (rrf_k + rank)
ranked_ids = sorted(
fused,
key=fused.get,
reverse=True,
)
return [
(chunks_by_id[chunk_id], fused[chunk_id])
for chunk_id in ranked_ids
]
Абляцыя навакол яго простая:
def hybrid_search(query, lsa, bm25, candidate_k=20):
vector_hits = lsa.search(query, limit=candidate_k)
bm25_hits = bm25.search(query, limit=candidate_k)
return fuse_rankings(vector_hits, bm25_hits)
Гібрыдны спосаб рэтрыбліяцыі падняў показнік MRR да 0,958 і выправіў пачатковыя прычыны недастатку Recall@3. Усё ж такі ён прасіўся ад BM25. Эксперымент не быў пераскладваны, пакуль не выграў болей сложныя архітэктуры, адтуды гэта бы супераслабіла мету. Большая колькасць компанентаў не значыць автаматычна большую рэлевантнасць.
Дышэўскі рэранкер
Працэс рэтрыбліяцыі вымагае шырокага пошуку, таму логічна, каб ён быў дышэўскі. Калі ёсць 10 чы 20 правдападобных кандыдатаў, можна сабе парадзіць болей сфокусаваную перацэнку рэлевантнасці. У гэтым локальным эксперыменце такойя перацэнкай быў скораўчык на адной н-граме. Гэта дышэўскі рэранкер для эксперыментаў, а не рэкамендацыя для практычнага выкарыстоўвання. У рэальным сістэме варта было б рассмотрзець варыянт з выучаным крос-энкодэрам.
def cheap_local_rerank(query, candidates, limit=5):
"""
Good enough for this experiment.
I'd use a learned reranker for a real deployment.
"""
candidate_text = [
chunk["text"]
for chunk, _ in candidates
]
tfidf = TfidfVectorizer(
analyzer="char_wb",
ngram_range=(3, 5),
min_df=1,
)
matrix = tfidf.fit_transform(
[query, *candidate_text]
)
relevance = cosine_similarity(
matrix[0],
matrix[1:],
)[0]
reranked = sorted(
zip(candidates, relevance),
key=lambda row: row[1],
reverse=True,
)
return [
(chunk, float(score))
for ((chunk, _), score) in reranked[:limit]
]
У канале апрацоўкі падае простая два-этапная структура.
Алгорытм спачатку збірае вялікі набор кандыдатаў, а пасля перэранжуе іх так, каб толькі найболей значныя фрагменты дасяглі LLM. Пасля пераранжавання маленькі тэстовы набор парадураваў ідэальныя 1,000 балоў па показніках Recall@1, Recall@3 і MRR. Гэты показнік не паслужыць падтверджэнням таго, што задача адзысквання інфармацыі рашана, адколькі дванаццаць запытанняў дапамагаюць у розвіцці, але мало ў чым свідчаюць пра ўзнэманную надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на надзею на н
Якбы історыю было запрацавана ўсё раней, чым пачалася эксперыментацыя, кожны рад бы выглядаў апрантным. Але так не сталося. BM25, простая версія, зроўнанасіліла фінальную схему апрацоўкі даных. Гібрыдны пошук паспрабаваў паўпрацаваць на аснове пачатковых семантычных параметраў, але не зайняў першае месца. Большыя фрагменты даных рашылі адну проблему, але паднялі іншыя пытанні, адносна бюджэту кантэксту. Неапрантнасць рэзультата робіць яго болей правдападобным, і гэта адкрыло канкрэтную рэч пра гэты завод: точная тэрмінологія мела вельмі важлівую ролю. Гэта лепшая, чым выграць конкурс популярнасці архітэктуры.
Першае пытанне было такім:
How long is a CSV export link usable?
Даўжыню рангавання да змян была такая:
1. CSV exports run asynchronously...
2. Large exports are split...
3. Users can cancel an export...
4. A completed CSV download link remains active for seven days.
Пасля:
1. A completed CSV download link remains active for seven days.
2. Users can cancel an export while it is still queued...
3. CSV exports run asynchronously...
Адыянальныя дакументы засталіся тымі ж, як і запытанне, і не было патрэбы ў болей розумным LLM. Змяніліся доказы.
Парадак адлучэння пры паказванні паслабых адпаведзей RAG
Гэта разліка вялікае значэнне для таго, як рэагаваць, калі адпаведзь RAG выходзіць за межы. Калі неабходныя доказы ніколі не прабачаюцца ў кантэксце, трэба шукаць у процэсе ўзяць іх. Калі доказы явна ў кантэксце, а мадэль все равно дае безсэнсавыя адпаведзі, толькі тады трэба аналізаваць процэс стварэння адпаведзі, спосабы стварэння запытання, парадак кантэкста і поведэнне мадэлі. Існуе ўсьмо трэці варыант абякання: правільная адпаведзь прабачаецца ў кантэксце, але знаходзіцца пад шматам нероўнасвярнавучанага тэксту. Гэта проблема класыфікаціі або стварэння кантэкста, а прызначэнне всьму гэтам назвы „галюцінацыя“ маскіруе яе.
Практычны ўзлокі першыя пераглядаюць атрыманыя доказы, а пасля выявляюць, чыя яна адпавядае за проблему — за працэс атрымання чы за стварэння. Самэй важлівы ўзлок — гэта момент прымэры. Калі якасць Recall@3 нізкая, нема сэнсу вітрачаць гадзіну на перапісванне фразы «Адпавядаць толькі на адпаведны кантэкст» шэсцьма ўсё бол строгімі способамі, адколі сам кантэкст вялікі час яўляецца некорэктным.
Змены запрошэнняў таксама даюцца быстрэй. Вы правяліяеце, запускаеце зноў і адразу бачыце новую адпаведзь. Работа з атрыманням дакументаў ўсё сповольней і менш задовольняючая: трэба чытаць фрагменты, атрымляць ацэнкі ўзаемной адноснасці, пераглядаць рангі, спарачывацься з межамі фрагментаў, а іноды выявляць, што старыя лексычныя методы атрымання працуюць лепшая, чым болей складныя рашэнні. Аднак такія методы даюць болей надзеяныя рэзультаты.
Найболей карытая пытанне — гэта не тое, чаму LLM адпаведзіў некорэктна, а тое, чы робат-інтэлект узагалі атрымаў неабходную інфармацыю. Гэта здаецца майжэ занадта бяспераблівым, каб прыкладзваць да гэтага увагу, пры тым чыста часта яго ігнаруецца.