Files

214 lines
9.0 KiB
Python

"""
Deep Search Agent — самописный поисковый агент на основе deep agents from scratch.
Возможности:
1. Поиск информации в интернете (DuckDuckGo)
2. Виртуальная файловая система в памяти
3. Выгрузка виртуальных файлов в реальную файловую систему
Основан на: https://github.com/langchain-ai/deep-agents-from-scratch
"""
import os
import json
import datetime
from typing import Optional
from pydantic import BaseModel, Field
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
# ═══════════════════════════════════════════════════════════════════════════════
# Виртуальная файловая система
# ═══════════════════════════════════════════════════════════════════════════════
class VirtualFileSystem:
"""Простая файловая система в памяти."""
def __init__(self):
self.files: dict[str, str] = {}
def read(self, file_path: str) -> str:
"""Прочитать файл из виртуальной ФС."""
if file_path not in self.files:
return f"Error: File '{file_path}' not found."
return self.files[file_path]
def write(self, file_path: str, content: str) -> str:
"""Записать (или перезаписать) файл в виртуальной ФС."""
self.files[file_path] = content
return f"Successfully wrote {len(content)} bytes to '{file_path}'."
def list_files(self) -> str:
"""Список всех файлов в виртуальной ФС."""
if not self.files:
return "No files in virtual filesystem."
lines = [f" {path} ({len(content)} bytes)"
for path, content in self.files.items()]
return "Virtual filesystem:\n" + "\n".join(lines)
def export_to_disk(self, output_dir: str = "./output") -> str:
"""Выгрузить все виртуальные файлы на реальный диск."""
os.makedirs(output_dir, exist_ok=True)
exported = []
for path, content in self.files.items():
full_path = os.path.join(output_dir, path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "w", encoding="utf-8") as f:
f.write(content)
exported.append(full_path)
return f"Exported {len(exported)} files to '{output_dir}/':\n" + \
"\n".join(f" {p}" for p in exported)
# Глобальный экземпляр виртуальной ФС
vfs = VirtualFileSystem()
# ═══════════════════════════════════════════════════════════════════════════════
# Инструменты агента
# ═══════════════════════════════════════════════════════════════════════════════
@tool
def search_web(query: str) -> str:
"""
Поиск информации в интернете через DuckDuckGo.
Используй для поиска фактов, новостей, документации и любой информации.
Вход: поисковый запрос (строка).
"""
try:
from duckduckgo_search import DDGS
with DDGS() as ddgs:
results = list(ddgs.text(query, max_results=5))
if not results:
return "No results found."
output = []
for i, r in enumerate(results, 1):
output.append(
f"[{i}] {r.get('title', 'No title')}\n"
f" URL: {r.get('href', 'N/A')}\n"
f" {r.get('body', 'No description')}"
)
return "\n\n".join(output)
except ImportError:
return ("Error: duckduckgo_search not installed. "
"Run: pip install duckduckgo-search")
@tool
def write_file(file_path: str, content: str) -> str:
"""
Записать файл в виртуальную файловую систему.
Вход: file_path — путь файла (например, 'report.md', 'data/notes.txt'),
content — содержимое файла.
"""
return vfs.write(file_path, content)
@tool
def read_file(file_path: str) -> str:
"""
Прочитать файл из виртуальной файловой системы.
Вход: file_path — путь файла.
"""
return vfs.read(file_path)
@tool
def list_files() -> str:
"""Показать список всех файлов в виртуальной файловой системе."""
return vfs.list_files()
@tool
def export_files(output_dir: str = "./output") -> str:
"""
Выгрузить все виртуальные файлы на реальный диск.
Вход: output_dir — директория для выгрузки (по умолчанию './output').
"""
return vfs.export_to_disk(output_dir)
TOOLS = [search_web, write_file, read_file, list_files, export_files]
# ═══════════════════════════════════════════════════════════════════════════════
# Сборка агента
# ═══════════════════════════════════════════════════════════════════════════════
SYSTEM_PROMPT = """Ты — Deep Search Agent, автономный поисковый агент.
Твои возможности:
- Искать информацию в интернете (search_web)
- Создавать и редактировать файлы в виртуальной файловой системе (write_file, read_file)
- Просматривать список файлов (list_files)
- Выгружать файлы на реальный диск (export_files)
Правила работы:
1. Сначала исследуй тему через search_web, собери информацию
2. Сохраняй полезные находки в виртуальную файловую систему через write_file
3. При необходимости читай ранее сохранённые файлы через read_file
4. В конце работы ВСЕГДА выгружай файлы на диск через export_files
5. Дай краткий итог выполненной работы
Будь методичен: ищи → анализируй → сохраняй → выгружай.
"""
def build_agent() -> tuple:
"""Создать и вернуть агента + виртуальную ФС."""
api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
api_key = os.environ.get("OPENAI_API_KEY", "")
model_name = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
llm = ChatOpenAI(
model=model_name,
openai_api_base=api_base,
openai_api_key=api_key,
temperature=0,
)
agent = create_react_agent(
model=llm,
tools=TOOLS,
prompt=SYSTEM_PROMPT,
)
return agent, vfs
def run_agent(task: str, output_dir: str = "./output") -> dict:
"""
Запустить агента на выполнение задачи.
Возвращает словарь с результатом и списком файлов.
"""
agent, vfs = build_agent()
# Добавляем инструкцию о выгрузке в конец задачи
full_task = (
f"{task}\n\n"
f"После завершения исследования ОБЯЗАТЕЛЬНО:\n"
f"1. Вызови list_files() чтобы увидеть все созданные файлы\n"
f"2. Вызови export_files('{output_dir}') чтобы выгрузить файлы на диск"
)
result = agent.invoke(
{"messages": [{"role": "user", "content": full_task}]}
)
# Собираем ответ
messages = result.get("messages", [])
last_ai_msg = None
for msg in reversed(messages):
if hasattr(msg, "content") and msg.type == "ai":
last_ai_msg = msg.content
break
return {
"response": last_ai_msg,
"files_created": list(vfs.files.keys()),
"output_dir": output_dir,
}