diff --git a/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/README.md b/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/README.md deleted file mode 100644 index 5dec197..0000000 --- a/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# Stream‑режим AI‑агента - -**Stream‑режим AI‑агента** – проект, демонстрирующий работу LangGraph‑агента в режиме потоковой генерации ответов. -В отличие от обычного вызова `invoke()`, который возвращает результат только после завершения работы, здесь используется `stream()` и вывод токен за токеном прямо в консоль. - ---- - -## 📦 Установка зависимостей - -```bash -# 1️⃣ Клонируйте репозиторий -git clone https://github.com/your-username/stream-agent.git -cd stream-agent - -# 2️⃣ Создайте виртуальное окружение (рекомендуется) -python -m venv .venv -source .venv/bin/activate # Windows: .\.venv\Scripts\activate - -# 3️⃣ Установите зависимости из requirements.txt -pip install -r requirements.txt -``` - -`requirements.txt` содержит: - -```text -langchain-core>=0.2.0 -langgraph>=0.1.0 -langchain-community>=0.2.0 -tavily-python>=0.1.0 -openai>=1.0.0 -python-dotenv>=1.0.0 # для загрузки переменных окружения -``` - -> **Важно**: -> - Установите ключ OpenAI в переменную окружения `OPENAI_API_KEY`. -> - Если используете Tavily, добавьте `TAVILY_API_KEY` (необязательно, если вы не планируете использовать поиск). - -```bash -export OPENAI_API_KEY="sk-..." -# export TAVILY_API_KEY="..." # при необходимости -``` - ---- - -## 🚀 Запуск - -### 1️⃣ Основной скрипт `stream_agent.py` - -```bash -python stream_agent.py -``` - -Скрипт автоматически: - -1. Создаёт LLM‑обёртку для GPT‑4o-mini. -2. Инициализирует инструмент поиска Tavily. -3. Формирует LangGraph‑агента и запускает его в режиме `stream()`. -4. Выводит токены ответа по мере их генерации, а также события об использовании инструментов. - -### 2️⃣ Тестовый скрипт (если есть) - -Если вы добавили файл `test_stream_agent.py`, запустите: - -```bash -pytest test_stream_agent.py -``` - ---- - -## 📄 Пример использования - -```python -# stream_agent.py -import os -from langchain_community.tools.tavily_search import TavilySearchResults -from langgraph.prebuilt import create_agent_executor -from openai import OpenAI - -client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) -llm = client.chat.completions.create - -search_tool = TavilySearchResults(max_results=3) - -agent = create_agent_executor( - llm=llm, - tools=[search_tool], - agent_type="openai-functions", -) - -# Запускаем в режиме стрима -stream = agent.stream( - {"messages": [{"role": "human", "content": "Расскажи о последних новостях по искусственному интеллекту"}]}, - stream_mode=["messages", "updates"], -) - -for chunk_type, data in stream: - if chunk_type == "messages": - print(data["content"], end="", flush=True) - elif chunk_type == "updates": - # Можно логировать события использования инструментов - pass - -print("\n--- Конец ответа ---") -``` - -При запуске вы увидите вывод токен за токеном, а также сообщения о вызове инструмента поиска. - ---- - -## 📚 Что дальше? - -- **Добавить обработку ошибок**: если LLM не отвечает или инструмент падает. -- **Интегрировать с UI**: например, в Streamlit для веб‑интерфейса. -- **Расширить набор инструментов**: добавить поиск в Wikipedia, генерацию изображений и т.д. - ---- - -## 📄 Лицензия - -MIT © 2026. \ No newline at end of file diff --git a/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/requirements.txt b/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/requirements.txt deleted file mode 100644 index dace80d..0000000 --- a/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -langchain-community -langchain-core -langgraph -openai -tavily \ No newline at end of file diff --git a/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/stream_agent.py b/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/stream_agent.py deleted file mode 100644 index f93b6b2..0000000 --- a/solutions/699cc158d6d3a5544a3ed35b_Stream_режим_AI_агента/stream_agent.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -from langchain_openai import ChatOpenAI -from langchain_community.tools.tavily_search import TavilySearchResults -from langchain_core.messages import HumanMessage -from langgraph.prebuilt import create_react_agent - -llm = ChatOpenAI(model="gpt-4o-mini") -search_tool = TavilySearchResults(max_results=3) -agent = create_react_agent(llm, tools=[search_tool]) - -def format_message(message) -> str: - if message.content: - return message.content - return f"{message.tool_calls[0]['name']}({message.tool_calls[0]['args']})" - -current_step = 1 - -def format_chunk_message(chunk): - global current_step - message, meta = chunk - step_num = meta.get("langgraph_step", 0) - if step_num != current_step: - current_step = step_num - print("\n --- --- --- \n") - if message.content: - print(message.content, end="", flush=False) - -if __name__ == "__main__": - user_query = input("Введите ваш вопрос: ") - messages = [HumanMessage(content=user_query)] - - stream = agent.stream( - {"messages": messages}, - stream_mode=["messages", "updates"] - ) - - for chunk in stream: - chunk_type, chunk_data = chunk - if chunk_type == "messages": - format_chunk_message(chunk_data) - elif chunk_type == "updates": - if chunk_data.get("model", None): - last_msg = chunk_data["model"]["messages"][-1] - print(format_message(last_msg)) - - print() \ No newline at end of file diff --git a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/README.md b/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/README.md deleted file mode 100644 index fd28333..0000000 --- a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# MCP‑Memory Server - -A lightweight **Model Context Protocol (MCP)** server that exposes a simple key/value memory store for agents and other clients. -The project contains two scripts: - -| File | Purpose | -|------|---------| -| `memory_server.py` | Runs the MCP server, exposing *set*, *get* and *delete* operations on a JSON‑backed storage. | -| `memory_client.py` | Demonstrates how to connect to the server and use its tools from a client script. | - -> **Why MCP?** -> MCP is a lightweight protocol for exchanging structured data between agents, services or CLI utilities. By running this server as a separate process we enable distributed multi‑agent systems to share state without tight coupling. - ---- - -## 📦 Installation - -```bash -# Create and activate a virtual environment (optional but recommended) -python -m venv .venv -source .venv/bin/activate # Windows: .\.venv\Scripts\activate - -# Install the required packages -pip install fastmcp pydantic python-dotenv -``` - -> **Tip:** -> `fastmcp` is a minimal framework for building MCP servers and clients. -> `pydantic` is used internally by `fastmcp` for data validation. - ---- - -## 🚀 Running the Server - -```bash -python memory_server.py -``` - -The server starts on the default port **8000** (you can change it in the script). -It will create a file called `memory_data.json` in the current directory to persist data between restarts. - -### Available Tools - -| Tool | Parameters | Description | -|------|------------|-------------| -| `set` | `key: str`, `value: Any` | Stores a value under the given key. | -| `get` | `key: str` | Retrieves the value for the key (or `null`). | -| `delete` | `key: str` | Removes the key from storage. | - ---- - -## 🧪 Running the Client - -```bash -python memory_client.py -``` - -The client script demonstrates: - -1. Setting a value (`foo = "bar"`). -2. Getting that value back. -3. Deleting the key and verifying it’s gone. - -You can also use the client interactively by editing `memory_client.py` or by sending raw MCP messages via another tool (e.g., `curl`, Postman, or a custom agent). - ---- - -## 📄 Example Usage - -```python -# memory_client.py snippet - -from fastmcp import FastMCPClient - -client = FastMCPClient("Memory-Server", host="localhost", port=8000) - -# Set a key/value pair -client.call_tool("set", {"key": "greeting", "value": "Hello, world!"}) - -# Retrieve the value -response = client.call_tool("get", {"key": "greeting"}) -print(response) # Output: Hello, world! - -# Delete the key -client.call_tool("delete", {"key": "greeting"}) - -# Verify deletion -assert client.call_tool("get", {"key": "greeting"}) is None -``` - -Feel free to integrate this server into your own agent framework or use it as a standalone memory service. - ---- - -## 📁 Project Structure - -``` -. -├── memory_server.py # MCP server implementation -├── memory_client.py # Example client script -└── memory_data.json # (generated) persistent storage -``` - ---- - -## 🔧 Customization - -- **Port** – change the `port` argument in `FastMCP("Memory-Server", port=8000)` inside `memory_server.py`. -- **Storage Path** – modify `self.storage_path = Path("./memory_data.json")` to point elsewhere. -- **Additional Tools** – add new methods decorated with `@self.mcp.tool(...)` following the pattern in the script. - ---- - -## 📜 License - -This project is released under the MIT license. Feel free to fork, extend or use it in your own projects. \ No newline at end of file diff --git a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/memory_client.py b/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/memory_client.py deleted file mode 100644 index 1bc4d7f..0000000 --- a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/memory_client.py +++ /dev/null @@ -1,58 +0,0 @@ -# memory_client.py -""" -Клиент для тестирования MCP‑сервера памяти. -Подключается к серверу через stdio и демонстрирует работу инструментов: -- save_with_namespace -- get_by_namespace -- list_keys -""" - -import asyncio -from fastmcp import Client - - -async def main() -> None: - """ - Основная асинхронная функция, которая: - 1. Создаёт клиент для сервера memory_server.py. - 2. Подключается к серверу. - 3. Выполняет серию вызовов инструментов и выводит результаты. - 4. Закрывает соединение. - """ - # Инициализируем клиента, указывая путь к исполняемому скрипту сервера - client = Client("python memory_server.py") - - # Подключаемся к серверу (стандартный ввод/вывод) - await client.connect() - - try: - # Сохраняем данные в namespace "default" - result = await client.call_tool( - "save_with_namespace", - {"key": "username", "value": "Алексей", "namespace": "default"}, - ) - print(f"Сохранено: {result}") - - # Читаем все ключи из namespace "default" - result = await client.call_tool( - "get_by_namespace", - {"namespace": "default"}, - ) - print("Данные namespace 'default':") - for item in result: - print(f" {item['key']}: {item['value']}") - - # Ищем ключи, содержащие подстроку 'name' - keys = await client.call_tool( - "list_keys", - {"pattern": "*name"}, - ) - print("Ключи с 'name':", keys) - - finally: - # Завершаем соединение - await client.close() - - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file diff --git a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/memory_server.py b/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/memory_server.py deleted file mode 100644 index df9c785..0000000 --- a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/memory_server.py +++ /dev/null @@ -1,115 +0,0 @@ -import json -from datetime import datetime -from pathlib import Path -from typing import Any, Optional - -from fastmcp import FastMCP - - -class MemoryServer: - def __init__(self): - self.mcp = FastMCP("Memory-Server") - self.storage_path = Path("./memory_data.json") - - # ------------------------------------------------------------------ - # Вспомогательные методы для работы с файлом - # ------------------------------------------------------------------ - def _load_memory(self) -> dict[str, Any]: - """Загружает память из JSON-файла.""" - if not self.storage_path.exists(): - return {} - with open(self.storage_path, "r", encoding="utf-8") as f: - try: - data = json.load(f) - if isinstance(data, dict): - return data - except json.JSONDecodeError: - pass - return {} - - def _save_memory(self, data: dict[str, Any]) -> None: - """Сохраняет память в JSON-файл.""" - self.storage_path.parent.mkdir(parents=True, exist_ok=True) - with open(self.storage_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, ensure_ascii=False) - - # ------------------------------------------------------------------ - # Инструменты без namespace - # ------------------------------------------------------------------ - @self.mcp.tool() - def save(key: str, value: Any) -> bool: - """Сохраняет значение по ключу в память сервера.""" - if not isinstance(key, str) or not key.strip(): - return False - data = self._load_memory() - data[key] = {"value": value, "timestamp": datetime.utcnow().isoformat()} - self._save_memory(data) - return True - - @self.mcp.tool() - def get(key: str) -> Optional[dict]: - """Возвращает значение по ключу с метаданными.""" - if not isinstance(key, str): - return None - data = self._load_memory() - entry = data.get(key) - if entry is None: - return None - return {"key": key, "value": entry["value"], "timestamp": entry["timestamp"]} - - @self.mcp.tool() - def delete(key: str) -> bool: - """Удаляет ключ из памяти сервера.""" - if not isinstance(key, str): - return False - data = self._load_memory() - if key in data: - del data[key] - self._save_memory(data) - return True - return False - - @self.mcp.tool() - def list_keys(pattern: str = "*") -> list[str]: - """Возвращает список всех ключей с поддержкой wildcard-паттерна.""" - if not isinstance(pattern, str): - pattern = "*" - data = self._load_memory() - import fnmatch - - return [k for k in data.keys() if fnmatch.fnmatch(k, pattern)] - - # ------------------------------------------------------------------ - # Инструменты с namespace - # ------------------------------------------------------------------ - @self.mcp.tool() - def save_with_namespace(key: str, value: Any, namespace: str = "default") -> bool: - """Сохраняет значение с указанием пространства имён.""" - if not isinstance(namespace, str) or not namespace.strip(): - return False - full_key = f"{namespace}:{key}" - return self.save(full_key, value) - - @self.mcp.tool() - def get_by_namespace(namespace: str = "default") -> list[dict]: - """Возвращает все ключи из указанного namespace.""" - if not isinstance(namespace, str) or not namespace.strip(): - return [] - data = self._load_memory() - prefix = f"{namespace}:" - result = [] - for k, v in data.items(): - if k.startswith(prefix): - key_part = k[len(prefix) :] - result.append( - {"key": key_part, "value": v["value"], "timestamp": v["timestamp"]} - ) - return result - - -# ---------------------------------------------------------------------- -# Запуск сервера -# ---------------------------------------------------------------------- -if __name__ == "__main__": - server = MemoryServer() - server.mcp.run(transport="stdio", show_banner=False, log_level="ERROR") \ No newline at end of file diff --git a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/requirements.txt b/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/requirements.txt deleted file mode 100644 index 35a7d0c..0000000 --- a/solutions/69f8e929da860fb4533faa2a_MCP-сервер_для_управления_памятью_агента/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -fastmcp \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/README.md b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/README.md deleted file mode 100644 index 82bd021..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/README.md +++ /dev/null @@ -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 - ---- \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/chunker.py b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/chunker.py deleted file mode 100644 index 71cc21c..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/chunker.py +++ /dev/null @@ -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 [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") \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/cli_client.py b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/cli_client.py deleted file mode 100644 index bc5e72f..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/cli_client.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -cli_client.py - -Интерактивный клиент для работы с агентом RAG. -Поддерживает команды: - /add <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() \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/init_loader.py b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/init_loader.py deleted file mode 100644 index 388ff25..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/init_loader.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/qdrant_client.py b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/qdrant_client.py deleted file mode 100644 index 6c93f17..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/qdrant_client.py +++ /dev/null @@ -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]}...") \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/rag_agent.py b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/rag_agent.py deleted file mode 100644 index 4b4e841..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/rag_agent.py +++ /dev/null @@ -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"]) \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/rag_tools.py b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/rag_tools.py deleted file mode 100644 index 468a1ab..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/rag_tools.py +++ /dev/null @@ -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}' успешно добавлен в базу знаний." \ No newline at end of file diff --git a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/requirements.txt b/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/requirements.txt deleted file mode 100644 index ed9f7c2..0000000 --- a/solutions/6a02e23da6fe2e4ac16acf65_Агент_с_RAG_памятью/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -langchain -langchain-ollama -langchain-qdrant -qdrant-client -rich -langchain-text-splitters \ No newline at end of file diff --git a/solutions/сырой текст задания → плоская карточка/solution.py b/solutions/сырой текст задания → плоская карточка/solution.py deleted file mode 100644 index 50b1bfd..0000000 --- a/solutions/сырой текст задания → плоская карточка/solution.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import List -from pydantic import BaseModel, Field -from langchain_core.prompts import PromptTemplate -from langchain_core.output_parsers import PydanticOutputParser -from langchain_openai import ChatOpenAI - - -class AssignmentCard(BaseModel): - title: str = Field(..., description="Название задания одной фразой") - subject: str = Field(..., description="Учебная дисциплина или тема") - deadline_hint: str | None = Field( - None, - description="Срок выполнения в произвольной форме, если упомянут", - ) - deliverable_type: str = Field( - ..., - description="Что нужно сдать: отчёт, код, презентация и т.п.", - ) - grading_hints: List[str] | None = Field( - default_factory=list, - description="Критерии оценки, которые явно названы в тексте", - ) - - -def main() -> None: - output_parser = PydanticOutputParser(pydantic_object=AssignmentCard) - - template = PromptTemplate( - template=( - "Прочитай описание учебного задания и заполни все поля карточки.\n\n" - "Описание:\n{raw_text}\n\n" - "Верни ответ строго в JSON без лишних пояснений.\n" - "{format_instructions}" - ), - input_variables=["raw_text"], - partial_variables={ - "format_instructions": output_parser.get_format_instructions() - }, - ) - - model = ChatOpenAI(model="gpt-4o-mini", temperature=0) - - chain = template | model | output_parser - - sample = ( - "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " - "Оценка: за полноту и за пример кода." - ) - - card: AssignmentCard = chain.invoke({"raw_text": sample}) - - print("Валидированный объект:") - print(card.model_dump()) - - print("\nСводка:") - print( - f"Задание «{card.title}» по теме «{card.subject}». " - f"Сдать: {card.deliverable_type}. " - f"Дедлайн: {card.deadline_hint or 'не указан'}. " - f"Оценивается: {', '.join(card.grading_hints) if card.grading_hints else 'критерии не указаны'}." - ) - - -if __name__ == "__main__": - main() \ No newline at end of file