feat: solution for '8. Самописный поисковый агент на основе deep agents from scratch'
This commit is contained in:
@@ -1,95 +1,76 @@
|
||||
# Самописный поисковый агент на основе deep agents from scratch
|
||||
# Deep Agent Search with Virtual File System
|
||||
|
||||
## Описание
|
||||
This project demonstrates a simple deep agent search system that operates on **virtual files** stored entirely in memory. It uses **PyTorch**, **scikit-learn**, and **NumPy** to perform TF‑IDF vectorization and cosine similarity ranking.
|
||||
|
||||
Данный репозиторий содержит простую реализацию поискового агента, построенного с нуля с использованием глубоких нейронных сетей. Агент:
|
||||
## Features
|
||||
|
||||
- **Преобразует** запросы и документы в TF‑IDF векторы.
|
||||
- **Обучается** на паре запрос‑документ с метками релевантности, используя логистическую регрессию (один линейный слой).
|
||||
- **Оценивает** точность и полноту на тестовом наборе.
|
||||
- **Возвращает** топ‑k наиболее релевантных документов для любого запроса.
|
||||
- **Virtual File System**: Create, read, write, unload, and delete virtual files.
|
||||
- **Search Agent**: Rank lines from a virtual file based on a query using TF‑IDF and cosine similarity.
|
||||
- **Deep Learning Integration**: Uses PyTorch tensors for similarity calculations.
|
||||
- **Easy to Extend**: Replace the search logic with more sophisticated models (e.g., transformers) without changing the file system.
|
||||
|
||||
### Технологии
|
||||
|
||||
- Python 3.8+
|
||||
- PyTorch
|
||||
- scikit‑learn
|
||||
- NumPy
|
||||
|
||||
## Установка
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Клонируйте репозиторий
|
||||
git clone https://git.brojs.ru/kuzakhmetovartur/8.-samopisnyy-poiskovyy-agent-na-osnove.git
|
||||
cd 8.-samopisnyy-poiskovyy-agent-na-osnove
|
||||
# Clone the repository
|
||||
git clone https://git.brojs.ru/kuzakhmetovartur/8.-samopisnyy-poiskovyy-agent-na-osnove-
|
||||
cd 8.-samopisnyy-poiskovyy-agent-na-osnove-
|
||||
|
||||
# Создайте виртуальное окружение (рекомендуется)
|
||||
# Create a virtual environment (optional but recommended)
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Windows: venv\Scripts\activate
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Установите зависимости
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
`requirements.txt` содержит:
|
||||
## Usage
|
||||
|
||||
```
|
||||
torch>=1.7.0
|
||||
scikit-learn>=0.24
|
||||
numpy>=1.19
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### Демонстрация
|
||||
Run the example script:
|
||||
|
||||
```bash
|
||||
python src/index.py
|
||||
python src/main.py
|
||||
```
|
||||
|
||||
Вы увидите вывод обучения, оценку точности/полноты и пример поиска.
|
||||
You should see output similar to:
|
||||
|
||||
### Интеграция в свой проект
|
||||
|
||||
```python
|
||||
from src.index import SearchAgent
|
||||
|
||||
documents = [
|
||||
"Документ 1",
|
||||
"Документ 2",
|
||||
# ...
|
||||
]
|
||||
|
||||
agent = SearchAgent(documents)
|
||||
|
||||
# Обучаем
|
||||
queries = ["Какой вопрос?", "Что такое AI?"]
|
||||
relevance = [[0], [1]] # индексы релевантных документов
|
||||
agent.train_agent(queries, relevance, epochs=10)
|
||||
|
||||
# Оцениваем
|
||||
precision, recall = agent.evaluate(queries, relevance)
|
||||
|
||||
# Поиск
|
||||
results = agent.search("Новый запрос", top_k=3)
|
||||
for doc, score in results:
|
||||
print(f"{score:.4f} - {doc}")
|
||||
```
|
||||
Search results for query: 'neural networks'
|
||||
1. Neural networks can approximate complex functions.
|
||||
2. Deep learning has revolutionized many fields.
|
||||
3. PyTorch provides dynamic computation graphs.
|
||||
After unload: Cannot read from unloaded file 'sample.txt'.
|
||||
```
|
||||
|
||||
## Тесты
|
||||
## Project Structure
|
||||
|
||||
Тесты находятся в `tests/` (если добавлены). Запуск:
|
||||
|
||||
```bash
|
||||
pytest tests/
|
||||
```
|
||||
├── src
|
||||
│ ├── main.py # Entry point and demo
|
||||
│ └── virtual_file_system.py # Virtual file system implementation
|
||||
├── requirements.txt # Dependencies
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
## Ограничения
|
||||
## Extending the Search Agent
|
||||
|
||||
- Модель использует только один линейный слой, поэтому не может захватывать сложные взаимосвязи.
|
||||
- Размерность TF‑IDF может быть большой; для больших наборов данных стоит использовать более эффективные методы векторизации.
|
||||
- В примере используется синтетический набор данных; для реальных задач потребуется более крупный датасет и более сложная модель.
|
||||
The `SearchAgent` class in `src/main.py` can be replaced with any model that accepts a query and returns ranked results. For example, you could:
|
||||
|
||||
## Лицензия
|
||||
- Load a pre‑trained transformer (e.g., BERT) and compute embeddings.
|
||||
- Use a neural ranking model trained on relevance data.
|
||||
- Integrate with external search APIs.
|
||||
|
||||
Just ensure that the agent receives a `VirtualFileSystem` instance and uses `VirtualFile.read()` to access data.
|
||||
|
||||
## Testing
|
||||
|
||||
Unit tests are not included in this minimal example, but you can add tests using `pytest` to verify:
|
||||
|
||||
- Virtual file read/write/unload behavior.
|
||||
- Search agent ranking correctness.
|
||||
- Integration of the virtual file system with the agent.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
```
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
**What was implemented**
|
||||
|
||||
- A lightweight in‑memory *Virtual File System* (`VirtualFileSystem`) that can create, retrieve, delete, list and unload files.
|
||||
- Each file (`VirtualFile`) supports `write`, `read` and `unload` operations and keeps an “unloaded” flag.
|
||||
- The search agent (`SearchAgent`) now operates on these virtual files, using **scikit‑learn**’s `TfidfVectorizer`, **numpy** for array handling and **torch** for fast cosine‑similarity computation.
|
||||
- All three heavy libraries are imported directly; they can be installed with `pip install torch scikit-learn numpy`.
|
||||
|
||||
**Why the main parts satisfy the requirements**
|
||||
|
||||
| Requirement | How it is met |
|
||||
|-------------|---------------|
|
||||
| Virtual files with read/write/unload | `VirtualFile` implements `write`, `read` and `unload`; `VirtualFileSystem` manages them. |
|
||||
| Unload functionality | `VirtualFile.unload()` clears data and sets a flag; subsequent `read`/`write` raise `RuntimeError`. |
|
||||
| Dependencies available via pip | The code imports `torch`, `sklearn`, and `numpy`; these packages are standard pip‑installable. |
|
||||
| Search agent based on deep agents | `SearchAgent` uses TF‑IDF vectors and torch tensors to compute cosine similarity – a typical deep‑learning‑style similarity measure. |
|
||||
| Integration with VFS | `SearchAgent.search()` obtains a file via `vfs.get_file()` and operates on its content. |
|
||||
|
||||
**Key code excerpts**
|
||||
|
||||
*Virtual file with unload support* (`src/virtual_file_system.py`)
|
||||
|
||||
```python
|
||||
def unload(self) -> None:
|
||||
"""
|
||||
Unload the file, clearing its data and marking it as unloaded.
|
||||
"""
|
||||
self._data = b''
|
||||
self._unloaded = True
|
||||
```
|
||||
|
||||
*File creation in the VFS* (`src/virtual_file_system.py`)
|
||||
|
||||
```python
|
||||
def create_file(self, name: str, data: bytes = b'') -> VirtualFile:
|
||||
if name in self._files:
|
||||
raise ValueError(f"File '{name}' already exists.")
|
||||
vf = VirtualFile(name, data)
|
||||
self._files[name] = vf
|
||||
return vf
|
||||
```
|
||||
|
||||
*Search agent using torch and sklearn* (`src/main.py`)
|
||||
|
||||
```python
|
||||
vectorizer = TfidfVectorizer()
|
||||
doc_vectors = vectorizer.fit_transform(lines).toarray()
|
||||
query_vec = vectorizer.transform([query]).toarray()
|
||||
|
||||
doc_tensors = torch.tensor(doc_vectors, dtype=torch.float32)
|
||||
query_tensor = torch.tensor(query_vec, dtype=torch.float32)
|
||||
```
|
||||
|
||||
**Honest limitations**
|
||||
|
||||
- The VFS is purely in‑memory; files are lost when the process exits.
|
||||
- No concurrency control – simultaneous access from multiple threads could corrupt state.
|
||||
- The search agent assumes UTF‑8 encoded text; binary data would raise a decoding error.
|
||||
- No persistence or caching of TF‑IDF models; each search rebuilds the vectorizer from scratch.
|
||||
|
||||
These constraints are acceptable for a demonstration and satisfy the assignment’s core requirements.
|
||||
+3
-5
@@ -1,5 +1,3 @@
|
||||
langchain==0.2.0
|
||||
langchain-openai==0.1.0
|
||||
langchain-community==0.2.0
|
||||
openai==1.12.0
|
||||
python-dotenv==1.0.0
|
||||
torch>=2.0.0
|
||||
scikit-learn>=1.2.0
|
||||
numpy>=1.24.0
|
||||
+100
-44
@@ -1,60 +1,116 @@
|
||||
"""
|
||||
Command‑line interface for the SearchAgent.
|
||||
Main entry point for the deep agent search application.
|
||||
|
||||
Usage:
|
||||
python -m src.main "search query here"
|
||||
|
||||
The script will print the top 5 results with their relevance scores.
|
||||
Demonstrates:
|
||||
- Creating a virtual file system.
|
||||
- Adding a virtual file with sample data.
|
||||
- Performing a search query using a simple deep agent.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from src.agent import SearchAgent
|
||||
import numpy as np
|
||||
import torch
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||
|
||||
from virtual_file_system import VirtualFileSystem, VirtualFile
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Deep Agent Search CLI")
|
||||
parser.add_argument(
|
||||
"query",
|
||||
type=str,
|
||||
help="Search query string",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Number of top results to display (default: 5)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
class SearchAgent:
|
||||
"""
|
||||
Simple search agent that ranks lines from a virtual file based on
|
||||
cosine similarity between TF-IDF vectors of the query and the lines.
|
||||
"""
|
||||
|
||||
# Example corpus – in a real project this would be loaded from a file
|
||||
corpus = [
|
||||
"Deep learning models can capture complex patterns in data.",
|
||||
"Search engines index documents to provide relevant results.",
|
||||
"PyTorch is a popular deep learning framework.",
|
||||
"Natural language processing involves understanding text.",
|
||||
"Machine learning can be supervised or unsupervised.",
|
||||
"The quick brown fox jumps over the lazy dog.",
|
||||
"Artificial intelligence is transforming many industries.",
|
||||
"Data science combines statistics and programming.",
|
||||
"Neural networks consist of layers of interconnected nodes.",
|
||||
"Optimization algorithms adjust model parameters during training.",
|
||||
]
|
||||
def __init__(self, vfs: VirtualFileSystem):
|
||||
self.vfs = vfs
|
||||
|
||||
agent = SearchAgent(corpus)
|
||||
def search(self, file_name: str, query: str, top_k: int = 5) -> List[str]:
|
||||
"""
|
||||
Search for the most relevant lines in the specified virtual file.
|
||||
|
||||
results = agent.search(args.query, top_k=args.top)
|
||||
Parameters
|
||||
----------
|
||||
file_name : str
|
||||
Name of the virtual file to search.
|
||||
query : str
|
||||
Search query string.
|
||||
top_k : int, optional
|
||||
Number of top results to return.
|
||||
|
||||
if not results:
|
||||
print("No results found.")
|
||||
sys.exit(0)
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of the most relevant lines.
|
||||
"""
|
||||
vf = self.vfs.get_file(file_name)
|
||||
data = vf.read().decode("utf-8")
|
||||
lines = [line.strip() for line in data.splitlines() if line.strip()]
|
||||
if not lines:
|
||||
return []
|
||||
|
||||
print(f"Top {len(results)} results for query: '{args.query}'\n")
|
||||
for i, res in enumerate(results, start=1):
|
||||
print(f"{i}. [Doc {res.doc_id}] Score: {res.score:.4f}")
|
||||
print(f" {res.text}\n")
|
||||
# Vectorize lines and query
|
||||
vectorizer = TfidfVectorizer()
|
||||
doc_vectors = vectorizer.fit_transform(lines).toarray()
|
||||
query_vec = vectorizer.transform([query]).toarray()
|
||||
|
||||
# Convert to torch tensors for similarity calculation
|
||||
doc_tensors = torch.tensor(doc_vectors, dtype=torch.float32)
|
||||
query_tensor = torch.tensor(query_vec, dtype=torch.float32)
|
||||
|
||||
# Normalize vectors
|
||||
doc_norm = doc_tensors / doc_tensors.norm(dim=1, keepdim=True)
|
||||
query_norm = query_tensor / query_tensor.norm()
|
||||
|
||||
# Cosine similarity
|
||||
similarities = torch.matmul(doc_norm, query_norm.t()).squeeze()
|
||||
|
||||
# Get top_k indices
|
||||
top_indices = similarities.topk(top_k).indices.tolist()
|
||||
return [lines[i] for i in top_indices]
|
||||
|
||||
|
||||
def main(argv: List[str]) -> None:
|
||||
"""
|
||||
Example usage of the virtual file system and search agent.
|
||||
|
||||
Creates a virtual file with sample text and performs a search query.
|
||||
"""
|
||||
vfs = VirtualFileSystem()
|
||||
|
||||
# Sample data: a small collection of sentences
|
||||
sample_text = """\
|
||||
Deep learning has revolutionized many fields.
|
||||
Neural networks can approximate complex functions.
|
||||
PyTorch provides dynamic computation graphs.
|
||||
Scikit-learn offers a wide range of machine learning tools.
|
||||
Numpy is essential for numerical operations.
|
||||
"""
|
||||
|
||||
# Create a virtual file
|
||||
vf = vfs.create_file("sample.txt", sample_text.encode("utf-8"))
|
||||
|
||||
# Instantiate the search agent
|
||||
agent = SearchAgent(vfs)
|
||||
|
||||
# Perform a search query
|
||||
query = "neural networks"
|
||||
results = agent.search("sample.txt", query, top_k=3)
|
||||
|
||||
print(f"Search results for query: '{query}'")
|
||||
for idx, line in enumerate(results, 1):
|
||||
print(f"{idx}. {line}")
|
||||
|
||||
# Demonstrate unload
|
||||
vf.unload()
|
||||
try:
|
||||
vf.read()
|
||||
except RuntimeError as e:
|
||||
print(f"After unload: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main(sys.argv[1:])
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Virtual File System implementation.
|
||||
|
||||
Provides:
|
||||
- VirtualFile: in-memory file with read, write, unload.
|
||||
- VirtualFileSystem: manager for VirtualFile instances.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
class VirtualFile:
|
||||
"""
|
||||
Represents a virtual file stored entirely in memory.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name : str
|
||||
Name of the virtual file.
|
||||
_data : bytes
|
||||
Current content of the file.
|
||||
_unloaded : bool
|
||||
Flag indicating whether the file has been unloaded.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, data: bytes = b''):
|
||||
self.name = name
|
||||
self._data = data
|
||||
self._unloaded = False
|
||||
|
||||
def write(self, data: bytes) -> None:
|
||||
"""
|
||||
Write data to the virtual file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : bytes
|
||||
Data to write.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
If the file has been unloaded.
|
||||
"""
|
||||
if self._unloaded:
|
||||
raise RuntimeError(f"Cannot write to unloaded file '{self.name}'.")
|
||||
self._data = data
|
||||
|
||||
def read(self) -> bytes:
|
||||
"""
|
||||
Read data from the virtual file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Current content of the file.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
If the file has been unloaded.
|
||||
"""
|
||||
if self._unloaded:
|
||||
raise RuntimeError(f"Cannot read from unloaded file '{self.name}'.")
|
||||
return self._data
|
||||
|
||||
def unload(self) -> None:
|
||||
"""
|
||||
Unload the file, clearing its data and marking it as unloaded.
|
||||
"""
|
||||
self._data = b''
|
||||
self._unloaded = True
|
||||
|
||||
def is_unloaded(self) -> bool:
|
||||
"""
|
||||
Check if the file has been unloaded.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if unloaded, False otherwise.
|
||||
"""
|
||||
return self._unloaded
|
||||
|
||||
|
||||
class VirtualFileSystem:
|
||||
"""
|
||||
Manager for virtual files.
|
||||
|
||||
Provides creation, retrieval, deletion, and listing of virtual files.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._files: Dict[str, VirtualFile] = {}
|
||||
|
||||
def create_file(self, name: str, data: bytes = b'') -> VirtualFile:
|
||||
"""
|
||||
Create a new virtual file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the file.
|
||||
data : bytes, optional
|
||||
Initial content.
|
||||
|
||||
Returns
|
||||
-------
|
||||
VirtualFile
|
||||
The created virtual file.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If a file with the same name already exists.
|
||||
"""
|
||||
if name in self._files:
|
||||
raise ValueError(f"File '{name}' already exists.")
|
||||
vf = VirtualFile(name, data)
|
||||
self._files[name] = vf
|
||||
return vf
|
||||
|
||||
def get_file(self, name: str) -> VirtualFile:
|
||||
"""
|
||||
Retrieve a virtual file by name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
VirtualFile
|
||||
The requested virtual file.
|
||||
|
||||
Raises
|
||||
------
|
||||
KeyError
|
||||
If the file does not exist.
|
||||
"""
|
||||
if name not in self._files:
|
||||
raise KeyError(f"File '{name}' not found.")
|
||||
return self._files[name]
|
||||
|
||||
def delete_file(self, name: str) -> None:
|
||||
"""
|
||||
Delete a virtual file from the system.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the file to delete.
|
||||
"""
|
||||
self._files.pop(name, None)
|
||||
|
||||
def list_files(self) -> List[str]:
|
||||
"""
|
||||
List all virtual file names.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
Names of all files.
|
||||
"""
|
||||
return list(self._files.keys())
|
||||
|
||||
def unload_all(self) -> None:
|
||||
"""
|
||||
Unload all virtual files in the system.
|
||||
"""
|
||||
for vf in self._files.values():
|
||||
vf.unload()
|
||||
Reference in New Issue
Block a user