Now that you understand why tools are important for extending an agent's abilities and how to define a simple tool like a calculator, it's time to get your hands dirty with a more dynamic capability: adding web search to your agent. This will allow your agent to find information that isn't part of its initial training data, such as current events or highly specific facts.In this practical exercise, we'll guide you through integrating a search tool. You'll learn how to:Set up a function to call an external search API.Describe this tool effectively so your LLM agent can understand its purpose and how to use it.Modify your agent's prompting to encourage the use of the search tool when appropriate.Observe the agent using the tool to answer questions it couldn't answer before.Let's begin by building the search functionality.Setting Up Your Search ToolTo perform a web search, our agent needs a way to interact with a search engine. This is typically done through an API (Application Programming Interface). Many services offer search APIs, such as SerpApi, Google Custom Search API, or Bing Search API. For this exercise, we'll outline a generic approach. You'll need to sign up for one of these services and obtain an API key. Most offer a free tier that's sufficient for learning purposes.1. Install Necessary LibrariesIf you're interacting with a web API, you'll likely need the requests library in Python to make HTTP requests. If you don't have it installed, open your terminal or command prompt and run:pip install requests2. Create the Search FunctionLet's write a Python function that takes a search query, calls an external search API, and returns a summary of the results. Below is a template. You'll need to replace "YOUR_API_KEY" and the API_ENDPOINT with details from your chosen search API provider. The structure of the API response will also vary, so you'll need to adjust how results are extracted and formatted.import requests import json # Used for parsing the API response # Replace with your actual API key and endpoint SEARCH_API_KEY = "YOUR_SEARCH_API_KEY" SEARCH_API_ENDPOINT = "https://api.example-search.com/search" # Replace with actual API endpoint def web_search(query: str) -> str: """ Performs a web search for the given query using an external API and returns a formatted string of search results. """ if SEARCH_API_KEY == "YOUR_SEARCH_API_KEY": return "Search API key not configured. Please set it up to use the search tool." params = { "q": query, "api_key": SEARCH_API_KEY, # Add any other parameters required by your search API # e.g., 'num_results': 3 } try: response = requests.get(SEARCH_API_ENDPOINT, params=params) response.raise_for_status() # Raises an HTTPError for bad responses (4XX or 5XX) search_data = response.json() # Assumes the API returns JSON # Process the search_data. This part is highly dependent on your API's response format. # For example, you might extract titles and snippets. snippets = [] if "organic_results" in search_data: # Example for SerpApi-like structure for result in search_data["organic_results"][:3]: # Get top 3 results snippet = f"Title: {result.get('title', 'N/A')}\nSnippet: {result.get('snippet', 'N/A')}\nLink: {result.get('link', 'N/A')}" snippets.append(snippet) elif "webPages" in search_data and "value" in search_data["webPages"]: # Example for Bing-like structure for result in search_data["webPages"]["value"][:3]: snippet = f"Title: {result.get('name', 'N/A')}\nSnippet: {result.get('snippet', 'N/A')}\nLink: {result.get('url', 'N/A')}" snippets.append(snippet) else: # Fallback or more generic parsing if the structure is unknown or different # This might involve just returning a string representation of the JSON # or trying to find common keys. For simplicity, we'll indicate if results are not found. return "No specific results found in the expected format. Raw data might be available." if not snippets: return "No search results found." return "\n\n".join(snippets) except requests.exceptions.RequestException as e: return f"Error during search: {e}" except json.JSONDecodeError: return "Error: Could not decode search results from API." # Example usage (optional, for testing the function directly) # if __name__ == "__main__": # test_query = "latest advancements in LLM agents" # search_results = web_search(test_query) # print(f"Search results for '{test_query}':\n{search_results}")Important: Remember to replace placeholders with your actual API credentials and adapt the result parsing logic to match the specific API you choose. Read the documentation for your chosen search API carefully.Describing the Tool for the AgentNow that we have a web_search function, we need to tell our LLM agent about it. As discussed in "Prompting Agents to Utilize Tools," the LLM needs a clear name and description for each tool to understand what it does and when to use it.Let's define a description for our search tool:search_tool_description = { "name": "WebSearch", "description": "Use this tool to find current information on the internet, answer questions about recent events, or get up-to-date details on any topic. Input should be a clear search query.", "function": web_search, # Reference to our Python function "input_schema": { # Describes the expected input for the tool "type": "object", "properties": { "query": { "type": "string", "description": "The search query to look up on the internet." } }, "required": ["query"] } }This dictionary provides:A name: A short, identifiable name for the tool (e.g., WebSearch).A description: A clear explanation of what the tool does and when the agent should consider using it. This is very important for the LLM's decision-making process.The function itself: So the agent framework can call it.An input_schema: This tells the LLM (and the agent framework) what arguments the tool expects. Here, it expects a single string argument named query.Your agent framework will use this information. When the LLM decides to use "WebSearch", the framework will know to call the web_search function and will try to extract a query argument from the LLM's generated parameters.Integrating the Tool into Your AgentHow you integrate this tool depends on the specific agent framework you might be using or the structure of your custom agent. For this practical, let's assume you have a basic agent loop that involves:Getting a user's request.Presenting this request (and tool descriptions) to an LLM.The LLM deciding whether to respond directly or use a tool.If a tool is chosen, the agent executes the tool with LLM-provided arguments.The tool's output is then fed back to the LLM for a final response.Here's a simplified flow of how your agent might use this. Imagine you have a core function run_agent_turn(user_request, available_tools):# This is a simplified representation. Your actual agent logic might be more complex. # Assume an LLM interaction function like `get_llm_decision(prompt, tools_descriptions)` # which returns either a direct answer or a tool call instruction. def get_llm_decision(prompt_text, tools_list_for_llm): """ Placeholder for your LLM call. This function would send the prompt and tool descriptions to the LLM. The LLM would respond with either: 1. A direct answer. 2. A JSON object indicating a tool call, e.g., {'tool_name': 'WebSearch', 'tool_input': {'query': 'current AI trends'}} """ # In a real scenario, this involves an API call to an LLM. # For this example, we'll simulate the LLM's decision based on keywords. print(f"\n[LLM Input Prompt]:\n{prompt_text}\n") # Show what LLM sees if "weather in Paris" in prompt_text.lower() and "WebSearch" in str(tools_list_for_llm): print("[LLM Simulated Decision]: Use WebSearch for 'current weather in Paris'") return {"tool_name": "WebSearch", "tool_input": {"query": "current weather in Paris"}} elif "latest news on AI" in prompt_text.lower() and "WebSearch" in str(tools_list_for_llm): print("[LLM Simulated Decision]: Use WebSearch for 'latest news on AI'") return {"tool_name": "WebSearch", "tool_input": {"query": "latest news on AI"}} else: print("[LLM Simulated Decision]: Answer directly (or I don't know).") return {"answer": "I can only answer if I decide to use a tool for specific queries like weather or news in this simulation."} # Our list of available tools for the agent available_tools = { "WebSearch": search_tool_description # You could add other tools here, like the calculator from a previous section } def run_agent_turn(user_request: str): print(f"User: {user_request}") # Prepare the list of tool descriptions for the LLM tools_for_llm_prompt = [] for tool_name, tool_details in available_tools.items(): tools_for_llm_prompt.append({ "name": tool_details["name"], "description": tool_details["description"], "parameters": tool_details["input_schema"] # LLM needs to know what parameters to generate }) # Construct a prompt for the LLM # This is a very basic prompt. Real agent prompts are more sophisticated. prompt = f"""You are a helpful assistant. You have access to the following tools: {json.dumps(tools_for_llm_prompt, indent=2)} User query: "{user_request}" Based on the user query and the available tools, decide if a tool should be used. If yes, respond with a JSON object specifying the tool name and its input. For example: {{"tool_name": "WebSearch", "tool_input": {{"query": "your search query"}}}} If no tool is needed, or you can answer directly, provide the answer. """ llm_response = get_llm_decision(prompt, tools_for_llm_prompt) if "tool_name" in llm_response: tool_name = llm_response["tool_name"] tool_input_args = llm_response["tool_input"] if tool_name in available_tools: selected_tool = available_tools[tool_name] tool_function = selected_tool["function"] # Here, we assume the LLM provides arguments matching the function's needs. # For `web_search`, it expects a 'query' argument. query_arg = tool_input_args.get("query") if query_arg is None: print(f"Agent: LLM decided to use {tool_name} but didn't provide a 'query'.") return print(f"Agent: Using tool '{tool_name}' with query: '{query_arg}'") tool_output = tool_function(query_arg) print(f"Tool '{tool_name}' output:\n{tool_output}") # Now, we would typically send this output back to the LLM for a final synthesis. # For simplicity in this step-by-step, we'll just print it. # In a full ReAct loop, the prompt for this second LLM call would be: # final_prompt = f"{prompt}\nObservation: {tool_output}\nThought: Now I will use this information to answer the user.\nFinal Answer:" # final_answer_from_llm = get_llm_decision(final_prompt, []) # No tools needed for final answer # print(f"Agent (final answer based on tool): {final_answer_from_llm.get('answer', 'Could not process tool output.')}") print(f"Agent: Based on the search, here's what I found: {tool_output}") else: print(f"Agent: LLM tried to use an unknown tool: {tool_name}") elif "answer" in llm_response: print(f"Agent: {llm_response['answer']}") else: print("Agent: I'm not sure how to proceed.") # Let's try it out! # First, ensure your SEARCH_API_KEY in web_search function is set, # otherwise it will return "Search API key not configured." run_agent_turn("What is the latest news on AI?") print("\n" + "="*50 + "\n") run_agent_turn("Tell me a joke.") # This query likely won't trigger the search tool in our simulationDiagram: Agent Tool Usage FlowTo visualize how the agent, LLM, and tool interact, consider the following diagram:digraph G { rankdir=TB; node [shape=box, style="rounded,filled", fillcolor="#e9ecef", fontname="Arial"]; edge [fontname="Arial"]; User [fillcolor="#a5d8ff"]; AgentCore [label="Agent Core Logic"]; LLM [label="Large Language Model", fillcolor="#ffec99"]; SearchTool [label="WebSearch Tool\n(Python function)", fillcolor="#96f2d7"]; ExternalAPI [label="External Search API", shape=cylinder, fillcolor="#ffc9c9"]; User -> AgentCore [label="1. User Request"]; AgentCore -> LLM [label="2. Request + Tool Info"]; LLM -> AgentCore [label="3. Decision: Use WebSearch\n(tool_name, query)"]; AgentCore -> SearchTool [label="4. Execute Tool(query)"]; SearchTool -> ExternalAPI [label="5. Call API(query)"]; ExternalAPI -> SearchTool [label="6. API Response"]; SearchTool -> AgentCore [label="7. Tool Output (results)"]; AgentCore -> LLM [label="8. Output + Original Request (for synthesis)"]; LLM -> AgentCore [label="9. Final Answer"]; AgentCore -> User [label="10. Present Answer"]; }This diagram illustrates the sequence of operations when a user's request leads the agent to use the WebSearch tool. The agent core orchestrates interactions between the user, the LLM, and the tool, which in turn communicates with an external API.Running and Testing Your Enhanced AgentTo fully test this:Replace Placeholders: Ensure you've configured the web_search function with a valid API key and endpoint.LLM Integration: The get_llm_decision function provided is a simulation. In a real agent, this function would make an API call to an LLM (like OpenAI's GPT models, Anthropic's Claude, or a locally run model via an interface like Ollama). You would pass the user's query, the system message, and the JSON descriptions of available tools to the LLM, and it would decide whether to call a tool.Test Queries:Try asking questions that require current information: "What's the weather like in London today?" or "Who won the latest major tennis tournament?"Ask questions that your LLM might know but could be verified or updated: "What is the capital of France?" (The agent might answer directly or still choose to search, depending on its prompting).Ask questions that don't require search: "Tell me a story about a brave knight."Observe how your agent (or your simulated LLM decision logic) decides whether to use the WebSearch tool. If it uses the tool, check the output.Troubleshooting and ConsiderationsAPI Key Errors: Ensure your API key is correct and has not exceeded its quota.API Response Parsing: The structure of JSON responses from search APIs varies. You'll need to print(response.json()) inside your web_search function during testing to understand the structure and adapt your parsing logic (snippets creation) accordingly.LLM Not Using the Tool:Prompting: Your main prompt to the LLM needs to clearly state that tools are available and encourage their use. The tool description itself must be very clear.Tool Description: If the description in search_tool_description is ambiguous or doesn't clearly convey when to use the tool, the LLM might ignore it or use it incorrectly.LLM "Laziness": Sometimes, LLMs might try to answer from their pre-trained knowledge even if a tool would provide a better answer. More advanced prompting techniques can help mitigate this.Too Eager to Use the Tool: Conversely, the LLM might try to use the search tool for everything. Fine-tuning the prompt and tool descriptions can help balance this.Error Handling in web_search: The example function has basic error handling. Tools should handle network issues, timeouts, and unexpected API responses gracefully.This hands-on exercise provides a foundational step in making your LLM agents more capable and connected to information. By equipping them with tools like search, you significantly expand the range of tasks they can perform and the quality of their responses. As you build more complex agents, you'll find yourself creating and integrating a variety of tools to suit different needs.