--- title: "AI Agents" track: "practical-ai" difficulty: "intermediate" estimatedTime: "30 min" prerequisites: ["practical-ai/rag"] tags: ["agents", "tool-use", "function-calling", "react", "multi-agent", "planning"] --- ## Introduction An AI agent is a system that uses a language model as its reasoning engine to decide what actions to take, execute those actions, observe the results, and iterate until a task is complete. While a standard LLM call is a single input-output exchange, an agent operates in a **loop** — thinking, acting, and adapting. Agents represent a fundamental shift from using LLMs as text generators to using them as autonomous decision-makers. They can browse the web, write and execute code, query databases, send emails, and orchestrate complex multi-step workflows. ## The Agent Loop Every agent follows the same basic cycle: ``` Observe → Think → Act → Observe → Think → Act → ... → Done ``` 1. **Observe** — receive the current state (user query, tool outputs, environment) 2. **Think** — the LLM reasons about what to do next 3. **Act** — execute a tool or produce a final response 4. **Repeat** until the task is complete ```python def agent_loop(user_query, tools, max_steps=10): """Simplified agent loop.""" messages = [{"role": "user", "content": user_query}] for step in range(max_steps): # Think: ask the LLM what to do response = call_llm(messages, tools=tools) # Check if the agent wants to use a tool if response.tool_use: # Act: execute the tool tool_name = response.tool_use.name tool_input = response.tool_use.input tool_result = execute_tool(tool_name, tool_input) # Observe: add tool result to context messages.append({"role": "assistant", "content": response}) messages.append({ "role": "user", "content": [{"type": "tool_result", "content": tool_result}], }) else: # Done: the agent produced a final response return response.text return "Max steps reached without completing the task." ``` ## Tool Use and Function Calling Tools are the agent's hands. They bridge the gap between the LLM's reasoning ability and the real world. Modern APIs support **function calling** — you define tools with schemas, and the model decides when and how to call them. ### Defining Tools ```python import anthropic client = anthropic.Anthropic() # Define tools the agent can use tools = [ { "name": "get_weather", "description": "Get the current weather for a city.", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "The city name, e.g., 'San Francisco'", }, }, "required": ["city"], }, }, { "name": "search_web", "description": "Search the web for current information.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "The search query", }, }, "required": ["query"], }, }, ] response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, tools=tools, messages=[{ "role": "user", "content": "What's the weather like in Tokyo today?", }], ) # The model will respond with a tool_use block for block in response.content: if block.type == "tool_use": print(f"Tool: {block.name}") print(f"Input: {block.input}") # Tool: get_weather # Input: {"city": "Tokyo"} ``` ### Executing Tools and Continuing ```python def run_agent(user_message, tools, tool_implementations): """Complete agent with tool execution.""" messages = [{"role": "user", "content": user_message}] while True: response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, tools=tools, messages=messages, ) # Check if we need to execute tools tool_uses = [b for b in response.content if b.type == "tool_use"] if not tool_uses: # No tool calls — return the text response return response.content[0].text # Execute each tool and collect results messages.append({"role": "assistant", "content": response.content}) tool_results = [] for tool_use in tool_uses: # Run the actual tool result = tool_implementations[tool_use.name](tool_use.input) tool_results.append({ "type": "tool_result", "tool_use_id": tool_use.id, "content": str(result), }) messages.append({"role": "user", "content": tool_results}) ``` The quality of your tool descriptions is critical. The LLM decides which tool to use based on the name and description. Vague descriptions lead to wrong tool selections. Be specific: instead of "search for information," write "Search a product catalog by name, category, or price range. Returns up to 10 matching products." ## The ReAct Pattern **ReAct** (Reasoning + Acting) is a foundational pattern where the agent alternates between reasoning (thinking through the problem) and acting (using tools). The reasoning step is explicit in the output. ``` Question: What is the population of the capital of France? Thought: I need to find the capital of France first, then its population. Action: search_web("capital of France") Observation: The capital of France is Paris. Thought: Now I need the population of Paris. Action: search_web("population of Paris 2024") Observation: The population of Paris is approximately 2.1 million. Thought: I now have the answer. Answer: The population of the capital of France (Paris) is approximately 2.1 million people. ``` The key insight is that **explicit reasoning improves tool selection and task decomposition**. Without the thought step, agents make more errors in choosing which tool to use and when. ## Planning and Reasoning More complex agents use structured planning before executing: ### Plan-and-Execute ```python system_prompt = """You are a research agent. When given a question: 1. PLAN: Break the question into sub-tasks 2. EXECUTE: Complete each sub-task using available tools 3. SYNTHESIZE: Combine results into a final answer Always start by writing your plan before taking any actions.""" ``` ### Self-Reflection Agents can evaluate their own outputs and correct mistakes: ```python reflection_prompt = """Review your previous answer for: 1. Factual accuracy — are all claims supported by the retrieved data? 2. Completeness — did you address all parts of the question? 3. Coherence — does the answer flow logically? If you find issues, fix them. If the answer is good, confirm it.""" ``` ## Building a Simple Agent Here is a complete, working agent that can do math and look up information: ```python import anthropic import json import math client = anthropic.Anthropic() # Tool implementations def calculate(expression): """Safely evaluate a math expression.""" allowed = { "abs": abs, "round": round, "min": min, "max": max, "sqrt": math.sqrt, "pow": pow, "pi": math.pi, "e": math.e, } try: return str(eval(expression, {"__builtins__": {}}, allowed)) except Exception as e: return f"Error: {e}" def get_current_date(): """Get today's date.""" from datetime import date return date.today().isoformat() # Tool definitions for the API tools = [ { "name": "calculate", "description": "Evaluate a mathematical expression. Supports +, -, *, /, sqrt, pow, pi, e.", "input_schema": { "type": "object", "properties": { "expression": { "type": "string", "description": "Math expression, e.g. 'sqrt(144) + pow(2, 10)'", }, }, "required": ["expression"], }, }, { "name": "get_current_date", "description": "Get today's date in ISO format (YYYY-MM-DD).", "input_schema": { "type": "object", "properties": {}, }, }, ] tool_functions = { "calculate": lambda args: calculate(args["expression"]), "get_current_date": lambda args: get_current_date(), } def run_agent(question): """Run the agent to answer a question.""" messages = [{"role": "user", "content": question}] print(f"\nQuestion: {question}") print("-" * 50) for step in range(5): response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system="You are a helpful assistant with access to tools. Use them when needed.", tools=tools, messages=messages, ) tool_uses = [b for b in response.content if b.type == "tool_use"] text_blocks = [b for b in response.content if b.type == "text"] if text_blocks: for block in text_blocks: print(f"Agent: {block.text}") if not tool_uses: return messages.append({"role": "assistant", "content": response.content}) tool_results = [] for tool_use in tool_uses: print(f" [Tool: {tool_use.name}({json.dumps(tool_use.input)})]") result = tool_functions[tool_use.name](tool_use.input) print(f" [Result: {result}]") tool_results.append({ "type": "tool_result", "tool_use_id": tool_use.id, "content": result, }) messages.append({"role": "user", "content": tool_results}) # Try it run_agent("What is the square root of 729 plus 2 to the power of 8?") run_agent("What is today's date, and how many days until the end of the year?") ``` ## Multi-Agent Systems Complex tasks benefit from multiple specialized agents working together: ### Common Patterns 1. **Supervisor** — one agent delegates tasks to specialist agents 2. **Pipeline** — agents pass output sequentially (researcher -> writer -> reviewer) 3. **Debate** — multiple agents propose solutions and critique each other 4. **Swarm** — agents independently work on subtasks and merge results ```python # Simplified multi-agent: Researcher + Writer def research_agent(topic): """Agent that gathers information.""" return run_agent_with_tools( f"Research this topic thoroughly: {topic}", tools=[search_web, read_page], ) def writer_agent(research, style): """Agent that writes based on research.""" return run_agent_with_tools( f"Write a {style} article based on this research:\n{research}", tools=[], # Writer doesn't need tools ) # Pipeline execution research = research_agent("quantum computing breakthroughs 2024") article = writer_agent(research, style="beginner-friendly blog post") ``` Start with a single agent and simple tools. Multi-agent systems add complexity — coordinating agents, handling failures, and managing shared state is significantly harder than a single agent loop. Only add agents when a single agent genuinely cannot handle the task. ## Key Takeaways - Agents use LLMs as reasoning engines in an observe-think-act loop - Tool use (function calling) lets agents interact with the real world - The ReAct pattern improves accuracy by alternating explicit reasoning with actions - Good tool descriptions are essential — the LLM selects tools based on descriptions - Multi-agent systems are powerful but complex; start with a single agent first - Always set a maximum step limit to prevent infinite loops - [ReAct: Synergizing Reasoning and Acting (Yao et al., 2022)](https://arxiv.org/abs/2210.03629) - The foundational ReAct paper - [Anthropic Tool Use Documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) - Official guide for Claude function calling - [Building Effective Agents (Anthropic)](https://www.anthropic.com/research/building-effective-agents) - Anthropic's guide to agent design patterns - [LangGraph Documentation](https://langchain-ai.github.io/langgraph/) - Framework for building stateful agents - [OpenAI Function Calling Guide](https://platform.openai.com/docs/guides/function-calling) - OpenAI's approach to tool use