Add multiagent example
This commit is contained in:
		
							parent
							
								
									54d6857da2
								
							
						
					
					
						commit
						59993e6a76
					
				|  | @ -0,0 +1,176 @@ | ||||||
|  | <!--Copyright 2024 The HuggingFace Team. All rights reserved. | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||||||
|  | the License. You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  | http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||||||
|  | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||||||
|  | specific language governing permissions and limitations under the License. | ||||||
|  | 
 | ||||||
|  | ⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be | ||||||
|  | rendered properly in your Markdown viewer. | ||||||
|  | 
 | ||||||
|  | --> | ||||||
|  | # 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... | ||||||
|  | @ -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`. | 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. | We use python-dotenv to load it. | ||||||
| ```py | ```py | ||||||
| from python_dotenv import load_dotenv | from dotenv import load_dotenv | ||||||
| load_dotenv() | load_dotenv() | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -71,8 +71,6 @@ class DocCodeExtractor: | ||||||
|         assert len(combined_code) > 0, "Code is empty!" |         assert len(combined_code) > 0, "Code is empty!" | ||||||
|         tmp_file = Path(tmp_dir) / "test_script.py" |         tmp_file = Path(tmp_dir) / "test_script.py" | ||||||
| 
 | 
 | ||||||
|         print("COFF", combined_code) |  | ||||||
| 
 |  | ||||||
|         with open(tmp_file, "w", encoding="utf-8") as f: |         with open(tmp_file, "w", encoding="utf-8") as f: | ||||||
|             f.write(combined_code) |             f.write(combined_code) | ||||||
| 
 | 
 | ||||||
|  | @ -110,7 +108,20 @@ class TestDocs: | ||||||
|             content = f.read() |             content = f.read() | ||||||
| 
 | 
 | ||||||
|         code_blocks = self.extractor.extract_python_code(content) |         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}") |             pytest.skip(f"No Python code blocks found in {doc_path.name}") | ||||||
| 
 | 
 | ||||||
|         # Validate syntax of each block individually by parsing it |         # Validate syntax of each block individually by parsing it | ||||||
|  | @ -119,20 +130,11 @@ class TestDocs: | ||||||
| 
 | 
 | ||||||
|         # Create and execute test script |         # Create and execute test script | ||||||
|         try: |         try: | ||||||
|             excluded_snippets = [ |  | ||||||
|                 "ToolCollection", |  | ||||||
|                 "image_generation_tool", |  | ||||||
|                 "from_langchain", |  | ||||||
|                 "while llm_should_continue(memory):", |  | ||||||
|             ] |  | ||||||
|             code_blocks = [ |             code_blocks = [ | ||||||
|                 block.replace("<YOUR_HUGGINGFACEHUB_API_TOKEN>", self.hf_token).replace( |                 block.replace("<YOUR_HUGGINGFACEHUB_API_TOKEN>", self.hf_token).replace( | ||||||
|                     "{your_username}", "m-ric" |                     "{your_username}", "m-ric" | ||||||
|                 ) |                 ) | ||||||
|                 for block in code_blocks |                 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) |             test_script = self.extractor.create_test_script(code_blocks, self._tmpdir) | ||||||
|             run_command(self.launch_args + [str(test_script)]) |             run_command(self.launch_args + [str(test_script)]) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue