RAG ад з нуля: фрагментаванне, умяшчэнне, зберагчыце, адзысканне, адказ з кантэкстам
Паспортны ўказанні паэтапнага адгэтування каналу RAG з LangChain і FAISS: разбіўка на часткі, стварэнне імбеддзінгаў, адзысканне даных, прыкладжэнне запітаў, частыя памылкі і майбутнія удосконаленні.
Крыніцы: RAG, Explained the Way I Wish Someone Had Explained It to Me
Калі запытаць мадэль для чату, калякія правілы вярнення грошаў у конкрэтнай кампаніі, яна рэдкасцю з’явіць сваю некаштовнасць. Чашчае ён стварае павільную, правдопадобную параграф, які не мае нічынага з рэальнымі правіламі. Прычына не ў дыфекце. Мадэль мовы трэнуецца аднойчы, а потым застаецца незменной. Яна ніколі не чытала прыватных PDF-файлаў, внутрашніх вікі чы аб’ектаў Notion, і не ведае нічога, што было напісана пасля канцэна працы над ёю.
Метод генеравання з падтрымкай выкарыстоўвання інфармацыі, які зазвычай скрацваецца як RAG, ёсць стандартным рашэннем, і ідэя яго прыгожа проста. Уместо таго, каб чакаць, што мадэль запамятае адпаведны адказ, сістэма знаходзіць неабходны тэкст і практычна безпосерэдна вставляе яго ў запіт, а потым прасіць мадэль прачытаць яго і адпавесці. Усе іншае — цэ гэтыя тэхнічныя рашэнні, якія робяць такое выкарыстоўвання інфармацыі надзеяным. Прыводны прыклад ніжэй — цэ бот для падтрымкі, які адпавядае на запытанні з PDF-файла правілаў кампаніі.
Двое студэнтав, адны экзамен
Уявіце двух адміністрацый, які складаюць адно і тое жа тэст. Першыя запамятаў учбовык месяцы назад і пішае з памяці. Калі кніга была застарэлая, або якісь роздзелы былі напаўзабутыя, адпаведзі з’яўляюцца абавязковымі, але некорэктнымі. Другія адміністрацый маюць права на відкрыты кнігу. Перш чым пісаць, яны пераходзяць на правую сторунку, чытаюць яе і спаўнаюць тую інфармацыю з своім загальным мышлэннем.
Зазвычай другі студэнт выграўвае, і не завдзяк дадатковай інтэлігэнцыі. Перад тым, як говорыць, пераглядаць інфармацыю — гэта проста лепшы спосаб выдаваць адпаведзі. У сістэме RAG мовны модэль выступае як другі студэнт, даныя компаніі — як відкрыты кніга, а база вектарных даных — як індэкс, які паказвае, на якую сторунку пераходзіць.
Два каналы, а не адны
Будзь-якая сістэма RAG, як бы складная ёна не была, складаецца з двух адзінаковых каналоў, з’едыненых у серэдзіне.
- Індэксаванне адзейнае паавтонамо, адназначы і зноў кожны раз, калі змінююцца дакументы. Неапранутыя файлы ператвараюцца ў індэкс, які можна шукаць.
- Выкарыстоўванне і стварэнне рэзультатаў адбываецца за кожны запіт. Шукаецца ў індэксе, выбіраюцься найлепшыя элементы і падаюцься модэлю.
Новачкі часта прабуюць уявіць сабе адны дзьяловы ланцоўкі, што стварае плутанне. Яго раздзелэнне на два часты спрыяе, адколі кожная частка працюе ў разны момант і рашае разныя задачы.
Стварэнне індэкса
Завантажыце дакументы
Пачніце з ператварэння неапранутага кантэнту ў звычны тэкст. PDF-файлы, файлы Word, веб-сторункі і экспортаваныя нотаткі всі выклікаюць патрэбу ў прыемніку, які вяртае тэкст.
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("company_policy.pdf")
documents = loader.load()
print(f"Loaded {len(documents)} pages")
Разбіўка тэксту на часткі
Разбіўцы тэксту на часткі заслуговуюць на большую увагу, ніж зазвычай ўдзеляецца ўсім іншым аспектам. Дужа распашчастая першая памылка — гэта калі весь документ помішчаецца у сховішча вектараў як адна вялічэзная елементарная частка. Мадэль эмбедынгу складзець урок у адны вектар фіксаванага розмеру. Калі падаць ёй дваццаць стораніц, рэзультатам будзе розмыты сярэдній значэння усяго, што ёсць, і ён можа паказаць, што файл у загальных рысах прыняжытае до правіл компаніі, але не можа паказаць конкрэтны абзац пра трыдцядзевядздзёднадзе дней на поверненне грошаў.
Рашэннем ёсць разбіўка дакументаў на маленькія часткі, якія перакрываюць адна другую. Перакрыцце мае значэнне, таму што рэчы, разрэзаныя адносна точна на межы, втрачаюць свой кантэкст з обох бакоў, а маржа прыблізна дзесята-двадцатая частка розмеру часткі служыць календарэм безпекі.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # characters per chunk
chunk_overlap=80, # overlap between consecutive chunks
separators=["\n\n", "\n", ". ", " "]
)
chunks = splitter.split_documents(documents)
print(f"Split into {len(chunks)} chunks")
Як кераван, занадта большыя фрагменты спячваюць неяснае адзысканне інфармацыі, тады як занадта маленькія фрагменты пазбіраюць кантэкст. Розумныя пачатковыя значэнні — ад 300 да 800 сімвоў, пасля чаго іх можна скорэктаваць пасля аналізу рэальных адказаў.
Уключыце кожны фрагмент
Мадэль уключэння ператварае тэкст у спіс цяжэй, які відбівае яго значэнне, а не проста формулюванне. Две рэчы, якія гавораць адно і тое ж, стаюць вектарамі, якія знаходзяцца поблізу адзінаго ад другога, нават калі ў іх няма жадных спільных слоў.
from langchain_openai import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
# under the hood, this is what happens per chunk:
vector = embedding_model.embed_query("30-day refund window for annual plans")
print(len(vector)) # e.g. 1536 numbers representing this sentence's meaning
Адзін спосаб прыкладзіць гэта — гэта плоска карта, дзе супарадныя ідэі скупляюцца ў кластэры.
"refund policy" •
\
• "money-back guarantee"
"vacation days" •
\
• "paid time off"
Фраза на кшталт «палітры вярнення грошаў» з’являецца па боку ад гарантыі вярнення грошаў, хоча слова разныя, і самэ гэта ёсць сэрца семантычнага пошуку. Гэта пояснюе, чаму метод RAG працюе лепшая, чым стары спосаб падбору ключоў за дапамогою Ctrl+F.
Зберагаць вектары
Кожны вектар зберагаецца ў базе дадзеных вектараў разам з аднойчыным тэкстам. Гэтая база ёсць індэксам, які вікорыстоўваецца пад час пошуку.
from langchain_community.vectorstores import FAISS
vectorstore = FAISS.from_documents(chunks, embedding_model)
vectorstore.save_local("faiss_index")
FAISS — гэта зручны пункт для пачатку, калі толькі ён бесплатны і працюе на локальным камп’ютары, а Chroma таксама досыць просты. Управляемыя службы, такія як Pinecone чыста Weaviate, становяцца прыгоднымі, калі пачынаецца масштабаванне сістэмы. Канцэпцыя застаецца аднаковай у всіх варыянтах, а разлікаяцца толькі сховішча і інфраструктура.
Індэксаванне ужо завершана. Рэзультатам ёсць пошукаваемая база дадзеных, створаная на адной з дакументаў, і яе трэба перзаснаваць толькі тады, калі ція дакументацыя змінюецца, а не з кожным запітам карыстальніка.
Адпаведзенне на запіт
Гэты частак працюе ў кожны раз, калі хтось вводзіць запіт.
Уключыць запіт за дапамою таго ж модэлю
query = "Can I get a refund on my annual subscription?"
Запіт неабяжна трэба падставіць выключна з тым самым модэлем, які быў выкарыстоўваны пад час стварэння індэкса. Людзі постаўляюць на гэта ўсё час. Калі індэкс быў створаны з аднаго модэля падставлення даных, а запыт выкананы з іншага, вектары знаходзяцца ў разных прасторах значэнняў, і пошук сэродзелнасці тыхчы прыпыняе сваю працу. Не пазірае жадных памылак, толькі паслабленыя рэзультаты, таму правілам ёсць выкарыстоўвання аднаго модэля на всю гэтую працу.
З’явіць найбліжэйшыя фрагменты
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
relevant_chunks = retriever.invoke(query)
for chunk in relevant_chunks:
print(chunk.page_content[:100], "...")
У сябе FAISS адклёкае пошук садоўжнасці. Ён вылічвае адстань, зазвычай садоўжнасць косай, межу вектара запіту і вектара кожнага зямлёванага фрагмента, а пасля вяртае найбліжэйшыя з іх. Гэта знову тая самая карціна кластэраў на карце, але прыменена у зворачным напрамку, каб знайсці кластэр, да якога належы запіт.
Укладзіце фрагменты ў запіт
Гэта той крок, які робіць RAG эфектываў: адзямлёваны тэкст буквальна вставляецца ў запіт.
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("""
Answer the question using ONLY the context below.
If the answer isn't in the context, say "I don't have that information."
Context:
{context}
Question: {question}
""")
Інструкцыя працаваць толькі на адназначаным кантэксце ўтрымвае мадэль у межах рэальнасці і не дазволяе ёй выдумваць рэшэнні. Яна адразняе экзамен з адкрытым кнігам ад экзамену з закрытым кнігам, дзе дазволена імпровізацыя.
Складзіце яго ў ланцюг
Цяпер усі элементы складаюцца ў ланцюг RAG, які маюць на увазе люды, калі говораюць пра стварэнне такога ланцюга. У LangChain яго зазвычай пішуць за дапамою опэратора трубкі, які перадае выход дакумента з левага канца на наступны крок з правага канца.
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini")
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("Can I get a refund on my annual subscription?")
print(answer)
Чытаецца з левага на правае, ланцуг сам сабе апаведамляе. Уводзіцца запіт, выявляюцца фрагменты і фарматаваюцца як кантэкст, тады запіт праходзіць без змян, заполняецца шаблон запіта, запускаецца мадэль, а з яе адказа парсуецца звычайны тэкст.
Запусціўшы ўсё, замест вымышленага прыказу павінен з’явіцца ўтварэнне на кшталт: «Так, годовыя планы па правілу вярнення грошаў падлягаюць повнаму вярненню прыблізна за трыдцьць дзён пасля пакупкі, як працюе політыка вярнення». Адказ ёсць обоснованым і конкрэтным, і яго можна знайсці ў рэальным абзаце ў PDF-файле.
Чаму не паставіць усё ўнутрь?
Справядлівая працоўка — гэта тое, што акны кантэксту зараз вельмі вялікія. Чаму бы не паставіць весь документ у кожны запіт і не абыцца ад векторнага пошуку?
- Косц і час адаптавання. Кожны адправлены токен коштае грошы і выклікае паўзу. Калі база знання налічвае 500 сторанак, адправка всіх дакументаў па кожнаму запытанню ёсць марнатрапствам, калі адпаведзь знаходзіцца ў аднам абаратку.
- Падышоўшыя показнікі запамятовання. Нават з вялікімі обсягамі дакументаў, мадэлі гэральна гірш адмаўляюцца з інформацыяй, якая знаходзіцца серэд іншых патраграфаў запытання, чым з інформацыяй, якая даўаецца безпосерэдна і кораце.
- Масштабаванне. Адправка всіх дакументаў не ўздаяецца, калі база знання перастае пасвядчаць у межах дадзеных обсягаў, напрыклад, калі йдзе пра тыячы PDF-файлаў, цэлую вікі-базу компаніі чы рокі пісьменных запытанняў па падтрымку.
RAG дазволяе сістэме растаць з аднаго дакумента да цэлай базы знання, пры чым розмер запытання застаецца тым жа. Толькі крок адзысквання інформацыі працюе з усім наборам дадзеных, а запытанне бачыць толькі калькі актуальных фрагментаў.
Паўстанні, якія з’яўляюцца рана
- Няпрацэйнае разбіянне на часткі паслабляе працэс выкарыстоўвання інфармацыі, незалежна ад якосці модэлі. Калі табліцу разбіць на две часты, а рэчыцу — насерэдзі, нічыя наступныя крокі гэта не паспрацюе. Пераканаўцеся ў якосці выкарыстоўвання інфармацыі, прытым як не крытыкаваць модэль.
- Несувярэнныя модэлі эмбедынгу робяць рэйтынгі сэродзелів бессэнсавымі. Індэкс, створаны з text-embedding-3-small, не можа быць выкарыстоўваны з іншымі модэлямі.
- Выкарыстоўванне занадта малай колькасці частак або занадта большай колькасці таксама шкодзі. Калі k=1, можа не быць знайдзена справжняя адпаведнасць, а калі k=20, модэль тоне ў шуме і збільшуецца вартасць апрацоўкі. Пачніце з трох-пяці частак і наладзіце значэння на адпаведнасці з рэзультатамі.
- Без альтэрнатывы, калі няма інформацыі, модэль все равно стварае адпаведны адказ, нават калі рэлевантны кантэкст знаходзіцца пры яе. Чытка апісаць, што робіць, калі кантэкст не мае адказу.
Куды працаваць далей
Калі базовы цыкл працюе, тры паўрадкі ўдосконалення ёсць наступнымі логічнымі крокамі.
- Гібрыдны рэшук спалюе сэмліярнасць вектараў з падборам ключоўых слоў, таму што дзеяныя запыткі, такія як точныя коды або назвы продуктав, лепей адпавядаюць ключоўым словам.
- Практыка пераранжавання спачатку прыносіць шырэй скупку кандыдатаў, а пасля дазваляе другаму, болей тачнаму модэлю пераранжаваць іх, прычым языковы модэль яшчо нічога не бачыць.
- Фільтрацыя метаданаў прыкладзея дакладнасці, такія як дата, выхадны адказ чы віддзел, да кожнага фрагмента, тады адзысканне можа быць сфармаванае раней, чым ўжо запускаецца пошук на адпаведнасць.
Нічога з гэтага не мае значэння, пакуль базовы цыкл фрагмента, укладкі, зберагчэння, адзыскання і стварэння адказа працуе. Найшырэйшы спосаб рэальна зразумець гэта — запрацаваць адны PDF, які мае значэнне, навмысна нарабіць проблемы, такія як несувярэнныя модэлі укладкі чы фрагменты без перакрыцця, і пазірэць, як падыходзяць адказы. З RAG способы неудач научаюць больш, чым успешны падход.
Супаўзеўныя матэрыялы
- Раз'яснення прынцыпа Retrieval-Augmented Generation: Як усунуць прасоўкі ў знаннях LLM — Дазвольце вам дазнацца, чаму LLMі ствараюць халюцынаціі і стаюць застарэлымі, а пасля пазнаць па кроках, як тэхнологія RAG адбірае інфармацыю, яе фрагментуе, інтэгруе і дапамагае у падготовцы запітоў, каб усунуць гэтыя проблемы.
- Заражэнне дадзеннямі RAG: Як хакеры прыходзяць да пайплайнаў без змены самага LLM — Як зловеры заражаюць базы знанняў RAG праз дакументы, метададзеныя і кантролюемую інжэкцыю запітоў, а таксама якія меры кіравання жыццёвым цыклам дадзенняў насправды заходзяць проты гэтага.