Галоўная / Артыкулы / Практычныя прытамулкі: Створыце систему RAG з нуля — практычна практыка, без костаў API

Практычныя прытамулкі: Створыце систему RAG з нуля — практычна практыка, без костаў API

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

2697 слоў

Наступныя прыміткі паказваюць практычны шлях адпрацоўкі тэмы «Стварэнне системы RAG з нуля — практычна, без костаў API». Акцэнс ставіцца на контракты, перакананняя та месцы для коду, а не на мотывацыйныя аспекты. Калі працуеце на стадіі агляду, спачатку запісайце контракт: неабходныя даны, сігнал успеху та тое, што выканаецца у разы частковага невялікога браку. Такі чарт дапамагае заліцвачыць пазнейшыя змены ў кодзе. Документавайце як шлях успеху, так і шлях вяселення. Перапрыбуткі, людзкі контроль та обработка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней.

Шта такое RAG на самай працэ (60 секунд)

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

question ──► [embed] ──► [search your docs] ──► top chunks ──┐
                                                             ▼
                                          [LLM: "answer using this context"] ──► answer

Крок 0 — Наладжэнне

Этап налагоджэння кроку 0 працюе найкраща, калі яго розглядаць як меркавыя плошча. Запісайце адна ідеальная версія рэзультата, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану, перш чым расширваць масштабы. Разглядзайце гэты этап як кантракт межа вхіднымі даннымі і пераканаленымі выходнымі рэзультатамі. Дайце назвы артыфактам, задаце критэрыі успеху і не падзейцеся частым, непূরным выкананням задач. Раздзеліце правілы часткавага апранкавання данных ад правіл ўзяць іх. Змена адных не павинна прымусіваць перапісванне іншых, калі змянююцца паказателі якосці.

pip install sentence-transformers transformers torch numpy

Крок 1 — База знанняя, якую модель ніколі не бачыла

Этап 1 А, як стадія накоплэння знаёмасцей, працюе наяўнай краща, калі яго спрыяваць як мерыемую плошчу. Зберагучы адны ідеальны прыклад роботы, адзін кейс неудачы і прыметкі па адвярненню змян, перш чым расширваць масштабы. Запісвайце час выканання і кост токеноў або запытаў праза функцыйнае рэзультат. Відразлівае паказанне костаў з’являецца перашкоду неспакойным рахункам, калі процес пераходзіць з дэмаверсіі ў спяльныя среды. Задаце бюджет токеноў на кожны раунд і на кожную сесію. Інструменты-агенты агрэсывна расширваюць контекст; строгі ліміты не дазволяюць дэмаверсіям ператварыцца на неспакойныя рахункі. Этап 1 А, як стадія накоплэння знаёмасцей, працюе наяўнай краща, калі яго спрыяваць як мерыемую плошчу. Зберагучы адны ідеальны прыклад роботы, адзін кейс неудачы і прыметкі па адвярненню змян, перш чым расширваць масштабы. Дакументавайце як успішны, так і вярнучыся парадоксы разам. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных запытаў ёсць часткай продукту, а не пасляднім дапрацоўкам.

# rag.py
DOCUMENTS = [
    """Nimbus is a fictional note-taking app launched in 2023. The free plan,
    called Nimbus Lite, allows up to 50 notes and 1 GB of storage. There are no
    collaboration features on the free plan.""",
    """Nimbus Pro costs 8 dollars per month billed annually, or 10 dollars billed
    monthly. Pro removes the note limit, gives 50 GB of storage, and unlocks
    real-time collaboration with up to 5 people per note.""",    """Nimbus stores all notes encrypted at rest using AES-256. End-to-end
    encryption is only available on the Pro plan and must be enabled manually in
    Settings > Security. Once enabled it cannot be turned off for that note.""",    """The Nimbus mobile app supports offline editing. Changes made offline are
    queued and sync automatically the next time the device is online. If two
    devices edit the same note offline, Nimbus keeps both versions and flags a
    conflict for the user to resolve.""",    """Nimbus offers a 30-day refund policy on all paid plans, no questions asked.
    Refunds are processed to the original payment method within 5 business days.
    Annual plans cancelled after 30 days are not refundable but stay active until
    the end of the billing period.""",    """Nimbus support is available via email at help@nimbus.example and live chat.
    Live chat is only staffed for Pro customers, Monday to Friday, 9am to 6pm UTC.
    Free-plan users receive email support with a typical 48-hour response time.""",
]
from transformers import pipeline
gen = pipeline("text2text-generation", model="google/flan-t5-base")
print(gen("How much does Nimbus Pro cost?", max_new_tokens=50)[0]["generated_text"])

