add main.py
This commit is contained in:
@@ -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.")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 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() в конце
|
|
||||||
"""
|
|
||||||
|
|
||||||
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:
|
else:
|
||||||
print(" (нет файлов)")
|
messages.append("Invalid create file syntax. Use 'create file: path::content'.")
|
||||||
|
elif text.lower() == "done":
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
messages.append(text)
|
||||||
|
# Dump virtual files to real filesystem
|
||||||
|
self.vfs.dump_to_real_fs()
|
||||||
|
return "Agent finished. Files written to disk."
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user