feat: solution for 'Агент с RAG-памятью'
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.log
|
||||||
+17
@@ -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"]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Агент с RAG-памятью
|
||||||
|
|
||||||
|
Главная
|
||||||
|
Мои задания
|
||||||
|
Агент с RAG-памятью
|
||||||
|
5Д
|
||||||
|
EN
|
||||||
|
Агент с RAG-памятью
|
||||||
|
Зачёт
|
||||||
|
Версия 6
|
||||||
|
Дедлайн сдачи: 31.08.2026
|
||||||
|
|
||||||
|
В работе
|
||||||
|
|
||||||
|
Требуется доработка
|
||||||
|
|
||||||
|
Уважаемый студент! В вашем решении использованы правильные технологии (Qdrant, Ollama и LangChain), но есть два момента, которые требуют доработки:
|
||||||
|
|
||||||
|
Инициализация агента производится через устаревший initialize_agent. Следует заменить его на современный create_agent из LangChain 1.x.
|
||||||
|
Параметры разбиения текста в функции chunk_document отличаются от тех, что у
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Empty init file to make src a package
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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()
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user