Files
task-6a1864f78a94f887e50d46da/main.py
T
2026-06-27 13:56:39 +00:00

136 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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())