Asosiy qismga o'tish

RAG Turlari

2-bob 31 kod bloki 5199 so'z

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 2-bob · Ollama + pgvector · ~70 qator

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.

python9 qator
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.

python16 qator
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 chunks

3. Embedding va bazaga yozish

Har bir chunk ni vektorga aylantiramiz va pgvector ustuniga saqlaymiz. Bu bir marta qilinadigan ish — keyin faqat o'qiymiz.

python20 qator
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))
Natija
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.

python12 qator
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.

python17 qator
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?"))
Natija
Nihol kompaniyasi Ucell uchun billing tizimini modernizatsiya qilgan.
Loyiha doirasida abonentlarni hisob-kitob qilish moduli yangilangan va
to'lov shlyuzi integratsiya qilingan.
Shu yerda to'xtang va o'ylang. Bu tizim ishlaydi, lekin uchta joyda sinadi: (1) savol qisqa bo'lsa retrieval noto'g'ri chunk topadi, (2) o'xshash mavzudagi begona hujjatlar 1-o'ringa chiqadi, (3) javob to'g'rimi yoki yo'qmi — bilmaysiz.

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

matn11 qator
+----------+     +-----------+     +----------+     +-----------+     +----------+
|          |     |           |     |          |     |           |     |          |
|  Savol   | --> | Embedding | --> | Vektor   | --> |   LLM     | --> |  Javob   |
|          |     | Model     |     | Qidiruv  |     |           |     |          |
+----------+     +-----------+     +----------+     +-----------+     +----------+
                                        |
                                        v
                                   +----------+
                                   | Vektor   |
                                   | Bazasi   |
                                   +----------+

Ishlash tartibi

python11 qator
# 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

  1. Query optimization yo'q — foydalanuvchi "железка" desa, tizim "temir yo'l" ni topa olmaydi. Savol qanday yozilgan bo'lsa, shunday qidiriladi.

  2. 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.

  3. 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.

  4. 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

matn45 qator
+----------+
|  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:

python24 qator
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

python6 qator
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.

Kengaytirilgan so'rov embedding ga aylantiriladi va pgvector da qidiriladi. Til va rubrika filtrlari SQL darajasida qo'llanadi:

python8 qator
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:

python15 qator
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.

matn7 qator
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

matn25 qator
+---------------------------------------------------+
|                  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

python28 qator
# 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 2-bob · entity + relationship · ~110 qator

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.

python26 qator
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))
Natija
{
 "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.

python33 qator
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()}")
Natija
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.

python29 qator
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)
Natija
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.

Eng katta xavf — narx. Har bir chunk uchun LLM chaqiruvi. 451 chunk = 451 chaqiruv, lokal Qwen da bu bir necha soat.

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

python9 qator
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

matn16 qator
                    +--------+
                    | Nihol  |
                    +---+----+
                   /    |     \
          mijoz  /     |      \ yetkazuvchi
               /       |       \
        +-----+   +----+----+  +----------+
        |Ucell|   |Orient FB|  |IBM Power |
        +--+--+   +----+----+  +----+-----+
           |            |            |
    soha   |     soha   |     turi   |
           v            v            v
     +---------+  +--------+  +-----------+
     |Telekom  |  | Bank   |  |Server     |
     +---------+  +--------+  |uskunasi   |
                               +-----------+

Qachon Graph RAG kerak?

  1. Entity lar orasidagi aloqalar muhim: "Ucell va COSCOM bilan ishlagan barcha kompaniyalar" — bu savol oddiy chunk qidiruvda yaxshi ishlamaydi, lekin grafda oson.

  2. Ko'p bosqichli savollar: "Nihol qaysi telekom kompaniyalar bilan ishlagan va ularga qanday uskunalar yetkazgan?" — bu ikki aloqani birlashtirish kerak.

  3. 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 2-bob · CLI + Leiden klasterlash · ~70 qator

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.

python18 qator
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.

python25 qator
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.

python11 qator
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()
Natija
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.
python15 qator
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])
Natija
--- 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.

71 daqiqa — bu haqiqiy raqam. 24 ta fayl, 451 chunk, lokal 7B model. Indekslash eng qimmat qadam, va korpus o'zgarganda to'liq qayta ishlash kerak (inkremental yangilash cheklangan).

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 2-bob · tool calling · ~100 qator

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.

python39 qator
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.

python50 qator
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]}")
Natija
 → 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.

Kichik modellar bilan ishlamaydi. Agentic RAG uchun model JSON ni ishonchli chiqarishi va reja tuza olishi kerak. 7B modellar ko'pincha JSON ni buzadi yoki cheksiz bir xil vositani chaqiradi.

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

matn30 qator
+----------+
|  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

yaml18 qator
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

matn44 qator
                    +----------+
                    |  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?

  1. Nihol bilim bazasi hajmi o'rtacha — minglab chunk, millionlab emas. Graph RAG ning murakkabligi ortiqcha.
  2. Savollar ko'pincha to'g'ridan-to'g'ri — "Ucell uchun nima qilgansiz?" — agentic RAG ning murakkab savol dekompoziysiyasi shart emas.
  3. Tezlik muhim — chat foydalanuvchisi bir necha soniyada javob kutadi. Agentic RAG 10-15 soniya ketishi mumkin.
  4. 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

python37 qator
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 question

app/services/chat.py — chunk tanlash

python17 qator
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.

Telegramda yozish Narxlar