feat: solution for 'Агент с RAG-памятью'

This commit is contained in:
2026-06-24 14:17:50 +03:00
commit 589a621340
10 changed files with 276 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules/
.env
dist/
build/
*.log
+17
View File
@@ -0,0 +1,17 @@
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy source code
COPY src ./src
COPY data ./data
COPY main.py .
# Expose port if needed (not required for CLI)
# EXPOSE 8000
CMD ["python", "src/main.py"]
+20
View File
@@ -0,0 +1,20 @@
# Агент с RAG-памятью
Главная
Мои задания
Агент с RAG-памятью
EN
Агент с RAG-памятью
Зачёт
Версия 6
Дедлайн сдачи: 31.08.2026
В работе
Требуется доработка
Уважаемый студент! В вашем решении использованы правильные технологии (Qdrant, Ollama и LangChain), но есть два момента, которые требуют доработки:
Инициализация агента производится через устаревший initialize_agent. Следует заменить его на современный create_agent из LangChain 1.x.
Параметры разбиения текста в функции chunk_document отличаются от тех, что у
+16
View File
@@ -0,0 +1,16 @@
[project]
name = "rag-agent"
version = "0.1.0"
description = "Agent with RAG memory using LangChain 1.x, Qdrant, and Ollama."
requires-python = ">=3.10"
dependencies = [
"langchain==1.0.0",
"langchain-community==0.0.20",
"langchain-ollama==0.0.3",
"qdrant-client==1.0.0",
"python-dotenv==1.0.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
+5
View File
@@ -0,0 +1,5 @@
langchain==1.0.0
langchain-community==0.0.20
langchain-ollama==0.0.3
qdrant-client==1.0.0
python-dotenv==1.0.0
+1
View File
@@ -0,0 +1 @@
# Empty init file to make src a package
+66
View File
@@ -0,0 +1,66 @@
from typing import List, Callable
from langchain_ollama import Ollama
from langchain.vectorstores import Qdrant
from langchain.agents import Tool, AgentExecutor, create_agent
class RAGAgent:
"""
Agent that uses a Qdrant vector store and an Ollama LLM to answer queries
using Retrieval-Augmented Generation (RAG).
"""
def __init__(
self,
llm: Ollama,
vector_store: Qdrant,
chunk_document_func: Callable[[str, int, int], List[str]] = None,
):
self.llm = llm
self.vector_store = vector_store
self.chunk_document_func = chunk_document_func
def add_documents(self, documents: List[str]) -> None:
"""
Adds a list of documents to the vector store after chunking them.
Args:
documents: List of raw text documents.
"""
if self.chunk_document_func is None:
raise ValueError("chunk_document_func must be provided")
for doc in documents:
chunks = self.chunk_document_func(doc)
self.vector_store.add_texts(chunks)
def _retrieve(self, query: str) -> str:
"""
Retrieves relevant documents from the vector store for a given query.
Args:
query: The user query.
Returns:
A concatenated string of relevant document contents.
"""
docs = self.vector_store.as_retriever().get_relevant_documents(query)
return "\n".join([doc.page_content for doc in docs])
def create_agent(self) -> AgentExecutor:
"""
Creates an AgentExecutor that uses the retrieval tool and the LLM.
Returns:
An AgentExecutor ready to handle queries.
"""
retrieve_tool = Tool(
name="RAG",
func=self._retrieve,
description="Use this tool to retrieve relevant information from the knowledge base.",
)
agent_executor = create_agent(
llm=self.llm,
tools=[retrieve_tool],
agent_type="chat-conversational-react-description",
verbose=True,
)
return agent_executor
+30
View File
@@ -0,0 +1,30 @@
from typing import List
def chunk_document(text: str, chunk_size: int = 500, chunk_overlap: int = 100) -> List[str]:
"""
Splits the input text into chunks of specified size with overlap.
Args:
text: The full text to split.
chunk_size: Maximum number of characters per chunk.
chunk_overlap: Number of characters to overlap between consecutive chunks.
Returns:
A list of text chunks.
"""
if chunk_size <= 0:
raise ValueError("chunk_size must be positive")
if chunk_overlap < 0:
raise ValueError("chunk_overlap cannot be negative")
if chunk_overlap >= chunk_size:
raise ValueError("chunk_overlap must be smaller than chunk_size")
chunks = []
start = 0
text_length = len(text)
while start < text_length:
end = min(start + chunk_size, text_length)
chunk = text[start:end]
chunks.append(chunk)
start += chunk_size - chunk_overlap
return chunks
+53
View File
@@ -0,0 +1,53 @@
import os
from dotenv import load_dotenv
from langchain_ollama import Ollama
from langchain_community.embeddings import OllamaEmbeddings
from langchain.vectorstores import Qdrant
from qdrant_client import QdrantClient
from src.agent import RAGAgent
from src.chunk_document import chunk_document
def main() -> None:
# Load environment variables if any
load_dotenv()
# Initialize LLM and embeddings
llm = Ollama(model="llama3")
embeddings = OllamaEmbeddings(model="llama3")
# Connect to Qdrant (assumes Qdrant is running locally on port 6333)
qdrant_client = QdrantClient(host="localhost", port=6333)
vector_store = Qdrant(
client=qdrant_client,
collection_name="rag_collection",
embeddings=embeddings,
)
# Create the RAG agent
rag_agent = RAGAgent(llm=llm, vector_store=vector_store, chunk_document_func=chunk_document)
# Example documents to add to the vector store
sample_docs = [
"LangChain is a framework for developing applications powered by language models.",
"Qdrant is a vector database that can store embeddings and perform similarity search.",
"Ollama provides a lightweight interface to run LLMs locally.",
]
rag_agent.add_documents(sample_docs)
# Build the agent executor
agent_executor = rag_agent.create_agent()
print("RAG Agent is ready. Type your question (or 'exit' to quit).")
while True:
user_input = input(">>> ")
if user_input.lower() in {"exit", "quit"}:
print("Goodbye!")
break
try:
response = agent_executor.invoke({"input": user_input})
print(response["output"])
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
import os
from typing import List
from pathlib import Path
from langchain.embeddings import OllamaEmbeddings
from langchain.vectorstores import Qdrant
from qdrant_client import QdrantClient
def chunk_document(text: str, chunk_size: int = 500, chunk_overlap: int = 100) -> List[str]:
"""
Splits the input text into chunks of specified size with overlap.
:param text: The full text to chunk.
:param chunk_size: Number of words per chunk.
:param chunk_overlap: Number of overlapping words between consecutive chunks.
:return: List of chunk strings.
"""
words = text.split()
chunks = []
start = 0
while start < len(words):
end = min(start + chunk_size, len(words))
chunk = " ".join(words[start:end])
chunks.append(chunk)
start += chunk_size - chunk_overlap
return chunks
def get_ollama_embeddings() -> OllamaEmbeddings:
"""
Returns an OllamaEmbeddings instance using the default Ollama model.
:return: OllamaEmbeddings instance.
"""
return OllamaEmbeddings(model="llama3.1")
def init_qdrant_collection(collection_name: str = "rag_collection") -> Qdrant:
"""
Initializes a Qdrant collection. Creates it if it does not exist.
:param collection_name: Name of the collection.
:return: Qdrant vector store instance.
"""
client = QdrantClient(url="http://localhost:6333")
if not client.collection_exists(collection_name):
client.create_collection(
collection_name=collection_name,
vectors_config={"size": 1024, "distance": "Cosine"}
)
return Qdrant(client=client, collection_name=collection_name)
def add_chunks_to_qdrant(chunks: List[str], embeddings: OllamaEmbeddings, qdrant: Qdrant) -> None:
"""
Adds text chunks to the Qdrant collection with embeddings.
:param chunks: List of text chunks.
:param embeddings: Embeddings instance to compute vectors.
:param qdrant: Qdrant vector store instance.
"""
qdrant.add_texts(texts=chunks, embeddings=embeddings)