Этап 2 — Разбіўка на часткі

У стадії чанкінгу на 2-му кроцы неабяжна практычна апранаванне інпутаў, вялікога адпаведальнага за крок і крэтарыяў завершэння працы перад змінайом коду. Аператары павінны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы прыхованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшаў, прычына неудачы павінна вказываць на адзіну адпаведальнасць, а не на заплутаны ланцужок задач. Наводзіце тыя часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх цитатаў аператары не зможаць разлічыць галюцинацію ад працягу індэксавання.

def chunk_text(text, chunk_size=60, overlap=15):
    """Split text into overlapping chunks of `chunk_size` words."""
    words = text.split()
    chunks = []
    start = 0
    while start < len(words):
        end = start + chunk_size
        chunks.append(" ".join(words[start:end]))
        if end >= len(words):
            break
        start = end - overlap   # step back by `overlap` so context isn't cut
    return chunks
# Build our chunk list, remembering which doc each chunk came from
chunks = []
for doc_id, doc in enumerate(DOCUMENTS):
    for c in chunk_text(doc):
        chunks.append({"doc_id": doc_id, "text": c})print(f"{len(DOCUMENTS)} documents -> {len(chunks)} chunks")
for c in chunks[:3]:
    print("-", c["text"][:70], "...")

3-й крок — Ембеддынгі: ператварэнне тексту на векторы

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

from sentence_transformers import SentenceTransformer
embedder = SentenceTransformer("all-MiniLM-L6-v2")# Embed every chunk. normalize_embeddings=True makes the vectors unit-length,
# which lets us measure similarity with a simple dot product later.
chunk_texts = [c["text"] for c in chunks]
chunk_vectors = embedder.encode(chunk_texts, normalize_embeddings=True)print("vector shape:", chunk_vectors.shape)   # (num_chunks, 384)
import numpy as np
pairs = embedder.encode(
    ["the price of the pro plan", "how much does it cost", "the weather in Paris"],
    normalize_embeddings=True,
)
print("price vs cost :", round(float(pairs[0] @ pairs[1]), 3))   # should be HIGH
print("price vs weather:", round(float(pairs[0] @ pairs[2]), 3)) # should be LOW

Крок 4 — Адзысканне: знаходжэнне фрагментаў, якія адпавядаюць на запыт

Для этапу адаптавання дадзейнаў у Чэрговым кроку 4 неабходна перад зменай коду задаць вхідныя даны, адпаведальнага за крок і критэрыя завершэння. Аператары должны магчымае перайсці на гэты крок з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Запісвайце час выконання і вартасьць токенаў або запытак па боку функцыйнаых рэзультаатаў. Відразлівае паказанне вартасцей запобегае неспадзяваным рахункам, калі процес пераходзіць з дэмовай среды ў спяльнаныя сераўеры. Указвайце тыя часткі тексту, якія фактычна лежалі в основе адпаведнай адказу. Без цых цітатаў аператары не можаць розразліць галюцинацію ад прычын, зв’язаных з недастаткам індэксавання. Для этапу адаптавання дадзейнаў у Чэрговым кроку 4 неабходна перад зменай коду задаць вхідныя даны, адпаведальнага за крок і критэрыя завершэння. Аператары должны магчымае перайсці на гэты крок з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Аддзейнавайце разам інструкціі па успішным працэсе і спосабы вярнення ў нормальны стан. Перапрыбуткі, людзкі контроль і обработка некоректных дадзейнаў є частью продукту, а не елементамі, якія дадаюцца пазней.

import numpy as np
def retrieve(question, k=3):
    q_vec = embedder.encode([question], normalize_embeddings=True)[0]
    scores = chunk_vectors @ q_vec              # cosine similarity to every chunk
    top_idx = np.argsort(scores)[::-1][:k]      # indices of the k highest scores
    return [(chunks[i]["text"], float(scores[i])) for i in top_idx]for text, score in retrieve("How much does Nimbus Pro cost?"):
    print(f"[{score:.3f}] {text[:80]}...")

Шаг 5 — Генераванне: нехай модэль адпавядае на падставе контексту

