एक स्थानीय RAG पाइपलाइन स्क्रेपलेस और ओलामा के साथ कैसे बनाएं
Expert in Web Scraping Technologies
TL;DR:
- पूरा लूप लोकल रूप से चलता है, जिसमें कोई एम्बेडिंग-प्रदाता या LLM-प्रदाता API कुंजी नहीं है। Scrapeless इस पाइपलाइन में एकमात्र भुगतान वाले कॉल हैं - स्रोत पृष्ठों को प्राप्त करना। Chunking, embedding, vector storage, retrieval, और उत्तर उत्पादन सभी इस मशीन पर चलते हैं।
- रिकवरी सिद्ध है, माना नहीं। दो अलग-अलग स्क्रैप किए गए लोगों के बारे में दो सवाल प्रत्येक 48 टुकड़ों में से सही व्यक्ति की जीवनी का टुकड़ा वापस लाते हैं - वास्तविक समानता की दूरी नीचे कैद की गई है, यह नहीं कहा गया है।
- उत्पादन चरण एक लाइव स्थानीय कॉल है, न कि एक टेम्पलेट उत्तर। एक 494M-पैरामीटर ओलामा मॉडल केवल प्राप्त किए गए टुकड़े को पढ़ता है और सही उत्तर देता है, जो कुछ मिनट पहले स्क्रैप किए गए पाठ पर आधारित है।
- Chunking और embedding जानबूझकर छोटे रहते हैं। प्रत्येक जीवनी 4-14 ओवरलैपिंग 60-शब्द टुकड़ों में बनती है, जिन्हें एक बैच स्थानीय कॉल में
sentence-transformersके साथ 384-आयामी वेक्टर में परिवर्तित किया जाता है - GPU की आवश्यकता नहीं है। - यह Scrapeless पाइपलाइनों के दो अन्य हिस्सों का डाउनस्ट्रीम आधा है। यदि आपके पास पहले से ही साफ टुकड़े या एक OpenAI-एंबेडेड इंडेक्स है, तो नीचे स्टेज 3 या स्टेज 5 पर जाएं - भिन्नताएँ इनलाइन में दर्शाई गई हैं।
- शुरुआत करने के लिए मुफ़्त। app.scrapeless.com पर मुफ्त योजना पर अपना Scrapeless API कुंजी बनाएं।
पाइपलाइन एक नज़र में
रिकवरी-उन्नत उत्पादन, जैसा कि मूल RAG पेपर में परिभाषित किया गया है, एक बाह्य संकलन पर एक रिकवरी करने वाले को एक जनरेटर के साथ जोड़ता है जो वही पढ़ता है जो रिकवरी करने वाला पाता है। उस विचार के अधिकांश वाकथों में से अधिकांश इसमें से एक आधे पर रुक जाते हैं। यह दोनों आधे, अंत से अंत तक, वास्तविक स्क्रैप किए गए पृष्ठों के खिलाफ बनाता है:
- प्राप्त करें — Scrapeless यूनिवर्सल स्क्रैपिंग API के माध्यम से एक निश्चित सेट के लेखक पृष्ठों के लिए रेंडर की गई HTML खींचें।
- निकालें और टुकड़े करें — प्रत्येक पृष्ठ को जीवनी पाठ में पार्स करें, ओवरलैपिंग शब्द खिड़कियों के साथ विभाजित करें जिनका स्रोत हो।
- एम्बेड करें — हर टुकड़े को लोकल रूप से
sentence-transformersके साथ एक वेक्टर में बदलें। - स्टोर करें — वेक्टर और उनके स्रोत मेटाडेटा को स्थानीय Chroma संग्रह में स्थायी रूप से संग्रहित करें।
- रिकवरी — एक प्रश्न को एंबेड करें, Chroma से निकटतम टुकड़े के लिए पूछें, और जांचें कि क्या सही व्यक्ति वापस आया।
- उत्पन्न करें — प्राप्त किए गए टुकड़े को एक स्थानीय ओलामा मॉडल को सौंपें और एक ग्राउंडेड उत्तर प्राप्त करें, जिसमें कहीं भी क्लाउड LLM कॉल नहीं है।
दो अन्य Scrapeless पोस्ट पहले से ही इसके कुछ हिस्सों को कवर कर चुकी हैं। वेब-पाठ इनजेशन गाइड प्राप्त करता है, निकालता है और corpus.jsonl में टुकड़े करता है, फिर जानबूझकर रुक जाता है: एंबेडिंग "डाउनस्ट्रीम है, जिसमें कोई भी स्टैक आप पहले से ही उपयोग करते हैं।" LLM-पाठ पाइपलाइन गाइड आगे बढ़ता है: OpenAI के साथ पता लगाएं, निकालें, टुकड़े करें और Chroma में एंबेड करें। लेकिन यह एंबेडेड रिकॉर्ड पर रुक जाता है - कोई स्टोर का क्वेरी नहीं करता है या उत्तर उत्पन्न नहीं करता है - और इसे एक भुगतान किए गए OPENAI_API_KEY की आवश्यकता होती है। यह गाइड वह भाग है जिसे उनमें से कोई भी कवर नहीं करता: शून्य एंबेडिंग-प्रदाता कुंजी के साथ स्थानीय एंबेडिंग, एक जीवित प्रमाण कि रिकवरी सही स्रोत को ढूंढती है, और एक स्थानीय मॉडल जो वास्तव में प्रश्न का उत्तर देता है। (RAG के स्वभाव की तुलना में, निर्माण के बजाय, देखें रिकवरी-उन्नत उत्पादन क्या है।)
पूर्वापेक्षाएँ
- Python 3.10 या बाद का।
- एक Scrapeless API कुंजी,
SCRAPELESS_API_KEYके रूप में निर्यातित - प्राप्ति चरण एकमात्र है जिसे इसकी आवश्यकता है। - ओलामा स्थापित है और चल रहा है, एक छोटे मॉडल को खींचा गया है:
ollama pull qwen2.5:0.5b। pip install sentence-transformers chromadb beautifulsoup4 requests।
स्थापित करें
sentence-transformers PyTorch को खींचता है, इसलिए पहला स्थापित करने में कुछ मिनट और लगभग एक गीगाबाइट डिस्क की आवश्यकता होती है। इसके बाद सब कुछ - मॉडल लोड करना, एंबेडिंग, और क्वेरी करना - एक बार मॉडल कैश होने के बाद ऑफलाइन चलता है।
bash
pip install sentence-transformers chromadb beautifulsoup4 requests
ollama pull qwen2.5:0.5b
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
चरण 1 — स्रोत पृष्ठों को प्राप्त करें
यह मार्गदर्शिका के लिए ज्ञानकोश में पांच लेखक-जीवनी पृष्ठ हैं quotes.toscrape.com, एक सार्वजनिक साइट जिसे स्क्रैपिंग प्रैक्टिस के लिए बनाया गया है: अल्बर्ट आइंस्टीन, जेन ऑस्टेन, मैरिलिन मुनरो, जे.के. रॉउलिंग, और थॉमस ए. एडिसन। पांच अलग-अलग लोगों के पांच वास्तव में अलग जन्मस्थान, तारीखें और करियर हैं जो चरण 5 की पुनः प्राप्ति जांच को अर्थपूर्ण बनाते हैं - उन में से एक के बारे में एक प्रश्न का एक सही स्रोत है, न कि पांच संभावित स्रोत।
यूनिवर्सल स्क्रैपिंग एपीआई के लिए प्रति पृष्ठ एक POST रेंडर की गई HTML को प्रतिक्रिया के data क्षेत्र में लौटाता है:
python
# fetch.py -- स्क्रैपलेस यूनिवर्सल स्क्रैपिंग एपीआई के माध्यम से रेंडर की गई लेखक-जीवनी पृष्ठों को खींचें
import os
import pathlib
import requests
ENDPOINT = "https://api.scrapeless.com/api/v2/unlocker/request"
HEADERS = {
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
SOURCE_HOST = "https://quotes.toscrape.com"
AUTHORS = ["Albert-Einstein", "Jane-Austen", "Marilyn-Monroe", "J-K-Rowling", "Thomas-A-Edison"]
pathlib.Path("pages").mkdir(exist_ok=True)
for slug in AUTHORS:
url = f"{SOURCE_HOST}/author/{slug}"
resp = requests.post(
ENDPOINT,
headers=HEADERS,
json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": False, "redirect": True}},
timeout=120,
)
resp.raise_for_status()
html = resp.json()["data"]
pathlib.Path("pages", f"{slug}.html").write_text(html, encoding="utf-8")
print(f"{slug}: {len(html):,} बाइट")
ये पृष्ठ सर्वर-साइड पर रेंडर होते हैं, और जीवनी को बनाने के लिए कोई क्लाइंट-साइड जावास्क्रिप्ट नहीं है, इसलिए js_render False रह जाता है - अनलॉकर फिर भी अनुरोध को संभालता है और पृष्ठ वापस करता है, लेकिन प्रारंभिक HTML में पहले से मौजूद सामग्री के लिए कोई रेंडरिंग लागत नहीं है। एक जीवित रन पांच अलग-अलग बाइट गणनाएँ लौटाता है, लेखक के प्रति एक:
text
Albert-Einstein: 5,329 बाइट
Jane-Austen: 3,413 बाइट
Marilyn-Monroe: 3,663 बाइट
J-K-Rowling: 5,347 बाइट
Thomas-A-Edison: 2,648 बाइट
मुफ्त योजना पर अपना API की प्राप्त करें: app.scrapeless.com
चरण 2 — निकालें और वर्गीकृत करें
प्रत्येक पृष्ठ में तीन समान फ़ील्ड होते हैं जो निश्चित वर्गों में होते हैं: author-title, author-born-date, author-born-location, और author-description। इन्हें BeautifulSoup से खींचें, जीवनी का नेतृत्व स्पष्ट जन्म-सत्र के साथ करें ताकि तथ्य एक खंड में अपने आप को संदर्भित करें, फिर 15 शब्दों के ओवरलैप के साथ 60-शब्द की खिड़कियों में विभाजित करें:
python
# chunk_corpus.py -- pages/*.html -> corpus.jsonl (स्रोत के साथ ओवरलैपिंग शब्द-खिड़की खंड)
import json
import pathlib
import re
from bs4 import BeautifulSoup
CHUNK_WORDS = 60
OVERLAP_WORDS = 15
def extract_author(html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
name = soup.find("h3", class_="author-title").get_text(strip=True)
born_date = soup.find("span", class_="author-born-date").get_text(strip=True)
born_loc = soup.find("span", class_="author-born-location").get_text(strip=True)
desc = soup.find("div", class_="author-description").get_text(" ", strip=True)
return {"name": name, "born_date": born_date, "born_location": born_loc,
"description": re.sub(r"\s+", " ", desc)}
def chunk(words: list[str]):
step = CHUNK_WORDS - OVERLAP_WORDS
for start in range(0, max(len(words) - OVERLAP_WORDS, 1), step):
yield start, " ".join(words[start:start + CHUNK_WORDS])
total = 0
with open("corpus.jsonl", "w", encoding="utf-8") as out:
for page in sorted(pathlib.Path("pages").glob("*.html")):
author = extract_author(page.read_text(encoding="utf-8"))
lead = f"{author['name']} का जन्म {author['born_date']} {author['born_location']} में हुआ था।"
words = f"{lead} {author['description']}".split()
for start, body in chunk(words):
out.write(json.dumps({"id": f"{page.stem}-{start}", "source": page.stem,
"author": author["name"], "word_offset": start,
"text": body}) + "\n")
total += 1
print(f"{total} खंड -> corpus.jsonl")
पांच प्राप्त पृष्ठों के खिलाफ एक जीवित रन 48 खंडों का उत्पादन करता है, जो प्रत्येक जीवनी द्वारा कहा गया है उसी से असमान रूप से विभाजित होता है:
text
Albert Einstein: 615 शब्द -> 14 खंड
J.K. Rowling: 644 शब्द -> 14 खंड
Jane Austen: 327 शब्द -> 7 खंड
Marilyn Monroe: 376 शब्द -> 9 खंड
Thomas A. Edison: 195 शब्द -> 4 खंड
48 खंड -> corpus.jsonl
60 शब्द और 25% ओवरलैप एक छोटे ज्ञानकोश सेटिंग है, जानबूझकर ऊपर दिए गए भोजन मार्गदर्शिका में 220/40-शब्द की खिड़की से कड़ी है - ये जीवनी प्रत्येक में कुछ सौ शब्द चलाती हैं, न कि उस मार्गदर्शिका खंडों की बहु-हज़ार-शब्द लेख। अपनी स्रोत लंबाई के लिए खिड़की को ट्यून करें, न कि एक निश्चित डिफ़ॉल्ट।
चरण 3 — स्थानीय रूप से एम्बेड करें
प्रत्येक टुकड़ा एक 384-आयामी वेक्टर बन जाता है जिसमें all-MiniLM-L6-v2, एक संक्षिप्त वाक्य-embedding मॉडल है, जो CPU पर चलाने के लिए पर्याप्त छोटा है और MTEB बेंचमार्क सूट पर बड़े मॉडलों के साथ बेंचमार्क किया गया है। यह मॉडल एक बार डाउनलोड होता है (लगभग 90 MB) और उसके बाद हर एन्कोड करने का कॉल ऑफ़लाइन चलता है:
python
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
vectors = model.encode(texts, show_progress_bar=False).tolist()
चरण 4 — स्थानीय वेक्टर डेटाबेस में संग्रहीत करें
Chrom's PersistentClient संग्रह को डिस्क पर लिखता है, जिससे इंडेक्स चलाने के बीच जीवित रहता है बिना किसी प्रबंधन सर्वर के। प्रत्येक वेक्टर इसके लेखक और स्रोत पृष्ठ को मेटाडेटा के रूप में रखता है, जो चंके को एक विशिष्ट पृष्ठ पर ट्रेस करना संभव बनाता है, न कि एक गुमनाम पाठ की स्ट्रिंग:
python
import chromadb
client = chromadb.PersistentClient(path=".chroma")
collection = client.create_collection("author_bios")
collection.add(
ids=[c["id"] for c in chunks],
documents=[c["text"] for c in chunks],
embeddings=vectors,
metadatas=[{"author": c["author"], "source": c["source"], "word_offset": c["word_offset"]}
for c in chunks],
)
48 चंक के खिलाफ एक लाइव रन पुष्टि करता है कि संग्रह में सब कुछ रखा हुआ है जो एम्बेड किया गया था:
text
48 चंक्स एम्बेडेड, डिम 384, संग्रह संख्या 48
चरण 5 — सही टुकड़ा पुनः प्राप्त करें
यह वह कदम है जिसे अधिकांश स्थानीय-RAG वॉकथ्रू छोड़ देते हैं: यह साबित करना कि पुनर्प्राप्ति वास्तव में सही स्रोत लौटाती है न कि बस कुछ। प्रश्न को उसी तरह एम्बेड करें जिस तरह चंक्स एम्बेड किए गए थे, फिर क्रोमा से अपने निकटतम पड़ोसियों के लिए पूछें, जहां छोटा नंबर निकटतम मिलान दर्शाता है:
python
query_vector = model.encode([question]).tolist()
result = collection.query(query_embeddings=query_vector, n_results=2)
top_author = result["metadatas"][0][0]["author"]
top_distance = result["distances"][0][0]
दो अलग-अलग लोगों के बारे में दो प्रश्न, उसी 48-चंक इंडेक्स के खिलाफ, प्रत्येक सही व्यक्ति को और किसी और की जीवनी को वापस खींचते हैं:
text
प्रश्न: अल्बर्ट आइंस्टीन कहाँ पैदा हुए थे?
शीर्ष मैच लेखक: अल्बर्ट आइंस्टीन | दूरी: 0.6465
चंक: अल्बर्ट आइंस्टीन का जन्म 14 मार्च 1879 को उल्म, जर्मनी में हुआ था। 1879 में, अल्बर्ट आइंस्टीन का जन्म उल्म, जर्मनी में हुआ था। उन्होंने ज़्यूरिख विश्वविद्यालय से अपनी पीएच.डी. पूरी की...
प्रश्न: जे.के. रोलिंग कहाँ पैदा हुई थीं?
शीर्ष मैच लेखक: जे.के. रोलिंग | दूरी: 0.4566
चंक: जे.के. रोलिंग का जन्म 31 जुलाई 1965 को येट, साउथ ग्लूसेस्टरशायर, इंग्लैंड, विद यूनाइटेड किंगडम में हुआ। देखें: रॉबर्ट गैलब्रैथ हालाँकि वह पेन नाम के तहत लिखती हैं...
कोई भी प्रश्न जेन ऑस्टेन, मैरिलिन मुनरो, या थॉमस एडिसन से एक चंक पुनः प्राप्त नहीं करता है — एम्बेडिंग स्पेस पांच असंबंधित जीवनी को इस प्रकार अलग करता है कि एक व्यक्ति के बारे में तथ्यात्मक प्रश्न दूसरे का सामना नहीं करता है।
चरण 6 — एक ग्राउंडेड उत्तर उत्पन्न करें
पुनः प्राप्त टुकड़ा केवल वही संदर्भ बन जाता है जो एक स्थानीय ओलम्मा मॉडल प्राप्त करता है। प्रांप्ट एकल JSON फ़ील्ड के लिए पूछता है — बस उत्तर — क्योंकि प्रामाणिकता पहले से स्टेज 5 से पुनर्प्राप्ति मेटाडेटा में रहती है; एक छोटे मॉडल से एक उद्धरण फ़ील्ड को फिर से बताने के लिए कहना एक ऐसा कार्य करने के लिए कहना है जो वेक्टर स्टोर पहले ही निःशुल्क किया है, और यह 494M-पैरामीटर मॉडल से पूछने के लिए एक विश्वसनीय अतिरिक्त फ़ील्ड नहीं है:
python
import json
import requests
prompt = (
'नीचे दिए गए संदर्भ का उपयोग करते हुए एक छोटे वाक्य में प्रश्न का उत्तर दें। '
'केवल JSON {"answer": "..."} के साथ उत्तर दें।\n\n'
f"संदर्भ:\n{context}\n\nप्रश्न: {question}"
)
resp = requests.post(
"http://localhost:11434/api/generate",
json={"model": "qwen2.5:0.5b", "prompt": prompt, "stream": False,
"format": "json", "options": {"temperature": 0, "num_predict": 40}},
timeout=600,
)
answer = json.loads(resp.json()["response"])
एक लाइव कॉल, केवल पुनः प्राप्त आइंस्टीन टुकड़े की फीडिंग करके, बिना अन्य चार जीवितियों को देखे सही उत्तर देती है:
text
पुनः प्राप्त चंक लेखक (वेक्टर स्टोर मेटाडेटा से): अल्बर्ट आइंस्टीन
प्रश्न: अल्बर्ट आइंस्टीन कहाँ पैदा हुए, और किस वर्ष?
उत्पन्न उत्तर: अल्बर्ट आइंस्टीन का जन्म 14 मार्च 1879 को उल्म, जर्मनी में हुआ था।
यही पूरा लूप है: एक प्रश्न अंदर जाता है, वेक्टर स्टोर 48 उम्मीदवारों को एक प्रासंगिक टुकड़े तक संकीर्ण करता है, और localhost पर चल रहा एक मॉडल उस टुकड़े को एक वाक्य में रूपांतरित करता है — कोई OpenAI कुंजी, कोई स्क्रैपिंग ब्राउज़र, मशीन के प्रारंभिक फेच के बाद कुछ भी नहीं छोड़ता है।
संपूर्ण पाइपलाइन
ऊपर दिए गए प्रत्येक चरण एक स्क्रिप्ट में श्रृंखला बनाते हैं: पाँच पृष्ठ फ़ेच करें, उन्हें टुकड़ों में काटें, परिणाम को एम्बेड और संग्रहित करें, दोनों पुनर्प्राप्ति जांच चलाएँ, फिर अंतिम उत्तर उत्पन्न करें। यहाँ कुछ भी फर्जी नहीं है या अलग-अलग सत्रों से असेंबल नहीं किया गया है — यह उन्हीं छह चरणों का एक ही क्रियान्वयन है, शीर्ष से नीचे:
python
Here is the translation in Hindi:
# full_pipeline.py -- fetch -> chunk -> embed -> store -> retrieve -> generate, end to end
import json
import logging
import os
import pathlib
import re
os.environ.setdefault("HF_HOME", "/usr/local/share/hf-cache")
os.environ.setdefault("HF_HUB_OFFLINE", "1")
import chromadb
import requests
from bs4 import BeautifulSoup
from sentence_transformers import SentenceTransformer
logging.getLogger("chromadb.telemetry.product.posthog").setLevel(logging.CRITICAL)
# चरण 1: फ़ेच करें
ENDPOINT = "https://api.scrapeless.com/api/v2/unlocker/request"
HEADERS = {"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]}
SOURCE_HOST = "https://quotes.toscrape.com"
AUTHORS = ["Albert-Einstein", "Jane-Austen", "Marilyn-Monroe", "J-K-Rowling", "Thomas-A-Edison"]
pathlib.Path("pages").mkdir(exist_ok=True)
for slug in AUTHORS:
url = f"{SOURCE_HOST}/author/{slug}"
resp = requests.post(
ENDPOINT, headers=HEADERS,
json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": False, "redirect": True}},
timeout=120,
)
resp.raise_for_status()
html = resp.json()["data"]
pathlib.Path("pages", f"{slug}.html").write_text(html, encoding="utf-8")
print(f"{slug}: {len(html):,} बाइट्स")
# चरण 2: निकालें और टुकड़े करें
CHUNK_WORDS, OVERLAP_WORDS = 60, 15
def extract_author(html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
name = soup.find("h3", class_="author-title").get_text(strip=True)
born_date = soup.find("span", class_="author-born-date").get_text(strip=True)
born_loc = soup.find("span", class_="author-born-location").get_text(strip=True)
desc = soup.find("div", class_="author-description").get_text(" ", strip=True)
return {"name": name, "born_date": born_date, "born_location": born_loc,
"description": re.sub(r"\s+", " ", desc)}
def chunk_words(words: list[str]):
step = CHUNK_WORDS - OVERLAP_WORDS
for start in range(0, max(len(words) - OVERLAP_WORDS, 1), step):
yield start, " ".join(words[start:start + CHUNK_WORDS])
chunks = []
with open("corpus.jsonl", "w", encoding="utf-8") as out:
for page in sorted(pathlib.Path("pages").glob("*.html")):
author = extract_author(page.read_text(encoding="utf-8"))
lead = f"{author['name']} का जन्म {author['born_date']} {author['born_location']} में हुआ।"
words = f"{lead} {author['description']}".split()
n_chunks = 0
for start, body in chunk_words(words):
rec = {"id": f"{page.stem}-{start}", "source": page.stem, "author": author["name"],
"word_offset": start, "text": body}
out.write(json.dumps(rec) + "\n")
chunks.append(rec)
n_chunks += 1
print(f"{author['name']}: {len(words)} शब्द -> {n_chunks} टुकड़े")
print(f"{len(chunks)} टुकड़े -> corpus.jsonl")
# चरण 3 और 4: एंबेड और स्टोर करें
model = SentenceTransformer("all-MiniLM-L6-v2")
client = chromadb.PersistentClient(path=".chroma", settings=chromadb.Settings(anonymized_telemetry=False))
if "author_bios" in [c.name for c in client.list_collections()]:
client.delete_collection("author_bios")
collection = client.create_collection("author_bios")
texts = [c["text"] for c in chunks]
vectors = model.encode(texts, show_progress_bar=False).tolist()
collection.add(
ids=[c["id"] for c in chunks],
documents=texts,
embeddings=vectors,
metadatas=[{"author": c["author"], "source": c["source"], "word_offset": c["word_offset"]} for c in chunks],
)
print(f"संग्रहीत {len(chunks)} टुकड़े, आयाम {len(vectors[0])}, संग्रह की संख्या {collection.count()}")
# चरण 5: पुनर्प्राप्त करें
for question in ["Albert Einstein का जन्म कहाँ हुआ?", "J.K. Rowling का जन्म कहाँ हुआ?"]:
query_vector = model.encode([question]).tolist()
result = collection.query(query_embeddings=query_vector, n_results=2)
top_author = result["metadatas"][0][0]["author"]
top_distance = result["distances"][0][0]
top_text = result["documents"][0][0]
print(f"प्रश्न: {question}")
print(f" शीर्ष मैच लेखक: {top_author} | दूरी: {top_distance:.4f}")
print(f" टुकड़ा: {top_text[:160]}...")
# चरण 6: उत्पन्न करें
question = "Albert Einstein का जन्म कहाँ हुआ, और किस वर्ष?"
query_vector = model.encode([question]).tolist()
result = collection.query(query_embeddings=query_vector, n_results=1)
context = result["documents"][0][0]
retrieved_author = result["metadatas"][0][0]["author"]
prompt = (
"नीचे दिए गए संदर्भ का उपयोग करके एक छोटे से वाक्य में प्रश्न का उत्तर दें। "
'केवल JSON {"answer": "..."} के साथ उत्तर दें।\n\n'
f"संदर्भ:\n{context}\n\nप्रश्न: {question}"
)
resp = requests.post(
"http://localhost:11434/api/generate",
json={"model": "qwen2.5:0.5b", "prompt": prompt, "stream": False,
"format": "json", "options": {"temperature": 0, "num_predict": 40}},
timeout=600,
)
resp.raise_for_status()
answer = json.loads(resp.json()["response"])
```python
print("मिली जानकारी लेखक (वेक्टर स्टोर मेटाडेटा से):", retrieved_author)
print("प्रश्न:", question)
print("उत्पन्न उत्तर:", answer["answer"])
एक ही ऊपर से नीचे की दौड़ हर संख्या को चरण दर चरण पुन: पेश करती है: पांच बाइट गणनाएँ, 48 खंड, 48 वेक्टरों का 384-आयामी संग्रह, दो सही पुनर्प्राप्तियाँ, और एक ग्राउंडेड उत्तर।
जिम्मेदार स्रोत निक्षेप
जीवनी पृष्ठ वास्तविक लोगों का विवरण देते हैं, यहां तक कि एक प्रैक्टिस साइट पर जो स्क्रैपिंग के लिए बनी है। जब आप इस पैटर्न को उत्पादन स्रोतों पर इंगित करते हैं, तो तीन प्रथाएँ पुनर्प्राप्ति को उचित बनाती हैं: केवल सार्वजनिक पृष्ठों को संग्रहित करें और प्रत्येक साइट की सेवा की शर्तें और रोबोट निर्देश को देखने से पहले किसी डोमेन को फेच सूची में जोड़ें; प्रत्येक खंड के साथ स्रोत URL और लेखक का नाम संलग्न रखें, जैसे कि इस पाइपलाइन का मेटाडेटा करता है, ताकि एक उत्पन्न उत्तर को उसके स्रोत पर वापस ट्रेस किया जा सके; और अनुरोध मात्रा को इस वॉकथ्रू के अनुसार व्यवहार करें - पांच पृष्ठ, एक बार, किसी ऐसी साइट पर नहीं जो एक खड़ा क्रॉल करने के लिए सहमत नहीं हुई है।
निष्कर्ष
पाँच पृष्ठों में, एक ग्राउंडेड वाक्य बाहर, और प्रारंभिक फेच के बाद हर चरण इस मशीन पर चलता है। पुनर्प्राप्ति आधा वही हिस्सा है जिस पर भरोसा करना चाहिए, जनरेशन आधे से पहले: 48 खंड, दो प्रश्न, दो सही लेखक, उनके प्रमाणित दूरी के साथ — और तभी स्थानीय मॉडल उत्तर देने में सक्षम होता है। स्रोत पृष्ठों, एम्बेडिंग मॉडल, या ओलामा मॉडल को बदलें, और आकार स्थिर रहता है; जब सामग्री बदलती है तो पाइपलाइन नहीं बदलती।
क्या आप अपने RAG पाइपलाइन बनाने के लिए तैयार हैं?
डेटा पाइपलाइन बनाने वाले समुदाय में शामिल हों: डिस्कॉर्ड · टेलीग्राम।
app.scrapeless.com पर फ्री ट्रायल क्रेडिट के लिए साइन अप करें, और स्टेज 1 को उन पृष्ठों पर इंगित करें जिनकी आपकी अपनी पुनर्प्राप्ति कॉर्पस को आवश्यकता है। डेवलपर डॉक unlocker.webunlocker अनुरोध आकार को कवर करता है, और वर्तमान योजनाएँ मूल्य निर्धारण पृष्ठ पर हैं।
सामान्य प्रश्न
प्रश्न: क्या इस पाइपलाइन के किसी भाग को Scrapeless के अलावा क्लाउड API कुंजी की आवश्यकता है?
नहीं। Scrapeless केवल एक भुगतान कॉल है, और यह केवल चरण 1 के फेच के लिए आवश्यक है। एम्बेडिंग sentence-transformers के माध्यम से स्थानीय रूप से चलती है, वेक्टर स्टोर डिस्क पर एक क्रोमा फ़ाइल है, और जनरेशन एक स्थानीय ओलामा मॉडल के माध्यम से चलती है - अन्य किसी चीज़ को प्रदाता कुंजी की आवश्यकता नहीं है।
प्रश्न: यह Scrapeless के अन्य RAG से संबंधित पोस्टों से कैसे भिन्न है?
कवरेज, न कि ओवरलैप। इंजेशन गाइड पुनर्प्राप्त करता है, निकालता है, और खंड बनाता है, फिर जानबूझकर एम्बेडिंग से पहले रुक जाता है। LLM-टेक्स्ट पाइपलाइन गाइड OpenAI के साथ एम्बेड करता है और क्रोमा में संग्रहीत करता है, फिर क्वेरी या जनरेट करने से पहले रुक जाता है। यह गाइड स्थानीय, जीरो-एंबेडिंग-कुंजी आधा है जो लूप को पूरा करता है: पुनर्प्राप्त करें, फिर उत्पन्न करें।
प्रश्न: क्या मुझे GPU की आवश्यकता है?
नहीं। इस वॉकथ्रू के लिए all-MiniLM-L6-v2 और qwen2.5:0.5b दोनों CPU पर चलाए गए। एक GPU बड़े स्थानीय मॉडल को तेज करता है, लेकिन न तो एम्बेडिंग चरण और न ही छोटे जनरेशन मॉडल को पाइपलाइन बनाने और परीक्षण करने के लिए एक की आवश्यकता होती है।
प्रश्न: मैं खंड के आकार का चयन कैसे करूँ?
इसे इस अनुसार मेल करें कि एकल स्रोत वास्तव में कितना कहता है। इस वॉकथ्रू की जीवनी कुछ सौ शब्दों की होती है, इसलिए 60 शब्द 15-शब्द ओवरलैप के साथ एक विचार के लिए लगभग एक खंड रखता है। एक हजार से अधिक शब्दों के लेख के लिए एक चौड़े विंडो की आवश्यकता होती है - उपरोक्त इंजेक्शन गाइड ने बिल्कुल उसी कारण के लिए 220 शब्दों का उपयोग किया है जिसमें 40 का ओवरलैप है।
प्रश्न: अगर पुनर्प्राप्ति गलत खंड लौटाए?
यह आमतौर पर दर्शाता है कि कॉर्पस में निकट-डुप्लिकेट सामग्री है जिसे एम्बेडिंग मॉडल अलग नहीं कर सकता, या प्रश्न उस तरीके से नहीं है जिस तरह से स्रोत पाठ को शब्दित किया गया है। खंड ओवरलैप का विस्तार करना, बड़े मॉडल के साथ एम्बेडिंग करना, या शीर्ष उम्मीदवारों पर दूसरी रीरैंकिंग पास जोड़ना सभी मदद करते हैं; इस वॉकथ्रू के पांच विभिन्न जीवनी जानबूझकर अलग बताई जा रही हैं, जो एक साफ पुनर्प्राप्ति परिणाम को दिखाना संभव बनाती हैं न कि केवल दावा करना।
प्रश्न: क्या मैं जनरेशन के लिए एक बड़ा स्थानीय मॉडल स्वैप कर सकता हूँ?
हाँ - अनुरोध आकार नहीं बदलता है, केवल model स्ट्रिंग बदलती है। qwen2.5:0.5b CPU पर लूप को प्रदर्शित करने के लिए काफी तेज है; एक 3B या बड़े ओलामा मॉडल गंदे संदर्भ पर अधिक विश्वसनीय उत्तर देता है यदि हार्डवेयर के पास मेमोरी की बचत है।
प्रश्न: क्या जीवनवृत्तियों के पृष्ठों को स्क्रैप करना और उनसे उत्तर उत्पन्न करना कानूनी है?
सार्वजनिक रूप से उपलब्ध पृष्ठों को स्क्रैप करना सामान्यतः अनुमति प्राप्त है, लेकिन साइट की शर्तें और क्षेत्राधिकार दोनों महत्वपूर्ण हैं। केवल सार्वजनिक पृष्ठों को संग्रहित करें, लक्षित साइट की सेवा की शर्तों और रोबोट्स निर्देशों की पहले जाँच करें, मात्रा को सीमित रखें, और कोर्स में किसी भी व्यक्तिगत डेटा को ऐसे गोपनीयता कानूनों के तहत संभालें जो आप पर लागू होते हैं।
स्क्रैपलेस में, हम केवल सार्वजनिक रूप से उपलब्ध डेटा का उपयोग करते हैं, जबकि लागू कानूनों, विनियमों और वेबसाइट गोपनीयता नीतियों का सख्ती से अनुपालन करते हैं। इस ब्लॉग में सामग्री केवल प्रदर्शन उद्देश्यों के लिए है और इसमें कोई अवैध या उल्लंघन करने वाली गतिविधियों को शामिल नहीं किया गया है। हम इस ब्लॉग या तृतीय-पक्ष लिंक से जानकारी के उपयोग के लिए सभी देयता को कोई गारंटी नहीं देते हैं और सभी देयता का खुलासा करते हैं। किसी भी स्क्रैपिंग गतिविधियों में संलग्न होने से पहले, अपने कानूनी सलाहकार से परामर्श करें और लक्ष्य वेबसाइट की सेवा की शर्तों की समीक्षा करें या आवश्यक अनुमतियाँ प्राप्त करें।



