Files
task-6a1864f78a94f887e50d46da/main.py
T
2026-06-04 09:25:14 +00:00

110 lines
3.8 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 pathlib import Path
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_tavily import TavilySearchResults
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
# ---------- LLM ----------
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,
)
# ---------- Backend ----------
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# ---------- Vectorstore utilities ----------
PERSIST_DIR = Path("./chroma_db")
PERSIST_DIR.mkdir(parents=True, exist_ok=True)
# Create or load Chroma vectorstore
vectorstore = Chroma(
persist_directory=str(PERSIST_DIR),
embedding_function=OllamaEmbeddings(model="nomic-embed-text"),
)
# Load documents from a directory into the vectorstore
def load_documents(directory: str, vectorstore):
docs = []
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
for file_path in Path(directory).glob("**/*"):
if file_path.suffix.lower() in {".txt", ".md"}:
text = file_path.read_text(encoding="utf-8")
docs.extend(splitter.split_text(text))
# Convert to LangChain Documents
from langchain_core.documents import Document
documents = [Document(page_content=chunk) for chunk in docs]
vectorstore.add_documents(documents)
vectorstore.persist()
# Load documents once at startup (if not already loaded)
if not any(PERSIST_DIR.iterdir()):
load_documents("./documents", vectorstore)
# ---------- Tools ----------
@tool
def search_local_kb(query: str, top_k: int = 3) -> str:
"""Semantic search in the local ChromaDB knowledge base."""
retriever = vectorstore.as_retriever(search_kwargs={"k": top_k})
docs = retriever.get_relevant_documents(query)
if not docs:
return "No relevant documents found in local KB."
return "\n---\n".join(doc.page_content for doc in docs)
@tool
def web_search(query: str) -> str:
"""Web search using Tavily."""
tavily = TavilySearchResults(api_key=os.getenv("TAVILY_API_KEY"))
results = tavily.run(query)
if not results:
return "No web results found."
return "\n---\n".join(f"{r['title']}\n{r['url']}\n{r.get('content', '')}" for r in results)
# ---------- Agent ----------
agent = create_deep_agent(
model=llm,
tools=[search_local_kb, web_search],
backend=backend,
system_prompt=(
"You are an AI assistant that answers user questions.\n"
"If the question is about information that should be in the local knowledge base,\n"
"use the search_local_kb tool.\n"
"If the question requires uptodate information from the web,\n"
"use the web_search tool.\n"
"Always indicate the source of the answer in the format:\n"
"[Source: chromadb] or [Source: tavily] before the answer."
),
)
# ---------- CLI ----------
async def main():
print("RAG Agent with ChromaDB and Tavily. Type 'exit' to quit.")
while True:
user_input = input("\nЗапрос: ")
if user_input.lower() in {"exit", "quit"}:
print("Goodbye!")
break
result = await agent.ainvoke(
{"messages": [HumanMessage(content=user_input)]},
{"configurable": {"thread_id": "session-1"}},
)
# The agent returns a list of messages; the last is the assistant reply
reply = result["messages"][-1].content
print(f"\n{reply}")
if __name__ == "__main__":
asyncio.run(main())