Калі працюяце над шагам генеравання, спачатку запісайце умовы: неабяжлівыя данні, сігнал успеху і тое, што выходзіць у разе частковага няўспэху. Такі список контроля дапамагае заліцьварыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь шаг не выйшоў, няўспэх павінен вказваць на адну конкретную адпаведальнасць, а не на заплутаны ланцужок задач. Зберагаюце у кэшы стабільныя інструкцыі системы і схемы інструментаў. Перадзял працэсу ідентычным прамаргам ўжо часта становіцца прычыной некароткага часу выканання.

from transformers import pipeline
generator = pipeline("text2text-generation", model="google/flan-t5-base")def rag_answer(question, k=3):
    retrieved = retrieve(question, k=k)
    context = "\n".join(text for text, _ in retrieved)    prompt = f"""Answer the question using only the context below.
If the answer is not in the context, say you don't know.Context:
{context}Question: {question}
Answer:"""    out = generator(prompt, max_new_tokens=80)[0]["generated_text"]
    return out.strip(), retrievedanswer, sources = rag_answer("How much does Nimbus Pro cost?")
print("ANSWER:", answer)
print("\nBased on:")
for text, score in sources:
    print(f"  [{score:.3f}] {text[:70]}...")

Шаг 6 — Аб’еднанне всього

Калі працюеце над этапам 6 «Выконанне», спачатку запісайце контракт: неабяжлівыя даны, сигнал успеху і тое, што выходзіць у разе частковага невыпання. Такі список пераконтролюе чыстасць пазнейшых змян у кодзе. Спрэцьвуйце гэты этап як контракт межа данымі і перакананымі выходамі. Дайце назвы элементам, задаце критэрыі успеху і не падзельвайцеся на часткова выпання без адказу. Замерайце ступень запам’ятоввання на фіксаванай сэтцы запитаў прычымо да налаштавання прапазнікаў. Частае змяненне прапазнікаў рэдка калі вярна выправляе слабую систему адзыскання інформаціі.

# rag.py — a complete, local, no-API RAG system
import numpy as np
from sentence_transformers import SentenceTransformer
from transformers import pipeline
DOCUMENTS = [
    """Nimbus is a fictional note-taking app launched in 2023. The free plan,
    called Nimbus Lite, allows up to 50 notes and 1 GB of storage. There are no
    collaboration features on the free plan.""",
    """Nimbus Pro costs 8 dollars per month billed annually, or 10 dollars billed
    monthly. Pro removes the note limit, gives 50 GB of storage, and unlocks
    real-time collaboration with up to 5 people per note.""",
    """Nimbus stores all notes encrypted at rest using AES-256. End-to-end
    encryption is only available on the Pro plan and must be enabled manually in
    Settings > Security. Once enabled it cannot be turned off for that note.""",
    """The Nimbus mobile app supports offline editing. Changes made offline are
    queued and sync automatically the next time the device is online. If two
    devices edit the same note offline, Nimbus keeps both versions and flags a
    conflict for the user to resolve.""",
    """Nimbus offers a 30-day refund policy on all paid plans, no questions asked.
    Refunds are processed to the original payment method within 5 business days.
    Annual plans cancelled after 30 days are not refundable but stay active until
    the end of the billing period.""",
    """Nimbus support is available via email at help@nimbus.example and live chat.
    Live chat is only staffed for Pro customers, Monday to Friday, 9am to 6pm UTC.
    Free-plan users receive email support with a typical 48-hour response time.""",
]def chunk_text(text, chunk_size=60, overlap=15):
    words = text.split()
    chunks, start = [], 0
    while start < len(words):
        end = start + chunk_size
        chunks.append(" ".join(words[start:end]))
        if end >= len(words):
            break
        start = end - overlap
    return chunksprint("Loading models (first run downloads them)...")
embedder = SentenceTransformer("all-MiniLM-L6-v2")
generator = pipeline("text2text-generation", model="google/flan-t5-base")# Index the documents once at startup
chunks = []
for doc_id, doc in enumerate(DOCUMENTS):
    for c in chunk_text(doc):
        chunks.append({"doc_id": doc_id, "text": c})
