fix(needs_fixes): 1 исправлений, 0 отстояно — main.py

This commit is contained in:
+56 -63
View File
@@ -1,13 +1,11 @@
import os import os
import asyncio import asyncio
from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma from langchain_chroma import Chroma
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_tavily import TavilySearchResults
from langchain.tools import tool from langchain.tools import tool
from deepagents import create_deep_agent from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
@@ -15,97 +13,92 @@ from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeB
# Load environment variables # Load environment variables
load_dotenv() 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 ---------- # ---------- Vector Store ----------
CHROMA_DIR = Path("./chroma_db")
vector_store = Chroma( def create_vectorstore(persist_directory="./chroma_db"):
collection_name="knowledge", """Create a Chroma vector store with Ollama embeddings."""
embedding_function=embeddings, embeddings = OllamaEmbeddings(model="nomic-embed-text")
persist_directory=str(CHROMA_DIR), return Chroma(persist_directory=persist_directory, embedding_function=embeddings)
)
def load_documents(directory, vectorstore):
"""Load .txt and .md files from *directory*, chunk them, and add to *vectorstore*.
The function preserves the file name in metadata for later reference.
"""
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
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()
docs = splitter.split_text(text)
documents = [Document(page_content=chunk, metadata={"source": fname}) for chunk in docs]
vectorstore.add_documents(documents)
# ---------- Tools ---------- # ---------- Tools ----------
vectorstore = create_vectorstore()
@tool @tool
def search_local_kb(query: str, top_k: int = 3) -> str: def search_local_kb(query: str, top_k: int = 3) -> str:
"""Semantic search in the local knowledge base.""" """Semantic search in the local knowledge base (ChromaDB)."""
docs = vector_store.similarity_search(query, k=top_k) retriever = vectorstore.as_retriever(search_kwargs={"k": top_k})
docs = retriever.get_relevant_documents(query)
if not docs: if not docs:
return "No relevant documents found in local KB." return "No relevant local knowledge found."
return "\n---\n".join(f"{i+1}. {doc.page_content[:200]}..." for i, doc in enumerate(docs)) return "\n---\n".join([f"{doc.metadata.get('source', 'unknown')}\n{doc.page_content}" for doc in docs])
@tool @tool
def web_search(query: str) -> str: def web_search(query: str) -> str:
"""Web search using Tavily.""" """Web search using Tavily."""
tavily = TavilySearchResults(api_key=os.getenv("TAVILY_API_KEY")) from tavily import TavilyClient
results = tavily.run(query) client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
results = client.search(query, max_results=3)
if not results: if not results:
return "No web results found." return "No web results found."
return "\n---\n".join(f"{i+1}. {res['title']}\n{res['content'][:200]}..." for i, res in enumerate(results)) return "\n---\n".join([f"{r['title']}\n{r['content']}" for r in results])
# ---------- Agent ----------
llm = ChatOllama(model="llama3", temperature=0.0)
# ---------- Backend ----------
backend = CompositeBackend([ backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"), LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(), FilesystemBackend(),
]) ])
# ---------- Agent ---------- system_prompt = (
"You are an AI assistant with access to two tools: "
"search_local_kb for local knowledge and web_search for up-to-date information. "
"When answering a user query, first decide which tool is appropriate. "
"If the answer can be derived from the local documents, use search_local_kb; "
"otherwise use web_search. "
"Always indicate the source of the information in your response: "
"[Local KB] or [Web Search]."
)
agent = create_deep_agent( agent = create_deep_agent(
model=llm, model=llm,
tools=[search_local_kb, web_search], tools=[search_local_kb, web_search],
backend=backend, backend=backend,
system_prompt=( system_prompt=system_prompt,
"You are a helpful assistant. For any user query, decide whether to use the local knowledge base or perform a web search. "
"If the query is about recent events, news, or requires uptodate information, use the web_search tool. "
"Otherwise, use search_local_kb. "
"Always return the source used in the response (either 'chromadb' or 'tavily')."
),
) )
# ---------- Document Loader ----------
def load_documents(directory: str, vectorstore: Chroma):
"""Load .txt and .md files from a directory, chunk them, and add to the vector store."""
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
docs = []
for file_path in Path(directory).glob("**/*"):
if file_path.suffix.lower() in {".txt", ".md"}:
text = file_path.read_text(encoding="utf-8")
chunks = splitter.split_text(text)
docs.extend([Document(page_content=chunk, metadata={"source": str(file_path)}) for chunk in chunks])
if docs:
vectorstore.add_documents(docs)
vectorstore.persist()
# ---------- CLI ----------
async def main(): async def main():
# Ensure vector store is loaded print("RAG Agent ready. Type 'exit' to quit.")
if not CHROMA_DIR.exists() or not any(CHROMA_DIR.iterdir()):
print("Loading documents into ChromaDB…")
load_documents("./documents", vector_store)
print("RAG agent ready. Type 'exit' to quit.")
while True: while True:
user_input = input("\nЗапрос: ") user_input = input("\nЗапрос: ")
if user_input.lower() in {"exit", "quit"}: if user_input.strip().lower() == "exit":
print("Goodbye!")
break break
result = await agent.ainvoke( result = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]}, {"messages": [{"role": "user", "content": user_input}]},
{"configurable": {"thread_id": "session-1"}}, {"configurable": {"thread_id": "session-1"}},
) )
# Extract last message content # The last message is the assistant's reply
content = result["messages"][-1].content reply = result["messages"][-1].content
print(f"\nОтвет:\n{content}") print(f"\n{reply}")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())