Image

Как построить RAG-систему за вечер с помощью 5 open source-инструментов

Содержание

Команда Python for Devs подготовила практическое руководство по сборке полноценной RAG-системы из пяти open source-инструментов. MarkItDown, LangChain, ChromaDB, Ollama и Gradio превращают разрозненные документы в умную базу знаний с потоковой генерацией ответов. Всё локально, без облаков и с открытым кодом — попробуйте собрать свой ChatGPT прямо у себя.

Бывало, вы тратили по полчаса, просматривая ветки Slack, вложения к письмам и общие диски, лишь чтобы найти ту самую техническую спецификацию, о которой коллега упоминал на прошлой неделе?

Это типичный сценарий, который ежедневно повторяется в компаниях по всему миру. Специалисты тратят ценное время на поиск информации, которая должна быть доступна мгновенно, и в итоге теряют продуктивность.

Системы Retrieval-Augmented Generation (RAG) решают эту проблему, превращая ваши документы в интеллектуальную, доступную по запросам базу знаний. Задавайте вопросы на естественном языке и получайте мгновенные ответы со ссылками на источники, избавляясь от долгих ручных поисков.

В этой статье мы построим полноценный RAG-пайплайн, который превратит коллекции документов в систему ответов на вопросы на базе ИИ.

Ключевые выводы

Вот чему вы научитесь:

  • Конвертировать документы с MarkItDown за 3 строки

  • Умно разбивать текст с помощью LangChain RecursiveCharacterTextSplitter

  • Генерировать эмбеддинги локально с моделью SentenceTransformers

  • Хранить векторы в персистентной базе ChromaDB

  • Генерировать ответы с локальными LLM в Ollama

  • Развернуть веб-интерфейс с потоковой передачей (streaming) в Gradio

Получить код: полный исходник и Jupyter-ноутбук для этого руководства доступны на GitHub. Клонируйте репозиторий, чтобы идти по шагам!

Введение в RAG-системы

RAG (Retrieval-Augmented Generation) объединяет поиск по документам и генерацию текста, создавая интеллектуальные системы вопросов-ответов. Вместо того чтобы полагаться только на обучающие данные, RAG-системы просматривают ваши документы, находят релевантную информацию и используют этот контекст для генерации точных ответов с указанием источников.

Настройка окружения

Установите необходимые библиотеки для сборки вашего RAG-пайплайна:

pip install markitdown[pdf] sentence-transformers langchain-text-splitters chromadb gradio langchain-ollama ollama

Эти библиотеки дают следующее:

  • markitdown: инструмент Microsoft для конвертации документов, который преобразует PDF, Word и другие форматы в чистый markdown

  • sentence-transformers: локальная генерация эмбеддингов для преобразования текста в поисковые векторы

  • langchain-text-splitters: интеллектуальное разбиение текста на фрагменты с сохранением смысла

  • chromadb: самохостируемая векторная база данных для хранения и запросов эмбеддингов документов

  • gradio: конструктор веб-интерфейсов для создания удобных Q&A-приложений

  • langchain-ollama: интеграция LangChain для локального инференса LLM

Установите Ollama и скачайте модель:

curl -fsSL https://ollama.com/install.sh | sh ollama pull llama3.2

Далее создайте структуру проекта для организации файлов:

mkdir processed_docs documents

Эти каталоги нужны для порядка:

  • processed_docs: хранит конвертированные markdown-файлы

  • documents: содержит исходные файлы (PDF, Word и т. п.)

Создайте эти каталоги в текущей рабочей директории с корректными правами на чтение/запись.

Подготовка датасета: техническая документация по Python

Для демонстрации RAG-конвейера мы используем книгу «Think Python» Аллена Дауни — полноценное руководство по программированию, свободно доступное по лицензии Creative Commons.

Скачаем это руководство по Python и сохраним его в каталоге documents.

import requests from pathlib import Path # Get the file path output_folder = «documents» filename = «think_python_guide.pdf» url = «https://greenteapress.com/thinkpython/thinkpython.pdf» file_path = Path(output_folder) / filename def download_file(url: str, file_path: Path): response = requests.get(url, stream=True, timeout=30) response.raise_for_status() file_path.write_bytes(response.content) # Download the file if it doesn’t exist if not file_path.exists(): download_file( url=url, file_path=file_path, )

