feat: full deep agent with virtual FS and web search
This commit is contained in:
@@ -1,12 +1,36 @@
|
||||
import os
|
||||
import asyncio
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.tools import tool
|
||||
from deepagents import create_deep_agent
|
||||
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
|
||||
"""
|
||||
Deep Agent — поисковый агент с виртуальной ФС на основе deepagents.
|
||||
|
||||
Агент умеет:
|
||||
1. Искать информацию в интернете (DuckDuckGo, без API-ключа)
|
||||
2. Создавать и редактировать файлы в виртуальной ФС через write_file / edit_file
|
||||
3. По завершении работы выгружает все виртуальные файлы в реальную ФС (./output/)
|
||||
|
||||
Использование:
|
||||
python main.py
|
||||
python main.py "что такое LangGraph"
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from langchain.tools import tool
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from deepagents import create_deep_agent
|
||||
from deepagents.backends import CompositeBackend, FilesystemBackend, LocalShellBackend
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LLM
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# LLM via BroJS
|
||||
llm = ChatOpenAI(
|
||||
model="openai/gpt-oss-20b:free",
|
||||
base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1",
|
||||
@@ -14,43 +38,149 @@ llm = ChatOpenAI(
|
||||
temperature=0.5,
|
||||
)
|
||||
|
||||
# Backend: local shell + virtual FS
|
||||
backend = CompositeBackend([
|
||||
LocalShellBackend(workspace_dir="./workspace"),
|
||||
FilesystemBackend(),
|
||||
])
|
||||
# ---------------------------------------------------------------------------
|
||||
# Workspace dirs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
VIRTUAL_ROOT = Path("./workspace")
|
||||
REAL_OUT = Path("./output")
|
||||
VIRTUAL_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
REAL_OUT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tools
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Web search tool using duckduckgo-search
|
||||
@tool
|
||||
def web_search(query: str) -> str:
|
||||
"""Search the web for information."""
|
||||
"""Ищет информацию в интернете и возвращает краткие сниппеты.
|
||||
|
||||
Args:
|
||||
query: поисковый запрос на любом языке
|
||||
"""
|
||||
try:
|
||||
from duckduckgo_search import DDGS
|
||||
with DDGS() as ddgs:
|
||||
results = list(ddgs.text(query, max_results=5))
|
||||
return "\n".join(f"{r['title']}: {r['body']}" for r in results)
|
||||
except Exception as e:
|
||||
return f"Search error: {e}"
|
||||
results = list(ddgs.text(query, max_results=7))
|
||||
if not results:
|
||||
return "Ничего не найдено."
|
||||
lines = []
|
||||
for r in results:
|
||||
lines.append(f"### {r.get('title', '')}")
|
||||
lines.append(r.get("body", ""))
|
||||
lines.append(f"URL: {r.get('href', '')}")
|
||||
lines.append("")
|
||||
return "
|
||||
".join(lines)
|
||||
except Exception as exc:
|
||||
return f"Ошибка поиска: {exc}"
|
||||
|
||||
|
||||
@tool
|
||||
def export_virtual_files() -> str:
|
||||
"""Копирует все файлы из виртуальной ФС (workspace/) в реальную папку output/.
|
||||
|
||||
Вызывай этот инструмент в самом конце, после того как все нужные файлы созданы.
|
||||
"""
|
||||
try:
|
||||
copied = []
|
||||
for src in VIRTUAL_ROOT.rglob("*"):
|
||||
if src.is_file():
|
||||
rel = src.relative_to(VIRTUAL_ROOT)
|
||||
dst = REAL_OUT / rel
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
copied.append(str(rel))
|
||||
if copied:
|
||||
return f"Выгружено {len(copied)} файл(ов) в {REAL_OUT}:\n" + "\n".join(f" {f}" for f in copied)
|
||||
return f"Виртуальная ФС пуста — нет файлов для выгрузки."
|
||||
except Exception as exc:
|
||||
return f"Ошибка экспорта: {exc}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backend — виртуальная ФС поверх workspace/
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_shell_backend = LocalShellBackend(
|
||||
root_dir=str(VIRTUAL_ROOT),
|
||||
virtual_mode=True,
|
||||
inherit_env=True,
|
||||
)
|
||||
|
||||
_fs_backend = FilesystemBackend(
|
||||
root_dir=str(VIRTUAL_ROOT),
|
||||
virtual_mode=True,
|
||||
)
|
||||
|
||||
backend = CompositeBackend(
|
||||
default=_shell_backend,
|
||||
routes={"/fs/": _fs_backend},
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Agent
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SYSTEM_PROMPT = """\
|
||||
Ты — исследовательский агент с виртуальной файловой системой.
|
||||
|
||||
## Доступные инструменты
|
||||
- web_search(query) — ищет информацию в интернете, возвращает сниппеты
|
||||
- write_file(path, content) — создаёт/перезаписывает файл в виртуальной ФС
|
||||
- edit_file(path, old, new) — редактирует файл
|
||||
- read_file(path) — читает файл
|
||||
- ls(path) — список файлов
|
||||
- export_virtual_files() — копирует все файлы из виртуальной ФС в ./output/
|
||||
|
||||
## Порядок работы
|
||||
1. Найди информацию по запросу через web_search
|
||||
2. Обработай и сохрани результаты в один или несколько файлов через write_file
|
||||
3. Проверь сохранённые файлы через read_file
|
||||
4. В самом конце вызови export_virtual_files() чтобы выгрузить файлы в реальную ФС
|
||||
|
||||
## Требования к файлам
|
||||
- Сохраняй информацию в структурированном виде (markdown, JSON или plain text)
|
||||
- Имена файлов — осмысленные (например results.md, summary.txt)
|
||||
- Обязательно вызови export_virtual_files() в конце
|
||||
"""
|
||||
|
||||
# Deep agent
|
||||
agent = create_deep_agent(
|
||||
llm=llm,
|
||||
tools=[web_search],
|
||||
model=llm,
|
||||
tools=[web_search, export_virtual_files],
|
||||
backend=backend,
|
||||
system_prompt="You are a helpful research agent that can search the web and create virtual files. At the end, export files to the real filesystem.",
|
||||
system_prompt=SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
async def main():
|
||||
# Example: ask agent to research "Python async" and create a file with results
|
||||
user_query = "Python async programming" # can be replaced by user input
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def run(query: str) -> None:
|
||||
print(f"Запрос: {query}\n")
|
||||
config = {"configurable": {"thread_id": f"search-{hash(query) & 0xFFFF:04x}"}}
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content=user_query)]},
|
||||
{"configurable": {"thread_id": "session-1"}},
|
||||
{"messages": [HumanMessage(content=query)]},
|
||||
config,
|
||||
)
|
||||
# The agent will create virtual files during its execution.
|
||||
# After completion, export virtual FS to real FS
|
||||
await backend.export_to_real_fs("./exported_files")
|
||||
print("Exported virtual files to ./exported_files")
|
||||
last = result["messages"][-1]
|
||||
print("\n=== Ответ агента ===")
|
||||
print(getattr(last, "content", str(last)))
|
||||
print("\n=== Файлы в output/ ===")
|
||||
out_files = list(REAL_OUT.rglob("*"))
|
||||
if out_files:
|
||||
for f in out_files:
|
||||
if f.is_file():
|
||||
print(f" {f.relative_to(REAL_OUT)} ({f.stat().st_size} байт)")
|
||||
else:
|
||||
print(" (нет файлов)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
query = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else (
|
||||
"Найди информацию о фреймворке LangGraph: что это, основные концепции, "
|
||||
"примеры использования. Сохрани результаты в файл langgraph_research.md"
|
||||
)
|
||||
asyncio.run(run(query))
|
||||
|
||||
Reference in New Issue
Block a user