add main.py

This commit is contained in:
2026-05-26 12:48:24 +00:00
parent deb4d57191
commit b02814fa38
+70 -162
View File
@@ -1,36 +1,10 @@
"""
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 os
import shutil import json
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 langchain_openai import ChatOpenAI
from langchain.tools import tool
from duckduckgo_search import DDGS
from deepagents import create_deep_agent # LLM configuration
from deepagents.backends import CompositeBackend, FilesystemBackend, LocalShellBackend
load_dotenv()
# ---------------------------------------------------------------------------
# LLM
# ---------------------------------------------------------------------------
llm = ChatOpenAI( llm = ChatOpenAI(
model="openai/gpt-oss-20b:free", model="openai/gpt-oss-20b:free",
base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1", base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1",
@@ -38,149 +12,83 @@ llm = ChatOpenAI(
temperature=0.5, temperature=0.5,
) )
# --------------------------------------------------------------------------- # Simple virtual file system
# Workspace dirs class VirtualFileSystem:
# --------------------------------------------------------------------------- def __init__(self):
self.files = {}
VIRTUAL_ROOT = Path("./workspace") def write(self, path: str, content: str):
REAL_OUT = Path("./output") self.files[path] = content
VIRTUAL_ROOT.mkdir(parents=True, exist_ok=True)
REAL_OUT.mkdir(parents=True, exist_ok=True)
# --------------------------------------------------------------------------- def list(self):
# Tools return list(self.files.keys())
# ---------------------------------------------------------------------------
def dump_to_real_fs(self, base_dir: str = "."):
for path, content in self.files.items():
full_path = os.path.join(base_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)
# Tool for web search using duckduckgo
@tool @tool
def web_search(query: str) -> str: def web_search(query: str) -> str:
"""Ищет информацию в интернете и возвращает краткие сниппеты. """Search the web for a query and return top 3 results as a formatted string."""
Args:
query: поисковый запрос на любом языке
"""
try: try:
from duckduckgo_search import DDGS
with DDGS() as ddgs: with DDGS() as ddgs:
results = list(ddgs.text(query, max_results=7)) results = list(ddgs.text(query, max_results=3))
if not results: if not results:
return "Ничего не найдено." return "No results found."
lines = [] formatted = []
for r in results: for i, r in enumerate(results, 1):
lines.append(f"### {r.get('title', '')}") formatted.append(f"{i}. {r['title']}\n{r['body']}\nURL: {r['href']}")
lines.append(r.get("body", "")) return "\n\n".join(formatted)
lines.append(f"URL: {r.get('href', '')}") except Exception as e:
lines.append("") return f"Search error: {e}"
return "
".join(lines)
except Exception as exc:
return f"Ошибка поиска: {exc}"
# Deep agent implementation
class DeepAgent:
def __init__(self, llm, tools):
self.llm = llm
self.tools = {t.__name__: t for t in tools}
self.vfs = VirtualFileSystem()
@tool def run(self, prompt: str):
def export_virtual_files() -> str: # Initial system message
"""Копирует все файлы из виртуальной ФС (workspace/) в реальную папку output/. system_msg = "You are a helpful assistant that can search the web and create virtual files."
# Build conversation
Вызывай этот инструмент в самом конце, после того как все нужные файлы созданы. messages = [system_msg, prompt]
""" # Simple loop: ask for tool usage
try: while True:
copied = [] # Ask LLM for next action
for src in VIRTUAL_ROOT.rglob("*"): response = self.llm.invoke(messages)
if src.is_file(): text = response.content.strip()
rel = src.relative_to(VIRTUAL_ROOT) if text.lower().startswith("search:"):
dst = REAL_OUT / rel query = text[7:].strip()
dst.parent.mkdir(parents=True, exist_ok=True) result = web_search(query)
shutil.copy2(src, dst) self.vfs.write(f"search_{query.replace(' ', '_')}.txt", result)
copied.append(str(rel)) messages.append(f"Search result for '{query}' written to virtual file.")
if copied: elif text.lower().startswith("create file:"):
return f"Выгружено {len(copied)} файл(ов) в {REAL_OUT}:\n" + "\n".join(f" {f}" for f in copied) parts = text[12:].strip().split("::", 1)
return f"Виртуальная ФС пуста — нет файлов для выгрузки." if len(parts) == 2:
except Exception as exc: path, content = parts
return f"Ошибка экспорта: {exc}" self.vfs.write(path.strip(), content.strip())
messages.append(f"File '{path.strip()}' created.")
else:
# --------------------------------------------------------------------------- messages.append("Invalid create file syntax. Use 'create file: path::content'.")
# Backend — виртуальная ФС поверх workspace/ elif text.lower() == "done":
# --------------------------------------------------------------------------- break
else:
_shell_backend = LocalShellBackend( messages.append(text)
root_dir=str(VIRTUAL_ROOT), # Dump virtual files to real filesystem
virtual_mode=True, self.vfs.dump_to_real_fs()
inherit_env=True, return "Agent finished. Files written to disk."
)
_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=query)]},
config,
)
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__": if __name__ == "__main__":
query = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else ( agent = DeepAgent(llm, tools=[web_search])
"Найди информацию о фреймворке LangGraph: что это, основные концепции, " # Example usage: search for LangChain and create a file
"примеры использования. Сохрани результаты в файл langgraph_research.md" user_prompt = (
"Search for 'LangChain deep agent' and create a file named 'summary.txt' with the first search result."
) )
asyncio.run(run(query)) result = agent.run(user_prompt)
print(result)