Далее преобразуем этот PDF в формат, который наша RAG-система сможет обрабатывать и искать по нему.

Загрузка документов с помощью MarkItDown

RAG-системам нужны документы в структурированном формате, который модели ИИ могут корректно понимать и эффективно обрабатывать.

MarkItDown решает эту задачу, конвертируя любые форматы документов в чистый markdown с сохранением исходной структуры и смысла.

Преобразование вашего Python-руководства

Начните с конвертации руководства по Python, чтобы понять, как работает MarkItDown:

from markitdown import MarkItDown # Initialize the converter md = MarkItDown() # Convert the Python guide to markdown result = md.convert(file_path) python_guide_content = result.text_content # Display the conversion results print(«First 300 characters:») print(python_guide_content[:300] + «…»)

В этом коде:

  • MarkItDown() создаёт конвертер документов, который автоматически обрабатывает несколько форматов.

  • convert() обрабатывает PDF и возвращает объект результата с извлечённым текстом.

  • text_content предоставляет «чистый» markdown-текст, готовый к дальнейшей обработке.

Вывод:

First 300 characters: Think Python How to Think Like a Computer Scientist Version 2.0.17 Think Python How to Think Like a Computer Scientist Version 2.0.17 Allen Downey Green Tea Press Needham, Massachusetts Copyright © 2012 Allen Downey. Green Tea Press 9 Washburn Ave Needham MA 02492 Permission is granted…

MarkItDown автоматически распознаёт формат PDF и извлекает «чистый» текст, сохраняя структуру книги — главы, разделы и примеры кода.

Подготовка документа к обработке

Теперь, когда вы поняли базовую конвертацию, подготовим содержимое документа к обработке. Мы сохраним текст руководства с информацией об источнике, чтобы позже использовать её при разбиении на фрагменты и извлечении данных.

# Organize the converted document processed_document = { ‘source’: file_path, ‘content’: python_guide_content } # Create a list containing our single document for consistency with downstream processing documents = [processed_document] # Document is now ready for chunking and embedding print(f»Document ready: {len(processed_document[‘content’]):,} characters»)

Вывод:

Document ready: 460,251 characters

Когда документ успешно преобразован в markdown, следующий шаг — разбить его на небольшие, удобные для поиска части.

Интеллектуальное разбиение с LangChain

Модели ИИ не могут обрабатывать целые документы из-за ограниченных окон контекста. Разбиение (chunking) делит документы на более мелкие, поисковые фрагменты при сохранении смысловой связности.

Понимание разбиения текста на простом примере

Давайте посмотрим, как работает разбиение текста на примере простого документа.

from langchain_text_splitters import RecursiveCharacterTextSplitter # Create a simple example that will be split sample_text = «»» Machine learning transforms data processing. It enables pattern recognition without explicit programming. Deep learning uses neural networks with multiple layers. These networks discover complex patterns automatically. Natural language processing combines ML with linguistics. It helps computers understand human language effectively. «»» # Apply chunking with smaller size to demonstrate splitting demo_splitter = RecursiveCharacterTextSplitter( chunk_size=150, # Small size to force splitting chunk_overlap=30, separators=[«nn», «n», «. «, » «, «»], # Split hierarchy ) sample_chunks = demo_splitter.split_text(sample_text.strip()) print(f»Original: {len(sample_text.strip())} chars → {len(sample_chunks)} chunks») # Show chunks for i, chunk in enumerate(sample_chunks): print(f»Chunk {i+1}: {chunk}»)

Вывод:

Original: 336 chars → 3 chunks Chunk 1: Machine learning transforms data processing. It enables pattern recognition without explicit programming. Chunk 2: Deep learning uses neural networks with multiple layers. These networks discover complex patterns automatically. Chunk 3: Natural language processing combines ML with linguistics. It helps computers understand human language effectively.

Обратите внимание, как работает разделитель текста:

  • Разбил текст длиной 336 символов на 3 фрагмента, каждый меньше лимита в 150 символов.

  • Применил перекрытие в 30 символов между соседними фрагментами.

  • Сепараторы учитывают смысловые границы в порядке приоритета: абзацы (nn) → предложения (.) → слова () → отдельные символы.

Обработка множества документов на масштабе

Теперь давайте настроим разделитель на более крупные фрагменты и применим его ко всем преобразованным документам.

