diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..474b603 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__ +agent_env +.lock +.sqlite +qdrant_data +qdrant_storage \ No newline at end of file diff --git a/agent_core.py b/agent_core.py index 79642a7..0ef45f1 100644 --- a/agent_core.py +++ b/agent_core.py @@ -1,15 +1,13 @@ from typing import TypedDict, List, Optional, Annotated import operator from langchain_community.llms import Ollama -from langchain_community.embeddings import OllamaEmbeddings from langchain_community.vectorstores import Qdrant -from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages -from langchain_core.messages import HumanMessage, AIMessage, SystemMessage +from langchain_core.messages import AIMessage, SystemMessage from langchain.tools import tool -from langchain.agents import AgentExecutor, create_react_agent +# from langchain.agents import AgentExecutor, create_react_agent import numpy as np # Определяем состояние агента @@ -22,16 +20,11 @@ class AgentState(TypedDict): # Инициализация LLM через Ollama llm = Ollama( - model="llama3.2:3b", + model="qwen2.5-coder-16k:14b", temperature=0.1, # Низкая температура для консистентности num_predict=1024, # Максимальная длина ответа ) -# Инициализация эмбеддингов -embeddings = OllamaEmbeddings( - model="nomic-embed-text", # Хорошие локальные эмбеддинги -) - # Создаем инструменты для агента @tool def search_knowledge_base(query: str) -> str: @@ -127,6 +120,3 @@ def create_agent_graph(): workflow.add_edge("generate", END) return workflow.compile() - -# Создаем и компилируем граф -agent_graph = create_agent_graph() \ No newline at end of file diff --git a/agentic_rag_interface.py b/agentic_rag_interface.py index c5698ab..23cc012 100644 --- a/agentic_rag_interface.py +++ b/agentic_rag_interface.py @@ -1,6 +1,10 @@ import gradio as gr import asyncio +import os from typing import List +from agent_core import create_agent_graph +from local_knowledge_base import LocalKnowledgeBase +from langchain_core.messages import HumanMessage class AgenticRAGInterface: def __init__(self, agent_graph, knowledge_base): @@ -61,8 +65,15 @@ class AgenticRAGInterface: self.conversation_history = [] return "История очищена", "" +# Инициализируем базу +kb = LocalKnowledgeBase() +# Загружаем документы (если есть) +if os.path.exists("./documents"): + kb.load_documents("./documents") + + # Создаем интерфейс -interface = AgenticRAGInterface(agent_graph, kb) +interface = AgenticRAGInterface(create_agent_graph(), kb) # Создаем Gradio интерфейс def create_gradio_interface(): diff --git a/local_knowledge_base.py b/local_knowledge_base.py index 71e8714..2dc335d 100644 --- a/local_knowledge_base.py +++ b/local_knowledge_base.py @@ -1,23 +1,32 @@ import os from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct -from langchain_community.document_loaders import TextLoader, PyPDFLoader, DirectoryLoader -from langchain.text_splitter import Language +from langchain_community.document_loaders import ( + TextLoader, + PyPDFLoader, + DirectoryLoader, +) +from langchain_text_splitters import Language, RecursiveCharacterTextSplitter +from langchain_community.embeddings import OllamaEmbeddings + class LocalKnowledgeBase: def __init__(self, collection_name="documents", persist_dir="./qdrant_data"): # Инициализируем клиент Qdrant self.client = QdrantClient( - path=persist_dir, # Локальное хранение - prefer_grpc=True + path=persist_dir, prefer_grpc=True # Локальное хранение ) - + self.collection_name = collection_name - self.embeddings = embeddings - + + # Инициализация эмбеддингов + self.embeddings = OllamaEmbeddings( + model="nomic-embed-text", # Хорошие локальные эмбеддинги + ) + # Создаем коллекцию если её нет self._create_collection() - + def _create_collection(self): try: self.client.get_collection(self.collection_name) @@ -28,26 +37,25 @@ class LocalKnowledgeBase: collection_name=self.collection_name, vectors_config=VectorParams( size=768, # Размерность эмбеддингов nomic-embed-text - distance=Distance.COSINE - ) + distance=Distance.COSINE, + ), ) print(f"Создана коллекция {self.collection_name}") - + def load_documents(self, directory_path: str): """Загрузка документов из директории""" loaders = { - '.pdf': PyPDFLoader, - '.txt': lambda path: DirectoryLoader(path, glob="**/*.txt"), - '.docx': lambda path: DirectoryLoader(path, glob="**/*.docx"), - '.hpp': lambda path: TextLoader(path, encoding='utf-8'), - '.cpp': lambda path: TextLoader(path, encoding='utf-8'), # добавить - '.h': lambda path: TextLoader(path, encoding='utf-8'), # добавить - '.cc': lambda path: TextLoader(path, encoding='utf-8'), # добавить - + ".pdf": PyPDFLoader, + ".txt": lambda path: DirectoryLoader(path, glob="**/*.txt"), + ".docx": lambda path: DirectoryLoader(path, glob="**/*.docx"), + ".hpp": lambda path: TextLoader(path, encoding="utf-8"), + ".cpp": lambda path: TextLoader(path, encoding="utf-8"), # добавить + ".h": lambda path: TextLoader(path, encoding="utf-8"), # добавить + ".cc": lambda path: TextLoader(path, encoding="utf-8"), # добавить } - + all_documents = [] - + for ext, loader_class in loaders.items(): for file_path in os.listdir(directory_path): if file_path.endswith(ext): @@ -59,31 +67,31 @@ class LocalKnowledgeBase: print(f"Загружен {file_path}: {len(documents)} страниц") except Exception as e: print(f"Ошибка загрузки {file_path}: {e}") - + # Разбиваем на чанки text_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.CPP, chunk_size=1000, chunk_overlap=200, - length_function=len + length_function=len, ) - + chunks = text_splitter.split_documents(all_documents) print(f"Всего чанков: {len(chunks)}") - + # Создаем эмбеддинги и сохраняем в Qdrant self._index_documents(chunks) - + return chunks - + def _index_documents(self, documents): """Индексация документов в Qdrant""" points = [] - + for i, doc in enumerate(documents): # Создаем эмбеддинг для каждого чанка embedding = self.embeddings.embed_query(doc.page_content) - + point = PointStruct( id=i, vector=embedding, @@ -91,55 +99,45 @@ class LocalKnowledgeBase: "text": doc.page_content, "source": doc.metadata.get("source", "unknown"), "page": doc.metadata.get("page", 0), - "file_type": "cpp" - } + "file_type": "cpp", + }, ) points.append(point) - + # Пакетная загрузка каждые 100 точек if len(points) >= 100: - self.client.upsert( - collection_name=self.collection_name, - points=points - ) + self.client.upsert(collection_name=self.collection_name, points=points) points = [] print(f"Индексировано {i+1} документов") - + # Загружаем оставшиеся if points: - self.client.upsert( - collection_name=self.collection_name, - points=points - ) - + self.client.upsert(collection_name=self.collection_name, points=points) + print(f"Индексация завершена. Всего документов: {len(documents)}") - + def search(self, query: str, top_k: int = 5): """Поиск в базе знаний""" # Создаем эмбеддинг запроса query_embedding = self.embeddings.embed_query(query) - + # Ищем в Qdrant - search_result = self.client.search( + search_result = self.client.query_points( collection_name=self.collection_name, - query_vector=query_embedding, - limit=top_k + query=query_embedding, + limit=top_k, ) - + # Форматируем результаты results = [] - for hit in search_result: - results.append({ - "text": hit.payload["text"], - "score": hit.score, - "source": hit.payload.get("source", "unknown") - }) - + for hit in search_result.points: + results.append( + { + "text": hit.payload["text"], + "score": hit.score, + "source": hit.payload.get("source", "unknown"), + } + ) + return results -# Инициализируем базу знаний -kb = LocalKnowledgeBase() - -# Загружаем документы (если есть) -if os.path.exists("./documents"): - kb.load_documents("./documents") \ No newline at end of file