Files
task-6a1864f78a94f887e50d46da/main.py
T
2026-06-04 16:18:19 +00:00

119 lines
4.2 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 dotenv import load_dotenv
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from langchain_tavily import TavilySearchResults
from langchain.tools import tool
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
from langchain_core.messages import HumanMessage
# Load environment variables
load_dotenv()
# ---------- LLM and Embeddings ----------
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 = OpenAIEmbeddings(
model="text-embedding-3-small",
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENAI_API_KEY"),
)
# ---------- Vector Store ----------
CHROMA_DIR = "./chroma_db"
vector_store = Chroma(collection_name="knowledge", embedding_function=embeddings, persist_directory=CHROMA_DIR)
# ---------- Document Loader ----------
def load_documents(directory: str):
"""Read .txt/.md files, split into chunks, and add to Chroma collection."""
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
docs = []
for root, _, files in os.walk(directory):
for file in files:
if file.lower().endswith(('.txt', '.md')):
path = os.path.join(root, file)
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
chunks = splitter.split_text(text)
docs.extend([Document(page_content=c, metadata={"source": path}) for c in chunks])
if docs:
vector_store.add_documents(docs)
vector_store.persist()
# Load documents once at startup
if not os.path.exists(CHROMA_DIR) or not os.listdir(CHROMA_DIR):
load_documents("./documents")
# ---------- Tavily Search ----------
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
if not TAVILY_API_KEY:
raise ValueError("TAVILY_API_KEY not set in .env")
# ---------- Tools ----------
@tool
def search_local_kb(query: str, top_k: int = 3) -> str:
"""Semantic search in the local Chroma knowledge base."""
docs = vector_store.similarity_search(query, k=top_k)
if not docs:
return "No relevant local knowledge found."
return "\n---\n".join([f"{d.metadata.get('source', 'unknown')}\n{d.page_content}" for d in docs])
@tool
def web_search(query: str) -> str:
"""Web search using Tavily."""
results = TavilySearchResults(api_key=TAVILY_API_KEY, max_results=3)
search_results = results.run(query)
if not search_results:
return "No web results found."
return "\n---\n".join([f"{r['title']}\n{r['content']}" for r in search_results])
# ---------- Backend ----------
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# ---------- Agent ----------
agent = create_deep_agent(
model=llm,
tools=[search_local_kb, web_search],
backend=backend,
system_prompt=(
"You are a RAG agent with a local knowledge base and web search capability. "
"When a user asks a question, decide whether the answer can be found in the local "
"knowledge base or requires uptodate information from the web. Use the tool "
"search_local_kb for local queries and web_search for web queries. "
"Always indicate the source of the information in your final answer: "
"(chromadb) or (tavily)."
),
)
# ---------- CLI ----------
async def main():
print("RAG Agent ready. 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())