From 7d0c6ff35cc900c90c7158061792b4eb660a6aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=205f1b81b8-4f5d-11e8-9c2d-fa7ae01?= =?UTF-8?q?bbebc?= Date: Sat, 27 Jun 2026 13:56:39 +0000 Subject: [PATCH] add: main.py --- main.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..cce0e01 --- /dev/null +++ b/main.py @@ -0,0 +1,135 @@ +import os +import asyncio +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_chroma import Chroma +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_core.messages import HumanMessage +from tavily import TavilySearchResults + +# --------------------- +# Configuration +# --------------------- +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +TAVILY_API_KEY = os.getenv("TAVILY_API_KEY") + +# --------------------- +# Vector store utilities +# --------------------- + +def create_vectorstore(persist_directory: str = "./chroma_db") -> Chroma: + """Create or load a Chroma vector store with OpenAI embeddings.""" + embeddings = OpenAIEmbeddings( + model="text-embedding-3-small", + base_url="https://openrouter.ai/api/v1", + api_key=OPENAI_API_KEY, + ) + return Chroma( + collection_name="knowledge", + embedding_function=embeddings, + persist_directory=persist_directory, + ) + + +def load_documents(directory: str, vectorstore: Chroma) -> None: + """Load .txt and .md files from *directory* into *vectorstore* using chunking.""" + splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) + docs = [] + for root, _, files in os.walk(directory): + for fname in files: + if fname.lower().endswith(('.txt', '.md')): + path = os.path.join(root, fname) + with open(path, "r", encoding="utf-8") as f: + text = f.read() + # Create Document objects + docs.extend( + [Document(page_content=chunk, metadata={"source": path}) for chunk in splitter.split_text(text)] + ) + if docs: + vectorstore.add_documents(docs) + vectorstore.persist() + +# --------------------- +# Tools +# --------------------- + +vectorstore = create_vectorstore() + +# Ensure we have some data loaded – load from ./documents if collection empty +if len(vectorstore.get_all_documents()) == 0: + load_documents("./documents", vectorstore) + +@tool +def search_local_kb(query: str, top_k: int = 3) -> str: + """Search the local knowledge base for relevant passages.""" + docs = vectorstore.similarity_search(query, k=top_k) + if not docs: + return "[Local KB] No relevant information found." + result = "\n\n".join([f"{d.metadata.get('source', 'unknown')}\n{d.page_content}" for d in docs]) + return f"[Local KB]\n{result}" + +@tool +def web_search(query: str) -> str: + """Perform a web search using Tavily and return top results.""" + results = TavilySearchResults(query=query, max_results=3, api_key=TAVILY_API_KEY) + if not results.results: + return "[Web Search] No results found." + snippets = [] + for r in results.results: + snippets.append(f"{r.get('title', 'No title')}\n{r.get('content', 'No content')}\nURL: {r.get('url', '')}") + return f"[Web Search]\n\n".join(snippets) + +# --------------------- +# Agent setup +# --------------------- + +llm = ChatOpenAI( + model="openai/gpt-oss-20b:free", + base_url="https://openrouter.ai/api/v1", + api_key=OPENAI_API_KEY, + temperature=0.0, +) + +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +system_prompt = ( + "You are a helpful RAG agent. For questions about local documents, use the tool `search_local_kb`. " + "For current news or facts that may not be in your local knowledge base, use `web_search`. " + "Return the answer prefixed with either `[Local KB]` or `[Web Search]` to indicate the source." +) + +agent = create_deep_agent( + model=llm, + tools=[search_local_kb, web_search], + backend=backend, + system_prompt=system_prompt, +) + +# --------------------- +# CLI loop +# --------------------- + +async def main(): + print("RAG Agent ready. Type your question (or 'exit' to quit).") + while True: + user_input = input("\nQuery: ") + if user_input.lower() in {"exit", "quit", "q"}: + print("Goodbye!") + break + # Invoke agent + result = await agent.ainvoke( + {"messages": [HumanMessage(content=user_input)]}, + {"configurable": {"thread_id": "session-1"}}, + ) + # The agent returns a list of messages; get the last one + reply = result["messages"][-1].content + print(f"\n{reply}") + +if __name__ == "__main__": + asyncio.run(main())