Обновить vector_store.py
This commit is contained in:
+36
-63
@@ -1,100 +1,73 @@
|
|||||||
"""
|
|
||||||
vector_store.py — модуль для работы с векторным хранилищем Qdrant + Ollama.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
from langchain_ollama import OllamaEmbeddings
|
|
||||||
from langchain_qdrant import QdrantVectorStore
|
from langchain_qdrant import QdrantVectorStore
|
||||||
|
from langchain_ollama import OllamaEmbeddings
|
||||||
from langchain_core.documents import Document
|
from langchain_core.documents import Document
|
||||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
|
||||||
from qdrant_client import QdrantClient
|
from qdrant_client import QdrantClient
|
||||||
from qdrant_client.models import Distance, VectorParams
|
from qdrant_client.models import Distance, VectorParams
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
import uuid
|
||||||
|
|
||||||
# ── Конфигурация ──────────────────────────────────────────────────────────────
|
COLLECTION_NAME = "knowledge_base"
|
||||||
QDRANT_URL = "http://localhost:6333"
|
EMBEDDING_MODEL = "nomic-embed-text"
|
||||||
COLLECTION_NAME = "rag_memory"
|
VECTOR_SIZE = 768
|
||||||
EMBEDDING_MODEL = "nomic-embed-text"
|
|
||||||
EMBEDDING_DIM = 768 # размер вектора nomic-embed-text
|
|
||||||
|
|
||||||
CHUNK_SIZE = 500
|
|
||||||
CHUNK_OVERLAP = 100
|
|
||||||
|
|
||||||
|
|
||||||
# ── Эмбеддинги через Ollama ───────────────────────────────────────────────────
|
def get_embeddings():
|
||||||
embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL)
|
return OllamaEmbeddings(model=EMBEDDING_MODEL)
|
||||||
|
|
||||||
|
|
||||||
# ── Qdrant-клиент и коллекция ─────────────────────────────────────────────────
|
def get_qdrant_client():
|
||||||
def _get_client() -> QdrantClient:
|
return QdrantClient(host="localhost", port=6333)
|
||||||
return QdrantClient(url=QDRANT_URL)
|
|
||||||
|
|
||||||
|
|
||||||
def init_collection() -> None:
|
def init_collection(client: QdrantClient):
|
||||||
"""Создаёт коллекцию в Qdrant, если её ещё нет."""
|
collections = [c.name for c in client.get_collections().collections]
|
||||||
client = _get_client()
|
if COLLECTION_NAME not in collections:
|
||||||
existing = [c.name for c in client.get_collections().collections]
|
|
||||||
if COLLECTION_NAME not in existing:
|
|
||||||
client.create_collection(
|
client.create_collection(
|
||||||
collection_name=COLLECTION_NAME,
|
collection_name=COLLECTION_NAME,
|
||||||
vectors_config=VectorParams(size=EMBEDDING_DIM, distance=Distance.COSINE),
|
vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
|
||||||
)
|
)
|
||||||
print(f"[vector_store] Коллекция «{COLLECTION_NAME}» создана.")
|
|
||||||
else:
|
|
||||||
print(f"[vector_store] Коллекция «{COLLECTION_NAME}» уже существует.")
|
|
||||||
|
|
||||||
|
|
||||||
def _get_store() -> QdrantVectorStore:
|
def get_vector_store() -> QdrantVectorStore:
|
||||||
"""Возвращает готовый QdrantVectorStore."""
|
client = get_qdrant_client()
|
||||||
|
init_collection(client)
|
||||||
|
embeddings = get_embeddings()
|
||||||
return QdrantVectorStore(
|
return QdrantVectorStore(
|
||||||
client=_get_client(),
|
client=client,
|
||||||
collection_name=COLLECTION_NAME,
|
collection_name=COLLECTION_NAME,
|
||||||
embedding=embeddings,
|
embedding=embeddings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ── Чанкинг ───────────────────────────────────────────────────────────────────
|
def add_documents(content: str, title: str) -> int:
|
||||||
def split_text(content: str, title: str = "") -> List[Document]:
|
|
||||||
"""
|
|
||||||
Разбивает текст на чанки с помощью RecursiveCharacterTextSplitter
|
|
||||||
и добавляет метаданные (title, chunk_index).
|
|
||||||
"""
|
|
||||||
splitter = RecursiveCharacterTextSplitter(
|
splitter = RecursiveCharacterTextSplitter(
|
||||||
chunk_size=CHUNK_SIZE,
|
chunk_size=500,
|
||||||
chunk_overlap=CHUNK_OVERLAP,
|
chunk_overlap=50,
|
||||||
separators=["\n\n", "\n", ".", " ", ""],
|
separators=["\n\n", "\n", ".", " ", ""],
|
||||||
)
|
)
|
||||||
chunks = splitter.split_text(content)
|
chunks = splitter.split_text(content)
|
||||||
docs = [
|
docs = [
|
||||||
Document(
|
Document(
|
||||||
page_content=chunk,
|
page_content=chunk,
|
||||||
metadata={"title": title, "chunk_index": i, "source": title or "manual"},
|
metadata={"title": title, "chunk_index": i, "source": title},
|
||||||
)
|
)
|
||||||
for i, chunk in enumerate(chunks)
|
for i, chunk in enumerate(chunks)
|
||||||
]
|
]
|
||||||
return docs
|
store = get_vector_store()
|
||||||
|
|
||||||
|
|
||||||
# ── Публичный API ─────────────────────────────────────────────────────────────
|
|
||||||
def add_documents(content: str, title: str = "") -> int:
|
|
||||||
"""
|
|
||||||
Добавляет документ в векторное хранилище.
|
|
||||||
Возвращает количество добавленных чанков.
|
|
||||||
"""
|
|
||||||
docs = split_text(content, title)
|
|
||||||
store = _get_store()
|
|
||||||
store.add_documents(docs)
|
store.add_documents(docs)
|
||||||
return len(docs)
|
return len(docs)
|
||||||
|
|
||||||
|
|
||||||
def search(query: str, max_results: int = 5) -> List[Tuple[Document, float]]:
|
def search_documents(query: str, max_results: int = 5) -> list[dict]:
|
||||||
"""
|
store = get_vector_store()
|
||||||
Семантический поиск с метрикой релевантности.
|
|
||||||
Возвращает список (Document, score).
|
|
||||||
"""
|
|
||||||
store = _get_store()
|
|
||||||
results = store.similarity_search_with_relevance_scores(query, k=max_results)
|
results = store.similarity_search_with_relevance_scores(query, k=max_results)
|
||||||
return results
|
output = []
|
||||||
|
for doc, score in results:
|
||||||
|
output.append(
|
||||||
|
{
|
||||||
|
"content": doc.page_content,
|
||||||
|
"metadata": doc.metadata,
|
||||||
|
"score": round(score, 4),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return output
|
||||||
Reference in New Issue
Block a user