Обновить solutions/69de7223f309a98be0007e09_8__Самописный_поисковый_агент_на_основе_/solution.py
This commit is contained in:
+141
-53
@@ -14,30 +14,21 @@ from rich.table import Table
|
||||
# Виртуальная файловая система
|
||||
# --------------------------------------------------------------------------- #
|
||||
class VirtualFileSystem:
|
||||
"""
|
||||
Хранит файлы в памяти. Позволяет создавать и экспортировать их.
|
||||
"""
|
||||
"""Хранит файлы в памяти. Позволяет создавать и экспортировать их."""
|
||||
|
||||
def __init__(self):
|
||||
self.files: Dict[str, str] = {}
|
||||
|
||||
def write(self, filename: str, content: str) -> None:
|
||||
"""Создаёт или перезаписывает файл."""
|
||||
self.files[filename] = content
|
||||
|
||||
def read(self, filename: str) -> str | None:
|
||||
"""Возвращает содержимое файла, если он существует."""
|
||||
return self.files.get(filename)
|
||||
|
||||
def list_files(self) -> List[str]:
|
||||
"""Список всех файлов."""
|
||||
return list(self.files.keys())
|
||||
|
||||
def export_all(self, directory: str = ".") -> None:
|
||||
"""
|
||||
Записывает все файлы в указанную директорию.
|
||||
Если файл с таким именем уже существует – он будет перезаписан.
|
||||
"""
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
for name, content in self.files.items():
|
||||
path = os.path.join(directory, name)
|
||||
@@ -50,13 +41,12 @@ class VirtualFileSystem:
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def web_search(query: str) -> str:
|
||||
"""
|
||||
Выполняет поиск в интернете через DuckDuckGo и возвращает первые 3 результата.
|
||||
"""
|
||||
"""Поиск в интернете через DuckDuckGo, возвращает первые 3 результата."""
|
||||
url = "https://duckduckgo.com/html/"
|
||||
params = {"q": query}
|
||||
try:
|
||||
resp = requests.get(url, params=params, timeout=10)
|
||||
resp = requests.get(url, params=params, timeout=10,
|
||||
headers={"User-Agent": "Mozilla/5.0"})
|
||||
resp.raise_for_status()
|
||||
except Exception as e:
|
||||
return f"Ошибка при поиске: {e}"
|
||||
@@ -74,53 +64,143 @@ def web_search(query: str) -> str:
|
||||
|
||||
|
||||
def create_file(vfs: VirtualFileSystem, filename: str, content: str) -> str:
|
||||
"""
|
||||
Создаёт или обновляет виртуальный файл.
|
||||
"""
|
||||
"""Создаёт или обновляет виртуальный файл."""
|
||||
vfs.write(filename, content)
|
||||
return f"Файл '{filename}' успешно создан/обновлён."
|
||||
|
||||
|
||||
def read_file(vfs: VirtualFileSystem, filename: str) -> str:
|
||||
"""Читает файл из виртуальной ФС."""
|
||||
content = vfs.read(filename)
|
||||
if content is None:
|
||||
return f"Файл '{filename}' не найден."
|
||||
return content
|
||||
|
||||
|
||||
def list_files(vfs: VirtualFileSystem) -> str:
|
||||
"""Список файлов в виртуальной ФС."""
|
||||
files = vfs.list_files()
|
||||
if not files:
|
||||
return "Виртуальная ФС пуста."
|
||||
return "\n".join(files)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Основной логика
|
||||
# Описание инструментов для LLM
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def run_agent(task: str, vfs: VirtualFileSystem) -> str:
|
||||
TOOLS_DESCRIPTION = """Ты — автономный поисковый агент. Работай в цикле Thought/Action/Observation.
|
||||
|
||||
Доступные инструменты:
|
||||
1. web_search — поиск в интернете. Action Input: поисковый запрос (строка)
|
||||
2. create_file — создать файл в виртуальной ФС. Action Input: JSON {"filename": "...", "content": "..."}
|
||||
3. read_file — прочитать файл из виртуальной ФС. Action Input: имя файла (строка)
|
||||
4. list_files — список файлов в виртуальной ФС. Action Input: (пусто)
|
||||
5. finish — завершить работу. Action Input: финальный ответ пользователю
|
||||
|
||||
Формат каждого шага — СТРОГО такой (три строки, никаких отклонений):
|
||||
Thought: <что ты думаешь, что нужно сделать>
|
||||
Action: <одно из: web_search | create_file | read_file | list_files | finish>
|
||||
Action Input: <аргумент>
|
||||
|
||||
Правила:
|
||||
- Сначала ищи информацию через web_search.
|
||||
- После сбора информации сохрани результат через create_file.
|
||||
- Заверши работу командой finish только после того, как файл создан.
|
||||
- Никогда не выдумывай информацию — используй только то, что получил из web_search.
|
||||
"""
|
||||
Выполняет задачу с помощью OpenAI LLM.
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Агентный цикл (ReAct: Reason + Act)
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def run_agent(task: str, vfs: VirtualFileSystem, console: Console) -> str:
|
||||
"""
|
||||
Агентный цикл ReAct from scratch:
|
||||
LLM сам решает когда и какой инструмент вызвать,
|
||||
получает результат (Observation) и продолжает рассуждение.
|
||||
"""
|
||||
client = OpenAI()
|
||||
# Формируем запрос к модели
|
||||
prompt = (
|
||||
f"Задача: {task}\n"
|
||||
"Используйте web_search для получения информации.\n"
|
||||
"Сформируйте краткое резюме (не более 300 слов) и сохраните его в файл summary.txt."
|
||||
)
|
||||
# Получаем ответ модели
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": TOOLS_DESCRIPTION},
|
||||
{"role": "user", "content": task},
|
||||
]
|
||||
|
||||
for step in range(15):
|
||||
# --- LLM генерирует следующий шаг ---
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-3.5-turbo",
|
||||
model="gpt-4o-mini",
|
||||
messages=messages,
|
||||
temperature=0,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
except Exception as e:
|
||||
return f"Ошибка при вызове OpenAI: {e}"
|
||||
|
||||
summary_text = response.choices[0].message.content.strip()
|
||||
# Сохраняем в виртуальный файл
|
||||
create_file(vfs, "summary.txt", summary_text)
|
||||
return summary_text
|
||||
raw = response.choices[0].message.content.strip()
|
||||
console.print(f"\n[bold yellow][Шаг {step + 1}][/]\n{raw}")
|
||||
messages.append({"role": "assistant", "content": raw})
|
||||
|
||||
# --- Парсинг Action и Action Input ---
|
||||
action_match = re.search(r"^Action:\s*(\w+)", raw, re.MULTILINE)
|
||||
input_match = re.search(r"^Action Input:\s*(.+)", raw, re.MULTILINE | re.DOTALL)
|
||||
|
||||
def flush_virtual_fs(vfs: VirtualFileSystem) -> str:
|
||||
"""
|
||||
Экспортирует все файлы из виртуальной файловой системы на диск.
|
||||
"""
|
||||
if not action_match:
|
||||
break
|
||||
|
||||
action = action_match.group(1).strip()
|
||||
action_input = input_match.group(1).strip() if input_match else ""
|
||||
|
||||
# --- Выполнение инструмента ---
|
||||
if action == "finish":
|
||||
return action_input
|
||||
|
||||
elif action == "web_search":
|
||||
observation = web_search(action_input)
|
||||
|
||||
elif action == "create_file":
|
||||
try:
|
||||
vfs.export_all(".")
|
||||
return f"Все файлы выгружены в '{os.path.abspath('.')}'."
|
||||
except Exception as e:
|
||||
return f"Ошибка при экспорте файлов: {e}"
|
||||
args = json.loads(action_input)
|
||||
observation = create_file(vfs, args["filename"], args["content"])
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
observation = f"Ошибка: неверный формат JSON для create_file: {e}"
|
||||
|
||||
elif action == "read_file":
|
||||
observation = read_file(vfs, action_input)
|
||||
|
||||
elif action == "list_files":
|
||||
observation = list_files(vfs)
|
||||
|
||||
else:
|
||||
observation = (
|
||||
f"Неизвестный инструмент: '{action}'. "
|
||||
"Используй только: web_search, create_file, read_file, list_files, finish."
|
||||
)
|
||||
|
||||
# --- Передаём результат обратно в LLM ---
|
||||
preview = observation[:300] + ("..." if len(observation) > 300 else "")
|
||||
console.print(f"[dim]Observation: {preview}[/]")
|
||||
messages.append({"role": "user", "content": f"Observation: {observation}"})
|
||||
|
||||
return "Агент завершил работу (достигнут лимит шагов)."
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Выгрузка виртуальных файлов на диск
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def flush_virtual_fs(vfs: VirtualFileSystem, console: Console) -> None:
|
||||
"""Экспортирует все файлы из виртуальной ФС в реальную файловую систему."""
|
||||
if not vfs.list_files():
|
||||
console.print("[red]Нет виртуальных файлов для выгрузки.[/]")
|
||||
return
|
||||
output_dir = "output"
|
||||
vfs.export_all(output_dir)
|
||||
console.print(f"\n[green]Файлы выгружены в '{os.path.abspath(output_dir)}':[/]")
|
||||
for name in vfs.list_files():
|
||||
console.print(f" ✓ {name}")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -131,27 +211,35 @@ def main():
|
||||
console = Console()
|
||||
vfs = VirtualFileSystem()
|
||||
|
||||
task = (
|
||||
"Найди последние новости и обновления по версии Python 3.11, "
|
||||
"создай краткое резюме (не более 300 слов) и сохрани его в файле summary.txt."
|
||||
)
|
||||
console.print("[bold cyan]═══ Deep Search Agent ═══[/]\n")
|
||||
task = input("Введите задачу для агента: ").strip()
|
||||
if not task:
|
||||
console.print("[red]Задача не задана. Выход.[/]")
|
||||
return
|
||||
|
||||
console.print("[bold cyan]Запуск агента...[/]")
|
||||
result = run_agent(task, vfs)
|
||||
console.print(f"\n[bold]Задача:[/] {task}")
|
||||
console.print("[bold cyan]\nЗапуск агентного цикла...[/]")
|
||||
|
||||
console.print("\n[green]Итоговое сообщение от агента:[/]\n")
|
||||
# --- Запуск агента ---
|
||||
result = run_agent(task, vfs, console)
|
||||
|
||||
# --- Финальный ответ ---
|
||||
console.print("\n[bold green]═══ Финальный ответ агента ═══[/]")
|
||||
console.print(result)
|
||||
|
||||
# --- Таблица виртуальных файлов ---
|
||||
console.print("\n[bold magenta]Виртуальные файлы, созданные агентом:[/]")
|
||||
table = Table(show_header=True, header_style="bold blue")
|
||||
table.add_column("Имя файла", style="dim")
|
||||
table.add_column("Имя файла", style="dim", min_width=20)
|
||||
table.add_column("Размер (байт)")
|
||||
for fname in vfs.list_files():
|
||||
table.add_row(fname)
|
||||
size = str(len(vfs.read(fname) or ""))
|
||||
table.add_row(fname, size)
|
||||
console.print(table)
|
||||
|
||||
export_result = flush_virtual_fs(vfs)
|
||||
console.print("\n[green]Результат экспорта:[/]")
|
||||
console.print(export_result)
|
||||
# --- Выгрузка на диск ---
|
||||
console.print("\n[bold cyan]Выгрузка виртуальных файлов в реальную ФС...[/]")
|
||||
flush_virtual_fs(vfs, console)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user