diff --git a/docs/source/examples/multiagents.md b/docs/source/examples/multiagents.md new file mode 100644 index 0000000..bfc976d --- /dev/null +++ b/docs/source/examples/multiagents.md @@ -0,0 +1,176 @@ + +# Orchestrate a multi-agent system 🤖🤝🤖 + +[[open-in-colab]] + +In this notebook we will make a **multi-agent web browser: an agentic system with several agents collaborating to solve problems using the web!** + +It will be a simple hierarchy, using a `ManagedAgent` object to wrap the managed web search agent: + +``` + +----------------+ + | Manager agent | + +----------------+ + | + _______________|______________ + | | + Code interpreter +--------------------------------+ + tool | Managed agent | + | +------------------+ | + | | Web Search agent | | + | +------------------+ | + | | | | + | Web Search tool | | + | Visit webpage tool | + +--------------------------------+ +``` +Let's set up this system. + +Run the line below to install the required dependencies: + +``` +!pip install markdownify duckduckgo-search smolagents --upgrade -q +``` + +Let's login in order to call the HF Inference API: + +```py +from huggingface_hub import notebook_login + +notebook_login() +``` + +⚡️ Our agent will be powered by [Qwen/Qwen2.5-Coder-32B-Instruct](https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct) using `HfApiModel` class that uses HF's Inference API: the Inference API allows to quickly and easily run any OS model. + +_Note:_ The Inference API hosts models based on various criteria, and deployed models may be updated or replaced without prior notice. Learn more about it [here](https://huggingface.co/docs/api-inference/supported-models). + +```py +model = "Qwen/Qwen2.5-Coder-32B-Instruct" +``` + +### 🔍 Create a web search tool + +For web browsing, we can already use our pre-existing [`DuckDuckGoSearchTool`](https://github.com/huggingface/smolagents/blob/main/src/smolagents/default_tools/search.py) tool to provide a Google search equivalent. + +But then we will also need to be able to peak into the page found by the `DuckDuckGoSearchTool`. +To do so, we could import the library's built-in `VisitWebpageTool`, but we will build it again to see how it's done. + +So let's create our `VisitWebpageTool` tool from scratch using `markdownify`. + +```py +import re +import requests +from markdownify import markdownify +from requests.exceptions import RequestException +from smolagents import tool + + +@tool +def visit_webpage(url: str) -> str: + """Visits a webpage at the given URL and returns its content as a markdown string. + + Args: + url: The URL of the webpage to visit. + + Returns: + The content of the webpage converted to Markdown, or an error message if the request fails. + """ + try: + # Send a GET request to the URL + response = requests.get(url) + response.raise_for_status() # Raise an exception for bad status codes + + # Convert the HTML content to Markdown + markdown_content = markdownify(response.text).strip() + + # Remove multiple line breaks + markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content) + + return markdown_content + + except RequestException as e: + return f"Error fetching the webpage: {str(e)}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" +``` + +Ok, now let's initialize and test our tool! + +```py +print(visit_webpage("https://en.wikipedia.org/wiki/Hugging_Face")[:500]) +``` + +## Build our multi-agent system 🤖🤝🤖 + +Now that we have all the tools `search` and `visit_webpage`, we can use them to create the web agent. + +Which configuration to choose for this agent? +- Web browsing is a single-timeline task that does not require parallel tool calls, so JSON tool calling works well for that. We thus choose a `JsonAgent`. +- Also, since sometimes web search requires exploring many pages before finding the correct answer, we prefer to increase the number of `max_iterations` to 10. + +```py +from smolagents import ( + CodeAgent, + ToolCallingAgent, + HfApiModel, + ManagedAgent, +) +from smolagents.default_tools import DuckDuckGoSearchTool + +model = HfApiModel(model) + +web_agent = ToolCallingAgent( + tools=[DuckDuckGoSearchTool(), visit_webpage], + model=model, + max_iterations=10, +) +``` + +We then wrap this agent into a `ManagedAgent` that will make it callable by its manager agent. + +```py +managed_web_agent = ManagedAgent( + agent=web_agent, + name="search", + description="Runs web searches for you. Give it your query as an argument.", +) +``` + +Finally we create a manager agent, and upon initialization we pass our managed agent to it in its `managed_agents` argument. + +Since this agent is the one tasked with the planning and thinking, advanced reasoning will be beneficial, so a `CodeAgent` will be the best choice. + +Also, we want to ask a question that involves the current year: so let us add `additional_authorized_imports=["time"]` + +```py +manager_agent = CodeAgent( + tools=[], + model=model, + managed_agents=[managed_web_agent], + additional_authorized_imports=["time"], +) +``` + +That's all! Now let's run our system! We select a question that requires some calculation and + +```py +manager_agent.run("How many years ago was Stripe founded?") +``` + +Our agents managed to efficiently collaborate towards solving the task! ✅ + +💡 You can easily extend this to more agents: one does the code execution, one the web search, one handles file loadings... \ No newline at end of file diff --git a/docs/source/examples/rag.md b/docs/source/examples/rag.md index 80c03af..600550e 100644 --- a/docs/source/examples/rag.md +++ b/docs/source/examples/rag.md @@ -40,7 +40,7 @@ Run the line below to install required dependencies: To call the HF Inference API, you will need a valid token as your environment variable `HF_TOKEN`. We use python-dotenv to load it. ```py -from python_dotenv import load_dotenv +from dotenv import load_dotenv load_dotenv() ``` diff --git a/tests/test_all_docs.py b/tests/test_all_docs.py index 0e195cc..ef905f1 100644 --- a/tests/test_all_docs.py +++ b/tests/test_all_docs.py @@ -71,8 +71,6 @@ class DocCodeExtractor: assert len(combined_code) > 0, "Code is empty!" tmp_file = Path(tmp_dir) / "test_script.py" - print("COFF", combined_code) - with open(tmp_file, "w", encoding="utf-8") as f: f.write(combined_code) @@ -110,7 +108,20 @@ class TestDocs: content = f.read() code_blocks = self.extractor.extract_python_code(content) - if not code_blocks: + excluded_snippets = [ + "ToolCollection", + "image_generation_tool", + "from_langchain", + "while llm_should_continue(memory):", + ] + code_blocks = [ + block + for block in code_blocks + if not any( + [snippet in block for snippet in excluded_snippets] + ) # Exclude these tools that take longer to run and add dependencies + ] + if len(code_blocks) == 0: pytest.skip(f"No Python code blocks found in {doc_path.name}") # Validate syntax of each block individually by parsing it @@ -119,20 +130,11 @@ class TestDocs: # Create and execute test script try: - excluded_snippets = [ - "ToolCollection", - "image_generation_tool", - "from_langchain", - "while llm_should_continue(memory):", - ] code_blocks = [ block.replace("", self.hf_token).replace( "{your_username}", "m-ric" ) for block in code_blocks - if not any( - [snippet in block for snippet in excluded_snippets] - ) # Exclude these tools that take longer to run and add dependencies ] test_script = self.extractor.create_test_script(code_blocks, self._tmpdir) run_command(self.launch_args + [str(test_script)])