feat: solution for '8. Самописный поисковый агент на основе deep agents from scratch'

This commit is contained in:
2026-06-28 12:40:40 +03:00
parent b00f37f770
commit 380e236ecf
3 changed files with 335 additions and 96 deletions
+50 -24
View File
@@ -1,31 +1,57 @@
# 8. Самописный поисковый агент на основе deep agents from scratch # Deep Agent Search, Virtual Files, Export
Главная This project implements a **Deep Agent** following the *Deep Agents from Scratch* template.
Мои задания The agent can:
8. Самописный поисковый агент на основе deep agents from scratch
EN
8. Самописный поисковый агент на основе deep agents from scratch
Зачёт
Версия 1
Дедлайн сдачи: 31.08.2026
В работе 1. Search the web for information (using DuckDuckGo).
2. Create virtual files in memory.
3. Export those virtual files to the real filesystem.
Редактирование ответа ## Prerequisites
Заполните ответ и отправьте работу на проверку преподавателю. - Python 3.10+
- An OpenAI API key (set as `OPENAI_API_KEY` environment variable).
Тип ответа ## Installation
Текст
Ссылка
Файлы
Текст ответа
Прикреплённые файлы
Загрузить файл
Отправить на проверку
Отменить
Задание ```bash
# Clone the repository
git clone https://github.com/yourusername/deep-agent-demo.git
cd deep-agent-demo
Необходимо написать deepagent на основе курса deep agents from scratch пример такого аг # Create a virtual environment (optional but recommended)
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
```
## Usage
```bash
python src/main.py \
--prompt "Write a short report on the history of the Eiffel Tower" \
--output-dir ./exported_files \
--verbose
```
- `--prompt` The user query for the agent.
- `--output-dir` Directory where virtual files will be exported.
- `--verbose` Show detailed logs of tool calls.
After running, the agent will print the final answer and export any created virtual files to the specified directory.
## Project Structure
```
deep-agent-demo/
├── src/
│ └── main.py # Main script with Deep Agent implementation
├── requirements.txt # Python dependencies
└── README.md # This file
```
## License
MIT License
+3 -2
View File
@@ -1,3 +1,4 @@
langchain langchain==0.1.0
openai openai
duckduckgo-search requests
beautifulsoup4
+269 -57
View File
@@ -1,76 +1,288 @@
#!/usr/bin/env python3
"""
Deep Agent implementation based on the Deep Agents from Scratch template.
The agent can search the web, create virtual files, and export them to the real filesystem.
"""
import os import os
import sys import re
from pathlib import Path import ast
import argparse
import requests
from bs4 import BeautifulSoup
# Ensure the virtual_files package is importable from langchain.llms import OpenAI
sys.path.append(str(Path(__file__).resolve().parent)) from langchain.tools import Tool
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain.agents import BaseAgent
from langchain import OpenAI # --------------------------------------------------------------------------- #
from langchain.agents import initialize_agent # Virtual File System
from langchain.tools import DuckDuckGoSearchRun, Tool # --------------------------------------------------------------------------- #
from src.virtual_files import VirtualFileSystem class VirtualFileSystem:
"""In-memory virtual file system."""
def main(): def __init__(self):
# Initialize the virtual file system self.files = {}
vfs = VirtualFileSystem()
# Define a custom tool to write to the virtual file system def write_file(self, filename: str, content: str):
def write_file_tool(input_str: str) -> str: self.files[filename] = content
"""
Expected input format: filename|content def read_file(self, filename: str) -> str:
Example: python_history.txt|Python was created by Guido van Rossum... return self.files.get(filename, "")
"""
if "|" not in input_str: def export_to_disk(self, directory: str):
return "Error: Input must be in the format 'filename|content'." os.makedirs(directory, exist_ok=True)
filename, content = input_str.split("|", 1) for filename, content in self.files.items():
path = os.path.join(directory, filename)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
# --------------------------------------------------------------------------- #
# Tools
# --------------------------------------------------------------------------- #
class SearchTool(Tool):
"""Search the web using DuckDuckGo."""
name = "Search"
description = (
"Search the web for information. Input: query string. Output: search results as text."
)
def __init__(self):
super().__init__(name=self.name, description=self.description, func=self.run)
def run(self, query: str) -> str:
url = "https://duckduckgo.com/html/"
params = {"q": query}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
except Exception as e:
return f"Error during search: {e}"
soup = BeautifulSoup(response.text, "html.parser")
results = []
for a in soup.select("a.result__a"):
title = a.get_text()
href = a.get("href")
results.append(f"{title}\n{href}")
if not results:
return "No results found."
return "\n\n".join(results[:5]) # Return top 5 results
class VirtualFileSystemTool(Tool):
"""Create a virtual file with given filename and content."""
name = "CreateFile"
description = (
"Create a virtual file with given filename and content. "
"Input format: filename|content"
)
def __init__(self, vfs: VirtualFileSystem):
self.vfs = vfs
super().__init__(name=self.name, description=self.description, func=self.run)
def run(self, args: str) -> str:
parts = args.split("|", 1)
if len(parts) != 2:
return "Error: expected format 'filename|content'"
filename, content = parts
filename = filename.strip() filename = filename.strip()
content = content.strip() content = content.strip()
if not filename: self.vfs.write_file(filename, content)
return "Error: Filename cannot be empty." return f"File '{filename}' created."
vfs.write_file(filename, content)
return f"File '{filename}' written successfully."
write_tool = Tool(
name="WriteFile", class ExportTool(Tool):
func=write_file_tool, """Export all virtual files to the specified directory."""
description=(
"Writes content to a virtual file. " name = "ExportFiles"
"Use the format: filename|content. " description = "Export all virtual files to the specified directory. Input: directory path"
"The file will be stored in the virtual file system and exported at the end."
def __init__(self, vfs: VirtualFileSystem):
self.vfs = vfs
super().__init__(name=self.name, description=self.description, func=self.run)
def run(self, directory: str) -> str:
directory = directory.strip()
self.vfs.export_to_disk(directory)
return f"Exported {len(self.vfs.files)} files to '{directory}'."
# --------------------------------------------------------------------------- #
# Deep Agent
# --------------------------------------------------------------------------- #
class DeepAgent(BaseAgent):
"""Deep Agent following the Deep Agents from Scratch template."""
def __init__(self, llm, tools, memory, verbose=False):
super().__init__()
self.llm = llm
self.tools = tools
self.memory = memory
self.verbose = verbose
self.tool_names = [tool.name for tool in tools]
self.tool_map = {tool.name: tool for tool in tools}
# Prompt templates
self.plan_prompt = PromptTemplate(
input_variables=["input", "agent_scratchpad"],
template=(
"You are a helpful assistant. Your task is to answer the user query: {input}\n"
"You have access to the following tools: {tool_names}\n"
"You can use the tools to gather information.\n"
"Plan your steps. Each step should be a single tool call in the format: {tool_name}({arguments})\n"
"If you have enough information, provide the final answer.\n"
"Your plan:\n{agent_scratchpad}"
), ),
) )
# Search tool self.tool_prompt = PromptTemplate(
search_tool = DuckDuckGoSearchRun() input_variables=["tool_input", "agent_scratchpad"],
template=(
# LLM configuration "You are a tool. The user wants: {tool_input}\n"
llm = OpenAI(temperature=0) "You have the following context: {agent_scratchpad}\n"
"You should produce the tool output.\n"
# Initialize the agent with the tools "Tool output:"
agent_executor = initialize_agent( ),
tools=[search_tool, write_tool],
llm=llm,
agent="zero-shot-react-description",
verbose=True,
) )
# Example task: gather information about Python programming language self.final_prompt = PromptTemplate(
task = """ input_variables=["agent_scratchpad"],
You are a research assistant. Your task is to gather information about the Python programming language, including its history, key features, and popular libraries. template=(
Create a virtual file named 'python_history.txt' containing the history, a file named 'python_features.txt' containing key features, and a file named 'python_libraries.txt' containing a list of popular libraries. "You are a helpful assistant. Based on the previous steps, provide the final answer.\n"
Use the web search tool to find reliable information. After gathering the data, write each section to the corresponding virtual file using the WriteFile tool. "Answer:\n{agent_scratchpad}"
Finally, return a summary of what you have done. ),
""" )
self.plan_chain = LLMChain(llm=self.llm, prompt=self.plan_prompt)
self.tool_chain = LLMChain(llm=self.llm, prompt=self.tool_prompt)
self.final_chain = LLMChain(llm=self.llm, prompt=self.final_prompt)
def _get_scratchpad(self) -> str:
"""Return the conversation history as a string."""
history = self.memory.load_memory_variables({})["history"]
if isinstance(history, list):
return "\n".join([msg.content if hasattr(msg, "content") else str(msg) for msg in history])
return str(history)
def plan(self, input_text: str) -> str:
scratchpad = self._get_scratchpad()
plan = self.plan_chain.run(
input=input_text,
agent_scratchpad=scratchpad,
tool_names=", ".join(self.tool_names),
)
return plan
def parse_plan(self, plan_text: str) -> list:
"""Parse the plan into individual steps."""
lines = [line.strip() for line in plan_text.splitlines() if line.strip()]
return lines
def parse_step(self, step_text: str) -> tuple:
"""Parse a single step into tool name and arguments."""
match = re.match(r"(\w+)\((.*)\)", step_text)
if not match:
raise ValueError(f"Invalid step format: {step_text}")
tool_name = match.group(1)
args_str = match.group(2).strip()
# Try to parse arguments as a tuple
try:
args_tuple = ast.literal_eval(f"({args_str},)")
except Exception:
args_tuple = (args_str,)
return tool_name, args_tuple
def run(self, input_text: str) -> str:
"""Run the agent on the given input."""
self.memory.save_context({"input": input_text}, {})
plan = self.plan(input_text)
if self.verbose:
print("\n=== PLAN ===")
print(plan)
print("============\n")
steps = self.parse_plan(plan)
for step in steps:
tool_name, args_tuple = self.parse_step(step)
if tool_name not in self.tool_map:
raise ValueError(f"Unknown tool: {tool_name}")
tool = self.tool_map[tool_name]
# Use the first argument as the tool input
tool_input = args_tuple[0] if args_tuple else ""
if self.verbose:
print(f"\n=== TOOL CALL: {tool_name} ===")
print(f"Input: {tool_input}")
tool_output = tool.run(tool_input)
if self.verbose:
print(f"Output: {tool_output}\n")
self.memory.save_context({"tool_output": tool_output}, {})
final_answer = self.final_chain.run(agent_scratchpad=self._get_scratchpad())
return final_answer
# --------------------------------------------------------------------------- #
# Main
# --------------------------------------------------------------------------- #
def main():
parser = argparse.ArgumentParser(description="Deep Agent Demo")
parser.add_argument(
"--prompt",
type=str,
required=True,
help="The user query for the agent to answer.",
)
parser.add_argument(
"--output-dir",
type=str,
default="output_files",
help="Directory to export virtual files.",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Print detailed logs.",
)
args = parser.parse_args()
# Ensure OpenAI API key is set
if "OPENAI_API_KEY" not in os.environ:
raise RuntimeError("Please set the OPENAI_API_KEY environment variable.")
# Initialize components
llm = OpenAI(temperature=0, model_name="gpt-3.5-turbo")
vfs = VirtualFileSystem()
tools = [
SearchTool(),
VirtualFileSystemTool(vfs),
ExportTool(vfs),
]
memory = ConversationBufferMemory(memory_key="history", return_messages=True)
agent = DeepAgent(llm=llm, tools=tools, memory=memory, verbose=args.verbose)
# Run the agent # Run the agent
result = agent_executor.run(task) print("\n=== RUNNING AGENT ===")
print("\nAgent finished. Result:") final_answer = agent.run(args.prompt)
print(result) print("\n=== FINAL ANSWER ===")
print(final_answer)
# Export files (in case the agent didn't call ExportFiles)
if not any(tool.name == "ExportFiles" for tool in tools):
export_tool = ExportTool(vfs)
export_output = export_tool.run(args.output_dir)
print("\n=== EXPORT OUTPUT ===")
print(export_output)
# Export virtual files to disk
output_dir = Path(__file__).resolve().parent / "output_files"
vfs.export_to_disk(str(output_dir))
print(f"\nVirtual files exported to {output_dir}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()