# Configure the text splitter with Q&A-optimized settings text_splitter = RecursiveCharacterTextSplitter( chunk_size=600, # Optimal chunk size for Q&A scenarios chunk_overlap=120, # 20% overlap to preserve context separators=[«nn», «n», «. «, » «, «»] # Split hierarchy )

Затем используйте разделитель текста для обработки всех наших документов.

def process_document(doc, text_splitter): «»»Process a single document into chunks.»»» doc_chunks = text_splitter.split_text(doc[«content»]) return [{«content»: chunk, «source»: doc[«source»]} for chunk in doc_chunks] # Process all documents and create chunks all_chunks = [] for doc in documents: doc_chunks = process_document(doc, text_splitter) all_chunks.extend(doc_chunks)

Проанализируйте, как процесс разбиения распределил содержимое по документам.

from collections import Counter source_counts = Counter(chunk[«source»] for chunk in all_chunks) chunk_lengths = [len(chunk[«content»]) for chunk in all_chunks] print(f»Total chunks created: {len(all_chunks)}») print(f»Chunk length: {min(chunk_lengths)}-{max(chunk_lengths)} characters») print(f»Source document: {Path(documents[0][‘source’]).name}»)

Вывод:

Total chunks created: 1007 Chunk length: 68-598 characters Source document: think_python_guide.pdf

Наши текстовые фрагменты готовы. Далее мы преобразуем их в вид, который позволит выполнять «умный» поиск по сходству.

Создание поисковых эмбеддингов с SentenceTransformers

RAG-системам важно «понимать» смысл текста, а не просто совпадения по ключевым словам. SentenceTransformers преобразует текст в числовые векторы, отражающие семантические отношения, так что система находит действительно релевантную информацию, даже если точных совпадений слов нет.

Генерация эмбеддингов

Давайте сгенерируем эмбеддинги для наших текстовых фрагментов.

from sentence_transformers import SentenceTransformer # Load Q&A-optimized embedding model (downloads automatically on first use) model = SentenceTransformer(‘multi-qa-mpnet-base-dot-v1’) # Extract documents and create embeddings documents = [chunk[«content»] for chunk in all_chunks] embeddings = model.encode(documents) print(f»Embedding generation results:») print(f» — Embeddings shape: {embeddings.shape}») print(f» — Vector dimensions: {embeddings.shape[1]}»)

В этом коде:

  • SentenceTransformer() загружает модель, оптимизированную под Q&A, которая преобразует текст в 768-мерные векторы.

  • multi-qa-mpnet-base-dot-v1 специально обучена на 215 млн пар «вопрос–ответ» для лучшей точности в Q&A-сценариях.

  • model.encode() преобразует все текстовые фрагменты в числовые эмбеддинги за один пакетный проход.

На выходе видно, что 1007 фрагментов преобразованы в 768-мерные векторы.

Embedding generation results: — Embeddings shape: (1007, 768) — Vector dimensions: 768

Тест семантического сходства

Давайте проверим семантическое сходство, задав запросы по концепциям программирования на Python:

# Test how one query finds relevant Python programming content from sentence_transformers import util query = «How do you define functions in Python?» document_chunks = [ «Variables store data values that can be used later in your program.», «A function is a block of code that performs a specific task when called.», «Loops allow you to repeat code multiple times efficiently.», «Functions can accept parameters and return values to the calling code.» ] # Encode query and documents query_embedding = model.encode(query) doc_embeddings = model.encode(document_chunks)

Теперь рассчитаем оценки сходства и отсортируем результаты. Функция util.cos_sim() вычисляет косинусное сходство между векторами и возвращает значения от 0 (нет сходства) до 1 (идентичное значение):

# Calculate similarities using SentenceTransformers util similarities = util.cos_sim(query_embedding, doc_embeddings)[0] # Create ranked results ranked_results = sorted( zip(document_chunks, similarities), key=lambda x: x[1], reverse=True ) print(f»Query: ‘{query}'») print(«Document chunks ranked by relevance:») for i, (chunk, score) in enumerate(ranked_results, 1): print(f»{i}. ({score:.3f}): ‘{chunk}'»)

Вывод:

