Обновить solution.py
This commit is contained in:
+40
-26
@@ -2,8 +2,11 @@
|
|||||||
rag_agent.py — AI-агент с RAG-памятью на базе Ollama + Qdrant.
|
rag_agent.py — AI-агент с RAG-памятью на базе Ollama + Qdrant.
|
||||||
|
|
||||||
Стек: langchain-ollama, langchain-qdrant, langgraph, qdrant-client, rich
|
Стек: langchain-ollama, langchain-qdrant, langgraph, qdrant-client, rich
|
||||||
Установка: pip install langchain langchain-core langchain-ollama langchain-qdrant
|
Установка:
|
||||||
langchain-text-splitters langgraph qdrant-client rich
|
ollama pull llama3
|
||||||
|
ollama pull nomic-embed-text
|
||||||
|
pip install langchain langchain-core langchain-ollama langchain-qdrant \
|
||||||
|
langchain-text-splitters langgraph qdrant-client rich
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -15,7 +18,7 @@ from langchain_qdrant import QdrantVectorStore
|
|||||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from langchain_core.documents import Document
|
from langchain_core.documents import Document
|
||||||
from langgraph.prebuilt import create_react_agent
|
from langgraph.prebuilt import create_react_agent as create_agent # алиас
|
||||||
from qdrant_client import QdrantClient as _QdrantClient
|
from qdrant_client import QdrantClient as _QdrantClient
|
||||||
from qdrant_client.http.models import Distance, VectorParams
|
from qdrant_client.http.models import Distance, VectorParams
|
||||||
|
|
||||||
@@ -23,11 +26,11 @@ from qdrant_client.http.models import Distance, VectorParams
|
|||||||
# Конфигурация
|
# Конфигурация
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost")
|
QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost")
|
||||||
QDRANT_PORT = int(os.getenv("QDRANT_PORT", "6333"))
|
QDRANT_PORT = int(os.getenv("QDRANT_PORT", "6333"))
|
||||||
COLLECTION_NAME = os.getenv("QDRANT_COLLECTION", "rag_collection")
|
COLLECTION_NAME = os.getenv("QDRANT_COLLECTION", "rag_collection")
|
||||||
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "nomic-embed-text")
|
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "nomic-embed-text")
|
||||||
LLM_MODEL = os.getenv("LLM_MODEL", "llama3")
|
LLM_MODEL = os.getenv("LLM_MODEL", "llama3")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Векторное хранилище (Qdrant + Ollama-эмбеддинги)
|
# Векторное хранилище (Qdrant + Ollama-эмбеддинги)
|
||||||
@@ -65,7 +68,7 @@ class RagVectorStore:
|
|||||||
|
|
||||||
def search(self, query: str, k: int = 5) -> List[tuple]:
|
def search(self, query: str, k: int = 5) -> List[tuple]:
|
||||||
# similarity_search_with_relevance_scores возвращает score в [0..1],
|
# similarity_search_with_relevance_scores возвращает score в [0..1],
|
||||||
# где 1.0 = максимальная релевантность (исправлено с with_score)
|
# где 1.0 = максимальная релевантность.
|
||||||
results = self.store.similarity_search_with_relevance_scores(query, k=k)
|
results = self.store.similarity_search_with_relevance_scores(query, k=k)
|
||||||
return [(doc.page_content, score) for doc, score in results]
|
return [(doc.page_content, score) for doc, score in results]
|
||||||
|
|
||||||
@@ -144,7 +147,7 @@ def add_to_knowledge_base(content: str, title: str = "") -> str:
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Агент (create_react_agent из langgraph — актуальный API v0.3+)
|
# Агент (create_agent — обёртка над create_react_agent из langgraph v0.3+)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
llm = ChatOllama(model=LLM_MODEL, temperature=0.3)
|
llm = ChatOllama(model=LLM_MODEL, temperature=0.3)
|
||||||
@@ -159,7 +162,7 @@ SYSTEM_PROMPT = """Ты — интеллектуальный AI-ассистен
|
|||||||
5. Отвечай на том же языке, на котором задан вопрос.
|
5. Отвечай на том же языке, на котором задан вопрос.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
agent = create_react_agent(
|
agent = create_agent(
|
||||||
model=llm,
|
model=llm,
|
||||||
tools=[search_knowledge_base, add_to_knowledge_base],
|
tools=[search_knowledge_base, add_to_knowledge_base],
|
||||||
prompt=SYSTEM_PROMPT,
|
prompt=SYSTEM_PROMPT,
|
||||||
@@ -176,7 +179,7 @@ def run_agent(user_input: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Загрузка документов из директории
|
# Клиент инициализации: загрузка документов из директории
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def load_documents_from_directory(directory: str) -> int:
|
def load_documents_from_directory(directory: str) -> int:
|
||||||
@@ -185,6 +188,9 @@ def load_documents_from_directory(directory: str) -> int:
|
|||||||
Возвращает количество загруженных файлов.
|
Возвращает количество загруженных файлов.
|
||||||
"""
|
"""
|
||||||
path = Path(directory)
|
path = Path(directory)
|
||||||
|
if not path.is_dir():
|
||||||
|
raise ValueError(f"Директория не найдена: {directory}")
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
for file_path in path.rglob("*"):
|
for file_path in path.rglob("*"):
|
||||||
if file_path.suffix.lower() in {".txt", ".md"}:
|
if file_path.suffix.lower() in {".txt", ".md"}:
|
||||||
@@ -198,7 +204,7 @@ def load_documents_from_directory(directory: str) -> int:
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Интерактивный CLI-клиент
|
# Интерактивный тестовый CLI-клиент
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -211,10 +217,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
"[bold]RAG-агент[/bold] · Qdrant + Ollama\n\n"
|
"[bold]RAG-агент[/bold] · Qdrant + Ollama\n\n"
|
||||||
"[cyan]/add <текст>[/cyan] — добавить текст в базу знаний\n"
|
"[cyan]/add <текст>[/cyan] — добавить текст в базу знаний\n"
|
||||||
"[cyan]/add-file <путь>[/cyan] — загрузить файл в базу знаний\n"
|
"[cyan]/add-file <путь>[/cyan] — загрузить файл в базу знаний\n"
|
||||||
"[cyan]/search <запрос>[/cyan] — прямой поиск (без агента)\n"
|
"[cyan]/load-dir <папка>[/cyan] — загрузить все .txt/.md из папки\n"
|
||||||
"[cyan]/quit[/cyan] — выйти",
|
"[cyan]/search <запрос>[/cyan] — прямой поиск (без агента)\n"
|
||||||
|
"[cyan]/quit[/cyan] — выйти",
|
||||||
border_style="dim",
|
border_style="dim",
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -228,12 +235,12 @@ if __name__ == "__main__":
|
|||||||
if not user_input:
|
if not user_input:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# /quit
|
# ── /quit ──────────────────────────────────────────────────────────
|
||||||
if user_input.lower() in ("/quit", "/exit"):
|
if user_input.lower() in ("/quit", "/exit"):
|
||||||
console.print("[yellow]Выход.[/yellow]")
|
console.print("[yellow]Выход.[/yellow]")
|
||||||
break
|
break
|
||||||
|
|
||||||
# /add-file <путь>
|
# ── /add-file <путь> ───────────────────────────────────────────────
|
||||||
elif user_input.lower().startswith("/add-file "):
|
elif user_input.lower().startswith("/add-file "):
|
||||||
file_path = Path(user_input[10:].strip())
|
file_path = Path(user_input[10:].strip())
|
||||||
if file_path.is_file():
|
if file_path.is_file():
|
||||||
@@ -245,25 +252,32 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
console.print(f"[red]Файл не найден: {file_path}[/red]")
|
console.print(f"[red]Файл не найден: {file_path}[/red]")
|
||||||
|
|
||||||
# /add <текст> ← исправлено: теперь принимает текст напрямую,
|
# ── /load-dir <папка> ──────────────────────────────────────────────
|
||||||
# а не только путь к файлу
|
elif user_input.lower().startswith("/load-dir "):
|
||||||
|
directory = user_input[10:].strip()
|
||||||
|
console.print(f"[dim]Загрузка из «{directory}»...[/dim]")
|
||||||
|
try:
|
||||||
|
n = load_documents_from_directory(directory)
|
||||||
|
console.print(f"[green]✓ Загружено файлов: {n}[/green]")
|
||||||
|
except ValueError as e:
|
||||||
|
console.print(f"[red]{e}[/red]")
|
||||||
|
|
||||||
|
# ── /add <текст или путь> ──────────────────────────────────────────
|
||||||
elif user_input.lower().startswith("/add "):
|
elif user_input.lower().startswith("/add "):
|
||||||
raw = user_input[5:].strip()
|
raw = user_input[5:].strip()
|
||||||
path = Path(raw)
|
path = Path(raw)
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
# если передан путь к существующему файлу — загружаем файл
|
|
||||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||||
result = add_to_knowledge_base.invoke(
|
result = add_to_knowledge_base.invoke(
|
||||||
{"content": text, "title": path.name}
|
{"content": text, "title": path.name}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# иначе сохраняем как текст напрямую
|
|
||||||
result = add_to_knowledge_base.invoke(
|
result = add_to_knowledge_base.invoke(
|
||||||
{"content": raw, "title": "manual"}
|
{"content": raw, "title": "manual"}
|
||||||
)
|
)
|
||||||
console.print(f"[green]✓ {result}[/green]")
|
console.print(f"[green]✓ {result}[/green]")
|
||||||
|
|
||||||
# /search <запрос>
|
# ── /search <запрос> ───────────────────────────────────────────────
|
||||||
elif user_input.lower().startswith("/search "):
|
elif user_input.lower().startswith("/search "):
|
||||||
query = user_input[8:].strip()
|
query = user_input[8:].strip()
|
||||||
console.print(Rule("Результаты поиска"))
|
console.print(Rule("Результаты поиска"))
|
||||||
@@ -278,11 +292,11 @@ if __name__ == "__main__":
|
|||||||
border_style="dim",
|
border_style="dim",
|
||||||
))
|
))
|
||||||
|
|
||||||
# /search без аргумента
|
# ── /search без аргумента ──────────────────────────────────────────
|
||||||
elif user_input.lower() == "/search":
|
elif user_input.lower() == "/search":
|
||||||
console.print("[yellow]Укажите запрос: /search <текст>[/yellow]")
|
console.print("[yellow]Укажите запрос: /search <текст>[/yellow]")
|
||||||
|
|
||||||
# запрос к агенту
|
# ── запрос к агенту ────────────────────────────────────────────────
|
||||||
else:
|
else:
|
||||||
console.print("[dim]Агент думает...[/dim]")
|
console.print("[dim]Агент думает...[/dim]")
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user