from pathlib import Path import os from typing import List # LLM and embeddings via Ollama from langchain_ollama import ChatOllama, OllamaEmbeddings # Tools from langchain.tools import tool # Vector store from langchain_qdrant import QdrantVectorStore from qdrant_client import QdrantClient from qdrant_client.http.models import Distance, VectorParams # Text splitter from langchain.text_splitter import RecursiveCharacterTextSplitter # Agent from langchain.agents import create_agent # Documents from langchain_core.documents import Document # -------------------- 1. RAG tools -------------------- @tool def search_knowledge_base(query: str, max_results: int = 5) -> str: """Search the knowledge base for relevant documents.""" results = vector_store.similarity_search_with_score(query, k=max_results) if not results: return "No relevant documents found." response_lines = [] for doc, score in results: title = doc.metadata.get("title", "N/A") snippet = doc.page_content[:200] + ("..." if len(doc.page_content) > 200 else "") response_lines.append(f"Score: {score:.4f}\nTitle: {title}\nContent: {snippet}") return "\n\n".join(response_lines) @tool def add_to_knowledge_base(content: str, title: str) -> str: """Add a new document to the knowledge base.""" doc = Document(page_content=content, metadata={"title": title}) vector_store.add_documents([doc]) return f"Document '{title}' added successfully." # -------------------- 2. Qdrant setup -------------------- qdrant_client = QdrantClient(":memory:") qdrant_client.create_collection( collection_name="knowledge_base", vectors_config=VectorParams(size=384, distance=Distance.COSINE), ) embeddings = OllamaEmbeddings(model="nomic-embed-text") vector_store = QdrantVectorStore( client=qdrant_client, collection_name="knowledge_base", embedding=embeddings, ) # -------------------- 3. Text splitter -------------------- splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100) def load_and_index(directory: str): """Load all .txt files from directory and index them.""" docs: List[Document] = [] for file_path in Path(directory).glob("*.txt"): text = file_path.read_text(encoding="utf-8") chunks = splitter.split_text(text) for i, chunk in enumerate(chunks): docs.append( Document( page_content=chunk, metadata={ "title": f"{file_path.stem} #{i+1}", "source": str(file_path), }, ) ) vector_store.add_documents(docs) # -------------------- 4. Agent -------------------- system_prompt = """ You are an assistant that can search and add documents to a knowledge base. Use the tools `search_knowledge_base` and `add_to_knowledge_base` as needed. """ agent = create_agent( model=ChatOllama(model="llama3", temperature=0.2), tools=[search_knowledge_base, add_to_knowledge_base], system_prompt=system_prompt, ) # -------------------- 5. CLI client -------------------- def main(): # Load initial documents load_and_index("docs") # ensure a 'docs' folder with .txt files print( "RAG Agent ready. Commands: /add <content>, /search <query>, /quit" ) while True: user_input = input("> ").strip() if not user_input: continue if user_input.lower() in ("exit", "quit", "/quit"): break if user_input.startswith("/add"): try: _, title, content = user_input.split(" ", 2) result_msg = add_to_knowledge_base(content=content, title=title) print(result_msg) except ValueError: print("Usage: /add <title> <content>") elif user_input.startswith("/search"): query = user_input[len("/search") :].strip() if not query: print("Provide a search query.") continue response = agent.invoke({"messages": [{"role": "human", "content": query}]}) for msg in response["messages"]: if hasattr(msg, "content"): print(msg.content) else: # Regular chat with the agent response = agent.invoke( {"messages": [{"role": "human", "content": user_input}]} ) for msg in response["messages"]: if hasattr(msg, "content"): print(msg.content) if __name__ == "__main__": main()