Query: ‘How do you define functions in Python?’ Document chunks ranked by relevance: 1. (0.674): ‘A function is a block of code that performs a specific task when called.’ 2. (0.607): ‘Functions can accept parameters and return values to the calling code.’ 3. (0.461): ‘Loops allow you to repeat code multiple times efficiently.’ 4. (0.448): ‘Variables store data values that can be used later in your program.’

Оценки сходства демонстрируют понимание смысла: фрагменты, связанные с функциями, получают высокие баллы (0.7+), тогда как нерелевантные программные концепции — значительно ниже (0.2-).

Построение базы знаний с ChromaDB

Эти эмбеддинги показывают возможности семантического поиска, но хранение в памяти ограничено масштабируемостью. Большие наборы векторов быстро исчерпывают системные ресурсы.

Векторные базы данных дают ключевые возможности для продакшена:

  • Постоянное хранение: данные переживают перезапуски и сбои системы

  • Оптимизированная индексация: быстрый поиск по сходству с использованием алгоритмов HNSW

  • Эффективность по памяти: миллионы векторов без исчерпания RAM

  • Параллельный доступ: одновременные запросы от множества пользователей

  • Фильтрация по метаданным: поиск по свойствам и атрибутам документов

ChromaDB предоставляет эти возможности через Python-ориентированный API и безболезненно встраивается в ваш существующий конвейер данных.

Инициализация векторной базы

Сначала настроим клиент ChromaDB и создадим коллекцию для хранения векторов документов.

import chromadb # Create persistent client for data storage client = chromadb.PersistentClient(path=»./chroma_db») # Create collection for business documents (or get existing) collection = client.get_or_create_collection( name=»python_guide», metadata={«description»: «Python programming guide»} ) print(f»Created collection: {collection.name}») print(f»Collection ID: {collection.id}»)Created collection: python_guide Collection ID: 42d23900-6c2a-47b0-8253-0a9b6dad4f41

В этом коде:

  • PersistentClient(path=»./chroma_db») создает локальную векторную базу, которая сохраняет данные на диск

  • get_or_create_collection() создает новую коллекцию или возвращает существующую с тем же именем

Сохранение документов с метаданными

Теперь сохраним фрагменты документов с базовыми метаданными в ChromaDB с помощью метода add().

# Prepare metadata and add documents to collection metadatas = [{«document»: Path(chunk[«source»]).name} for chunk in all_chunks] collection.add( documents=documents, embeddings=embeddings.tolist(), # Convert numpy array to list metadatas=metadatas, # Metadata for each document ids=[f»doc_{i}» for i in range(len(documents))], # Unique identifiers for each document ) print(f»Collection count: {collection.count()}»)

Вывод:

Collection count: 1007

В базе теперь хранится 1007 искомых фрагментов документов с их векторными представлениями. ChromaDB сохраняет эти данные на диск, что позволяет сразу выполнять запросы без повторной обработки документов после перезапуска.

Запрос к базе знаний

Давайте выполним поиск по векторной базе с помощью вопросов на естественном языке и извлечем релевантные фрагменты документов.

def format_query_results(question, query_embedding, documents, metadatas): «»»Format and print the search results with similarity scores»»» from sentence_transformers import util print(f»Question: {question}n») for i, doc in enumerate(documents): # Calculate accurate similarity using sentence-transformers util doc_embedding = model.encode([doc]) similarity = util.cos_sim(query_embedding, doc_embedding)[0][0].item() source = metadatas[i].get(«document», «Unknown») print(f»Result {i+1} (similarity: {similarity:.3f}):») print(f»Document: {source}») print(f»Content: {doc[:300]}…») print() def query_knowledge_base(question, n_results=2): «»»Query the knowledge base with natural language»»» # Encode the query using our SentenceTransformer model query_embedding = model.encode([question]) results = collection.query( query_embeddings=query_embedding.tolist(), n_results=n_results, include=[«documents», «metadatas», «distances»], ) # Extract results and format them documents = results[«documents»][0] metadatas = results[«metadatas»][0] format_query_results(question, query_embedding, documents, metadatas)

В этом коде:

  • collection.query() выполняет поиск по сходству векторов, используя текст вопроса в качестве входных данных.

  • query_texts принимает список вопросов на естественном языке для пакетной обработки.

  • n_results ограничивает число возвращаемых наиболее похожих документов.

  • include задаёт, какие данные вернуть: текст документов, метаданные и значения дистанций (сходства).

Давайте проверим функцию запроса на примере вопроса:

