RAG на LangChain з нуля: загрузчыкі, фрагменты, эмбедынгі і ацэнка
Пакрокава будуем сістэму пытанняў і адказаў па дакументах: загрузка файлаў, разбіўка, эмбедынгі, фазы індэксацыі і пошуку і як ацэньваць пошук і адказы.
Крыніцы: Building a RAG System with LangChain
Сістэма, якая чытае вашы дакументы і адпавядае на запытанні пра яны, — адна з найкорыстнейшых рашэнняў, якія можна стварыць за дапамой LLM сёння. Гэты падход называецца RAG — retrieval-augmented generation. Хутчэй пазнаём усія складовыя, для чаго яны патрэбны, і як вывучыць, чы рашэння, якое вы стварылі, ўсё-такі якіснае.
Што такое LangChain
LangChain — не мадэль і не база дадзеных. Гэта фрэймворк, бліжы ў сваёй сутнасці да універсальнага адаптэра. Існуе дзесяткі прадаўцоў LLM з іхнімі сабскрыпцыямі, дзесяткі вектарных баз дадзеных, дзесяткі інструментаў для чытання дакументаў. Без проміжнага шару выкарыстоўванне GPT-4 сёння і Claude завтра значыць перапісваць палову вашага коду. LangChain з’яўляецца ў вароце едынага, адносныя інтэрфейсу для ўсіх іх.
Назва падае з-за яго другой функцыі. Працэйная прыкладка на асасе LLM рэдкасцю ёст канцэльным вызовам мадэлі. Можа быць, вы завантажыце дакумент, падыяграфіце яго, запусціце ўсё тое ж запіт на падыяграфах і сформатаваце рэзультат. Ланцюг сцепляе гэтыя крокі ў адну практычную схему. Для RAG ланцюг пачынаецца з запіта карыстальніка. Ён уключаецца ў структуру, якая керуе пошукам у базе дадзеных, а найболей рэлевантныя рэзультаты падставляюцца ў шаблон запіта. LLM вызываецца з гэтым запітам, яго адказ парасканавляецца, а пасля парасканавлены адказ вяртаецца корыстніку. LangChain забезпечвае элементы для кожнага кроку, таму вам не трэба ўсё склеіваць вручную.
Завантажэнне дакументаў
Спачатку дакументы павінны быць прынятыя, і то не у вазе сырых байтаў. Незалежна ад таго, чыія то даныя — PDF, веб-сторанка, CSV, файл Word чы ўпако папка з маркдауном, прыемнік ператворае іх у стандартны об’ект Document з двумя полямі: page_content для тэксту і metadata для інфармацыі пра тое, звядзе гэты дакумент быў запрашаны (сторанка, URL, будзь-яя іншая корысная інфармацыя). Метаданы маюць важнае значэнне пазней, каліколькі яны дазволяюць паказаць корыстнікам, з якага джэрага была отрымана адпаведная адказ.
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("handbook.pdf")
documents = loader.load()
Гэта дае адзін Document на кожную старонку PDF, пры чым кожны з іх несе свой тэкст і номер старонкі. Гэта прычына, за якую варта як можна раней выклікваць такую прычыну: завжды трэба залішаць восьведчанні пра месца падачы тэксту разам з яным, адколі кожны наступны крок, ад разбівання на часткі да цытавання выкладніка ў заканчальнай адпаведзі, залежыць ад гэтых восьведчанняў. Калі заменіць прыстрой для чытання на іншы тып файлу, выходны рэзультат застанецца тым жа.
Разбівання на часткі
Дакумент можа быць 200-стороннім PDF. Нават з вялікім вікнам кантэксту, вставлянне цэлых кніг у кожны запіт ёсць марнатрапнам, дорогім і частаў боюцца, чым надача модэлю сфокусаваных фрагментаў. Дужа дугія дакументы дзеляецца на часткі з калькуючымся колькасцю токенав, і толькі найболей значныя часткі падаюць да LLM, калі хтось задае запыт.
Самыя важныя ўстановкі — гэта Размер частак: ён павінен быць такім, каб кожная частка была достатнюю за дужыну; калі ёй занадта мало, то губіцца кантэкст, а калі занадта многа — уключаецца нерэлевантны тэкст. Прыблізна 500 токенав — гэта разумны пачатковы значэння. Перакрыцча — гэта ступень перакрыцця суседзячых частак. Калі немае перакрыцця, рэчы, распадзеленыя между двумя часткамі, тырадзуюць свой сэнс, а з перакрыцчам обе часткі бачаюць зв’язак между ямі.
RecursiveCharacterTextSplitter — гэта стандартны выбар. Ён вяршыць прыродныя межы, спачатку працюе з абзацамі, потым з рэчымаў, пасля — з словамі, і толькі як апошняе рашэнне дзелае з серэдынай часткі слова. Існуюць таксама спецыялізаваныя варыянты: MarkdownHeaderTextSplitter для дакументаў з загаловкамі і TokenTextSplitter, калі трэба лічыць рэальныя токены, а не просто сімволы.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(documents)
Разбіўка тэксту здаецца простай, але сільна паўтараецца на якасці. Люды таксама нарэшце налаштовуюць гэты параметр, хоча паслабленая разбіўка тыхамо пагрожае всему наступнаму працэсу. Якшто часткі тэксту некарэктныя, жадныя трукі з выкарыстоўванням інформацыі не зможу спасці сістэму.
Эмбеддынгі
Камп’ютары не можу запытаць «якая частка тэксту супадае з маём запытам?» пра необработаны тэкст. Мадэль эмбеддынгу, якая таксама называецца кодэрам, ператварае тэкст у вектор — спіс цяжэй, пры чым ключовая аўтаматыка заключаецца ў тым, што тэкст з падобным значэнням атрымоўвае падобныя векторы.
Уявіце сабе, што кожны фрагмент тэксту атрымоўвае адрэсу ў прасторы з сотнямі або тысячамі вімераў. Службацкі пес і юлаючы цуценя атрымоўваюць суседзкія адрэсы, тады як крах фондовага рынку знаходзіцца далека. Порэванне значэнняў ператвараецца ў геаметрыю: насколькі блізкая ёсць два точкі?
Два раз'яснення дапамагуць. Першае — вектары стварае кодэр, а не база дадзейнаў вектароў. Chroma, Pinecone, Weaviate і Qdrant толькі зберагаюць вектары і шукаюць іх быстра, пры чым ніхто з іх не розумее мову. Людзі постаўляюць гэта ў сплутанні сталыя. Другае — розмер вектара залежыць ад кодэра. text-embedding-3-small ад OpenAI выдае 1536 вымераў, тады як такі адкрытыя мадэль як all-MiniLM-L6-v2 — 384. Больш вымераў дазволяе зафіксаваць больш нюансаў, але за цену большога прыемлівання дадзейнаў і медленнейшага пошуку.
Рэальныя эмбеддынгі немагчыма уявіць, таму людзі прыводзяць іх да 2D або 3D фармату. Калі умястыць купку слоў і прывесты іх, то з’являюцца кластэры: жывотныя разам, фрукты ў іншым месцы, автамабілі — ў іншай часткі. Ніхто не рассказаў модэлю пра гэтыя катагорыі. Яна навучылася з вельмі большых кантэнтаў, што «сёбра» і «кот» з’являюцца ў падобных кантэкстах.
Гэта і ўсё, што робіць RAG эфектываў. Калі корыстнік запытаеся, што кампанія кажа пра дзецячую выпускну, вектар запытання падае поблізу фрагментаў пра „прыямы для семейнай выпускнай“ або „выпускна для новых бацькоў“, хоча гэтыя словы разныя.
Працэс апрацоўкі
Існуе два этапы. Індэксаванне адзейнаеся праз раз у момент наладжвання: завантажваюцца даны, яны дзельнуюцца на часткі, кожная з частак уключаецца, а векторы зберагаюцца. Новыя дакументы пазней проста уключаюцца і дадаюцца да базы. Адзыванне адбываецца праз кожны запіт: запыт уключаецца за дапамою таго ж кодэра, шукаюцься найбліжэйшыя часткі ў базе вектораў, яны падставляюцца ў запыт разам з самім запытом і адправляюцца да LLM, які формуе свой адказ на аднойчы зберагнутых у вас дакументах, а не на прыпусках. Одна база вектораў служыць для обох этапаў: да яе запісваюцца даны пад час індэксавання, а чытаюцца падчас адзывання. Гэта всяя архітэктура. Паколькі індэксаванне — гэта едынственны вітратны працэс, медленная частка (уключэнне тыяўчох частак) не вплывае на час чакання корыстніка. Толькі уключэнне запыту, пошук і вызов LLM вплываюць на час чакання.
У часе выканання бераюць участка чатыры элементы: корыстувач, код вашага прыемніка, элемент для выявлення і LLM. Корыстувач адправляе запіт вашаму коду. Ваш код прасіць элемент для выявлення прынадныя фрагменты, а элемент для выявлення вбудоввае запіт і шукае ў базе дадзеных. Вернуцца K найболей падобных фрагментаў. Ваш код стварае запрошэнне з гэтых фрагментаў плюс первісны запіт і адправляе яго LLM, які генеруе адпаведзь на базе заданага кантэксту. Ваш код вяртае гэтую адпаведзь.
LangChain пакрывае большую частку гэтага за дапамой двух абстракцый. LLM (альбо ChatModel) абгрунтуе вашу мадэль, а Retriever — працэс пошуку ў вектарным сховішчы. Споўнаюце іх ў ланцуг, і фрэймворк зробіць рэшту:
from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(),
retriever=vectorstore.as_retriever(),
return_source_documents=True
)
response = qa_chain.invoke({"query": "What is our parental leave policy?"})
Гэта рабочая сістэма RAG узкім кольцам ліній, і самэль гэтае час, калі хочацца зупніцца. Не робіце гэтага. Сістэма, якая адпавядае, яшчэ не ўсё тая сістэма, якой можна даверыць.
Чаму трэба ацэніваць
Пастка заключаецца у тым, што задаёмаеся адна запітанне, отрымваецца правдападобны адпаведзь і вважаецца, што все гатова. RAG паспялівае тыхамо. Апраналізавач можа выбраць некалькі неправільных фрагментоў, LLM можа стварыць халюцынаціі нават з хорашым кантэкстам, а разбіўка на фрагменты можа разлучыць адпаведзь на две часткі. Ацэніванне паказвае, якая частка не працуе: апраналіз, генераванне чы ўжо і тое, і другое, адколі кожна з гэтых частак трэба лагодзіць адначынна. Яно таксама дае вам крэтыну для парабярання, таму што вы будзеце пробаваць разныя розмеры фрагментоў, кодэраў і запрасаў, а без меры кожная змяна застаецца толькі спадзяваннем.
Метрыкі апраналізу
Чатыры стандартныя метрыкі, кожная з яых паказвае ўсё іншае:
- Recall@K: з усіх дакументаў, які павінны былі з’явіцца, скількі з іх увайшло ў топ-К? Калі ў корпусе ёсць 4 рэлевантных дакументы, а 3 з іх у вашам топ-5, то Recall@5 становіць 0,75. Высокі показнік recall азначае, што мало чаго было прыхована.
- Precision@K: з усіх K рэзультатаў, які былі вярнуты, скількі з іх было рэлевантным? Тры хорашыя апаведзенні з пяці даюць 0,60. Высокі показнік precision азначае мало шуму.
- MRR (Mean Reciprocal Rank): уваажаецца толькі пазычэнне першага правільнага рэзультата. Першыя апаведзенні на пазычэнні 2 даюць 0,50, а на пазычэнні 1 — 1,0. Гэты показнік награджае быстрае знаходжэнне правільнай адпаведзі.
- nDCG (Normalized Discounted Cumulative Gain): гэты показнік ёсць найболей тонкі. Ён вазнае, насколькі рэлевантны кожны дакумент і якае у ўсём корпусе яго пазычэнне, таму рэлевантныя элементы з вышэйшым пазычэннем маюць большую вагу, і ён прагледваеся з урахоўванням найкрашчага можлівага пазычэння.
У практыцы існуе два показнікі: адны для пераканання ў повнасці (Recall) і другі для ацэнкі якосці арранжавання (MRR або nDCG).
Якасць адказу
Нават ідэальна выкарыстоўвання інформацыі з можлівых рэзультатаў можа прынесці паслаблы адказ, таму адказы трэба ацэніць окрэма. Вы выкорыстоўвайце той самы падготавлены тэстовы набор, дзе кожны элемент супакоўвае запіт з фрагментамі, якія павінны быць прадстаўлены, і стандартным адказам. Чатыры вышэўказаныя показнікі выкарыстоўвання інформацыі пакрываюць першую частыну. Для самых адказоў ацэніце іх па іншых крытэрыях:
- Аддакватнасць: чы ён базуецца на выкарыстоўанай інформацыі чы ён вымышлены?
- Адноснае значэнне: чы ён адпавядае запыту?
- Правільнасць: як ён паўстанае у порэвананні з правільным адказам?
- Повнасць: чы ён пакрывае все, што павінен?
Звычным спосабам ёсць вторы LLM, які выступае як суддзя. Дайце йому запітанне, адказ і правільны адказ, і папрабуйце отрымаць оцэнку па кожным параметру. Гэты метод не ідэальны, але ён дозволяе масштабавацца.
Набор для тэставання
Кожны з вышэўказанных крокаў ацэнкі спакоюеся на адной рэчы: на зборцы запітанняў з вядомымі правільнымі адказамі.
Гэта самы недаацэнаваны элемент. Без яго немагчыма ніч га ацэніць. Стараюцца стварыць 50–кілька сотэў прыкладаў, кожны з якіх мае рэалістычнае запітанне, часткі, якія маюць адказ, і ідэальны адпаведны адказ. Аднакоў — гэта рэальныя запіты корыстувачаў, калі сістэма працюе, або сінтэтычныя: падайце сваі дакументы ў LLM, нехай ён напісае пары запітання-адказ, а потым перагляньце іх і выправіце.
Захавайце гэты набор. Калі ён вучынены, кожныя змяны ацэнююцца па яго стандартам, і саме так можна з’ясавіць, чыя змяны былі корысныя.
Дзе становіцца сложней
У стыслій формі RAG – гэта: падзелу дакументаў на частыні, векторызація кожноі з цых частын, іх зберыгання, включэння корыстувацькіх запітыв, пошук найбліжчых векторыв і перэдача цых частын до LLM. LangChain надае стандартні елементы для кожнага кроку, а оцінка показуе ефектывнісць працы. Справжній продукт ставіць складнішы запітання: які розмір частын документа ёсць оптымальнім, які кодувач найкрашчый, як підтрымуваты актуальнісць індэксу, колі перэгруповуваты даныя і чы вікорыстовуваты гібрыдній пошук. Ці рышэння візначаюць, чы будзе дэмо-версія корысною, чы ні.