diff --git a/README.md b/README.md index 42bbdef..28dc20d 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,114 @@ -# Deep Agent from Scratch +# Deep Agents from Scratch – LangChain Search Agent -This repository demonstrates a **Deep Agent** implementation using the **LangChain** library. -The agent follows the “Deep Agents from Scratch” template and can answer arbitrary questions by leveraging an LLM (OpenAI GPT‑3.5‑Turbo by default). It also showcases how to integrate a simple tool (`Echo`) and use a Planner/Executor pattern for a more realistic agent workflow. +This project demonstrates a **Deep Agent** built from scratch using **LangChain**. +The agent can answer user questions by searching the web with DuckDuckGo and +providing concise, up‑to‑date responses. + +> **Author**: Artur Kuzakhmetov +> **Course**: Deep Agents from Scratch (Lecture: Perplexity, 09.04.2026) +> **Deadline**: 31.08.2026 + +--- ## Features -- Implements the **Planner** and **Executor** pattern from the Deep Agents from Scratch template. -- Uses LangChain’s `OpenAI`, `Tool`, `PromptTemplate`, and `ConversationBufferMemory`. -- Configurable LLM model, temperature, and token limits. -- Simple command‑line interface for quick testing. -- Environment‑variable based configuration for API keys and model selection. -- Demonstrates tool integration (Echo tool) and the full agent template. +- **Custom Search Tool** – queries DuckDuckGo’s instant answer API. +- **Conversation Memory** – keeps context across turns. +- **REACT Agent** – follows the “Reason → Act → Think” pattern. +- **CLI** – simple command‑line interface for interactive use. +- **Unit Tests** – basic tests for the search tool. -## Prerequisites - -- Node.js 18+ (or any LTS version) -- An OpenAI API key +--- ## Setup -```bash -# Clone the repository -git clone https://git.brojs.ru/kuzakhmetovartur/8.-samopisnyy-poiskovyy-agent-na-osnove- -cd 8.-samopisnyy-poiskovyy-agent-na-osnove- +1. **Clone the repository** -# Install dependencies -npm install -``` + ```bash + git clone https://git.brojs.ru/kuzakhmetovartur/8.-samopisnyy-poiskovyy-agent-na-osnove-.git + cd 8.-samopisnyy-poiskovyy-agent-na-osnove- + ``` -Create a `.env` file in the project root: +2. **Create a virtual environment** -```dotenv -OPENAI_API_KEY=your_openai_api_key_here -OPENAI_MODEL=gpt-3.5-turbo # optional, defaults to gpt-3.5-turbo -``` + ```bash + python -m venv .venv + source .venv/bin/activate # On Windows: .venv\Scripts\activate + ``` -> **Tip:** Keep your `.env` file out of version control. Add it to `.gitignore` if you plan to push the repo. +3. **Install dependencies** + + ```bash + pip install -r requirements.txt + ``` + +4. **Set up OpenAI API key** + + Create a `.env` file in the project root: + + ```dotenv + OPENAI_API_KEY=sk-... + ``` + + Replace `sk-...` with your actual key. + +--- ## Usage -Run the agent with a question: +Run the agent: ```bash -npm start -- "What is the tallest mountain in the world?" +python -m src.index ``` -Or simply: +You will see: + +``` +Deep Agents from Scratch - LangChain Search Agent +Type 'exit' or 'quit' to stop. + +Enter your question: +``` + +Type a question, e.g.: + +``` +What is the capital of France? +``` + +The agent will search the web and return an answer. + +--- + +## Running Tests ```bash -node src/index.js "Your question here" +python -m unittest discover -s tests ``` -The agent will output the answer to the console. +--- ## Project Structure ``` -├── package.json # Project metadata and dependencies -├── src/ -│ ├── deepAgent.js # Core DeepAgent implementation (Planner/Executor) -│ └── index.js # CLI entry point +├── src +│ └── index.py # Main agent implementation +├── tests +│ └── test_search_tool.py # Unit tests for the search tool +├── requirements.txt # Project dependencies └── README.md # Documentation ``` -## Extending the Agent +--- -- **Add more sophisticated prompts**: Edit the `Planner` prompt in `deepAgent.js`. -- **Integrate additional tools**: Use LangChain’s `Tool` and add them to the `tools` array. -- **Switch LLM providers**: Replace `OpenAI` with another LangChain LLM implementation (e.g., `AzureOpenAI`, `Anthropic`). +## Contributing + +Feel free to fork the repository, create a feature branch, and submit a pull request. +Please ensure tests pass before merging. + +--- ## License -MIT © 2026 - ---- \ No newline at end of file +MIT License. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 27989f0..d9712ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -transformers -torch -requests -click -pytest \ No newline at end of file +langchain==0.1.0 +openai==1.3.0 +python-dotenv==1.0.0 +requests==2.31.0 \ No newline at end of file diff --git a/src/index.py b/src/index.py new file mode 100644 index 0000000..6f5c2b2 --- /dev/null +++ b/src/index.py @@ -0,0 +1,139 @@ +import os +import asyncio +from typing import Any + +import requests +from dotenv import load_dotenv +from langchain.chat_models import ChatOpenAI +from langchain.agents import initialize_agent, AgentType +from langchain.memory import ConversationBufferMemory +from langchain.tools import BaseTool + + +class DuckDuckGoSearchTool(BaseTool): + """ + A simple web search tool that queries DuckDuckGo's instant answer API. + """ + + name: str = "duckduckgo_search" + description: str = ( + "Use this tool to search the web for up-to-date information. " + "Input should be a search query." + ) + + def _run(self, query: str) -> str: + """ + Execute the search query and return a concise answer. + + Parameters + ---------- + query : str + The search query string. + + Returns + ------- + str + A short answer extracted from the search results. + """ + if not query: + return "No query provided." + + url = "https://api.duckduckgo.com/" + params = { + "q": query, + "format": "json", + "no_html": 1, + "skip_disambig": 1, + } + try: + response = requests.get(url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + except Exception as exc: + return f"Error during search: {exc}" + + # Prefer abstract text if available + abstract = data.get("AbstractText") + if abstract: + return abstract + + # Fallback to the first related topic + topics = data.get("RelatedTopics", []) + if topics: + first = topics[0] + if isinstance(first, dict): + return first.get("Text", "No relevant information found.") + return "No relevant information found." + + async def _arun(self, query: str) -> str: + """ + Asynchronous run implementation that delegates to the synchronous _run method. + """ + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, self._run, query) + + +def create_agent() -> Any: + """ + Create and configure the Deep Agent using LangChain. + + Returns + ------- + Any + The initialized agent executor. + """ + # Load environment variables (e.g., OPENAI_API_KEY) + load_dotenv() + + # Initialize the LLM + llm = ChatOpenAI(temperature=0) + + # Memory to keep conversation context + memory = ConversationBufferMemory(memory_key="chat_history") + + # Instantiate the custom search tool + search_tool = DuckDuckGoSearchTool() + + # Initialize the agent with the REACT description template + agent = initialize_agent( + tools=[search_tool], + llm=llm, + agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, + memory=memory, + verbose=True, + ) + return agent + + +def main() -> None: + """ + Simple CLI to interact with the Deep Agent. + """ + agent = create_agent() + print("Deep Agents from Scratch - LangChain Search Agent") + print("Type 'exit' or 'quit' to stop.\n") + + while True: + try: + query = input("Enter your question: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nExiting.") + break + + if query.lower() in {"exit", "quit"}: + print("Goodbye!") + break + + if not query: + print("Please enter a non-empty query.") + continue + + try: + result = agent.run(query) + print("\nAnswer:\n", result) + except Exception as exc: + print(f"Error: {exc}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests/test_search_tool.py b/tests/test_search_tool.py new file mode 100644 index 0000000..15fa764 --- /dev/null +++ b/tests/test_search_tool.py @@ -0,0 +1,21 @@ +import unittest +from src.index import DuckDuckGoSearchTool + + +class TestSearchTool(unittest.TestCase): + def setUp(self): + self.tool = DuckDuckGoSearchTool() + + def test_run_returns_string(self): + result = self.tool.run("Python programming language") + self.assertIsInstance(result, str) + self.assertTrue(len(result) > 0) + + def test_run_handles_empty_query(self): + result = self.tool.run("") + self.assertIsInstance(result, str) + self.assertTrue(len(result) > 0) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file