Files
task-6a02e23da6fe2e4ac16acf65/main.py
T
2026-06-16 16:49:08 +00:00

170 lines
6.9 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.
# main.py
# Полностью рабочий пример агента с RAG‑памятью на Qdrant и OpenRouter
# Использует deepagents, langchainopenai, langchainqdrant, langchaincore
# Запуск: python main.py
import os
import asyncio
from pathlib import Path
from typing import List
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.documents import Document
from langchain_qdrant import QdrantVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.tools import tool
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
from langchain_core.messages import HumanMessage
# ---------------------------------------------------------------------------
# Конфигурация LLM и 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"),
)
# ---------------------------------------------------------------------------
# Qdrant клиент и коллекция
# ---------------------------------------------------------------------------
# Предполагается, что Qdrant запущен локально на порту 6333
qdrant_url = "http://localhost:6333"
collection_name = "knowledge_base"
vector_store = QdrantVectorStore(
embeddings=embeddings,
url=qdrant_url,
collection_name=collection_name,
# Если коллекция не существует, она будет создана автоматически
)
# ---------------------------------------------------------------------------
# Чанкинг
# ---------------------------------------------------------------------------
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
# ---------------------------------------------------------------------------
# Инструменты для агента
# ---------------------------------------------------------------------------
@tool
def search_knowledge_base(query: str, max_results: int = 5) -> str:
"""Semantic search in the knowledge base.
Returns a formatted string with the top results.
"""
results = vector_store.similarity_search_with_score(query, k=max_results)
if not results:
return "No relevant documents found."
formatted = []
for doc, score in results:
formatted.append(f"Title: {doc.metadata.get('title', 'Untitled')}\nScore: {score:.4f}\nContent: {doc.page_content[:200]}...\n")
return "\n".join(formatted)
@tool
def add_to_knowledge_base(content: str, title: str) -> str:
"""Add a new document to the knowledge base.
The content is split into chunks, embedded and stored.
"""
# Split into chunks
chunks = text_splitter.split_text(content)
docs: List[Document] = []
for i, chunk in enumerate(chunks):
docs.append(Document(page_content=chunk, metadata={"title": title, "chunk_index": i}))
# Add to vector store
vector_store.add_documents(docs)
return f"Added {len(docs)} chunks for document '{title}'."
# ---------------------------------------------------------------------------
# Backend для deepagents
# ---------------------------------------------------------------------------
backend = CompositeBackend(
default=LocalShellBackend(root_dir="./workspace", virtual_mode=True, inherit_env=True),
routes={},
)
# ---------------------------------------------------------------------------
# Создание агента
# ---------------------------------------------------------------------------
agent = create_deep_agent(
model=llm,
tools=[search_knowledge_base, add_to_knowledge_base],
backend=backend,
system_prompt="You are a helpful assistant with access to a knowledge base. Use the provided tools to search and add information."
)
# ---------------------------------------------------------------------------
# Инициализация: загрузка документов из директории
# ---------------------------------------------------------------------------
DOCS_DIR = Path("./docs")
async def load_documents_from_dir(directory: Path):
if not directory.exists():
return
for file_path in directory.rglob("*.txt"):
content = file_path.read_text(encoding="utf-8")
title = file_path.stem
await agent.ainvoke(
{"messages": [HumanMessage(content=f"/add {title}")], "content": content},
{"configurable": {"thread_id": "init-session"}},
)
# ---------------------------------------------------------------------------
# Интерактивный клиент
# ---------------------------------------------------------------------------
async def interactive_loop():
print("Welcome to the RAG agent. Commands: /add <title>, /search <query>, /quit")
thread_id = "interactive-session"
while True:
user_input = input("> ")
if user_input.strip() == "/quit":
print("Goodbye!")
break
if user_input.startswith("/add "):
title = user_input[5:].strip()
print("Enter content (end with a single line containing only 'END'): ")
lines = []
while True:
line = input()
if line.strip() == "END":
break
lines.append(line)
content = "\n".join(lines)
await agent.ainvoke(
{"messages": [HumanMessage(content=f"/add {title}")], "content": content},
{"configurable": {"thread_id": thread_id}},
)
print(f"Document '{title}' added.")
elif user_input.startswith("/search "):
query = user_input[8:].strip()
result = await agent.ainvoke(
{"messages": [HumanMessage(content=f"/search {query}")]},
{"configurable": {"thread_id": thread_id}},
)
print(result["messages"][-1].content)
else:
# обычный запрос к LLM
result = await agent.ainvoke(
{"messages": [HumanMessage(content=user_input)]},
{"configurable": {"thread_id": thread_id}},
)
print(result["messages"][-1].content)
# ---------------------------------------------------------------------------
# Основной запуск
# ---------------------------------------------------------------------------
async def main():
# Загрузим документы из каталога docs при старте
await load_documents_from_dir(DOCS_DIR)
await interactive_loop()
if __name__ == "__main__":
asyncio.run(main())