Retrieval-Augmented Generation: inteligentní vyhledávání dokumentů v podnikové databázi
RAG spojuje vektorové vyhledávání a generativní AI pro přesné odpovědi na otázky k uzavřeným dokumentům. Systém extrahuje relevantní úryvky z PDF, Excel, kódu a generuje vysvětlení přímo z původních zdrojů, minimalizuje halucinace.
Proces začíná předzpracováním: dokumenty se analyzují, rozdělují na části s ohledem na typ obsahu a převádějí do vektorové reprezentace pro uložení do vektorové databáze.
Předzpracování dokumentů
Extrakce textu z různých formátů
Systém podporuje analýzu mnoha formátů bez ztráty dat. Pro každý formát se používá specializovaný extraktor.
SUPPORTED = {
'.pdf': lambda f: ''.join(p.extract_text() or '' for p in PyPDF2.PdfReader(f).pages),
'.docx': lambda f: '\n'.join(p.text for p in Document(f).paragraphs),
'.pptx': lambda f: '\n'.join(
shape.text
for slide in Presentation(f).slides
for shape in slide.shapes
if hasattr(shape, "text") and shape.text.strip()
),
'.xlsx': extract_xlsx,
'.csv': extract_csv,
# .txt, .json, .py, .js atd.
}
Na výstupu je čistý text připravený ke dalšímu zpracování.
Inteligentní rozdělení na části
Text se nerozděluje podle pevné velikosti, ale podle smyslu. Nejprve se detekuje typ obsahu:
def detect_content_type(text):
if re.search(r'<table|</td>|<tr>', text, re.IGNORECASE):
return 'table'
tab_lines = sum(1 for line in text.splitlines() if '\t' in line)
if tab_lines > max(3, len(text.splitlines()) * 0.4):
return 'table'
if re.search(r'function\s+\w+\s*\(|\bdef\s+\w+\s*\(|\bclass\s+\w+\b', text):
return 'code'
return 'prose'
- Tabulky: řádky s opakováním hlavičky v každé části
- Kód: rozdělení podle funkcí/tříd bez porušení logiky
- Proza: semantické hranice podle vektorové podobnosti vět
Pro tabulky:
def split_table_by_rows(text, chunk_size):
lines = [l for l in text.splitlines() if l.strip()]
header = lines[0]
chunks = []
cur_lines = [header]
for line in lines[1:]:
if sum(len(l) for l in cur_lines) + len(line) >= chunk_size:
chunks.append('\n'.join(cur_lines))
cur_lines = [header, line]
else:
cur_lines.append(line)
if cur_lines:
chunks.append('\n'.join(cur_lines))
return chunks
Semantické rozdělení využívá kosinovou podobnost sousedních vět s adaptivním prahem (25. percentil).
Generování vektorových reprezentací
Části se vektorizují modelem typu text-embedding-3-small (rozměr 1536). Vektory spolu s metadaty se ukládají do databáze.
def get_embeddings(texts, batch_size=100):
embeddings = []
for i in range(0, len(texts), batch_size):
resp = requests.post(
"https://openrouter.ai/api/v1/embeddings",
headers=HEADERS,
json={
"model": "openai/text-embedding-3-small",
"input": texts[i:i + batch_size],
"encoding_format": "float"
},
).json()
embeddings.extend(d["embedding"] for d in resp.get("data", []))
return np.array(embeddings)
Mnohetapové vyhledávání
Dotaz se převede na vektor, generují se přeformulace (3 varianty prostřednictvím LLM), pak hybridní vyhledávání.
Hybridní vyhledávání: vektory + BM25
- Vektorové: kosinová podobnost v prostoru vektorů
- BM25: tf-idf s parametry k1=1.5, b=0.75 pro přesné shody
Spojení prostřednictvím RRF:
# RRF fúze
for rank, idx in enumerate(sorted_by_vector):
rrf_scores[idx] += 1.0 / (k_rrf + rank)
for rank, idx in enumerate(sorted_by_bm25):
rrf_scores[idx] += bm25_normalized[idx] * (1.0 / (k_rrf + rank))
Reranking a filtry
Top 50 kandidátů hodnotí LLM, vybírá se 5–10 nejlepších. Vynucené vložení:
- Citace v uvozovkách
- Čísla/číselné identifikátory (regex: \b(№|N|číslo)\s*\d+\b)
quote_matches = re.findall(r'«([^»]+)»|"([^"]+)"', query)
for match in quote_matches:
quote = (match[0] or match[1]).strip()
if len(quote) > 5:
for i, chunk in enumerate(chunks_data):
if quote in chunk['content']:
forced_indices.append(i)
break
Optimalizace výkonu
Hierarchické vyhledávání
Nejprve top dokumenty podle anotací, poté části uvnitř nich.
doc_summaries = [chunks_data_by_doc[doc_id][0]['content'][:500] for doc_id in all_doc_ids]
doc_embs = get_embeddings(doc_summaries)
sims = np.dot(doc_embs_normalized, query_embedding_normalized)
top_doc_ids = [all_doc_ids[i] for i in np.argsort(sims)[::-1][:5]]
chunks_data = [ch for ch in chunks_data if ch['doc_id'] in top_doc_ids]
Iterativní vyhledávání
Pro složité dotazy – několik kol s upřesněním prostřednictvím LLM.
Co je důležité
- RAG minimalizuje halucinace, odpovědi generuje přímo z relevantních částí
- Hybridní vyhledávání (vektory + BM25 + RRF) zvyšuje přesnost o 20–30 % ve srovnání s čistě vektorovým přístupem
- Semantické rozdělení zachovává kontext tabulek a kódu, snižuje šum
- Hierarchický přístup zrychluje vyhledávání v velkých kolekcích (>10 tisíc dokumentů)
- Vynucené filtry zaručují vrácení faktů s čísly nebo citacemi
— Editorial Team
Zatím žádné komentáře.