From 8570d4912cfb09639a56d9d6b7295463ce14566c Mon Sep 17 00:00:00 2001 From: Danil Parunin 5f1b81b8-4f5d-11e8-9c2d-fa7ae01bbebc Date: Mon, 15 Jun 2026 12:21:44 +0000 Subject: [PATCH] add: main.py --- main.py | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..13d7632 --- /dev/null +++ b/main.py @@ -0,0 +1,147 @@ +""" +# main.py – RAG‑agent with Qdrant, OpenRouter, and deepagents +# ---------------------------------------------------------- +# 1. Imports and configuration +# 2. Qdrant vector store wrapper (embedding, add, search) +# 3. Text splitter (RecursiveCharacterTextSplitter) +# 4. LangChain tools: search_knowledge_base, add_to_knowledge_base +# 5. DeepAgent creation (create_deep_agent) +# 6. CLI client for /add, /search, /quit +# ---------------------------------------------------------- +""" +import os +import asyncio +import json +from pathlib import Path +from typing import List + +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_core.documents import Document +from langchain_text_splitters import RecursiveCharacterTextSplitter +from langchain.tools import tool +from deepagents import create_deep_agent +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend +from langchain_qdrant import QdrantVectorStore + +# ------------------------------------------------------------------ +# 1. Configuration +# ------------------------------------------------------------------ +# Load environment variables (e.g. OPENAI_API_KEY) +from dotenv import load_dotenv +load_dotenv() + +# LLM – OpenRouter (free tier) +llm = ChatOpenAI( + model="openai/gpt-oss-20b:free", + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENAI_API_KEY"), + temperature=0.0, +) + +# Embeddings – OpenAI via OpenRouter +embeddings = OpenAIEmbeddings( + model="text-embedding-3-small", + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENAI_API_KEY"), +) + +# Qdrant client – assumes Qdrant is running locally on default port +qdrant_url = os.getenv("QDRANT_URL", "http://localhost:6333") +collection_name = "knowledge_base" +vector_store = QdrantVectorStore( + url=qdrant_url, + collection_name=collection_name, + embeddings=embeddings, +) + +# Text splitter – 1000 chars max, 200 overlap +text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) + +# ------------------------------------------------------------------ +# 2. Tools +# ------------------------------------------------------------------ +@tool +def search_knowledge_base(query: str, max_results: int = 3) -> str: + """Semantic search in the Qdrant knowledge base.""" + docs: List[Document] = vector_store.similarity_search(query, k=max_results) + if not docs: + return "No relevant documents found." + return "\n\n---\n\n".join([f"{doc.metadata.get('title', 'Untitled')}:\n{doc.page_content}" for doc in docs]) + +@tool +def add_to_knowledge_base(content: str, title: str = "Untitled") -> str: + """Add a new document to the knowledge base. + The content is split into chunks before being stored. + """ + chunks = text_splitter.split_text(content) + docs = [Document(page_content=chunk, metadata={"title": title}) for chunk in chunks] + vector_store.add_documents(docs) + return f"Added {len(docs)} chunks for document '{title}'." + +# ------------------------------------------------------------------ +# 3. DeepAgent setup +# ------------------------------------------------------------------ +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +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.", +) + +# ------------------------------------------------------------------ +# 4. CLI client +# ------------------------------------------------------------------ +async def run_cli(): + print("Welcome to the RAG Agent CLI. Commands: /add <file>, /search <query>, /quit") + thread_id = "cli-session" + while True: + try: + user_input = input("> ") + except EOFError: + break + if not user_input: + continue + if user_input.startswith("/quit"): + print("Goodbye!") + break + if user_input.startswith("/add"): + parts = user_input.split(maxsplit=2) + if len(parts) < 3: + print("Usage: /add <title> <file_path>") + continue + title, file_path = parts[1], parts[2] + try: + content = Path(file_path).read_text(encoding="utf-8") + except Exception as e: + print(f"Error reading file: {e}") + continue + # Invoke tool directly + result = add_to_knowledge_base(content, title) + print(result) + continue + if user_input.startswith("/search"): + query = user_input[len("/search"):].strip() + if not query: + print("Usage: /search <query>") + continue + # Use agent to perform search via tool + response = await agent.ainvoke( + {"messages": [{"role": "user", "content": f"search {query}"}]}, + {"configurable": {"thread_id": thread_id}}, + ) + print(response["messages"][-1].content) + continue + # Default: treat as normal user message + response = await agent.ainvoke( + {"messages": [{"role": "user", "content": user_input}]}, + {"configurable": {"thread_id": thread_id}}, + ) + print(response["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(run_cli())