Delete directory 'solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью'

This commit is contained in:
2026-05-27 10:48:27 +00:00
parent 7fd3b4e8f6
commit aa9ba1750f
7 changed files with 0 additions and 874 deletions
@@ -1,132 +0,0 @@
# Агент с RAG‑памятью
## Описание проекта
Проект реализует AI‑агента, способного хранить и извлекать знания из локальной векторной базы Qdrant при помощи Ollama LLM и эмбеддингов.
- **RAG** (RetrievalAugmented Generation) агент сначала ищет релевантные документы, а затем генерирует ответ, опираясь на найденную информацию.
- Векторная база хранит чанки документов, которые разбиваются при помощи `RecursiveCharacterTextSplitter`.
- Агент использует два инструмента: поиск в базе и добавление новых документов.
## Стек технологий
| Технология | Версия / Пакет |
|------------|----------------|
| Python | 3.10+ |
| Qdrant | v1.x (Docker) |
| Ollama | `llama3`, `nomic-embed-text` |
| LangChain | 0.2+ |
## Установка зависимостей
```bash
# 1️⃣ Скачиваем модели Ollama
ollama pull llama3
ollama pull nomic-embed-text
# 2️⃣ Запускаем Qdrant (Docker)
docker run -d --name qdrant \
-p 6333:6333 \
ghcr.io/qdrant/qdrant:v1.9.0
# 3️⃣ Устанавливаем Python‑пакеты
pip install langchain langchain-qdrant langchain-ollama
```
> **Важно**
> * Qdrant должен быть запущен до запуска любого скрипта проекта.
> * Переменные окружения `QDRANT_HOST`, `QDRANT_PORT` и `QDRANT_COLLECTION` можно задать в `.env` или напрямую в терминале.
## Структура файлов
| Файл | Описание |
|------|----------|
| `rag_agent.py` | Основной скрипт, создающий агента, инструменты и запускает его. |
| `vector_store.py` | Модуль для работы с Qdrant: добавление документов, поиск. |
| `tools.py` | Декорированные функции‑инструменты (`search_knowledge_base`, `add_to_knowledge_base`). |
| `init_loader.py` | Инициализация и загрузка начальных данных в базу (опционально). |
| `cli_client.py` | CLI‑клиент для взаимодействия с агентом из терминала. |
## Запуск каждого файла
### 1️⃣ `rag_agent.py`
```bash
python rag_agent.py
```
> Скрипт инициализирует агента, добавляет в него инструменты и ожидает пользовательский ввод через `input()`.
### 2️⃣ `vector_store.py`
```bash
# Пример использования из REPL или другого скрипта
from vector_store import QdrantStore
store = QdrantStore()
store.add_document("Документ о Python", "Python – язык программирования.")
results = store.search("что такое Python")
print(results)
```
### 3️⃣ `tools.py`
```bash
# Функции можно импортировать и использовать в агенте
from tools import search_knowledge_base, add_to_knowledge_base
search_knowledge_base("Python", max_results=5)
add_to_knowledge_base("Новый контент", "Заголовок")
```
### 4️⃣ `init_loader.py`
```bash
python init_loader.py
```
> Скрипт читает файлы из папки `data/` и загружает их в Qdrant. Убедитесь, что каталог существует.
### 5️⃣ `cli_client.py`
```bash
python cli_client.py "Какой язык программирования лучше?"
```
> CLI‑клиент отправляет запрос агенту и выводит ответ в терминал.
## Пример использования
1. **Инициализация базы**
```bash
python init_loader.py
```
2. **Запуск агента**
```bash
python rag_agent.py
```
Введите вопрос, например:
```
Какой язык программирования лучше для веб‑разработки?
```
3. **CLI‑клиент**
```bash
python cli_client.py "Что такое Qdrant?"
```
## Тесты (опционально)
```bash
pip install pytest
pytest tests/
```
> В папке `tests/` находятся простые unit‑тесты для модуля `vector_store`.
## Поддержка и вопросы
- **Проблема с подключением к Qdrant** – убедитесь, что контейнер запущен и доступен по адресу `http://localhost:6333`.
- **Ошибка при загрузке модели Ollama** – проверьте, что модели скачаны (`ollama list`).
## Лицензия
MIT © 2026
@@ -1,158 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
cli_client.py
Интерактивный клиент для работы с агентом RAG‑памяти.
Поддерживает команды:
/add - добавить документ в базу знаний
/search - выполнить семантический поиск по базе
/quit - выйти из программы
Команды реализованы через инструменты, объявленные в модуле tools.py:
add_to_knowledge_base(content: str, title: str) -> None
search_knowledge_base(query: str, max_results: int = 5) -> list[dict]
Для красивого вывода используется библиотека rich.
"""
import sys
from pathlib import Path
try:
from rich.console import Console
from rich.table import Table
except ImportError as exc:
print("Необходимо установить rich: pip install rich", file=sys.stderr)
raise exc
# Импортируем инструменты из общего модуля. Предполагается, что они уже реализованы.
try:
from tools import add_to_knowledge_base, search_knowledge_base
except Exception as exc:
print("Не удалось импортировать инструменты из modules.tools", file=sys.stderr)
raise exc
console = Console()
def _print_help() -> None:
"""Вывод справки по доступным командам."""
console.print(
"""
[bold cyan]/add[/] – добавить документ в базу знаний
[bold cyan]/search[/] – выполнить поиск по базе
[bold cyan]/quit[/] – выйти из программы
Для добавления можно указать путь к файлу или вводить текст вручную.
"""
)
def _handle_add() -> None:
"""Обработчик команды /add."""
console.print("[green]Введите заголовок документа:[/]")
title = input("> ").strip()
if not title:
console.print("[red]Заголовок не может быть пустым.[/]")
return
console.print(
"[green]Выберите способ ввода содержимого:\n"
"1 – Ввести текст вручную\n"
"2 – Указать путь к файлу[/]"
)
choice = input("> ").strip()
if choice == "2":
path_str = input("[green]Введите путь к файлу:[/]").strip()
try:
content = Path(path_str).read_text(encoding="utf-8")
except Exception as exc:
console.print(f"[red]Не удалось прочитать файл: {exc}[/]")
return
else:
console.print("[green]Введите содержимое (конец ввода – пустая строка):[/]")
lines = []
while True:
line = input()
if line == "":
break
lines.append(line)
content = "\n".join(lines)
try:
add_to_knowledge_base(content=content, title=title)
console.print(f"[bold green]Документ '{title}' успешно добавлен.[/]")
except Exception as exc:
console.print(f"[red]Ошибка при добавлении документа: {exc}[/]")
def _handle_search() -> None:
"""Обработчик команды /search."""
query = input("[green]Введите запрос для поиска:[/]").strip()
if not query:
console.print("[red]Запрос не может быть пустым.[/]")
return
max_results_str = input(
"[green]Укажите количество результатов (по умолчанию 5):[/]"
).strip()
try:
max_results = int(max_results_str) if max_results_str else 5
except ValueError:
console.print("[red]Неверное число. Будет использовано значение по умолчанию: 5[/]")
max_results = 5
try:
results = search_knowledge_base(query=query, max_results=max_results)
except Exception as exc:
console.print(f"[red]Ошибка при поиске: {exc}[/]")
return
if not results:
console.print("[yellow]Ничего не найдено.[/]")
return
table = Table(title="Результаты поиска", show_lines=True)
table.add_column("", style="dim")
table.add_column("Заголовок", style="bold cyan")
table.add_column("Текст")
for idx, item in enumerate(results, start=1):
title = item.get("title", "Без заголовка")
snippet = item.get("content", "")[:200] + ("" if len(item.get("content", "")) > 200 else "")
table.add_row(str(idx), title, snippet)
console.print(table)
def main() -> None:
"""Главный цикл программы."""
console.print("[bold magenta]=== RAG‑Память CLI ===[/]")
_print_help()
while True:
try:
command = input("\n[bold cyan]> [/]").strip()
except (KeyboardInterrupt, EOFError):
console.print("\n[bold red]Выход...[/]")
break
if not command:
continue
if command.lower() == "/quit":
console.print("[bold red]Завершение работы.[/]")
break
elif command.lower() == "/add":
_handle_add()
elif command.lower() == "/search":
_handle_search()
else:
console.print(f"[red]Неизвестная команда: {command}[/]")
_print_help()
if __name__ == "__main__":
main()
@@ -1,106 +0,0 @@
<|channel|>final code<|message|>import os
from pathlib import Path
from typing import List
from langchain_ollama import OllamaEmbeddings
from langchain_qdrant import QdrantVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader, DirectoryLoader
def _get_documents_from_dir(directory: str) -> List[str]:
"""
Возвращает список путей к текстовым файлам в указанной директории.
"""
dir_path = Path(directory)
if not dir_path.is_dir():
raise ValueError(f"Путь {directory} не является директорией.")
# Поддерживаем только .txt, .md и .pdf (если понадобится)
files = list(dir_path.rglob("*"))
return [str(p) for p in files if p.suffix.lower() in {".txt", ".md"}]
def _load_documents(file_paths: List[str]) -> List[dict]:
"""
Загружает содержимое файлов в список словарей с полями 'content' и 'metadata'.
"""
documents = []
for path in file_paths:
loader = TextLoader(path, encoding="utf-8")
docs = loader.load()
# LangChain возвращает объекты Document; преобразуем их в dict
for doc in docs:
documents.append(
{
"content": doc.page_content,
"metadata": {"source": path},
}
)
return documents
def _chunk_documents(documents: List[dict], chunk_size: int = 1000, chunk_overlap: int = 200) -> List[str]:
"""
Разбивает документы на чанки с помощью RecursiveCharacterTextSplitter.
Возвращает список строк-чанков.
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
chunks = []
for doc in documents:
splits = splitter.split_text(doc["content"])
# Добавляем метаданные в конец чанка для удобства поиска
for s in splits:
chunks.append(s)
return chunks
def _embed_and_store(chunks: List[str], collection_name: str, host: str = "localhost", port: int = 6333):
"""
Создаёт клиент Qdrant и сохраняет чанки с эмбеддингами Ollama.
"""
# Инициализация векторного хранилища
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = QdrantVectorStore(
client_kwargs={"host": host, "port": port},
collection_name=collection_name,
embedding_function=embeddings,
)
# Добавляем чанки в коллекцию
vectorstore.add_texts(chunks)
def load_directory_to_qdrant(directory: str, collection_name: str = "knowledge_base"):
"""
Полный пайплайн загрузки документов из директории в Qdrant.
"""
file_paths = _get_documents_from_dir(directory)
if not file_paths:
print(f"В каталоге {directory} не найдено текстовых файлов.")
return
documents = _load_documents(file_paths)
chunks = _chunk_documents(documents)
_embed_and_store(chunks, collection_name)
print(
f"Загружено {len(chunks)} чанков из {len(file_paths)} документов в коллекцию '{collection_name}'."
)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Загрузка документов в Qdrant.")
parser.add_argument("directory", help="Путь к директории с документами")
parser.add_argument(
"--collection",
default="knowledge_base",
help="Имя коллекции в Qdrant (по умолчанию: knowledge_base)",
)
args = parser.parse_args()
load_directory_to_qdrant(args.directory, args.collection)
@@ -1,187 +0,0 @@
import os
from pathlib import Path
from typing import List
from langchain_ollama import OllamaEmbeddings, OllamaLLM
from langchain_qdrant import QdrantVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.tools import tool
from langchain.agents import create_agent, AgentExecutor, Tool
from langchain.schema import Document
# --------------------------------------------------------------------------- #
# Конфигурация и клиент Qdrant
# --------------------------------------------------------------------------- #
QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost")
QDRANT_PORT = int(os.getenv("QDRANT_PORT", 6333))
COLLECTION_NAME = os.getenv("QDRANT_COLLECTION", "rag_collection")
# Инициализируем клиент Qdrant
qdrant_store = QdrantVectorStore(
url=f"http://{QDRANT_HOST}:{QDRANT_PORT}",
collection_name=COLLECTION_NAME,
embeddings=OllamaEmbeddings(model="nomic-embed-text"),
)
# --------------------------------------------------------------------------- #
# Чанкинг текста
# --------------------------------------------------------------------------- #
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # символов в чанке
chunk_overlap=50,
)
# --------------------------------------------------------------------------- #
# Инструменты агента
# --------------------------------------------------------------------------- #
@tool("search_knowledge_base")
def search_knowledge_base(query: str, max_results: int = 5) -> List[Document]:
"""
Семантический поиск по базе знаний.
:param query: поисковый запрос
:param max_results: максимальное количество результатов
:return: список документов с метаданными
"""
results = qdrant_store.similarity_search(query, k=max_results)
return results
@tool("add_to_knowledge_base")
def add_to_knowledge_base(content: str, title: str) -> str:
"""
Добавление нового документа в базу знаний.
:param content: полный текст документа
:param title: заголовок/название документа
:return: подтверждение добавления
"""
# Разбиваем на чанки и сохраняем с метаданными
chunks = text_splitter.split_text(content)
docs = [
Document(page_content=chunk, metadata={"title": title})
for chunk in chunks
]
qdrant_store.add_documents(docs)
return f"Документ '{title}' успешно добавлен в базу знаний."
# --------------------------------------------------------------------------- #
# Создание агента
# --------------------------------------------------------------------------- #
llm = OllamaLLM(model="llama3")
tools: List[Tool] = [
Tool.from_function(
func=search_knowledge_base,
name="search_knowledge_base",
description="Используйте для поиска информации в базе знаний. Входные параметры: query, max_results.",
),
Tool.from_function(
func=add_to_knowledge_base,
name="add_to_knowledge_base",
description="Добавляет новый документ в базу знаний. Параметры: content, title.",
),
]
agent = create_agent(
llm=llm,
tools=tools,
system_message=(
"Вы — интеллектуальный агент с доступом к базе знаний. "
"При необходимости используйте инструмент search_knowledge_base для поиска информации. "
"Если нужно добавить новый материал, применяйте add_to_knowledge_base."
),
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# --------------------------------------------------------------------------- #
# Загрузка документов из директории
# --------------------------------------------------------------------------- #
def load_documents_from_directory(directory: str) -> None:
"""
Загружает все текстовые файлы из указанной папки в базу знаний.
:param directory: путь к каталогу с документами
"""
path = Path(directory)
for file_path in path.rglob("*"):
if file_path.suffix.lower() not in {".txt", ".md"}:
continue
content = file_path.read_text(encoding="utf-8")
title = file_path.stem
add_to_knowledge_base(content, title)
# --------------------------------------------------------------------------- #
# Интерактивный клиент
# --------------------------------------------------------------------------- #
def interactive_cli():
"""
Простая CLI для взаимодействия с агентом.
Команды:
/add <path> - добавить документ из файла
/search <q> - поиск по базе знаний
/quit - выйти
любой другой ввод передаётся агенту как вопрос
"""
print("=== RAG Агент ===")
while True:
try:
user_input = input("\n> ").strip()
except (EOFError, KeyboardInterrupt):
break
if not user_input:
continue
if user_input.lower() == "/quit":
print("До свидания!")
break
elif user_input.startswith("/add"):
_, *parts = user_input.split(maxsplit=1)
if parts:
file_path = parts[0]
try:
content = Path(file_path).read_text(encoding="utf-8")
title = Path(file_path).stem
print(add_to_knowledge_base(content, title))
except Exception as e:
print(f"Ошибка при добавлении: {e}")
else:
print("Используйте /add <путь_к_файлу>")
elif user_input.startswith("/search"):
_, *parts = user_input.split(maxsplit=1)
if parts:
query = parts[0]
results = search_knowledge_base(query, max_results=3)
for i, doc in enumerate(results, 1):
print(f"\nРезультат {i}:")
print(f"Титул: {doc.metadata.get('title', 'Неизвестно')}")
print(doc.page_content[:500], "...")
else:
print("Используйте /search <запрос>")
else:
# Любой другой ввод считается вопросом агента
response = agent_executor.run(user_input)
print(response)
# --------------------------------------------------------------------------- #
# Точка входа
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
# При запуске можно загрузить документы из папки docs/
docs_dir = os.getenv("DOCS_DIR", "docs")
if Path(docs_dir).exists():
load_documents_from_directory(docs_dir)
interactive_cli()
@@ -1,6 +0,0 @@
langchain
langchain-ollama
langchain-qdrant
langchain-text-splitters
qdrant-client
rich
@@ -1,89 +0,0 @@
# tools.py
"""
Модуль с инструментами для агента.
В файле определены два инструмента:
1. search_knowledge_base семантический поиск в Qdrant.
2. add_to_knowledge_base – добавление документа (с чанками) в базу.
Инструменты реализованы через декоратор @tool из langchain.tools,
что позволяет автоматически использовать их в агентах LangChain.
"""
from pathlib import Path
from typing import List, Tuple
# ──────────────────────── Зависимости ────────────────────────
from langchain.embeddings.ollama import OllamaEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.qdrant import QdrantVectorStore
from langchain.tools import tool
# ──────────────────────── Конфигурация ────────────────────────
# Путь к локальной базе Qdrant (предполагается, что она уже запущена)
QDRANT_URL = "http://localhost:6333"
COLLECTION_NAME = "knowledge_base"
# Создаём один экземпляр векторного хранилища и эмбеддеров,
# чтобы не создавать их каждый раз при вызове инструментов.
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = QdrantVectorStore(
client_kwargs={"url": QDRANT_URL},
collection_name=COLLECTION_NAME,
embedding_function=embeddings.embed_query, # функция для получения эмбеддингов
)
# ──────────────────────── Инструмент: поиск ────────────────────────
@tool("search_knowledge_base",
description="Семантический поиск в базе знаний. "
"Возвращает список строк с найденными документами и их релевантностью.")
def search_knowledge_base(query: str, max_results: int = 5) -> List[Tuple[str, float]]:
"""
Выполняет семантический поиск по запросу.
Args:
query (str): поисковый запрос.
max_results (int): максимальное количество результатов.
Returns:
List[Tuple[str, float]]: список кортежей (текст документа, релевантность).
"""
# similarity_search_with_score возвращает [(Document, score), ...]
results = vectorstore.similarity_search_with_score(query, k=max_results)
return [(doc.page_content, score) for doc, score in results]
# ──────────────────────── Инструмент: добавление ────────────────────────
@tool("add_to_knowledge_base",
description="Добавляет новый документ в базу знаний. "
"Документ разбивается на чанки и сохраняется с метаданными.")
def add_to_knowledge_base(content: str, title: str) -> str:
"""
Добавляет текстовый контент в Qdrant.
Args:
content (str): полный текст документа.
title (str): заголовок/имя документа.
Returns:
str: сообщение об успешном добавлении и количестве чанков.
"""
# Разбиваем документ на чанки
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""],
)
chunks = splitter.split_text(content)
# Создаём объекты Document с метаданными
from langchain.schema import Document
documents: List[Document] = [
Document(page_content=chunk, metadata={"title": title})
for chunk in chunks
]
# Добавляем векторные представления в Qdrant
vectorstore.add_documents(documents)
return f"Документ '{title}' добавлен. Количество чанков: {len(chunks)}"
@@ -1,196 +0,0 @@
<|channel|>final code<|message|>```python
# vector_store.py
"""
Модуль работы с Qdrant, Ollama и чанкингом.
Содержит:
- инициализацию клиента и коллекции;
- добавление документов (с разбиением на чанки);
- поиск по семантической схожести.
Используем:
- langchain_qdrant.QdrantVectorStore
- langchain_ollama.OllamaEmbeddings
- RecursiveCharacterTextSplitter
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import Iterable, List, Tuple
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_qdrant import QdrantVectorStore
from langchain_ollama import OllamaEmbeddings
from qdrant_client import QdrantClient
from qdrant_client.http.models import Distance, VectorParams
# --------------------------------------------------------------------------- #
# Конфигурация по умолчанию (можно переопределить через переменные окружения)
# --------------------------------------------------------------------------- #
QDRANT_HOST: str = os.getenv("QDRANT_HOST", "localhost")
QDRANT_PORT: int = int(os.getenv("QDRANT_PORT", "6333"))
COLLECTION_NAME: str = os.getenv("QDRANT_COLLECTION", "rag_knowledge_base")
EMBEDDING_MODEL: str = os.getenv("OLLAMA_EMBEDDINGS", "nomic-embed-text")
DIMENSIONS: int = 512 # размер эмбеддингов nomic-embed-text
# --------------------------------------------------------------------------- #
# Класс VectorStore
# --------------------------------------------------------------------------- #
class VectorStore:
"""
Обёртка над QdrantVectorStore с Ollama‑эмбеддингами и чанкингом.
"""
def __init__(
self,
host: str = QDRANT_HOST,
port: int = QDRANT_PORT,
collection_name: str = COLLECTION_NAME,
embedding_model: str = EMBEDDING_MODEL,
dimensions: int = DIMENSIONS,
) -> None:
"""
Создаёт клиент Qdrant и коллекцию (если её нет).
"""
self.client = QdrantClient(host=host, port=port)
self.collection_name = collection_name
# Проверяем наличие коллекции
if not self._collection_exists():
self._create_collection(dimensions)
# Создаём объект VectorStore
self.store = QdrantVectorStore(
client=self.client,
collection_name=self.collection_name,
embedding=OllamaEmbeddings(model=embedding_model),
)
# --------------------------------------------------------------------- #
# Внутренние методы для работы с коллекцией
# --------------------------------------------------------------------- #
def _collection_exists(self) -> bool:
"""Проверяем, существует ли коллекция."""
return self.collection_name in self.client.get_collections().collections
def _create_collection(self, dimensions: int) -> None:
"""Создаём новую коллекцию с заданными параметрами."""
self.client.recreate_collection(
collection_name=self.collection_name,
vectors_config=VectorParams(size=dimensions, distance=Distance.COSINE),
)
# --------------------------------------------------------------------- #
# Чанкинг
# --------------------------------------------------------------------- #
@staticmethod
def _split_text(text: str) -> List[Document]:
"""
Разбивает текст на чанки с метаданными.
Возвращает список Document.
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""],
)
texts = splitter.split_text(text)
return [Document(page_content=t) for t in texts]
# --------------------------------------------------------------------- #
# Добавление документов
# --------------------------------------------------------------------- #
def add_document(self, content: str, title: str | None = None) -> List[str]:
"""
Добавляет документ в базу:
* разбивает на чанки;
* сохраняет каждый чанк с метаданными.
Возвращает список ID добавленных чанков.
"""
docs = self._split_text(content)
# Добавляем заголовок как metadata, если он передан
if title:
for doc in docs:
doc.metadata["title"] = title
ids = self.store.add_documents(docs)
return ids
def add_documents_from_path(self, path: str | Path) -> List[str]:
"""
Загружает все текстовые файлы из директории (рекурсивно)
и добавляет их в базу.
Возвращает список всех ID.
"""
p = Path(path)
if not p.is_dir():
raise ValueError(f"Путь {path} не является директорией")
all_ids: List[str] = []
for file in p.rglob("*"):
if file.suffix.lower() in {".txt", ".md"}:
text = file.read_text(encoding="utf-8")
ids = self.add_document(text, title=file.stem)
all_ids.extend(ids)
return all_ids
# --------------------------------------------------------------------- #
# Поиск
# --------------------------------------------------------------------- #
def search(
self,
query: str,
k: int = 5,
filter_metadata: dict | None = None,
) -> List[Tuple[str, float]]:
"""
Семантический поиск по базе.
Возвращает список (document_id, score) упорядоченных по убыванию релевантности.
"""
results = self.store.similarity_search_with_score(
query=query,
k=k,
filter=filter_metadata,
)
return [(doc.metadata.get("id", ""), score) for doc, score in results]
# --------------------------------------------------------------------- #
# Удаление (необязательно)
# --------------------------------------------------------------------- #
def delete_document(self, doc_id: str) -> None:
"""Удаляет документ по ID."""
self.client.delete(
collection_name=self.collection_name,
points_selector={"ids": [doc_id]},
)
# --------------------------------------------------------------------------- #
# Пример использования (можно закомментировать при импорте)
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
store = VectorStore()
# Добавляем примерный документ
doc_text = """
LangChain – это библиотека для создания LLM‑агентов.
Она позволяет легко интегрировать внешние сервисы, такие как базы данных,
поисковые движки и другие инструменты. В этом примере мы используем Qdrant
в качестве хранилища эмбеддингов и Ollama для генерации эмбеддингов.
"""
store.add_document(doc_text, title="LangChain Overview")
# Поиск по запросу
results = store.search("что такое LangChain", k=3)
print("Результаты поиска:")
for doc_id, score in results:
print(f"ID: {doc_id} | Score: {score:.4f}")