feat: deep search agent with virtual filesystem and web search

This commit is contained in:
2026-05-08 16:11:45 +03:00
commit 85d4ae32a8
5 changed files with 299 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
OPENAI_API_KEY=your-api-key-here
OPENAI_API_BASE=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
+40
View File
@@ -0,0 +1,40 @@
# Задание 3: Самописный поисковый агент (Deep Agent)
## Описание
Deep search agent на основе курса "deep agents from scratch".
Агент умеет:
1. Искать информацию в интернете (DuckDuckGo)
2. Создавать и управлять виртуальными файлами в памяти
3. Выгружать виртуальные файлы в реальную файловую систему
## Стек
- Python 3.10+
- langchain-core, langchain-openai, langgraph, duckduckgo-search, pydantic
## Установка
```bash
pip install -r requirements.txt
cp .env.example .env
# Заполнить OPENAI_API_KEY в .env
```
## Запуск
```bash
python main.py
```
## Архитектура
- `agent.py` — виртуальная файловая система, инструменты агента, сборка ReAct-агента
- `main.py` — точка входа с примером задания (исследование темы LangGraph)
## Как работает
1. Агент получает задачу
2. Ищет информацию через `search_web()`
3. Сохраняет результаты в виртуальную ФС через `write_file()`
4. В конце вызывает `export_files()` для выгрузки на реальный диск
+213
View File
@@ -0,0 +1,213 @@
"""
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,
}
+37
View File
@@ -0,0 +1,37 @@
"""
Deep Search Agent — точка входа.
Запуск:
python main.py
Переменные окружения:
OPENAI_API_BASE — URL API (по умолчанию https://api.openai.com/v1)
OPENAI_API_KEY — ключ API
OPENAI_MODEL — модель (по умолчанию gpt-4o-mini)
"""
from agent import run_agent
TASK = (
"Исследуй тему: 'Что такое LangGraph и как он работает?'. "
"Найди основную информацию, ключевые концепции и примеры использования. "
"Сохрани результаты в файл 'research/langgraph_overview.md'. "
"Также сохрани краткое резюме в 'research/summary.txt'."
)
if __name__ == "__main__":
print("=" * 60)
print(" Deep Search Agent — запуск")
print("=" * 60)
result = run_agent(TASK, output_dir="./output")
print("\n--- Итог работы агента ---")
print(result["response"])
print(f"\n--- Созданные файлы ({len(result['files_created'])}) ---")
for f in result["files_created"]:
print(f" {f}")
print(f"\nФайлы выгружены в: {result['output_dir']}/")
+6
View File
@@ -0,0 +1,6 @@
langchain-core>=0.3.0
langchain-openai>=0.2.0
langgraph>=0.2.0
duckduckgo-search>=6.0.0
pydantic>=2.0.0
python-dotenv>=1.0.0