RAG Turlari
RAG arxitekturasi yillar davomida rivojlandi. Oddiy "qidir va javob ber" dan murakkab agent-asosli tizimlargacha — har birining o'z kuchi va zaif tomoni bor. Bu bobda oltita asosiy RAG turini batafsil ko'rib chiqamiz.
1. Naive RAG — Eng oddiy shakl
Kodlar: Naive RAG — eng oddiy ishlaydigan tizim
1. Nima quramiz?
Eng kichik RAG: hujjat → chunk → embedding → baza → qidiruv → javob. Hech qanday rerank, hech qanday query expansion. Shu 6 qadam ishlasa — sizda RAG bor.
Bu notebook qolgan barcha texnikalar uchun asos. Keyingi har bir texnika shu pipeline ning bitta bo'g'inini yaxshilaydi.
from sqlalchemy import create_engine, text from sqlalchemy.orm import sessionmaker from app.config import get_settings from app.services.chat import _ollama_chat, generate_query_embedding settings = get_settings() engine = create_engine(settings.database_url) Session = sessionmaker(bind=engine) session = Session()
2. Chunking — matnni bo'laklarga ajratish
LLM ga butun hujjatni bermaymiz. Sababi ikkita: context window cheklangan va keraksiz matn javob sifatini pasaytiradi.
Eng sodda usul — belgilangan uzunlikda kesish, lekin gap o'rtasidan emas: oxirgi nuqtani topib, o'sha yerdan kesamiz.
def chunk_text(body: str, size: int = 800, overlap: int = 100) -> list[str]:
body = " ".join(body.split())
chunks, start = [], 0
while start < len(body):
end = start + size
piece = body[start:end]
if end < len(body):
dot = piece.rfind(". ")
if dot > size * 0.5:
piece = piece[:dot + 1]
end = start + dot + 1
piece = piece.strip()
if len(piece) > 50:
chunks.append(piece)
start = max(end - overlap, start + 1)
return chunks3. Embedding va bazaga yozish
Har bir chunk ni vektorga aylantiramiz va pgvector ustuniga saqlaymiz. Bu bir marta qilinadigan ish — keyin faqat o'qiymiz.
INSERT_SQL = (
"INSERT INTO chunks (title, content, chunk_index, embedding) "
"VALUES (:t, :c, :i, :e)"
)
def index_document(session, doc_title: str, body: str) -> int:
saved = 0
for i, chunk in enumerate(chunk_text(body)):
embedding, err = generate_query_embedding(chunk)
if err or embedding is None:
continue
session.execute(text(INSERT_SQL),
{"t": doc_title, "c": chunk, "i": i, "e": str(embedding)})
saved += 1
session.commit()
return saved
body = open("ucell.txt", encoding="utf-8").read()
print(index_document(session, "Ucell billing loyihasi", body))14
4. Qidiruv — cosine similarity
pgvector-da <=> operatori cosine masofani beradi. Kichik masofa = yaqinroq, shuning uchun ORDER BY o'sish tartibida ketadi. Similarity ni ko'rsatish uchun 1 - masofa qilamiz.
SEARCH_SQL = (
"SELECT title, content, 1 - (embedding <=> CAST(:e AS vector)) AS score "
"FROM chunks "
"ORDER BY embedding <=> CAST(:e AS vector) "
"LIMIT :k"
)
def retrieve_chunks(session, embedding: list[float], top_k: int = 5) -> list[dict]:
rows = session.execute(
text(SEARCH_SQL), {"e": str(embedding), "k": top_k}
).mappings().all()
return [dict(r) for r in rows]5. Generation — kontekst bilan javob
Eng muhim qator — "faqat kontekstdan foydalan". Busiz LLM o'z bilimini aralashtiradi va hallucination boshlanadi.
temperature=0.2: bizga ijod kerak emas, faktik javob kerak.
SYSTEM = (
"Sen yordamchi assistentsan. Javobni FAQAT berilgan kontekstdan yoz. "
"Kontekstda javob bo'lmasa — 'Ma'lumot topilmadi' deb yoz. "
"Hech narsa o'ylab topma."
)
def ask(session, question: str, top_k: int = 5) -> str:
embedding, _ = generate_query_embedding(question)
chunks = retrieve_chunks(session, embedding, top_k)
context = "\n\n".join(f"[{c['title']}]\n{c['content']}" for c in chunks)
return _ollama_chat([
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Kontekst:\n{context}\n\nSavol: {question}"},
], temperature=0.2)
print(ask(session, "Ucell uchun qanday loyiha qilingan?"))Nihol kompaniyasi Ucell uchun billing tizimini modernizatsiya qilgan. Loyiha doirasida abonentlarni hisob-kitob qilish moduli yangilangan va to'lov shlyuzi integratsiya qilingan.
Kitobning qolgan qismi aynan shu uchta muammoni hal qiladi.
Naive RAG — bu RAG ning birinchi va eng sodda ko'rinishi. U uchta qadamdan iborat: savol embedding ga aylanadi, bazadan eng yaqin chunk lar topiladi, LLM javob beradi.
Arxitektura diagrammasi
+----------+ +-----------+ +----------+ +-----------+ +----------+
| | | | | | | | | |
| Savol | --> | Embedding | --> | Vektor | --> | LLM | --> | Javob |
| | | Model | | Qidiruv | | | | |
+----------+ +-----------+ +----------+ +-----------+ +----------+
|
v
+----------+
| Vektor |
| Bazasi |
+----------+
Ishlash tartibi
# Naive RAG — sodda implementatsiya def naive_rag(question: str) -> str: # 1-qadam: Savolni vektor ga aylantirish query_vector = embed(question) # 2-qadam: Eng yaqin chunk larni topish chunks = vector_db.search(query_vector, top_k=5) # 3-qadam: LLM ga berish prompt = f"Context: {chunks}\n\nSavol: {question}" return llm.generate(prompt)
Cheklovlari
-
Query optimization yo'q — foydalanuvchi "железка" desa, tizim "temir yo'l" ni topa olmaydi. Savol qanday yozilgan bo'lsa, shunday qidiriladi.
-
Reranking yo'q — embedding search 10 ta chunk qaytardi, lekin ularning barchasi bir xil darajada "tegishli" emas. 3-chi chunk eng yaxshi javobni saqlayotgan bo'lishi mumkin, lekin u 7-o'rinda turadi.
-
Noise filtrlash yo'q — agar 10 chunk dan 3 tasi aslida savolga aloqasiz bo'lsa ham, barchasi LLM ga beriladi. Bu LLM ni chalg'itadi.
-
Til filtratsiyasi yo'q — o'zbekcha savol berilsa, ruscha chunk lar ham qaytadi. Javob aralash chiqadi.
Naive RAG oddiy demo uchun yaxshi, lekin production tizim uchun yetarli emas.
2. Advanced RAG — Sifatli production tizim
Advanced RAG Naive RAG ning barcha kamchiliklarini bartaraf qiladi. U ikki muhim bosqich qo'shadi: pre-retrieval (qidiruvdan oldin) va post-retrieval (qidiruvdan keyin).
Arxitektura diagrammasi
+----------+
| Savol |
+----+-----+
|
v
+----+------------------+
| PRE-RETRIEVAL |
| |
| * Query rewriting |
| * Query expansion |
| * Slang dictionary |
| * Language detection |
| * HyDE (optional) |
+-----------+-----------+
|
v (bir nechta query)
+-----------+-----------+
| RETRIEVAL |
| |
| * Multi-query search |
| * Vektor qidiruv |
| * Similarity threshold|
| * Language filtering |
+-----------+-----------+
|
v
+-----------+-----------+
| POST-RETRIEVAL |
| |
| * Reranking |
| * Deduplication |
| * Compression |
| * Score filtering |
+-----------+-----------+
|
v
+-----------+-----------+
| LLM Generation |
| FAQAT context asosida |
+-----------+-----------+
|
v
+----------+
| Javob |
+----------+
Bizning Nihol tizimimiz — Advanced RAG
Nihol loyihasida biz Advanced RAG ishlatamiz. Keling har bir qadamni haqiqiy koddan ko'raylik.
Pre-retrieval: Query Expansion
Foydalanuvchi "телекому qanday loyihalar qilgansiz?" desa, tizim savolni qidiruvga mos kengaytirilgan so'rovga aylantiradi. Oldingi versiyada bu qo'lda yozilgan slang-lug'at bilan ishlagan; joriy versiyada LLM ning o'zi kengaytiradi:
def expand_query(question: str) -> str: prompt = ( "Перепиши вопрос пользователя в развёрнутый поисковый запрос " "для векторного поиска по документам. Раскрой сокращения, " "добавь синонимы и ключевые термины. Сохрани язык вопроса. " "Верни только переписанный запрос одной строкой.\n\n" f"Вопрос: {question}" ) try: resp = httpx.post( f"{settings.OLLAMA_URL}/api/generate", json={ "model": settings.CHAT_MODEL, "prompt": prompt, "stream": False, "think": False, "options": {"temperature": 0.0, "num_predict": 160}, }, timeout=120, ) out = resp.json().get("response", "").strip() return out or question except Exception: return question # xato bo'lsa — original savol bilan davom etamiz
Natija: "телеком" kabi qisqartma va slanglar avtomatik ochiladi, sinonimlar qo'shiladi -- lug'atni qo'lda yuritish shart emas. LLM javob bermasa, original savol ishlatiladi (graceful fallback).
Pre-retrieval: Language Detection
def detect_lang(text: str) -> str: cyrillic = sum(1 for c in text if "\u0400" <= c <= "\u04ff") latin = sum(1 for c in text if "a" <= c.lower() <= "z") if cyrillic > latin: return "RU" return "UZ"
Bu oddiy, lekin samarali usul. Kirill harflari ko'p bo'lsa — ruscha, lotin ko'p bo'lsa — o'zbekcha. Keyin qidiruv faqat shu tildagi chunk larda amalga oshiriladi.
Retrieval: Filtrlangan Vector Search
Kengaytirilgan so'rov embedding ga aylantiriladi va pgvector da qidiriladi. Til va rubrika filtrlari SQL darajasida qo'llanadi:
chunks = retrieve_chunks( session, embedding, top_k=15, # 15 tagacha nomzod chunk threshold=0.0, # similarity chegarasi (admin paneldan sozlanadi) lang_code=lang_code, # faqat savol tilidagi chunk lar rubrics=effective_rubrics, # faqat ruxsat etilgan rubrikalar )
Filtrlash chunk topilgandan KEYIN emas, SQL so'rovning ichida bajariladi -- keraksiz chunk lar umuman qaytarilmaydi. Bu Advanced RAG ning pre-retrieval va retrieval bosqichlarini bitta samarali so'rovda birlashtiradi.
Post-retrieval: Heuristik Chunk Tanlash
Oldingi versiyada bu bosqichda LLM-reranker ishlagan (har savolga +1-3 soniya). Joriy versiyada tez, deterministik heuristika ishlatiladi:
def select_context_chunks(chunks, question): top_chunks = chunks[:3] # eng yaxshi 3 har doim olinadi key_terms = [w[:6] if len(w) > 6 else w for w in question.lower().split() if len(w) > 3] extra = [] seen_ids = {c["chunk_id"] for c in top_chunks} for chunk in chunks[3:]: # qolganlari kalit so'z bo'yicha text_lower = chunk["chunk_text"].lower() if any(term in text_lower for term in key_terms): if chunk["chunk_id"] not in seen_ids: extra.append(chunk) return (top_chunks + extra)[:8] # jami maksimum 8
15 ta nomzoddan eng tegishli 8 tasini mikrosekundlarda tanlash -- qo'shimcha LLM chaqiruvisiz. Vector search allaqachon yaxshi tartiblagan; heuristika faqat kontekstni ixcham saqlaydi.
HyDE — Hypothetical Document Embeddings (qo'shimcha texnika)
HyDE g'oyasi oddiy: qidiruvdan oldin LLM ga savolga "faraziy javob" yozdirish, keyin shu javob ni embedding qilish.
Savol: "Nihol Ucell uchun nima qilgan?" HyDE faraziy javob: "Nihol kompaniyasi Ucell telekommunikatsiya kompaniyasi uchun server yetkazib berish va billing tizimini modernizatsiya qilish loyihalarini amalga oshirgan." Bu faraziy javobni embedding qilamiz -> bazada qidiramiz
Nima uchun ishlaydi? Chunki faraziy javob embedding space da haqiqiy javobga yaqinroq — savolga qaraganda.
3. Modular RAG — LEGO bloklari printsipi
Modular RAG — bu RAG tizimini mustaqil almashtirilishi mumkin bo'lgan komponentlardan qurish.
Arxitektura
+---------------------------------------------------+ | MODULAR RAG | | | | +-------------+ +-------------+ +------------+ | | | Embedding | | Retriever | | Reranker | | | | Module | | Module | | Module | | | | | | | | | | | | Tanlang: | | Tanlang: | | Tanlang: | | | | - OpenAI | | - pgvector | | - Jina | | | | - Qwen3-emb | | - Pinecone | | - Cohere | | | | - BGE-M3 | | - Weaviate | | - BGE | | | | - Cohere | | - ChromaDB | | - LLM-base | | | +-------------+ +-------------+ +------------+ | | | | +-------------+ +-------------+ +------------+ | | | Chunker | | LLM | | Memory | | | | Module | | Module | | Module | | | | | | | | | | | | Tanlang: | | Tanlang: | | Tanlang: | | | | - Fixed | | - GPT-4o | | - Redis | | | | - Semantic | | - Qwen3 | | - PostgreSQL| | | - Recursive | | - Claude | | - In-memory| | | | - Agentic | | - Llama | | | | | +-------------+ +-------------+ +------------+ | +---------------------------------------------------+
LEGO analogiyasi
Tasavvur qiling, LEGO konstruktor qurasyapsiz. Har bir blok (modul) mustaqil: - Embedding blokini Qwen3-embedding dan OpenAI ga almashtirdingiz — qolgan hamma narsa ishlayveradi - Reranker blokini LLM-based dan Jina Reranker ga o'zgartirdingiz — faqat shu modul o'zgaradi - LLM ni Qwen3 dan Claude ga almashtirdingiz — retrieval pipeline o'zgarmaydi
# Modular RAG misol class RAGPipeline: def __init__(self, embedder, retriever, reranker, llm): self.embedder = embedder # almashtirilishi mumkin self.retriever = retriever # almashtirilishi mumkin self.reranker = reranker # almashtirilishi mumkin self.llm = llm # almashtirilishi mumkin def answer(self, question: str) -> str: vector = self.embedder.embed(question) chunks = self.retriever.search(vector) ranked = self.reranker.rerank(chunks, question) return self.llm.generate(ranked, question) # Konfiguratsiyani o'zgartirish oson pipeline_v1 = RAGPipeline( embedder=QwenEmbedder(), retriever=PgvectorRetriever(), reranker=LLMReranker(), llm=Qwen3Chat(), ) pipeline_v2 = RAGPipeline( embedder=OpenAIEmbedder(), # faqat embedding o'zgardi retriever=PgvectorRetriever(), # retriever o'sha reranker=JinaReranker(), # reranker o'zgardi llm=Qwen3Chat(), # LLM o'sha )
Modular RAG production tizimlar uchun ideal — chunki har bir komponentni mustaqil test qilish, taqqoslash va almashtirish mumkin.
4. Graph RAG — Bilim grafiklari bilan RAG
Kodlar: Graph RAG — bog'lanishlar bo'ylab qidirish
1. Vector search bog'lanishni ko'rmaydi
"Ucell loyihasida ishlagan jamoa yana qaysi loyihalarni qilgan?" — bu savolga javob hech qaysi chunk da yo'q. U ikki hujjat orasidagi bog'lanishda yashiringan.
Graph RAG matndan obyektlar (entity) va munosabatlar (relationship) ni ajratib, graf quradi. Keyin graf bo'ylab yuradi.
import json
import networkx as nx
EXTRACT = (
"Quyidagi matndan obyektlar va ular orasidagi munosabatlarni ajrat.\n"
"Obyekt turlari: KOMPANIYA, LOYIHA, TEXNOLOGIYA, SHAXS, SANA\n"
"Faqat JSON qaytar:\n"
'{{"entities": [{{"name": "...", "type": "..."}}], '
'"relations": [{{"from": "...", "to": "...", "type": "..."}}]}}\n\n'
"Matn: {chunk}"
)
def extract_graph(chunk: str) -> dict:
out = _ollama_chat(
[{"role": "user", "content": EXTRACT.format(chunk=chunk[:1200])}],
temperature=0.0, max_tokens=500,
)
try:
start, end = out.index("{"), out.rindex("}") + 1
return json.loads(out[start:end])
except (ValueError, json.JSONDecodeError):
return {"entities": [], "relations": []}
sample = "Nihol kompaniyasi Ucell uchun billing tizimini FastAPI va PostgreSQL da qurgan."
print(json.dumps(extract_graph(sample), ensure_ascii=False, indent=1)){
"entities": [
{"name": "Nihol", "type": "KOMPANIYA"},
{"name": "Ucell", "type": "KOMPANIYA"},
{"name": "billing tizimi", "type": "LOYIHA"},
{"name": "FastAPI", "type": "TEXNOLOGIYA"},
{"name": "PostgreSQL", "type": "TEXNOLOGIYA"}
],
"relations": [
{"from": "Nihol", "to": "billing tizimi", "type": "BAJARGAN"},
{"from": "billing tizimi", "to": "Ucell", "type": "MIJOZ_UCHUN"},
{"from": "billing tizimi", "to": "FastAPI", "type": "ISHLATADI"},
{"from": "billing tizimi", "to": "PostgreSQL", "type": "ISHLATADI"}
]
}2. Grafni yig'ish — dedup eng qiyin qismi
"Ucell", "UCELL", "Ucell kompaniyasi" — bu bitta obyekt, lekin LLM uchta xil nom qaytaradi. Normalizatsiya qilmasangiz, graf uch bo'lakka bo'linib ketadi va hech narsa topilmaydi.
def normalize(name: str) -> str:
name = name.lower().strip()
for suffix in (" kompaniyasi", " mchj", " ltd", " loyihasi", " tizimi"):
if name.endswith(suffix):
name = name[: -len(suffix)]
return name.strip()
def build_graph(chunks: list[str]) -> nx.DiGraph:
G = nx.DiGraph()
for i, chunk in enumerate(chunks):
data = extract_graph(chunk)
for ent in data.get("entities", []):
key = normalize(ent.get("name", ""))
if not key:
continue
if G.has_node(key):
G.nodes[key]["chunks"].append(i)
else:
G.add_node(key, type=ent.get("type", "?"),
label=ent["name"], chunks=[i])
for rel in data.get("relations", []):
a, b = normalize(rel.get("from", "")), normalize(rel.get("to", ""))
if a and b and a != b:
G.add_edge(a, b, type=rel.get("type", "BOGLIQ"))
return G
graph = build_graph(chunks[:60])
print(f"Obyektlar : {graph.number_of_nodes()}")
print(f"Bog'lanish: {graph.number_of_edges()}")Obyektlar : 84 Bog'lanish: 137
3. Graf bo'ylab qidirish
Savoldan obyektlarni topamiz, keyin ulardan 2 qadam narigacha yuramiz. Yo'lda uchragan barcha obyektlarning chunk lari — bizning kontekstimiz.
def graph_retrieve(session, question: str, graph: nx.DiGraph,
hops: int = 2, top_k: int = 8) -> list[dict]:
found = extract_graph(question)
seeds = [normalize(e["name"]) for e in found.get("entities", [])]
seeds = [s for s in seeds if graph.has_node(s)]
if not seeds:
emb, _ = generate_query_embedding(question)
return retrieve_chunks(session, emb, top_k)
undirected = graph.to_undirected()
reached = set(seeds)
for seed in seeds:
reached |= set(nx.single_source_shortest_path_length(
undirected, seed, cutoff=hops).keys())
chunk_ids = set()
for node in reached:
chunk_ids.update(graph.nodes[node].get("chunks", []))
print(f"Boshlang'ich obyektlar: {seeds}")
print(f"{hops} qadamda topildi : {len(reached)} obyekt, {len(chunk_ids)} chunk")
rows = session.execute(text(
"SELECT id, title, content FROM chunks WHERE chunk_index = ANY(:ids) LIMIT :k"
), {"ids": list(chunk_ids), "k": top_k}).mappings().all()
return [dict(r) for r in rows]
hits = graph_retrieve(session, "Ucell loyihasida qaysi texnologiyalar ishlatilgan?", graph)Boshlang'ich obyektlar: ['ucell'] 2 qadamda topildi : 19 obyekt, 24 chunk
4. Nega bu kuchli
"Ucell" dan boshlab 2 qadamda 19 ta bog'liq obyektga chiqdik: loyihalar, texnologiyalar, sanalar, jamoa a'zolari. Vector search bu bog'lanishlarning hech birini ko'rmasdi.
Ikkinchi xavf — dedup.
normalize funksiyasi sodda; real loyihada fuzzy matching yoki embedding asosida birlashtirish kerak bo'ladi. Dedup yomon bo'lsa — graf foydasiz.
Tavsiya: Graph RAG ni butun korpusga emas, eng muhim hujjatlarga qo'llang va vector search bilan birga ishlating.
Graph RAG matn chunk lari o'rniga yoki ularga qo'shimcha ravishda bilim grafiklarini (knowledge graph) ishlatadi. Bu yerda ma'lumot entity lar (shaxslar, kompaniyalar, texnologiyalar) va ularning aloqalari sifatida saqlanadi.
Tushuncha
Oddiy RAG: "Nihol kompaniyasi Ucell uchun IBM Power CPU VRM modullarini yetkazib berdi." -> Bu bitta chunk, vektor sifatida saqlanadi Graph RAG: [Nihol] --yetkazib berdi--> [IBM Power CPU VRM] [Nihol] --mijoz--> [Ucell] [Ucell] --soha--> [Telekommunikatsiya] [IBM Power CPU VRM] --turi--> [Server uskunasi]
ASCII diagramma
+--------+
| Nihol |
+---+----+
/ | \
mijoz / | \ yetkazuvchi
/ | \
+-----+ +----+----+ +----------+
|Ucell| |Orient FB| |IBM Power |
+--+--+ +----+----+ +----+-----+
| | |
soha | soha | turi |
v v v
+---------+ +--------+ +-----------+
|Telekom | | Bank | |Server |
+---------+ +--------+ |uskunasi |
+-----------+
Qachon Graph RAG kerak?
-
Entity lar orasidagi aloqalar muhim: "Ucell va COSCOM bilan ishlagan barcha kompaniyalar" — bu savol oddiy chunk qidiruvda yaxshi ishlamaydi, lekin grafda oson.
-
Ko'p bosqichli savollar: "Nihol qaysi telekom kompaniyalar bilan ishlagan va ularga qanday uskunalar yetkazgan?" — bu ikki aloqani birlashtirish kerak.
-
Umumlashtirish: "Nihol asosan qaysi sohalarda ishlaydi?" — bu barcha loyihalarni guruhlash kerak. Microsoft ning GraphRAG aynan shu uchun yaratilgan.
Microsoft GraphRAG yondashuvi
Kodlar: Microsoft GraphRAG — community detection
1. Boshqa loyihalardan farqi
Microsoft GraphRAG — kutubxona emas, to'liq pipeline. Uni import qilmaysiz, CLI orqali ishga tushirasiz. Konfiguratsiya YAML faylda.
Asosiy g'oyasi — community detection: grafdagi obyektlar avtomatik guruhlarga (community) bo'linadi, va har bir guruh uchun xulosa yoziladi.
import subprocess, os, yaml
WORKDIR = "graphrag_nihol"
def init_graphrag(corpus_dir: str) -> None:
os.makedirs(f"{WORKDIR}/input", exist_ok=True)
for name in os.listdir(corpus_dir):
if name.endswith(".txt"):
src = os.path.join(corpus_dir, name)
dst = f"{WORKDIR}/input/{name}"
with open(src, encoding="utf-8") as fh_in, \
open(dst, "w", encoding="utf-8") as fh_out:
fh_out.write(fh_in.read())
subprocess.run(["graphrag", "init", "--root", WORKDIR], check=True)
init_graphrag("nihol_corpus")2. Lokal model bilan sozlash
Sukut bo'yicha GraphRAG OpenAI ga ulanadi. Ollama bilan ishlatish uchun settings.yaml ni tahrirlaymiz — Ollama OpenAI-mos API beradi.
def configure_local(model: str = "qwen2.5:7b") -> None:
path = f"{WORKDIR}/settings.yaml"
with open(path, encoding="utf-8") as fh:
cfg = yaml.safe_load(fh)
cfg["llm"] = {
"type": "openai_chat",
"model": model,
"api_base": "http://localhost:11434/v1",
"api_key": "ollama",
"max_tokens": 2000,
"concurrent_requests": 2,
}
cfg["embeddings"]["llm"] = {
"type": "openai_embedding",
"model": "nomic-embed-text",
"api_base": "http://localhost:11434/v1",
"api_key": "ollama",
}
with open(path, "w", encoding="utf-8") as fh:
yaml.safe_dump(cfg, fh, allow_unicode=True)
configure_local()3. Indekslash — eng qimmat qadam
Bu buyruq butun pipeline ni ishga tushiradi: obyekt ajratish → graf qurish → Leiden algoritmi bilan community aniqlash → har bir community uchun xulosa yozish.
def build_index() -> None:
result = subprocess.run(
["graphrag", "index", "--root", WORKDIR],
capture_output=True, text=True,
)
print(result.stdout[-1500:])
if result.returncode != 0:
print("XATO:", result.stderr[-800:])
build_index()Loading Input (text) - 24 files loaded create_base_text_units 100% 451/451 create_base_entity_graph 100% create_final_entities 100% 186 entities create_final_relationships 100% 294 relationships create_final_communities 100% 17 communities create_final_community_reports 100% 17 reports All workflows completed successfully. Total time: 71m 34s
4. Ikki xil so'rov rejimi
Bu GraphRAG ning asosiy hissasi — global va local rejimlar butunlay boshqa savollarga javob beradi.
- local — aniq obyekt haqida. Graf bo'ylab qo'shnilarni yig'adi.
- global — butun korpus haqida. Barcha community xulosalarini o'qib, umumlashtiradi.
def ask_graphrag(question: str, method: str = "local") -> str:
result = subprocess.run(
["graphrag", "query", "--root", WORKDIR,
"--method", method, "--query", question],
capture_output=True, text=True,
)
out = result.stdout
marker = "SUCCESS: "
return out[out.index(marker) + len(marker):].strip() if marker in out else out
print("--- LOCAL ---")
print(ask_graphrag("Ucell billing loyihasida nima qilingan?", "local")[:250])
print("\n--- GLOBAL ---")
print(ask_graphrag("Kompaniyaning asosiy yo'nalishlari qanday?", "global")[:250])--- LOCAL --- Ucell billing loyihasida abonent hisob-kitob moduli qayta yozilgan, Click va Payme to'lov shlyuzlari REST API orqali integratsiya qilingan, hisobot moduli qo'shilgan. Loyiha 2023-yilda yakunlangan [Data: Entities (14, 27); ...] --- GLOBAL --- Kompaniyaning faoliyati uchta asosiy yo'nalishda jamlangan: telekommunikatsiya (billing tizimlari, tarmoq yechimlari), bank sektori (ipoteka, kredit skoring, mobil banking) va davlat sektori uchun axborot tizimlari [Data: Reports (3, 7, 12)]
5. Global rejim — asosiy qiymat
"Kompaniyaning asosiy yo'nalishlari qanday?" — bu savolga javob hech qaysi chunk da yo'q. U 17 ta community xulosasini o'qib, ulardan umumlashtirildi.
Oddiy vector RAG bunday savolga hech qachon to'g'ri javob bera olmaydi.
Shuning uchun: Microsoft GraphRAG barqaror, kamdan-kam o'zgaradigan korpuslar uchun. Har kuni yangilanadigan bazada u mos emas.
Muqobil: agar sizga faqat "global" tipidagi savollar kerak bo'lsa — RAPTOR ancha arzon va shunga o'xshash natija beradi.
Microsoft 2024-yilda GraphRAG ni taqdim etdi. Uning asosiy g'oyasi: 1. Hujjatlardan entity lar va aloqalarni LLM yordamida chiqarish 2. Entity larni community larga guruhlash (Leiden algoritmi) 3. Har bir community uchun summary yaratish 4. Savol kelganda, tegishli community summary laridan javob berish
Bu "umumlashtiruvchi" savollarga yaxshi ishlaydi, lekin aniq fakt izlashda oddiy RAG yaxshiroq.
5. Agentic RAG — LLM agent sifatida
Kodlar: Agentic RAG — LLM o'zi vosita tanlaydi
1. Farq nimada?
Oddiy RAG: qidir → javob yoz. Har doim shu tartib, har doim bir marta.
Agentic RAG: LLM o'zi qaror qiladi — qidirsinmi, necha marta qidirsin, qaysi vositadan foydalansin, natija yetarlimi yoki yana qidirish kerakmi.
2. Vositalarni e'lon qilish
Har bir vosita — oddiy Python funksiyasi + tavsifi. Tavsif LLM uchun yoziladi, shuning uchun u aniq bo'lishi kerak: qachon ishlatish, qanday parametr kerak.
def tool_search_docs(question: str, top_k: int = 5) -> str:
emb, _ = generate_query_embedding(question)
chunks = retrieve_chunks(session, emb, top_k)
if not chunks:
return "Hech narsa topilmadi."
return "\n\n".join(f"[{c['title']}] {c['content'][:400]}" for c in chunks)
def tool_count_projects(sector: str = "") -> str:
sql = "SELECT COUNT(DISTINCT title) FROM chunks"
params = {}
if sector:
sql += " WHERE sector = :s"
params["s"] = sector
n = session.execute(text(sql), params).scalar_one()
return f"Loyihalar soni: {n}"
def tool_list_sectors() -> str:
rows = session.execute(text(
"SELECT DISTINCT sector FROM chunks WHERE sector IS NOT NULL"
)).scalars().all()
return "Sohalar: " + ", ".join(rows)
TOOLS = {
"search_docs": {
"fn": tool_search_docs,
"desc": "Hujjatlardan matn qidiradi. Parametr: question (savol matni).",
},
"count_projects": {
"fn": tool_count_projects,
"desc": "Loyihalar sonini sanaydi. Parametr: sector (ixtiyoriy soha nomi).",
},
"list_sectors": {
"fn": tool_list_sectors,
"desc": "Mavjud sohalar ro'yxatini qaytaradi. Parametr yo'q.",
},
}3. Agent sikli
LLM har qadamda JSON qaytaradi: yoki tool (vosita chaqir), yoki answer (javob tayyor). Natija tarixga qo'shiladi va sikl davom etadi.
max_steps — majburiy xavfsizlik chegarasi. Busiz agent cheksiz aylanishi mumkin.
AGENT_PROMPT = (
"Sen ma'lumot izlovchi agentsan. Quyidagi vositalar mavjud:\n{tools}\n\n"
"Har qadamda FAQAT JSON qaytar:\n"
' Vosita kerak bo\'lsa: {{"tool": "nom", "args": {{...}}}}\n'
' Javob tayyor bo\'lsa: {{"answer": "javob matni"}}\n\n'
"Javobni faqat vositalardan olingan ma'lumotga asosla.\n\n"
"Savol: {question}\n\nOldingi qadamlar:\n{history}"
)
def run_agent(question: str, max_steps: int = 5) -> dict:
tools_text = "\n".join(f"- {name}: {t['desc']}" for name, t in TOOLS.items())
history, trail = [], []
for step in range(1, max_steps + 1):
prompt = AGENT_PROMPT.format(
tools=tools_text,
question=question,
history="\n".join(history) or "(hali yo'q)",
)
raw = _ollama_chat([{"role": "user", "content": prompt}],
temperature=0.0, max_tokens=400)
try:
start, end = raw.index("{"), raw.rindex("}") + 1
decision = json.loads(raw[start:end])
except (ValueError, json.JSONDecodeError):
break
if "answer" in decision:
return {"answer": decision["answer"], "steps": step, "trail": trail}
name = decision.get("tool")
if name not in TOOLS:
break
args = decision.get("args") or {}
try:
result = TOOLS[name]["fn"](**args)
except TypeError as exc:
result = f"Xato: {exc}"
trail.append(f"{name}({args})")
history.append(f"Vosita {name} natijasi: {result[:600]}")
return {"answer": "Javob topilmadi.", "steps": max_steps, "trail": trail}
r = run_agent("Telekom sohasida nechta loyiha bor va ulardan biri haqida gapiring")
for t in r["trail"]:
print(" →", t)
print(f"\n({r['steps']} qadam)\n{r['answer'][:250]}") → list_sectors({})
→ count_projects({'sector': 'telekom'})
→ search_docs({'question': 'telekom loyihasi'})
(4 qadam)
Telekom sohasida 7 ta loyiha mavjud. Ulardan biri — Ucell uchun billing
tizimi modernizatsiyasi. Loyiha 2023-yilda amalga oshirilgan, abonent
hisob-kitob moduli qayta yozilgan va to'lov shlyuzlari integratsiya qilingan.4. Nima bo'ldi
Agent o'zi uch bosqichli reja tuzdi: avval sohalarni bilib oldi, keyin sanadi, keyin tafsilot qidirdi. Hech kim unga bu tartibni aytmadi.
Oddiy RAG bu savolga javob bera olmasdi — "nechta" savoliga vector search javob topa olmaydi, bu SQL COUNT ishi.
Production da majburiy: (1)
max_steps chegarasi, (2) har bir vosita chaqiruvini log qilish, (3) vosita xatosini ushlash — yuqoridagi try/except TypeError, chunki LLM noto'g'ri parametr yuborishi juda ko'p uchraydi.Agentic RAG da LLM nafaqat javob beradi, balki qaror qabul qiladi: qachon qidirish, nimani qidirish, necha marta qidirish, va qidiruv natijasi yetarlimi.
Arxitektura
+----------+
| Savol |
+----+-----+
|
v
+----+-----------------------+
| LLM AGENT |
| |
| "Bu savolga javob berish |
| uchun nima qilishim |
| kerak?" |
| |
| Qarorlar: |
| * Qidirish kerakmi? |
| * Qaysi bazadan? |
| * Savol qanday bo'lsin? |
| * Natija yetarlimi? |
| * Yana qidirish kerakmi? |
+----+-----------------------+
| ^
| | (yetarli emas -> qayta qidirish)
v |
+----+-----------------------+
| TOOL / ACTION |
| |
| * vector_search(query) |
| * web_search(query) |
| * sql_query(query) |
| * calculator(expression) |
+----------------------------+
Misol: Murakkab savol
Savol: "Nihol 2020-2024 yillarda telekom sohada qanday loyihalar qilgan
va ularning umumiy qiymati qancha bo'lgan?"
Naive RAG: Bitta qidiruv qiladi, natija to'liq bo'lmaydi
Agentic RAG:
Agent fikrlaydi: "Bu murakkab savol. Men uni ikki qismga ajrataman."
1-qadam: vector_search("Nihol телеком loyihalar 2020 2021 2022 2023 2024")
Agent: "4 ta loyiha topdim, lekin qiymat ma'lumoti yo'q."
2-qadam: vector_search("Nihol loyihalar narx qiymat summa telekommunikatsiya")
Agent: "2 ta chunk da qiymat bor."
3-qadam: sql_query("SELECT SUM(amount) FROM projects WHERE sector='telecom'")
Agent: "Umumiy ma'lumot olindi."
4-qadam: Barcha natijalarni birlashtiradi va javob beradi.
Agentic RAG ning asosiy xususiyati — self-correction (o'z-o'zini tuzatish). Agar birinchi qidiruv natijasi yomon bo'lsa, agent savolni qayta formulirovka qiladi va yana qidiradi.
6. LangGraph RAG — State Machine yondashuvi
LangGraph — bu LangChain jamoasining state machine asosidagi framework i. RAG workflow ni graf sifatida ifodalash imkonini beradi.
Asosiy g'oya
+----------+
| START |
+----+-----+
|
v
+----+-----+
| Classify |
| Question |
+----+-----+
/ \
factual general
/ \
v v
+------+------+ +------+------+
| Retrieve | | Direct LLM |
| from DB | | Answer |
+------+------+ +------+------+
| |
v |
+------+------+ |
| Grade | |
| Documents | |
+------+------+ |
/ \ |
relevant not relevant |
/ \ |
v v |
+------+------+ +------+------+ |
| Generate | | Rewrite | |
| Answer | | Query | |
+------+------+ +------+------+ |
| | |
v +----> Retrieve (qayta)
+------+------+
| Check |
| Hallucinate?|
+------+------+
/ \
no yes
| \
v v
+------+ +------+------+
| END | | Regenerate |
+------+ +-------------+
LangGraph ning kuchi — conditional routing (shartli yo'naltirish). Savol turiga qarab turli yo'llardan borish mumkin. Buni batafsil keyingi boblarda ko'rib chiqamiz.
Taqqoslash jadvali
| Xususiyat | Naive RAG | Advanced RAG | Modular RAG | Graph RAG | Agentic RAG | LangGraph RAG |
|---|---|---|---|---|---|---|
| Murakkablik | Past | O'rta | O'rta-Yuqori | Yuqori | Yuqori | Yuqori |
| Javob sifati | O'rta | Yuqori | Yuqori | Juda yuqori (aloqalar uchun) | Juda yuqori | Juda yuqori |
| Tezlik | Tez | O'rta | O'rta | Sekin | Sekin (ko'p qadam) | O'rta |
| Moslashuvchanlik | Past | O'rta | Juda yuqori | O'rta | Yuqori | Juda yuqori |
| Qo'llanilish | Demo, prototype | Production tizim | Katta loyihalar | Entity-centric | Murakkab savollar | Murakkab workflow |
| Implementatsiya vaqti | 1-2 kun | 1-2 hafta | 2-4 hafta | 3-6 hafta | 2-4 hafta | 2-4 hafta |
| Misol | Oddiy FAQ bot | Nihol chatbot | Enterprise RAG | Wikipedia QA | Research assistant | Multi-step QA |
Bizning tizimimiz: Advanced RAG
Nihol loyihasi Advanced RAG turiga kiradi. Keling, uni aniq komponentlar bo'yicha tahlil qilaylik:
Pre-retrieval optimizatsiyalar
| Komponent | Implementatsiya | Maqsad |
|---|---|---|
| Query expansion | expand_query() -- LLM so'rovni kengaytiradi |
Sinonim va terminlar qo'shish, qisqartmalarni ochish |
| Language detection | detect_lang() kirill/lotin hisoblash |
Faqat tegishli tildagi chunk larni olish |
Retrieval
| Komponent | Implementatsiya | Maqsad |
|---|---|---|
| Embedding | Qwen3-embedding:8b, 4096 dimension | Matnni vektor ko'rinishga o'tkazish |
| Vektor qidiruv | pgvector, cosine distance (<=>), top_k=15 |
Eng o'xshash chunk larni topish |
| Til + rubrika filtri | SQL darajasidagi JOIN va WHERE | Keraksiz chunk lar umuman qaytmasin |
| Similarity threshold | Admin paneldan sozlanadi (default 0.0) | Aloqasiz chunk larni filtrlash imkoni |
Post-retrieval
| Komponent | Implementatsiya | Maqsad |
|---|---|---|
| Chunk tanlash | select_context_chunks() heuristik |
15 nomzoddan top-3 + kalit so'z mosliklari, max 8 |
| Markdown stripping | _strip_markdown() |
Toza matn foydalanuvchiga berish |
| Repetition cutting | _cut_repetitions() |
Takroriy javoblarni qisqartirish |
Nima uchun Advanced RAG tanladik?
- Nihol bilim bazasi hajmi o'rtacha — minglab chunk, millionlab emas. Graph RAG ning murakkabligi ortiqcha.
- Savollar ko'pincha to'g'ridan-to'g'ri — "Ucell uchun nima qilgansiz?" — agentic RAG ning murakkab savol dekompoziysiyasi shart emas.
- Tezlik muhim — chat foydalanuvchisi bir necha soniyada javob kutadi. Agentic RAG 10-15 soniya ketishi mumkin.
- Qwen3 local model — biz cloud API emas, local Ollama ishlatamiz. Har bir qo'shimcha LLM call sezilarli vaqt oladi.
Advanced RAG bizning use case uchun sifat va tezlik ning optimal balansini ta'minlaydi.
Xulosa
RAG dunyosi tez rivojlanmoqda. Asosiy qoida:
- Oddiy loyiha, tez natija kerak -> Naive RAG
- Production sifat, real foydalanuvchilar -> Advanced RAG (bizning tanlov)
- Katta enterprise, ko'p komponentlar -> Modular RAG
- Entity aloqalari muhim -> Graph RAG
- Murakkab, ko'p bosqichli savollar -> Agentic RAG
- Murakkab workflow, conditional logic -> LangGraph RAG
Keyingi bobda embedding duniyosiga chuqur sho'ng'iymiz — vektor nima, qanday hosil bo'ladi, qanday taqqoslanadi.
</> Loyihaning haqiqiy kodi — bu bob amalda qanday ishlaydi
app/services/chat.py — query expansion
def expand_query(question: str) -> str:
settings = get_settings()
prompt = (
"Перепиши вопрос пользователя в развёрнутый поисковый запрос для векторного поиска по документам. "
"Раскрой сокращения, добавь синонимы и ключевые термины. Сохрани язык вопроса. "
"Верни только переписанный запрос одной строкой, без пояснений и кавычек.\n\n"
f"Вопрос: {question}"
)
try:
resp = httpx.post(
f"{settings.OLLAMA_URL}/api/generate",
json={
"model": settings.CHAT_MODEL,
"prompt": prompt,
"stream": False,
"think": False,
"options": {
"temperature": 0.0,
"num_predict": 160,
"repeat_penalty": 1.3,
"repeat_last_n": 64,
},
},
timeout=120,
)
resp.raise_for_status()
out = resp.json().get("response", "").strip()
out = re.sub(r"<think>[\s\S]*?</think>", "", out).strip()
if out:
seen = []
for part in (p.strip() for p in out.split(",")):
if part and part.lower() not in {s.lower() for s in seen}:
seen.append(part)
out = ", ".join(seen[:12])
return out or question
except Exception:
return questionapp/services/chat.py — chunk tanlash
def select_context_chunks(chunks: list[dict], question: str) -> list[dict]:
top_chunks = chunks[:3]
q_lower = question.lower()
key_terms = [w[:6] if len(w) > 6 else w for w in q_lower.split() if len(w) > 3]
extra = []
seen_ids = {c["chunk_id"] for c in top_chunks}
for chunk in chunks[3:]:
text_lower = chunk["chunk_text"].lower()
title_lower = (chunk.get("title") or "").lower()
if any(term in text_lower or term in title_lower for term in key_terms):
if chunk["chunk_id"] not in seen_ids:
extra.append(chunk)
seen_ids.add(chunk["chunk_id"])
return (top_chunks + extra)[:8]Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.