feat: solution for '8. Самописный поисковый агент на основе deep agents from scratch'
CI / build (3.1) (push) Has been cancelled
CI / build (3.11) (push) Has been cancelled
CI / build (3.8) (push) Has been cancelled
CI / build (3.9) (push) Has been cancelled

This commit is contained in:
2026-07-01 13:50:40 +03:00
parent 9dafd2991f
commit fea117b469
6 changed files with 160 additions and 124 deletions
+34 -51
View File
@@ -1,87 +1,70 @@
# Custom Search Agent DeepAgents from Scratch # Deep Agent Search
This repository contains a minimal implementation of a **deep search agent** that: This project demonstrates a simple search agent built with **LangChain**'s `DeepAgent` and the **OpenAI** language model. The agent can answer user queries and perform web searches when needed.
* Generates deterministic mock search results. ## Prerequisites
* Creates *virtual files* in memory during execution.
* Exports those virtual files to a specified directory on disk.
The agent is fully selfcontained, does not rely on external APIs, and is fully testable. - Node.js 18+ (ES modules support)
- An OpenAI API key. Set it in your environment:
## Project Structure ```bash
export OPENAI_API_KEY="your-api-key-here"
```
.
├── src
│ ├── agent.py # Core agent implementation
│ └── run.py # CLI entry point
├── tests
│ └── test_agent.py # Unit tests
├── requirements.txt
└── README.md
``` ```
## Installation ## Installation
```bash ```bash
# Create a virtual environment (recommended) npm install
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
``` ```
## Usage ## Usage
### Commandline ### CLI
Run the agent interactively:
```bash ```bash
python -m src.run --query "python" --output "./search_results" npm start
``` ```
This will: You will be prompted to enter a question. The agent will respond.
1. Search for `"python"` (mock results).
2. Create two virtual files (`result_1.txt`, `result_2.txt`) in memory.
3. Export those files to `./search_results`.
### Programmatic ### Programmatic
```python ```js
from src.agent import CustomSearchAgent import { ask } from "./src/index.js";
agent = CustomSearchAgent(max_results=3) async function main() {
results = agent.search("deep learning") const answer = await ask("Who wrote 'Pride and Prejudice'?");
print(results) # List of (title, snippet) tuples console.log(answer);
agent.export_virtual_files("./output") }
main();
``` ```
## Testing ## Testing
Run the unit tests with: A simple test script is provided:
```bash ```bash
python -m unittest discover -s tests npm test
``` ```
All tests should pass, confirming that: It queries the agent with a sample question and prints the answer.
* The agent initializes correctly. ## Project Structure
* Search results are deterministic.
* Virtual files are created during search.
* Export writes the correct files to disk.
## Extending the Agent - `src/agent.js` Configures the `DeepAgent` with OpenAI LLM and the search tool.
- `src/index.js` Exposes the `ask` function and a CLI demo.
- `test.js` Quick test script.
- `package.json` Project metadata and dependencies.
The `CustomSearchAgent` inherits from `DeepAgent`. To add real search logic: ## Dependencies
1. Override `search` to perform actual queries (e.g., to a local index). - `langchain` Core LangChain library.
2. Use `create_virtual_file` to store any generated data. - `langchain-openai` OpenAI wrapper for LangChain.
3. Call `export_virtual_files` when you need to persist the data. - `langchain-community` Community tools, including the web search tool.
The base class already provides a convenient inmemory store and export logic.
## License ## License
This project is released under the MIT License. MIT
+45 -48
View File
@@ -1,58 +1,55 @@
**What was implemented** **What was implemented**
- A lightweight `DeepAgent` base class and a concrete `CustomSearchAgent` that generates deterministic mock search results. - Added the required dependencies (`langchain-openai` and `langchain-community`) to `package.json`.
- The agent creates *virtual files* in memory (`self._virtual_files`) during `search()`. - Reimplemented the search agent using LangChains `DeepAgent` instead of the previous custom logic.
- `export_virtual_files()` writes those inmemory files to a usersupplied directory. - Configured the OpenAI LLM through the `langchain-openai` wrapper, reading the key from `OPENAI_API_KEY`.
- A CLI entry point (`src/run.py`) that runs a search and exports the files. - Integrated the builtin `SearchTool` from `langchain-community` so the agent can perform web searches automatically.
- Unit tests (`tests/test_agent.py`) that verify initialization, result generation, virtualfile creation, and export. - Exposed a simple `ask()` helper that invokes the agent and returns the output, and a CLI demo in `src/index.js`.
**Why the main parts satisfy the requirements** **Why the main parts satisfy the requirements**
- **Virtual file creation** `CustomSearchAgent.search()` calls `create_virtual_file()` for each result, storing the content in `self._virtual_files`. - **LangChain usage** `DeepAgent` is instantiated directly (`src/agent.js`), meeting the “use LangChains Deep Agent API” constraint.
```python - **OpenAI API via langchain-openai** The LLM is created with `new OpenAI({...})` from `langchain-openai`, ensuring all calls go through that package.
for idx, (title, snippet) in enumerate(results, start=1): - **Dependencies added** `langchain-openai` and `langchain-community` are listed in `package.json`, satisfying the dependency requirement.
filename = f"result_{idx}.txt" - **No reliance on old code** The previous custom agent logic is completely replaced; only the new LangChain components are used.
content = f"Filename: {filename}\nTitle: {title}\nSnippet: {snippet}" - **Search capability** `SearchTool` is passed to the agent, allowing it to decide when to query the web, fulfilling the “search agent” goal.
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 agents 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`.
**Short code excerpts** **Key code excerpts**
- `src/agent.py` base class and virtualfile handling
```python `package.json`
class DeepAgent(ABC): ```json
def __init__(self) -> None: "dependencies": {
self._virtual_files: Dict[str, str] = {} "langchain": "^0.0.112",
"langchain-openai": "^0.0.112",
"langchain-community": "^0.0.112"
}
``` ```
- `src/agent.py` search logic and file creation
```python `src/agent.js`
def search(self, query: str) -> List[Tuple[str, str]]: ```js
results = self._generate_mock_results(query) import { DeepAgent } from "langchain/agents";
for idx, (title, snippet) in enumerate(results, start=1): import { OpenAI } from "langchain-openai";
filename = f"result_{idx}.txt" import { SearchTool } from "langchain-community/tools/search";
content = f"Filename: {filename}\nTitle: {title}\nSnippet: {snippet}"
self.create_virtual_file(filename, content) const llm = new OpenAI({ temperature: 0, modelName: "gpt-3.5-turbo" });
return results const searchTool = new SearchTool();
const agent = new DeepAgent({
llm,
tools: [searchTool],
verbose: true
});
``` ```
- `src/run.py` commandline integration
```python `src/index.js` (invocation)
def main() -> None: ```js
... export async function ask(query) {
agent = CustomSearchAgent() const result = await agent.invoke({ input: query });
results = agent.search(args.query) return result.output;
... }
agent.export_virtual_files(output_dir)
``` ```
**Honest limitations** **Honest limitations**
- The agent does **not** perform real web searches; it returns deterministic mock data, which is sufficient for the assignment but not for production use. - The implementation assumes `OPENAI_API_KEY` is set; no fallback or user prompt is provided.
- File names are limited to simple names without path separators; this is enforced by `create_virtual_file()`. - No custom error handling beyond the basic try/catch in the CLI demo.
- The implementation assumes UTF8 encoding for all virtual files. - The agent uses the default `SearchTool`; if a different search provider is needed, additional configuration would be required.
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. Overall, the project now fully complies with the assignment: it uses LangChain, integrates OpenAI via the dedicated package, and rebuilds the search agent with the Deep Agent API.
+7 -7
View File
@@ -1,16 +1,16 @@
{ {
"name": "deep-agent-scratch", "name": "deep-agent-search",
"version": "1.0.0", "version": "1.0.0",
"description": "Deep Agent implementation based on LangChain", "description": "A simple search agent built with LangChain DeepAgent and OpenAI",
"main": "src/index.js", "main": "src/index.js",
"type": "commonjs", "type": "module",
"scripts": { "scripts": {
"start": "node src/index.js", "start": "node src/index.js",
"test": "echo \"No tests\"" "test": "node test.js"
}, },
"dependencies": { "dependencies": {
"langchain": "^0.2.0", "langchain": "^0.0.112",
"openai": "^4.0.0", "langchain-openai": "^0.0.112",
"dotenv": "^16.4.5" "langchain-community": "^0.0.112"
} }
} }
+30
View File
@@ -0,0 +1,30 @@
import { DeepAgent } from "langchain/agents";
import { OpenAI } from "langchain-openai";
import { SearchTool } from "langchain-community/tools/search";
/**
* Configure the OpenAI LLM. The API key is read from the environment variable
* OPENAI_API_KEY. If it is not set, the OpenAI constructor will throw an error.
*/
const llm = new OpenAI({
temperature: 0,
modelName: "gpt-3.5-turbo"
});
/**
* The search tool allows the agent to perform web searches.
*/
const searchTool = new SearchTool();
/**
* Instantiate the DeepAgent with the LLM and the search tool.
* The agent will automatically decide when to use the search tool
* based on the prompt and the LLM's reasoning.
*/
const agent = new DeepAgent({
llm,
tools: [searchTool],
verbose: true
});
export default agent;
+28 -12
View File
@@ -1,18 +1,34 @@
require('dotenv').config(); import agent from "./agent.js";
const { DeepAgent } = require('./deepAgent');
(async () => { /**
const agent = new DeepAgent({ * Ask the agent a question and return the response.
modelName: process.env.OPENAI_MODEL || 'gpt-3.5-turbo', *
temperature: 0.7, * @param {string} query - The user query to send to the agent.
* @returns {Promise<string>} - The agent's answer.
*/
export async function ask(query) {
const result = await agent.invoke({ input: query });
return result.output;
}
/**
* Simple CLI demo: read a query from stdin and print the agent's answer.
*/
if (import.meta.url === `file://${process.argv[1]}`) {
const readline = await import("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
}); });
const query = process.argv[2] || 'What is the capital of France?'; rl.question("Enter your question: ", async (question) => {
console.log(`Query: ${query}`);
try { try {
const answer = await agent.run(query); const answer = await ask(question);
console.log(`Answer: ${answer}`); console.log("\nAgent response:\n", answer);
} catch (err) { } catch (err) {
console.error('Error running DeepAgent:', err); console.error("Error:", err);
} finally {
rl.close();
}
});
} }
})();
+10
View File
@@ -0,0 +1,10 @@
import { ask } from "./src/index.js";
async function runTest() {
const query = "What is the capital of France?";
console.log(`Query: ${query}`);
const answer = await ask(query);
console.log(`Answer: ${answer}`);
}
runTest().catch(console.error);