diff --git a/main.py b/main.py index 4075e5a..00d383d 100644 --- a/main.py +++ b/main.py @@ -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}" -# Deep agent -agent = create_deep_agent( - llm=llm, - tools=[web_search], - 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.", + +@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, ) -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 +_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() в конце +""" + +agent = create_deep_agent( + model=llm, + tools=[web_search, export_virtual_files], + backend=backend, + system_prompt=SYSTEM_PROMPT, +) + +# --------------------------------------------------------------------------- +# 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))