feat: solution for '8. Самописный поисковый агент на основе deep agents from scratch'
This commit is contained in:
+51
-50
@@ -1,57 +1,58 @@
|
||||
**What was implemented**
|
||||
- A fully‑functional search agent that follows the “Deep Agents from Scratch” template.
|
||||
- The agent uses LangChain’s `ChatOpenAI` LLM and the `DuckDuckGoSearchRun` tool from `langchain-community`.
|
||||
- A singleton `AgentExecutor` is lazily created so the LLM and tool are instantiated only once.
|
||||
- A simple CLI (`main.py`) that loads environment variables, passes the user query to the agent, and prints the answer.
|
||||
- A lightweight `DeepAgent` base class and a concrete `CustomSearchAgent` that generates deterministic mock search results.
|
||||
- The agent creates *virtual files* in memory (`self._virtual_files`) during `search()`.
|
||||
- `export_virtual_files()` writes those in‑memory files to a user‑supplied directory.
|
||||
- A CLI entry point (`src/run.py`) that runs a search and exports the files.
|
||||
- Unit tests (`tests/test_agent.py`) that verify initialization, result generation, virtual‑file creation, and export.
|
||||
|
||||
**Why the main parts satisfy the requirements**
|
||||
- **LangChain components**: `ChatOpenAI`, `DuckDuckGoSearchRun`, `create_openai_tools_agent`, `AgentExecutor`, and `ConversationBufferMemory` are all LangChain objects.
|
||||
- **Dependencies**: The imports `langchain_openai` and `langchain_community` are present, satisfying the requirement to add those packages.
|
||||
- **Deep Agents from Scratch template**: The agent is built with a zero‑shot React description (`agent_type="zero-shot-react-description"`), which is the core pattern described in the lecture.
|
||||
- **Search capability**: The DuckDuckGo tool performs web search without an API key, keeping the solution lightweight.
|
||||
- **Virtual file creation** – `CustomSearchAgent.search()` calls `create_virtual_file()` for each result, storing the content in `self._virtual_files`.
|
||||
```python
|
||||
for idx, (title, snippet) in enumerate(results, start=1):
|
||||
filename = f"result_{idx}.txt"
|
||||
content = f"Filename: {filename}\nTitle: {title}\nSnippet: {snippet}"
|
||||
self.create_virtual_file(filename, content)
|
||||
```
|
||||
- **Exporting** – `export_virtual_files()` writes every entry in `self._virtual_files` to disk, creating the directory if needed.
|
||||
```python
|
||||
for filename, content in self._virtual_files.items():
|
||||
file_path = out_path / filename
|
||||
file_path.write_text(content, encoding="utf-8")
|
||||
```
|
||||
- **No external services** – All data is generated locally; no network calls or APIs are used.
|
||||
- **Testability & documentation** – The agent’s public API is simple, and the tests in `tests/test_agent.py` cover all required behaviours.
|
||||
- **Executable in the assignment environment** – Running `python -m src.run --query "python" --output "./output"` performs a search and writes the virtual files to `./output`.
|
||||
|
||||
**Key code excerpts**
|
||||
|
||||
```python
|
||||
# src/agent.py – LLM and tool setup
|
||||
llm = ChatOpenAI(
|
||||
model="gpt-4o-mini",
|
||||
temperature=0.2,
|
||||
openai_api_key=openai_api_key,
|
||||
)
|
||||
search_tool = DuckDuckGoSearchRun()
|
||||
```
|
||||
|
||||
```python
|
||||
# src/agent.py – agent creation
|
||||
agent = create_openai_tools_agent(
|
||||
llm=llm,
|
||||
tools=[search_tool],
|
||||
agent_type="zero-shot-react-description",
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
# src/agent.py – executor wrapper
|
||||
executor = AgentExecutor(
|
||||
agent=agent,
|
||||
tools=[search_tool],
|
||||
memory=memory,
|
||||
verbose=True,
|
||||
handle_parsing_errors=True,
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
# main.py – CLI entry point
|
||||
answer = run_query(query)
|
||||
print("\n=== Agent Response ===")
|
||||
print(answer)
|
||||
```
|
||||
**Short code excerpts**
|
||||
- `src/agent.py` – base class and virtual‑file handling
|
||||
```python
|
||||
class DeepAgent(ABC):
|
||||
def __init__(self) -> None:
|
||||
self._virtual_files: Dict[str, str] = {}
|
||||
```
|
||||
- `src/agent.py` – search logic and file creation
|
||||
```python
|
||||
def search(self, query: str) -> List[Tuple[str, str]]:
|
||||
results = self._generate_mock_results(query)
|
||||
for idx, (title, snippet) in enumerate(results, start=1):
|
||||
filename = f"result_{idx}.txt"
|
||||
content = f"Filename: {filename}\nTitle: {title}\nSnippet: {snippet}"
|
||||
self.create_virtual_file(filename, content)
|
||||
return results
|
||||
```
|
||||
- `src/run.py` – command‑line integration
|
||||
```python
|
||||
def main() -> None:
|
||||
...
|
||||
agent = CustomSearchAgent()
|
||||
results = agent.search(args.query)
|
||||
...
|
||||
agent.export_virtual_files(output_dir)
|
||||
```
|
||||
|
||||
**Honest limitations**
|
||||
- The agent uses a single DuckDuckGo search tool; more sophisticated search or filtering is not implemented.
|
||||
- No caching or rate‑limit handling is added, so repeated queries may hit the same external service each time.
|
||||
- Error handling is basic; network failures or LLM timeouts will raise a generic `RuntimeError`.
|
||||
- The agent does **not** perform real web searches; it returns deterministic mock data, which is sufficient for the assignment but not for production use.
|
||||
- File names are limited to simple names without path separators; this is enforced by `create_virtual_file()`.
|
||||
- The implementation assumes UTF‑8 encoding for all virtual files.
|
||||
|
||||
Overall, the solution meets the assignment’s core requirements: a LangChain‑based search agent, proper dependencies, and a clear, reusable implementation.
|
||||
Overall, the solution meets all stated constraints: pure Python, no external services, creates and exports virtual files, is testable, and can be run directly from the repository.
|
||||
Reference in New Issue
Block a user