from langchain_openai import ChatOpenAI from pydantic import SecretStr from langchain.tools import tool from langchain_qdrant import QdrantVectorStore from qdrant_client import QdrantClient from qdrant_client.http.models import Distance, VectorParams from langchain_ollama import OllamaEmbeddings from langchain_core.documents import Document from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain.agents import create_agent import os # ---------- LLM ---------- llm = ChatOpenAI( model="openai/gpt-oss-20b", base_url='https://platform.brojs.ru/jrnl-bh/api/inference/v1', api_key=SecretStr("jrnl_30283ab953615cbb6846ff9940a1eedce0b76d7b2f59a2394f29e74643e6a90d"), temperature=0.7, ) # ---------- Embeddings ---------- embeddings = OllamaEmbeddings(model="nomic-embed-text") # ---------- Qdrant ---------- client = QdrantClient(":memory:") collection_name = "knowledge_base" try: client.get_collection(collection_name) except Exception: # Use a typical embedding size for nomic-embed-text (768) client.create_collection( collection_name=collection_name, vectors_config=VectorParams(size=768, distance=Distance.COSINE), ) vector_store = QdrantVectorStore( client=client, collection_name=collection_name, embedding=embeddings, ) # ---------- Text splitter ---------- splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) # ---------- Tools ---------- @tool def search_knowledge_base(query: str, max_results: int = 5) -> str: """Search the knowledge base for relevant documents.""" docs_with_score = vector_store.similarity_search_with_score(query, k=max_results) if not docs_with_score: return "No results found." return "\n".join( f"{i+1}. {doc.page_content[:200]}..." for i, (doc, _) in enumerate(docs_with_score) ) @tool def add_to_knowledge_base(content: str, title: str = "") -> str: """Add a new document to the knowledge base.""" chunks = splitter.split_text(content) docs = [Document(page_content=c, metadata={"title": title}) for c in chunks] vector_store.add_documents(docs) return f"Added {len(chunks)} chunks under title '{title}'." # ---------- Agent ---------- system_prompt = """ You are an assistant that can search and add information to a knowledge base. Use the tools `search_knowledge_base` and `add_to_knowledge_base` as needed. """ agent = create_agent( model=llm, tools=[search_knowledge_base, add_to_knowledge_base], system_prompt=system_prompt, ) # ---------- CLI ---------- def load_directory(path: str): """Load all text files from a directory into the knowledge base.""" for root, _, files in os.walk(path): for file in files: if file.lower().endswith(".txt"): with open(os.path.join(root, file), encoding="utf-8") as f: content = f.read() add_to_knowledge_base(content=content, title=file) def main(): print("Welcome to the RAG agent. Commands: /add , /search , /load , /quit") while True: try: inp = input("> ").strip() except EOFError: break if not inp: continue if inp.lower() in ("/quit", "exit"): print("Goodbye!") break if inp.startswith("/add "): _, file_path = inp.split(maxsplit=1) try: with open(file_path, encoding="utf-8") as f: content = f.read() print(add_to_knowledge_base(content=content, title=os.path.basename(file_path))) except Exception as e: print(f"Error adding file: {e}") elif inp.startswith("/search "): _, query = inp.split(maxsplit=1) print(search_knowledge_base(query=query)) elif inp.startswith("/load "): _, dir_path = inp.split(maxsplit=1) load_directory(dir_path) print(f"Loaded documents from {dir_path}") else: # Regular conversation response = agent.invoke({"messages": [{"role": "human", "content": inp}]}) msg = response["messages"][-1] print(msg.content) if __name__ == "__main__": main()