""" DeepAgents RAG Agent with Qdrant and Ollama embeddings """ import os import asyncio from pathlib import Path from typing import List from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage from langchain.tools import tool from langchain_ollama.embeddings import OllamaEmbeddings from langchain_qdrant import QdrantVectorStore from langchain_text_splitters import RecursiveCharacterTextSplitter from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- # Environment variables OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") # required for OpenRouter LLM OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434") QDRANT_URL = os.getenv("QDRANT_URL", "http://localhost:6333") # LLM via OpenRouter llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", api_key=OPENAI_API_KEY, temperature=0.0, ) # Embeddings via Ollama embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url=OLLAMA_BASE_URL) # Qdrant vector store qdrant_vector_store = QdrantVectorStore( client_kwargs={"url": QDRANT_URL}, collection_name="knowledge_base", embeddings=embeddings, # Create collection if not exists create_collection=True, # Use cosine similarity distance="cosine", ) # Text splitter text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) # --------------------------------------------------------------------------- # Tools # --------------------------------------------------------------------------- @tool def search_knowledge_base(query: str, max_results: int = 3) -> str: """Semantic search in the knowledge base.""" docs = qdrant_vector_store.similarity_search(query, k=max_results) if not docs: return "No results found." return "\n\n".join(f"{i+1}. {doc.page_content}" for i, doc in enumerate(docs)) @tool def add_to_knowledge_base(content: str, title: str = "document") -> str: """Add content to the knowledge base after chunking.""" # Split content into chunks chunks = text_splitter.split_text(content) metadatas = [{"title": title, "chunk_index": i} for i in range(len(chunks))] # Add to Qdrant qdrant_vector_store.add_texts(chunks, metadatas=metadatas) return f"Added {len(chunks)} chunks from '{title}'." # --------------------------------------------------------------------------- # Backend setup # --------------------------------------------------------------------------- backend = CompositeBackend([ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ]) # --------------------------------------------------------------------------- # Agent creation # --------------------------------------------------------------------------- agent = create_deep_agent( model=llm, tools=[search_knowledge_base, add_to_knowledge_base], backend=backend, system_prompt="You are a helpful assistant with access to a knowledge base. Use the provided tools to search and add information.", ) # --------------------------------------------------------------------------- # Document loader for initialization # --------------------------------------------------------------------------- async def load_documents_from_dir(directory: str): """Load all .txt files from a directory into the knowledge base.""" dir_path = Path(directory) for file_path in dir_path.rglob("*.txt"): content = file_path.read_text(encoding="utf-8") title = file_path.stem await agent.ainvoke( {"messages": [HumanMessage(content=f"/add {content}")], "metadata": {"title": title}}, {"configurable": {"thread_id": "init-session"}}, ) # --------------------------------------------------------------------------- # CLI client # --------------------------------------------------------------------------- async def cli(): print("DeepAgents RAG CLI. Commands: /add , /search , /quit") while True: user_input = input("> ") if user_input.strip() == "/quit": print("Goodbye!") break if user_input.startswith("/add "): content = user_input[5:].strip() response = await agent.ainvoke( {"messages": [HumanMessage(content=f"/add {content}")], "metadata": {"title": "user_input"}}, {"configurable": {"thread_id": "cli-session"}}, ) print(response["messages"][-1].content) elif user_input.startswith("/search "): query = user_input[8:].strip() response = await agent.ainvoke( {"messages": [HumanMessage(content=f"/search {query}")], "metadata": {"query": query}}, {"configurable": {"thread_id": "cli-session"}}, ) print(response["messages"][-1].content) else: print("Unknown command. Use /add, /search, or /quit.") # --------------------------------------------------------------------------- # Main entry point # --------------------------------------------------------------------------- async def main(): # Optional: load initial documents from a folder named 'data' data_dir = Path("./data") if data_dir.exists(): await load_documents_from_dir(str(data_dir)) await cli() if __name__ == "__main__": asyncio.run(main())