Обновить solutions/69de7223f309a98be0007e09_8__Самописный_поисковый_агент_на_основе_/solution.py

This commit is contained in:
2026-05-27 13:55:12 +00:00
parent 5a4ea9d102
commit fb7c65552a
@@ -14,30 +14,21 @@ from rich.table import Table
# Виртуальная файловая система # Виртуальная файловая система
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
class VirtualFileSystem: class VirtualFileSystem:
""" """Хранит файлы в памяти. Позволяет создавать и экспортировать их."""
Хранит файлы в памяти. Позволяет создавать и экспортировать их.
"""
def __init__(self): def __init__(self):
self.files: Dict[str, str] = {} self.files: Dict[str, str] = {}
def write(self, filename: str, content: str) -> None: def write(self, filename: str, content: str) -> None:
"""Создаёт или перезаписывает файл."""
self.files[filename] = content self.files[filename] = content
def read(self, filename: str) -> str | None: def read(self, filename: str) -> str | None:
"""Возвращает содержимое файла, если он существует."""
return self.files.get(filename) return self.files.get(filename)
def list_files(self) -> List[str]: def list_files(self) -> List[str]:
"""Список всех файлов."""
return list(self.files.keys()) return list(self.files.keys())
def export_all(self, directory: str = ".") -> None: def export_all(self, directory: str = ".") -> None:
"""
Записывает все файлы в указанную директорию.
Если файл с таким именем уже существует – он будет перезаписан.
"""
os.makedirs(directory, exist_ok=True) os.makedirs(directory, exist_ok=True)
for name, content in self.files.items(): for name, content in self.files.items():
path = os.path.join(directory, name) path = os.path.join(directory, name)
@@ -50,13 +41,12 @@ class VirtualFileSystem:
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
def web_search(query: str) -> str: def web_search(query: str) -> str:
""" """Поиск в интернете через DuckDuckGo, возвращает первые 3 результата."""
Выполняет поиск в интернете через DuckDuckGo и возвращает первые 3 результата.
"""
url = "https://duckduckgo.com/html/" url = "https://duckduckgo.com/html/"
params = {"q": query} params = {"q": query}
try: 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() resp.raise_for_status()
except Exception as e: except Exception as e:
return f"Ошибка при поиске: {e}" return f"Ошибка при поиске: {e}"
@@ -74,53 +64,143 @@ def web_search(query: str) -> str:
def create_file(vfs: VirtualFileSystem, filename: str, content: str) -> str: def create_file(vfs: VirtualFileSystem, filename: str, content: str) -> str:
""" """Создаёт или обновляет виртуальный файл."""
Создаёт или обновляет виртуальный файл.
"""
vfs.write(filename, content) vfs.write(filename, content)
return f"Файл '{filename}' успешно создан/обновлён." 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() client = OpenAI()
# Формируем запрос к модели
prompt = ( messages = [
f"Задача: {task}\n" {"role": "system", "content": TOOLS_DESCRIPTION},
"Используйте web_search для получения информации.\n" {"role": "user", "content": task},
"Сформируйте краткое резюме (не более 300 слов) и сохраните его в файл summary.txt." ]
)
# Получаем ответ модели for step in range(15):
# --- LLM генерирует следующий шаг ---
try: try:
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-3.5-turbo", model="gpt-4o-mini",
messages=messages,
temperature=0, temperature=0,
messages=[{"role": "user", "content": prompt}],
) )
except Exception as e: except Exception as e:
return f"Ошибка при вызове OpenAI: {e}" return f"Ошибка при вызове OpenAI: {e}"
summary_text = response.choices[0].message.content.strip() raw = response.choices[0].message.content.strip()
# Сохраняем в виртуальный файл console.print(f"\n[bold yellow][Шаг {step + 1}][/]\n{raw}")
create_file(vfs, "summary.txt", summary_text) messages.append({"role": "assistant", "content": raw})
return summary_text
# --- Парсинг 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: try:
vfs.export_all(".") args = json.loads(action_input)
return f"Все файлы выгружены в '{os.path.abspath('.')}'." observation = create_file(vfs, args["filename"], args["content"])
except Exception as e: except (json.JSONDecodeError, KeyError) as e:
return f"Ошибка при экспорте файлов: {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() console = Console()
vfs = VirtualFileSystem() vfs = VirtualFileSystem()
task = ( console.print("[bold cyan]═══ Deep Search Agent ═══[/]\n")
"Найди последние новости и обновления по версии Python 3.11, " task = input("Введите задачу для агента: ").strip()
"создай краткое резюме (не более 300 слов) и сохрани его в файле summary.txt." if not task:
) console.print("[red]Задача не задана. Выход.[/]")
return
console.print("[bold cyan]Запуск агента...[/]") console.print(f"\n[bold]Задача:[/] {task}")
result = run_agent(task, vfs) 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(result)
# --- Таблица виртуальных файлов ---
console.print("\n[bold magenta]Виртуальные файлы, созданные агентом:[/]") console.print("\n[bold magenta]Виртуальные файлы, созданные агентом:[/]")
table = Table(show_header=True, header_style="bold blue") 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(): 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) console.print(table)
export_result = flush_virtual_fs(vfs) # --- Выгрузка на диск ---
console.print("\n[green]Результат экспорта:[/]") console.print("\n[bold cyan]Выгрузка виртуальных файлов в реальную ФС...[/]")
console.print(export_result) flush_virtual_fs(vfs, console)
if __name__ == "__main__": if __name__ == "__main__":