query_knowledge_base(«How do if-else statements work in Python?»)

Вывод:

Question: How do if-else statements work in Python? Result 1 (similarity: 0.636): Document: think_python_guide.pdf Content: 5.6 Chained conditionals Sometimes there are more than two possibilities and we need more than two branches. One way to express a computation like that is a chained conditional: if x < y: print ’ elif x > y: ’ print x is less than y ’ x is greater than y ’ else: print ’ x and y are equa… Result 2 (similarity: 0.605): Document: think_python_guide.pdf Content: 5. An unclosed opening operator—(, {, or [—makes Python continue with the next line as part of the current statement. Generally, an error occurs almost immediately in the next line. 6. Check for the classic = instead of == inside a conditional. 7. Check the indentation to make sure it lines up the…

Поиск находит релевантное содержимое с высокими показателями сходства (0.636 и 0.605).

Расширенная генерация ответов с Open Source LLMs

Поиск по сходству векторов извлекает связанные фрагменты, но результаты могут быть разбросаны по нескольким кускам и не образовывать цельного ответа.

LLM справляются с этим, «сшивая» извлечённый контекст в единый ответ, который непосредственно отвечает на вопрос пользователя.

В этом разделе мы интегрируем локальные LLM из Ollama с нашим векторным поиском, чтобы генерировать связные ответы на основе полученных фрагментов.

Реализация генерации ответов

Сначала настроим компоненты для генерации ответов с помощью LLM.

from langchain_ollama import OllamaLLM from langchain.prompts import PromptTemplate # Initialize the local LLM llm = OllamaLLM(model=»llama3.2:latest», temperature=0.1)

Затем создадим сфокусированный шаблон промпта для запросов по технической документации.

prompt_template = PromptTemplate( input_variables=[«context», «question»], template=»»»You are a Python programming expert. Based on the provided documentation, answer the question clearly and accurately. Documentation: {context} Question: {question} Answer (be specific about syntax, keywords, and provide examples when helpful):»»» ) # Create the processing chain chain = prompt_template | llm

Создадим функцию, которая извлекает релевантный контекст по заданному вопросу.

def retrieve_context(question, n_results=5): «»»Retrieve relevant context using embeddings»»» query_embedding = model.encode([question]) results = collection.query( query_embeddings=query_embedding.tolist(), n_results=n_results, include=[«documents», «metadatas», «distances»], ) documents = results[«documents»][0] context = «nn—SECTION—nn».join(documents) return context, documents def get_llm_answer(question, context): «»»Generate answer using retrieved context»»» answer = chain.invoke( { «context»: context[:2000], «question»: question, } ) return answer def format_response(question, answer, source_chunks): «»»Format the final response with sources»»» response = f»**Question:** {question}nn» response += f»**Answer:** {answer}nn» response += «**Sources:**n» for i, chunk in enumerate(source_chunks[:3], 1): preview = chunk[:100].replace(«n», » «) + «…» response += f»{i}. {preview}n» return response def enhanced_query_with_llm(question, n_results=5): «»»Query function combining retrieval with LLM generation»»» context, documents = retrieve_context(question, n_results) answer = get_llm_answer(question, context) return format_response(question, answer, documents)

Тестирование расширенной генерации ответов

Давайте протестируем улучшенную систему на нашем сложном вопросе:

# Test the enhanced query system enhanced_response = enhanced_query_with_llm(«How do if-else statements work in Python?») print(enhanced_response)

Вывод:

**Question:** How do if-else statements work in Python? **Answer:** If-else statements in Python are used for conditional execution of code. Here’s a breakdown of how they work: **Syntax** The basic syntax of an if-else statement is as follows: «`text if condition: # code to execute if condition is true elif condition2: # code to execute if condition1 is false and condition2 is true else: # code to execute if both conditions are false «`text **Keywords** The keywords used in an if-else statement are: * `if`: used to check a condition * `elif` (short for «else if»): used to check another condition if the first one is false * `else`: used to specify code to execute if all conditions are false **How it works** Here’s how an if-else statement works: 1. The interpreter evaluates the condition inside the `if` block. 2. If the condition is true, the code inside the `if` block is executed. 3. If the condition is false, the interpreter moves on to the next line and checks the condition in the `elif` block. 4. If the condition in the `elif` block is true, the code inside that block is executed. 5. If both conditions are false, the interpreter executes the code inside the `else` block. **Sources:** 1. 5.6 Chained conditionals Sometimes there are more than two possibilities and we need more than two … 2. 5. An unclosed opening operator—(, {, or [—makes Python continue with the next line as part of the c… 3. if x == y: print else: ’ x and y are equal ’ if x < y: 44 Chapter 5. Conditionals and recur…

Обратите внимание, как LLM организует несколько фрагментов в логические разделы с примерами синтаксиса и пошаговыми объяснениями. Такое преобразование превращает «сырое» извлечение в практические рекомендации по программированию.

Реализация потокового интерфейса

Пользователи теперь ожидают «живого» стриминга ответов, как в ChatGPT и Claude. Статические ответы, появляющиеся целиком, выглядят устаревшими и создают впечатление низкой производительности.

Побуквенная/потоковая генерация сглаживает этот разрыв, создавая привычный эффект печати, сигнализирующий об активной обработке.

Чтобы реализовать потоковый интерфейс, мы воспользуемся методом chain.stream(), который генерирует токены по одному.

def stream_llm_answer(question, context): «»»Stream LLM answer generation token by token»»» for chunk in chain.stream({ «context»: context[:2000], «question»: question, }): yield getattr(chunk, «content», str(chunk))

Давайте посмотрим, как работает стриминг, объединив наши модульные функции:

import time # Test the streaming functionality question = «What are Python loops?» context, documents = retrieve_context(question, n_results=3) print(«Question:», question) print(«Answer: «, end=»», flush=True) # Stream the answer token by token for token in stream_llm_answer(question, context): print(token, end=»», flush=True) time.sleep(0.05) # Simulate real-time typing effect

Вывод:

Question: What are Python loops? Answer: Python → loops → are → structures → that → repeat → code… [Each token appears with typing animation] Final: «Python loops are structures that repeat code blocks.»

Это создаёт знакомую анимацию «печати по буквам» в стиле ChatGPT, когда токены появляются постепенно.

Создание простого приложения на Gradio

Теперь, когда у нас есть полноценная RAG-система с расширенной генерацией ответов, сделаем её доступной через веб-интерфейс.

Вашей RAG-системе нужен интуитивный интерфейс, к которому легко смогут обращаться не технические пользователи. Gradio решает эту задачу благодаря:

  • Нулевой веб-разработке: интерфейсы создаются прямо из Python-функций

  • Автоматической генерации UI: поля ввода и кнопки появляются сами

  • Мгновенному деплою: веб-приложения запускаются одной строкой кода

Функция интерфейса

Создадим полноценный интерфейс Gradio, который объединит написанные функции в потоковую RAG-систему.

import gradio as gr def rag_interface(question): «»»Gradio interface reusing existing format_response function»»» if not question.strip(): yield «Please enter a question.» return # Use modular retrieval and streaming context, documents = retrieve_context(question, n_results=5) response_start = f»**Question:** {question}nn**Answer:** » answer = «» # Stream the answer progressively for token in stream_llm_answer(question, context): answer += token yield response_start + answer # Use existing formatting function for final response yield format_response(question, answer, documents)

Настройка и запуск приложения

Теперь настроим веб-интерфейс Gradio с примерами вопросов и запустим приложение для доступа пользователей.

# Create Gradio interface with streaming support demo = gr.Interface( fn=rag_interface, inputs=gr.Textbox( label=»Ask a question about Python programming», placeholder=»How do if-else statements work in Python?», lines=2, ), outputs=gr.Markdown(label=»Answer»), title=»Intelligent Document Q&A System», description=»Ask questions about Python programming concepts and get instant answers with source citations.», examples=[ «How do if-else statements work in Python?», «What are the different types of loops in Python?», «How do you handle errors in Python?», ], allow_flagging=»never», ) # Launch the interface with queue enabled for streaming if __name__ == «__main__»: demo.queue().launch(share=True)

В этом коде:

  • gr.Interface() создаёт аккуратное веб-приложение с автоматической генерацией UI

  • fn указывает функцию, которая вызывается при отправке вопроса пользователем (включая потоковый вывод)

  • inputs/outputs задают компоненты интерфейса (текстовое поле для вопросов, markdown для форматированных ответов)

  • examples предоставляет кликабельные примеры вопросов, демонстрирующие возможности системы

  • demo.queue().launch(share=True) включает потоковый вывод и создаёт локальный и публичный URL

Запуск приложения даёт следующий вывод:

* Running on local URL: http://127.0.0.1:7861 * Running on public URL: https://bb9a9fc06531d49927.gradio.live

Протестируйте интерфейс локально или поделитесь публичной ссылкой, чтобы показать возможности вашей RAG-системы.

0ce56ec7c6175c4f0a007c13761cb4e1

Публичный URL истекает через 72 часа. Для постоянного доступа задеплойте на Hugging Face Spaces.

gradio deploy

Теперь у вас есть полноценная RAG-система с поддержкой стриминга, готовая к продакшену: с генерацией токенов в реальном времени и указанием источников.

Русскоязычное сообщество про Python

6056da0de69a10b810f19746803b139b

Друзья! Эту статью подготовила команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!

Заключение

В этой статье мы построили полный RAG-пайплайн, который превращает ваши документы в систему ответов на вопросы на базе ИИ.

Мы использовали следующие инструменты:

  • MarkItDown для конвертации документов

  • LangChain для разбиения текста и генерации эмбеддингов

  • ChromaDB для хранения векторов

  • Ollama для локального инференса LLM

  • Gradio для веб-интерфейса

Поскольку все эти инструменты — Open source, вы можете без труда развернуть систему в своей инфраструктуре.

Источник: habr.com

✅ Найденные теги: Как, новости

ОСТАВЬТЕ СВОЙ КОММЕНТАРИЙ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Каталог бесплатных опенсорс-решений, которые можно развернуть локально и забыть о подписках

галерея

Фото сгенерированных лиц: исследование показывает, что люди не могут отличить настоящие лица от сгенерированных
Нейросети построили капитализм за трое суток: 100 агентов Claude заперли…
Скетч: цифровой осьминог и виртуальный мир внутри компьютера с человечком.
Сцена с жестами пальцами, где один жест символизирует "VPN", а другой "KHP".
‼️Paramount купила Warner Bros. Discovery — сумма сделки составила безумные…
Скриншот репозитория GitHub "Claude Scientific Skills" AI для научных исследований.
Структура эффективного запроса Claude с элементами задачи, контекста и референса.
Эскиз и готовая веб-страница платформы для AI-дизайна в современном темном режиме.
ideipro logotyp
Image Not Found
Звёздное небо с галактиками и туманностями, космос, Вселенная, астрофотография.

Система оповещения обсерватории Рубина отправила 800 000 сигналов в первую ночь наблюдений.

Астрономы будут получать оповещения о небесных явлениях в течение нескольких минут после их обнаружения. Теренс О'Брайен, редактор раздела «Выходные». Публикации этого автора будут добавляться в вашу ежедневную рассылку по электронной почте и в ленту новостей на главной…

Мар 2, 2026
Женщина с длинными тёмными волосами в синем свете, нейтральный фон.

Расследование в отношении 61-фунтовой машины, которая «пожирает» пластик и выплевывает кирпичи.

Обзор компактного пресса для мягкого пластика Clear Drop — и что будет дальше. Шон Холлистер, старший редактор Публикации этого автора будут добавляться в вашу ежедневную рассылку по электронной почте и в ленту новостей на главной странице вашего…

Мар 2, 2026
Черный углеродное волокно с текстурой плетения, отражающий свет.

Материал будущего: как работает «бессмертный» композит

Учёные из Университета штата Северная Каролина представили композит нового поколения, способный самостоятельно восстанавливаться после серьёзных повреждений.  Речь идёт о модифицированном армированном волокном полимере (FRP), который не просто сохраняет прочность при малом весе, но и способен «залечивать» внутренние…

Мар 2, 2026
Круглый экран с изображением замка и горы, рядом электронная плата.

Круглый дисплей Waveshare для креативных проектов

Круглый 7-дюймовый сенсорный дисплей от Waveshare создан для разработчиков и дизайнеров, которым нужен нестандартный экран.  Это IPS-панель с разрешением 1 080×1 080 пикселей, поддержкой 10-точечного ёмкостного сенсора, оптической склейкой и защитным закалённым стеклом, выполненная в круглом форм-факторе.…

Мар 2, 2026

Впишите свой почтовый адрес и мы будем присылать вам на почту самые свежие новости в числе самых первых