chunk_vectors = embedder.encode(
    [c["text"] for c in chunks], normalize_embeddings=True
)def retrieve(question, k=3):
    q_vec = embedder.encode([question], normalize_embeddings=True)[0]
    scores = chunk_vectors @ q_vec
    top_idx = np.argsort(scores)[::-1][:k]
    return [(chunks[i]["text"], float(scores[i])) for i in top_idx]def rag_answer(question, k=3):
    retrieved = retrieve(question, k=k)
    context = "\n".join(text for text, _ in retrieved)
    prompt = (
        "Answer the question using only the context below. "
        "If the answer is not in the context, say you don't know.\n\n"
        f"Context:\n{context}\n\nQuestion: {question}\nAnswer:"
    )
    out = generator(prompt, max_new_tokens=80)[0]["generated_text"]
    return out.strip()if __name__ == "__main__":
    print("RAG ready. Ask about Nimbus (or type 'quit').\n")
    while True:
        q = input("You: ").strip()
        if q.lower() in {"quit", "exit", ""}:
            break
        print("Nimbus bot:", rag_answer(q), "\n")
python rag.py

Этап 7 — Падтвердзіце, што RAG выканае роботу (тэст A/B)

Калі працуеце над стадзіяй Step 7 Prove RAG, спачатку запісайце умовы викорыстоўвання: неабходныя данні, сігнал успеху і тое, што выходзіць па частковай нявыполненасці. Такі список дапамагае залічыцца з пазнейшымі змянамі ў кодзе. Запісвайце час выконання і кост токеноў або запытаў разам з функцыйнальнымі рэзултатамі. Відразувыя данні пра косцы запобегаюць неспакойным рахункам, калі працэс пераходзіць з дэмовай среды ў спакульную. Замерьце рэкалі на фіксаваным наборы пытанняў прычыну налаштаванню прамптаў. Частыя змены прамптаў рэдка калі вядуць да павышэння якасці адзысквання інформацыі. Калі працуеце над стадзіяй Step 7 Prove RAG, спачатку запісайце умовы викорыстоўвання: неабходныя данні, сігнал успеху і тое, што выходзіць па частковай нявыполненасці. Такі список дапамагае залічыцца з пазнейшымі змянамі ў кодзе. Аддзекументавайце як шлях успеху, так і шлях вярнення да нормы. Перапрыбуткі, людзкія перакрыцчы і обработка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней.

def no_rag(question):
    out = generator(f"Question: {question}\nAnswer:", max_new_tokens=80)
    return out[0]["generated_text"].strip()
q = "Can free-plan Nimbus users use live chat support?"
print("WITHOUT context:", no_rag(q))
print("WITH context   :", rag_answer(q))

Шаг 8 — Падзейце яго лепша (выберыце тое, што вас цікавіць)

Шаг 8 «Падзейце яго лепша» работае наяўнай карыстна, калі яго спрыяваць як меркаваную плошчу. Запісаце адну «золатую» версію, адны прыклад неудачы і прыметку па поверненню да пярвінскага стану пры расшырэнні масштаба. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі шаг не выйшаў, прычына неудачы павінна вказываць на адную адпаведальнасць, а не на заплутаны процес. Раздзеліце правілы часткавання і правілы выкарыстоўвання дадзеных. Змена адных не павінна вымагаць перапісву другых, калі зменяюцца паказнікі якосці.

Ментальная модель, яку трэба падтрымваць

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

Усуненне проблем

Этап адміністрацыі проблем работае наявнасцю, калі яго спрыяюць як мерыемую плошчу. Зберагачыце адны ідеальны транскрыпт, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перад расшырэнням масштаба. Запісвайце часы выканання і косты токеноў або запытак па боку ад рэзультатаў функцыяналізацыі. Відразувая візуабельнасць костаў запобiegае неспакойным рахункам, калі процес пераходзіць з дэмовай среды ў спяльнаныя сераўеры. Раздзеляйце політыку частковага обробкі дадзеных і політыку ўтрымання іх. Змена адной з яных не павінна вымагаць перапісвы другой, калі зменяюцца паказнікі якосці. Этап адміністрацыі проблем работае наявнасцю, калі яго спрыяюць як мерыемую плошчу. Зберагачыце адны ідеальны транскрыпт, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перад расшырэнням масштаба. Дакументавайце як успішны, так і патэнцыйны шляхы рашэння проблем. Перапрыбуткі, людзкія контрольны пункты і обробка неканальных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней.

Чэк-ліст для эксплуатацыі

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

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

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

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

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

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

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

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