Экзамен: RAG-агент с ChromaDB и веб-поиском: main.py

This commit is contained in:
2026-05-28 17:08:48 +00:00
parent a0b29b5f78
commit d7a4e11b6f
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
main.py – CLI чат‑цикл для RAG‑агента с ChromaDB и веб‑поиском через Tavily.
"""
import os
from pathlib import Path
from dotenv import load_dotenv
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_openai_tools_agent
# --------------------------------------------------------------------------- #
# 1. Загрузка переменных окружения (TAVILY_API_KEY)
# --------------------------------------------------------------------------- #
load_dotenv()
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
if not TAVILY_API_KEY:
raise RuntimeError("Не найдено переменной окружения TAVILY_API_KEY")
# --------------------------------------------------------------------------- #
# 2. Векторное хранилище ChromaDB
# --------------------------------------------------------------------------- #
CHROMA_DIR = Path("./chroma_db")
CHROMA_DIR.mkdir(parents=True, exist_ok=True)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma(
persist_directory=str(CHROMA_DIR),
embedding_function=embeddings,
)
# --------------------------------------------------------------------------- #
# 3. Инструменты агента
# --------------------------------------------------------------------------- #
@tool("search_local_kb")
def search_local_kb(query: str, top_k: int = 5) -> str:
"""
Семантический поиск в локальной базе знаний (ChromaDB).
Возвращает объединённый текст найденных документов.
"""
retriever = vectorstore.as_retriever(search_kwargs={"k": top_k})
docs = retriever.invoke(query)
if not docs:
return "Ничего не найдено."
# Собираем содержимое всех документов
return "\n\n".join(doc.page_content for doc in docs)
@tool("web_search")
def web_search(query: str) -> str:
"""
Поиск в интернете через Tavily.
Возвращает краткое резюме результатов поиска.
"""
from tavily import TavilyClient
client = TavilyClient(api_key=TAVILY_API_KEY)
results = client.search(query=query, max_results=3)
if not results:
return "Ничего не найдено в интернете."
# Формируем строку с заголовками и ссылками
snippets = []
for r in results:
snippets.append(f"{r['title']}\n{r['url']}")
return "\n\n".join(snippets)
# --------------------------------------------------------------------------- #
# 4. Создание агента
# --------------------------------------------------------------------------- #
llm = ChatOllama(model="llama3", temperature=0)
tools = [search_local_kb, web_search]
agent = create_openai_tools_agent(
llm,
tools,
system_message="""
Вы – интеллектуальный агент, который отвечает на вопросы пользователя.
Если вопрос относится к локальным документам (например, конспектам, заметкам),
используйте инструмент «search_local_kb».
Если требуется актуальная информация из интернета (новости, факты), используйте
инструмент «web_search».
После выполнения запроса возвращайте ответ в формате:
[Источник] <текст ответа>
Где источник – либо «chromadb», либо «tavily».
""",
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False)
# --------------------------------------------------------------------------- #
# 5. Чат‑цикл CLI
# --------------------------------------------------------------------------- #
def main() -> None:
print("=== RAG‑агент с ChromaDB и Tavily ===")
print("Введите запрос (или 'exit' для выхода):")
while True:
user_input = input("\n> ").strip()
if not user_input or user_input.lower() == "exit":
print("До свидания!")
break
# Запускаем агента
try:
result = agent_executor.invoke({"input": user_input})
except Exception as e:
print(f"Ошибка при обработке запроса: {e}")
continue
# Результат – строка вида "[Источник] ...". Если формат не соответствует,
# просто выводим как есть.
if isinstance(result, str) and result.startswith("["):
try:
source_part, answer = result.split("]", 1)
source = source_part.strip("[")
print(f"\nОтвет: {answer.strip()}")
print(f"Источник: {source.lower()}")
except ValueError:
# Не удалось распарсить – выводим полностью
print("\nОтвет:", result)
else:
print("\nОтвет:", result)
if __name__ == "__main__":
main()