Delete directory 'solutions'

This commit is contained in:
2026-05-27 07:53:02 +00:00
parent 0646c27980
commit 96911dcdb9
16 changed files with 0 additions and 1451 deletions
@@ -1,169 +0,0 @@
# Агент с RAG‑памятью
## Описание проекта
Проект реализует **AI‑агента**, способного хранить и извлекать знания из локальной векторной базы Qdrant при помощи Ollama (LLM + эмбеддинги).
Основные возможности:
- Семантический поиск по базе знаний (`search_knowledge_base`).
- Добавление новых документов в базу (`add_to_knowledge_base`).
- Автоматическое разбиение больших текстов на чанки.
- Интеграция с LangChain для создания агента, использующего RAG‑память.
## Стек технологий
| Технология | Версия | Зачем |
|------------|--------|-------|
| Python | 3.10+ | Язык разработки |
| Qdrant | - | Векторная база данных |
| Ollama | - | Локальные LLM и эмбеддинги (`llama3`, `nomic-embed-text`) |
| LangChain | - | Фреймворк для агентов, RAG и инструментов |
## Установка зависимостей
```bash
# 1️⃣ Установить Ollama (если ещё не установлен)
# https://ollama.com/download
# Загрузить модели
ollama pull llama3
ollama pull nomic-embed-text
# 2️⃣ Создать виртуальное окружение и установить Python‑пакеты
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install langchain langchain-qdrant langchain-ollama
```
> **Важно**
> Qdrant должен быть запущен локально (по умолчанию `http://localhost:6333`).
> Если у вас нет Docker‑образа, можно установить его через:
> ```bash
> docker run -p 6333:6333 qdrant/qdrant
> ```
## Структура проекта
| Файл | Описание |
|------|----------|
| `rag_agent.py` | Основная логика агента и RAG‑инструментов. |
| `qdrant_client.py` | Обёртка над QdrantVectorStore (инициализация, добавление, поиск). |
| `chunker.py` | Модуль разбиения текста на чанки. |
| `init_loader.py` | Скрипт для первичной загрузки документов в базу. |
| `cli_client.py` | CLI‑интерфейс для взаимодействия с агентом. |
## Как запустить каждый файл
### 1️⃣ `rag_agent.py`
Файл содержит самих инструментов и пример их использования.
```bash
python rag_agent.py
```
> Вы увидите вывод о работе инструментов: добавление документа, поиск по запросу и ответ агента.
---
### 2️⃣ `qdrant_client.py`
Пример работы с клиентом Qdrant (добавление/поиск).
```bash
python qdrant_client.py
```
---
### 3️⃣ `chunker.py`
Показывает, как разбивается текст на чанки.
```bash
python chunker.py
```
---
### 4️⃣ `init_loader.py`
Загружает все файлы из папки `data/` в базу знаний.
Создайте каталог `data/`, поместите туда `.txt` или `.pdf` файлы, затем:
```bash
python init_loader.py
```
---
### 5️⃣ `cli_client.py`
CLI‑интерфейс для общения с агентом.
```bash
python cli_client.py
```
> Введите любой вопрос, и агент ответит, используя RAG‑память.
## Пример использования
1. **Инициализируйте базу знаний** (если ещё не сделано):
```bash
python init_loader.py
```
2. **Запустите CLI‑клиент**:
```bash
python cli_client.py
```
3. **Взаимодействуйте с агентом**:
```
> Какой был первый президент России?
Ответ: Первый президент Российской Федерации — Борис Ельцин.
```
4. **Добавьте новый документ вручную** (через `rag_agent.py` или любой другой скрипт):
```python
from rag_agent import add_to_knowledge_base
content = "Python – язык программирования, созданный Гвидо ван Россумом."
title = "О Python"
add_to_knowledge_base(content=content, title=title)
```
5. **Проверьте поиск**:
```python
from rag_agent import search_knowledge_base
results = search_knowledge_base(query="что такое python", max_results=3)
for r in results:
print(r.page_content[:200])
```
## Тесты и отладка
```bash
# Установить pytest (если нужен)
pip install pytest
# Запустить тесты (если есть)
pytest tests/
```
> В проекте пока нет автоматических тестов, но вы можете добавить их в папку `tests/`.
## FAQ
| Вопрос | Ответ |
|--------|-------|
| **Как изменить модель Ollama?** | Измените параметр `model` в `OllamaEmbeddings` и `OllamaLLM`. |
| **Можно ли использовать удалённый Qdrant?** | Да, укажите нужный URL при создании `KnowledgeBase`. |
| **Что делать, если база пустая?** | Запустите `init_loader.py` или добавьте документы через `add_to_knowledge_base`. |
## Лицензия
MIT © 2026
---
@@ -1,86 +0,0 @@
# chunker.py
"""
Модуль для разбиения больших текстов на небольшие части (чанки)
с помощью RecursiveCharacterTextSplitter из LangChain.
Используется в процессе загрузки документов в Qdrant:
1. Читаем исходный файл.
2. Разбиваем его на чанки, чтобы каждый чанк не превышал заданную длину.
3. Возвращаем список строк (чанков) и метаданные для каждого чанка
(например, номер чанка и оригинальный заголовок).
"""
from pathlib import Path
from typing import List, Tuple
from langchain_text_splitters import RecursiveCharacterTextSplitter
def split_document(
file_path: str | Path,
chunk_size: int = 1000,
chunk_overlap: int = 200,
) -> List[Tuple[str, dict]]:
"""
Разбивает содержимое файла на чанки.
Parameters
----------
file_path : str | Path
Путь к текстовому файлу (может быть .txt, .md и т.п.).
chunk_size : int, default=1000
Максимальная длина одного чанка в символах.
chunk_overlap : int, default=200
Количество пересекающихся символов между соседними чанками.
Returns
-------
List[Tuple[str, dict]]
Список кортежей (чанк, метаданные). Метаданные включают:
- `source`: путь к файлу,
- `chunk_index`: порядковый номер чанка в документе.
"""
# Читаем файл
path = Path(file_path)
if not path.is_file():
raise FileNotFoundError(f"Файл не найден: {file_path}")
text = path.read_text(encoding="utf-8")
# Создаём splitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
)
# Получаем чанки
chunks = splitter.split_text(text)
# Формируем список с метаданными
result: List[Tuple[str, dict]] = []
for idx, chunk in enumerate(chunks):
metadata = {
"source": str(path.resolve()),
"chunk_index": idx,
}
result.append((chunk, metadata))
return result
# Пример использования (можно закомментировать при импорте)
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python chunker.py <file_path> [chunk_size] [chunk_overlap]")
sys.exit(1)
file = sys.argv[1]
size = int(sys.argv[2]) if len(sys.argv) > 2 else 1000
overlap = int(sys.argv[3]) if len(sys.argv) > 3 else 200
for chunk, meta in split_document(file, size, overlap):
print(f"--- Chunk {meta['chunk_index']} ({len(chunk)} chars) ---")
print(chunk[:200] + "...\n")
@@ -1,136 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
cli_client.py
Интерактивный клиент для работы с агентом RAG.
Поддерживает команды:
/add <title> <content> – добавить документ в базу знаний
/search <query> – выполнить семантический поиск
/quit – выйти из программы
"""
import sys
from typing import List
# Rich для красивого вывода
try:
from rich.console import Console
from rich.table import Table
except ImportError: # pragma: no cover
print("Необходимо установить пакет 'rich'")
sys.exit(1)
# Инструменты агента (предполагается, что они реализованы в другом модуле)
try:
from rag_tools import search_knowledge_base, add_to_knowledge_base
except Exception as exc: # pragma: no cover
print(f"Ошибка при импорте инструментов: {exc}")
sys.exit(1)
console = Console()
def _parse_command(line: str) -> List[str]:
"""
Разбирает строку ввода пользователя.
Возвращает список токенов, разделённых пробелами,
но сохраняет аргументы в кавычках как один элемент.
"""
import shlex
try:
return shlex.split(line)
except ValueError as exc: # pragma: no cover
console.print(f"[red]Ошибка разбора команды:[/red] {exc}")
return []
def _handle_add(tokens: List[str]) -> None:
"""
Обработчик команды /add.
Ожидает два аргумента: title и content (в кавычках).
"""
if len(tokens) < 3:
console.print("[red]Неверное количество аргументов. Используйте:/add \"title\" \"content\"[/red]")
return
title = tokens[1]
content = " ".join(tokens[2:])
try:
add_to_knowledge_base(content=content, title=title)
console.print(f"[green]Документ '{title}' успешно добавлен.[/green]")
except Exception as exc: # pragma: no cover
console.print(f"[red]Ошибка при добавлении:[/red] {exc}")
def _handle_search(tokens: List[str]) -> None:
"""
Обработчик команды /search.
Ожидает один аргумент – запрос (в кавычках).
"""
if len(tokens) < 2:
console.print("[red]Неверное количество аргументов. Используйте:/search \"query\"[/red]")
return
query = " ".join(tokens[1:])
try:
results = search_knowledge_base(query=query, max_results=5)
if not results:
console.print("[yellow]Результаты не найдены.[/yellow]")
return
table = Table(title="Поиск", show_lines=True)
table.add_column("Номер", style="cyan")
table.add_column("Заголовок", style="magenta")
table.add_column("Содержание")
for idx, res in enumerate(results, start=1):
# Предполагается, что результат – словарь с ключами title и content
title = res.get("title", "Без заголовка")
content = res.get("content", "")
snippet = (content[:200] + "...") if len(content) > 200 else content
table.add_row(str(idx), title, snippet)
console.print(table)
except Exception as exc: # pragma: no cover
console.print(f"[red]Ошибка при поиске:[/red] {exc}")
def main() -> None:
"""
Основной цикл CLI.
"""
console.print("[bold green]Добро пожаловать в RAG-агент![/bold green]")
console.print("Доступные команды:")
console.print("/add \"title\" \"content\" – добавить документ")
console.print("/search \"query\" – выполнить поиск")
console.print("/quit – выйти из программы")
while True:
try:
line = console.input("\n[bold blue]Введите команду[/bold blue]> ")
except (KeyboardInterrupt, EOFError): # pragma: no cover
console.print("\n[bold red]Выход...[/bold red]")
break
if not line.strip():
continue
tokens = _parse_command(line)
if not tokens:
continue
command = tokens[0].lower()
if command == "/add":
_handle_add(tokens)
elif command == "/search":
_handle_search(tokens)
elif command in {"/quit", "exit"}:
console.print("[bold red]Выход...[/bold red]")
break
else:
console.print(f"[red]Неизвестная команда: {command}[/red]")
if __name__ == "__main__":
main()
@@ -1,109 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
init_loader.py
Скрипт для загрузки файлов из указанной директории в локальное Qdrant‑хранилище.
Использует:
* langchain.document_loaders.FileSystemLoader чтение всех файлов
* RecursiveCharacterTextSplitter разбиение на чанки
* OllamaEmbeddings (nomic-embed-text) – генерация эмбеддингов
* QdrantVectorStore – сохранение векторных представлений
Параметры:
--data-dir Путь к директории с исходными документами
--collection Название коллекции в Qdrant (по умолчанию: rag_collection)
"""
import argparse
import os
from pathlib import Path
from typing import List, Dict
# LangChain imports
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings.ollama import OllamaEmbeddings
from langchain.vectorstores.qdrant import QdrantVectorStore
from langchain.schema import Document
def load_documents(data_dir: Path) -> List[Document]:
"""
Загружает все файлы из указанной директории в список объектов Document.
Поддерживаются форматы *.txt, *.md, *.pdf (если установлен pdfminer).
"""
loader = DirectoryLoader(
str(data_dir),
glob="**/*",
suffixes=[".txt", ".md", ".pdf"],
show_progress=True,
)
return loader.load()
def chunk_documents(docs: List[Document], chunk_size: int = 1000, overlap: int = 200) -> List[Document]:
"""
Разбивает каждый документ на чанки фиксированного размера.
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=overlap,
separators=["\n\n", "\n", " ", ""],
)
return splitter.split_documents(docs)
def embed_and_store(chunks: List[Document], collection_name: str) -> None:
"""
Генерирует эмбеддинги для чанков и сохраняет их в Qdrant.
"""
# Инициализируем Ollama embeddings
embedding = OllamaEmbeddings(model="nomic-embed-text")
# Создаём (или подключаемся к) коллекцию Qdrant
vector_store = QdrantVectorStore(
collection_name=collection_name,
embedding=embedding,
url="http://localhost:6333",
)
# Добавляем документы в хранилище
vector_store.add_documents(chunks)
def main() -> None:
parser = argparse.ArgumentParser(description="Загрузка документов в Qdrant.")
parser.add_argument(
"--data-dir",
type=str,
required=True,
help="Путь к директории с документами.",
)
parser.add_argument(
"--collection",
type=str,
default="rag_collection",
help="Имя коллекции в Qdrant.",
)
args = parser.parse_args()
data_dir = Path(args.data_dir).expanduser().resolve()
if not data_dir.is_dir():
raise FileNotFoundError(f"Каталог {data_dir} не найден.")
print(f"[INFO] Загружаем документы из: {data_dir}")
docs = load_documents(data_dir)
print(f"[INFO] Найдено {len(docs)} документов. Разбиваем на чанки...")
chunks = chunk_documents(docs)
print(f"[INFO] Получено {len(chunks)} чанков. Генерируем эмбеддинги и сохраняем в Qdrant...")
embed_and_store(chunks, args.collection)
print("[SUCCESS] Загрузка завершена.")
if __name__ == "__main__":
main()
@@ -1,175 +0,0 @@
# qdrant_client.py
"""
Модуль инициализации клиента Qdrant и простых операций с ним.
Используется в агенте для RAG‑памяти.
Поддержка:
- Создание/получение коллекции
- Добавление документов (с чанками) с эмбеддингами Ollama
- Семантический поиск по запросу
Требуемые зависимости:
pip install langchain langchain-qdrant langchain-ollama qdrant-client
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import Iterable, List, Tuple
from langchain_ollama import OllamaEmbeddings
from langchain_qdrant import QdrantVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
from qdrant_client import QdrantClient as _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")
EMBEDDING_MODEL: str = os.getenv("OLLAMA_EMBEDDING_MODEL", "nomic-embed-text")
TEXT_SPLITTER_PARAMS = {
"chunk_size": 1000,
"chunk_overlap": 200,
}
# --------------------------------------------------------------------------- #
# Класс клиента Qdrant
# --------------------------------------------------------------------------- #
class QdrantClient:
"""
Обёртка над qdrant_client и langchain_qdrant для упрощения работы с коллекцией.
"""
def __init__(self, host: str = QDRANT_HOST, port: int = QDRANT_PORT,
collection_name: str = COLLECTION_NAME) -> None:
self.host = host
self.port = port
self.collection_name = collection_name
# Инициализируем низкоуровневый клиент
self.client = _QdrantClient(host=self.host, port=self.port)
# Создаём коллекцию при необходимости
if not self._collection_exists():
self._create_collection()
# Обёртка LangChain для упрощённого добавления/поиска
self.vector_store = QdrantVectorStore(
client=self.client,
collection_name=self.collection_name,
embedding=OllamaEmbeddings(model=EMBEDDING_MODEL),
)
# ----------------------------------------------------------------------- #
# Внутренние вспомогательные методы
# ----------------------------------------------------------------------- #
def _collection_exists(self) -> bool:
"""Проверяем наличие коллекции."""
try:
self.client.get_collection(name=self.collection_name)
return True
except Exception:
return False
def _create_collection(self) -> None:
"""Создаём коллекцию с параметрами по умолчанию."""
vector_params = VectorParams(size=384, distance=Distance.COSINE)
self.client.create_collection(
collection_name=self.collection_name,
vectors_config=vector_params
)
# ----------------------------------------------------------------------- #
# Публичные методы
# ----------------------------------------------------------------------- #
def add_documents(self, documents: Iterable[str], titles: Iterable[str]) -> None:
"""
Добавляет документы в коллекцию после разбиения на чанки.
:param documents: Итерируемый список текстов.
:param titles: Заголовки документов (один к одному с documents).
"""
splitter = RecursiveCharacterTextSplitter(**TEXT_SPLITTER_PARAMS)
all_chunks: List[Tuple[str, str]] = []
for doc_text, title in zip(documents, titles):
chunks = splitter.split_text(doc_text)
# Для каждого чанка сохраняем заголовок как метаданные
all_chunks.extend([(chunk, title) for chunk in chunks])
texts, metas = zip(*all_chunks)
self.vector_store.add_texts(
texts=texts,
metadatas=[{"title": meta} for meta in metas]
)
def search(self, query: str, limit: int = 5) -> List[dict]:
"""
Семантический поиск по запросу.
:param query: Текст запроса.
:param limit: Количество результатов.
:return: Список словарей с полями 'text', 'score' и метаданными.
"""
results = self.vector_store.similarity_search_with_score(query, k=limit)
return [
{
"text": r[0].page_content,
"score": r[1],
"metadata": r[0].metadata
}
for r in results
]
# ----------------------------------------------------------------------- #
# Утилиты для загрузки файлов из директории
# ----------------------------------------------------------------------- #
def load_from_directory(self, directory: str | Path) -> None:
"""
Загружает все текстовые файлы из указанной папки в коллекцию.
:param directory: Путь к каталогу.
"""
dir_path = Path(directory)
if not dir_path.is_dir():
raise ValueError(f"Папка {directory} не существует")
docs = []
titles = []
for file in dir_path.glob("**/*.txt"):
text = file.read_text(encoding="utf-8")
docs.append(text)
titles.append(file.stem)
self.add_documents(docs, titles)
# --------------------------------------------------------------------------- #
# Пример использования (можно удалить при импорте в другие модули)
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
client = QdrantClient()
# Загрузка из папки ./data
client.load_from_directory("./data")
# Поиск по запросу
query_text = "Какой смысл жизни?"
results = client.search(query_text, limit=3)
for i, res in enumerate(results, 1):
print(f"\nРезультат {i}:")
print(f"Score: {res['score']:.4f}")
print(f"Title: {res['metadata'].get('title')}")
print(f"Text snippet: {res['text'][:200]}...")
@@ -1,203 +0,0 @@
# rag_agent.py
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_react_agent, AgentExecutor
from langchain.prompts import PromptTemplate
from langchain.schema import Document
from qdrant_client import QdrantClient as _QdrantClient
from qdrant_client.http.models import Distance, VectorParams
# ---------------------------------------------------------------------------
# Конфигурация
# ---------------------------------------------------------------------------
QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost")
QDRANT_PORT = int(os.getenv("QDRANT_PORT", "6333"))
COLLECTION_NAME = os.getenv("QDRANT_COLLECTION", "rag_collection")
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "nomic-embed-text")
LLM_MODEL = os.getenv("LLM_MODEL", "llama3")
# ---------------------------------------------------------------------------
# Векторное хранилище (Qdrant + Ollama-эмбеддинги)
# ---------------------------------------------------------------------------
class RagVectorStore:
"""Обёртка над QdrantVectorStore с эмбеддингами через Ollama."""
def __init__(self, collection_name: str = COLLECTION_NAME):
self.embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL)
# Низкоуровневый клиент для управления коллекцией
self._client = _QdrantClient(host=QDRANT_HOST, port=QDRANT_PORT)
# Создаём коллекцию, если её ещё нет
if not self._collection_exists(collection_name):
self._client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)
self.store = QdrantVectorStore(
client=self._client,
collection_name=collection_name,
embedding=self.embeddings,
)
def _collection_exists(self, name: str) -> bool:
try:
self._client.get_collection(name)
return True
except Exception:
return False
def add_documents(self, docs: List[Document]) -> None:
self.store.add_documents(docs)
def search(self, query: str, k: int = 5) -> List[tuple]:
results = self.store.similarity_search_with_score(query, k=k)
return [(doc.page_content, score) for doc, score in results]
# ---------------------------------------------------------------------------
# Чанкинг
# ---------------------------------------------------------------------------
def chunk_document(text: str, title: str) -> List[Document]:
"""Разбивает текст на чанки с метаданными."""
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""],
)
return [
Document(page_content=chunk, metadata={"title": title})
for chunk in splitter.split_text(text)
]
# ---------------------------------------------------------------------------
# Инициализация хранилища (глобальный синглтон для инструментов)
# ---------------------------------------------------------------------------
rag_store = RagVectorStore()
# ---------------------------------------------------------------------------
# LangChain-инструменты агента (@tool)
# ---------------------------------------------------------------------------
@tool
def search_knowledge_base(query: str, max_results: int = 5) -> List[str]:
"""
Семантический поиск в базе знаний.
Args:
query: поисковый запрос.
max_results: максимальное количество результатов.
Returns:
Список найденных фрагментов с оценкой релевантности.
"""
results = rag_store.search(query, k=max_results)
return [f"[score={score:.4f}] {text}" for text, score in results]
@tool
def add_to_knowledge_base(content: str, title: str) -> str:
"""
Добавляет новый документ в базу знаний.
Args:
content: полный текст документа.
title: заголовок документа.
Returns:
Сообщение об успехе.
"""
docs = chunk_document(content, title)
rag_store.add_documents(docs)
return f"Документ '{title}' успешно добавлен в базу знаний."
# ---------------------------------------------------------------------------
# Агент на базе LangChain (create_react_agent)
# ---------------------------------------------------------------------------
llm = OllamaLLM(model=LLM_MODEL)
tools = [search_knowledge_base, add_to_knowledge_base]
prompt = PromptTemplate.from_template(
"Вы — интеллектуальный агент с доступом к локальной базе знаний.\n"
"При необходимости используйте инструменты search_knowledge_base и add_to_knowledge_base.\n\n"
"Доступные инструменты:\n{tools}\n\n"
"Формат ответа (строго соблюдайте):\n"
"Question: вопрос пользователя\n"
"Thought: что нужно сделать\n"
"Action: инструмент из [{tool_names}]\n"
"Action Input: входные данные для инструмента\n"
"Observation: результат инструмента\n"
"... (можно повторять Thought/Action/Observation)\n"
"Thought: теперь я знаю ответ\n"
"Final Answer: итоговый ответ пользователю\n\n"
"Begin!\n\n"
"Question: {input}\n"
"Thought: {agent_scratchpad}"
)
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# ---------------------------------------------------------------------------
# Загрузка документов из директории (для init_loader.py)
# ---------------------------------------------------------------------------
def load_documents_from_directory(directory: str) -> None:
"""Загружает все .txt и .md файлы из папки в базу знаний."""
path = Path(directory)
for file_path in path.rglob("*"):
if file_path.suffix.lower() in {".txt", ".md"}:
text = file_path.read_text(encoding="utf-8")
add_to_knowledge_base.invoke({"content": text, "title": file_path.stem})
print(f"Загружен: {file_path.name}")
# ---------------------------------------------------------------------------
# Запуск напрямую — интерактивный CLI
# ---------------------------------------------------------------------------
if __name__ == "__main__":
print("=== RAG Агент ===")
print("Команды: /add <path>, /search <query>, /quit")
while True:
try:
user_input = input("> ").strip()
except (EOFError, KeyboardInterrupt):
break
if not user_input:
continue
if user_input.lower() == "/quit":
print("Выход.")
break
elif user_input.startswith("/add "):
file_path = Path(user_input[5:].strip())
if file_path.is_file():
text = file_path.read_text(encoding="utf-8")
print(add_to_knowledge_base.invoke({"content": text, "title": file_path.stem}))
else:
print(f"Файл не найден: {file_path}")
elif user_input.startswith("/search "):
query = user_input[8:].strip()
for r in search_knowledge_base.invoke({"query": query, "max_results": 3}):
print(r)
else:
response = agent_executor.invoke({"input": user_input})
print(response["output"])
@@ -1,41 +0,0 @@
# rag_tools.py
"""
Инструменты для LangChain-агента. Используют QdrantClient из qdrant_client.py.
"""
from langchain.tools import tool
from qdrant_client import QdrantClient
_store = QdrantClient()
@tool
def search_knowledge_base(query: str, max_results: int = 5) -> list:
"""
Семантический поиск в базе знаний.
Args:
query: поисковый запрос.
max_results: максимальное количество результатов.
Returns:
Список найденных фрагментов с оценкой релевантности.
"""
results = _store.search(query, limit=max_results)
return [
{"title": r["metadata"].get("title", ""), "content": r["text"], "score": r["score"]}
for r in results
]
@tool
def add_to_knowledge_base(content: str, title: str) -> str:
"""
Добавляет новый документ в базу знаний.
Args:
content: полный текст документа.
title: заголовок документа.
Returns:
Сообщение об успехе.
"""
_store.add_documents([content], [title])
return f"Документ '{title}' успешно добавлен в базу знаний."
@@ -1,6 +0,0 @@
langchain
langchain-ollama
langchain-qdrant
qdrant-client
rich
langchain-text-splitters