Agentic AI TutorialAdvanced

Building Your First Agentic AI System

A practical guide to building an autonomous AI agent that can use tools and accomplish complex tasks.

Building Your First Agentic AI System

In this tutorial, you'll build an autonomous AI agent that can plan tasks, use tools, and accomplish goals with minimal human intervention.

What We're Building

An agentic AI system that can:

  • Accept high-level goals from users
  • Break goals into subtasks
  • Use tools (web search, calculator, file operations)
  • Track progress and adapt to failures
  • Provide clear status updates

Architecture Overview

User Goal → Agent Loop → Tool Execution → Result
              ↓             ↓
           Planning      Memory

Setup

pip install openai langchain python-dotenv

Create a .env file:

OPENAI_API_KEY=your_api_key_here

Core Components

1. The Agent Class

import os
from typing import List, Dict, Any
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

class AgenticAI:
    def __init__(self):
        self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        self.conversation_history = []
        self.tools = self._register_tools()

    def _register_tools(self) -> Dict[str, callable]:
        """Register available tools the agent can use"""
        return {
            "calculator": self.calculator,
            "web_search": self.web_search,
            "write_file": self.write_file,
            "read_file": self.read_file
        }

    def calculator(self, expression: str) -> str:
        """Evaluate mathematical expressions"""
        try:
            result = eval(expression)
            return f"Result: {result}"
        except Exception as e:
            return f"Error: {str(e)}"

    def web_search(self, query: str) -> str:
        """Simulate web search (replace with actual API)"""
        return f"Search results for: {query}"

    def write_file(self, filename: str, content: str) -> str:
        """Write content to a file"""
        try:
            with open(filename, 'w') as f:
                f.write(content)
            return f"Successfully wrote to {filename}"
        except Exception as e:
            return f"Error writing file: {str(e)}"

    def read_file(self, filename: str) -> str:
        """Read content from a file"""
        try:
            with open(filename, 'r') as f:
                return f.read()
        except Exception as e:
            return f"Error reading file: {str(e)}"

2. The Planning System

def plan_task(self, goal: str) -> List[str]:
    """Break down a goal into subtasks"""
    prompt = f"""
    Given the goal: "{goal}"

    Break this down into specific, actionable subtasks.
    Each subtask should be concrete and achievable.

    Available tools: {', '.join(self.tools.keys())}

    Return a numbered list of subtasks.
    """

    response = self.client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )

    return response.choices[0].message.content.split('\n')

3. The Execution Loop

def execute_task(self, task: str) -> str:
    """Execute a single task using available tools"""
    prompt = f"""
    Task: {task}

    Available tools:
    - calculator(expression): Evaluate math
    - web_search(query): Search the web
    - write_file(filename, content): Write to file
    - read_file(filename): Read from file

    Decide which tool to use and provide the parameters.
    Format: TOOL_NAME(param1, param2, ...)

    If no tool is needed, respond with: NO_TOOL_NEEDED
    """

    response = self.client.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "You are a task execution assistant."},
            {"role": "user", "content": prompt}
        ]
    )

    action = response.choices[0].message.content.strip()

    # Parse and execute tool call
    if action == "NO_TOOL_NEEDED":
        return "Task completed without tools"

    # Simple parsing (in production, use structured outputs)
    if "(" in action and ")" in action:
        tool_name = action[:action.index("(")]
        params = action[action.index("(")+1:action.index(")")].split(",")
        params = [p.strip().strip("'\"") for p in params]

        if tool_name in self.tools:
            return self.tools[tool_name](*params)

    return f"Could not execute: {action}"

4. The Main Agent Loop

def run(self, goal: str, max_iterations: int = 10):
    """Main agent loop"""
    print(f"🎯 Goal: {goal}\n")

    # Step 1: Plan
    print("📋 Planning...")
    subtasks = self.plan_task(goal)

    print("\nSubtasks:")
    for task in subtasks:
        print(f"  {task}")

    # Step 2: Execute
    print("\n⚡ Executing...\n")
    for i, task in enumerate(subtasks, 1):
        if not task.strip() or task.strip().startswith("#"):
            continue

        print(f"[{i}] {task}")
        result = self.execute_task(task)
        print(f"    ✓ {result}\n")

        # Add to memory
        self.conversation_history.append({
            "task": task,
            "result": result
        })

    print("✅ Goal completed!")

Using the Agent

# Create agent instance
agent = AgenticAI()

# Run with a goal
agent.run("Calculate the compound interest on $10,000 at 5% for 10 years and save the result to a file called results.txt")

Example Output

🎯 Goal: Calculate the compound interest on $10,000 at 5% for 10 years and save the result to a file called results.txt

📋 Planning...

Subtasks:
  1. Calculate compound interest using the formula A = P(1 + r)^t
  2. Format the result clearly
  3. Write the result to results.txt

⚡ Executing...

[1] Calculate compound interest using the formula A = P(1 + r)^t
    ✓ Result: 16288.946267774416

[2] Format the result clearly
    ✓ NO_TOOL_NEEDED

[3] Write the result to results.txt
    ✓ Successfully wrote to results.txt

✅ Goal completed!

Advanced Features

Memory System

Add long-term memory:

class AgentMemory:
    def __init__(self):
        self.short_term = []  # Recent tasks
        self.long_term = {}   # Learned patterns

    def remember(self, task: str, result: str):
        self.short_term.append({"task": task, "result": result})

        # Keep only last 10
        if len(self.short_term) > 10:
            self.short_term.pop(0)

    def recall_similar(self, task: str) -> List[Dict]:
        # In production, use embeddings for semantic similarity
        return [item for item in self.short_term
                if any(word in item["task"] for word in task.split())]

Error Recovery

Add retry logic:

def execute_with_retry(self, task: str, max_retries: int = 3) -> str:
    for attempt in range(max_retries):
        try:
            result = self.execute_task(task)
            if "error" not in result.lower():
                return result
            print(f"  Retry {attempt + 1}/{max_retries}")
        except Exception as e:
            if attempt == max_retries - 1:
                return f"Failed after {max_retries} attempts: {str(e)}"
    return result

Progress Tracking

Add status updates:

def run_with_progress(self, goal: str):
    total_tasks = len(self.plan_task(goal))

    for i, task in enumerate(subtasks, 1):
        progress = (i / total_tasks) * 100
        print(f"Progress: {progress:.0f}% [{i}/{total_tasks}]")
        # ... execute task

Best Practices

  1. Clear tool definitions: Make tool purposes obvious
  2. Structured outputs: Use JSON for tool calls in production
  3. Error handling: Always handle tool failures gracefully
  4. Safety limits: Set max iterations to prevent infinite loops
  5. Logging: Track all tool calls for debugging
  6. Human oversight: Allow human approval for sensitive operations

Common Challenges

Challenge 1: Hallucinated Tool Calls

Solution: Use structured outputs and validate tool parameters

Challenge 2: Infinite Loops

Solution: Implement max iteration limits and loop detection

Challenge 3: Context Window Limits

Solution: Summarize old conversations, keep only relevant history

Challenge 4: Tool Failures

Solution: Implement retry logic and fallback strategies

Production Considerations

For real-world deployment:

  1. Use structured outputs: OpenAI's function calling API
  2. Implement proper auth: Secure API keys and credentials
  3. Add rate limiting: Prevent excessive API calls
  4. Monitor costs: Track token usage
  5. Test extensively: Many edge cases in agentic systems
  6. Add safety rails: Prevent dangerous operations

Next Steps

Enhance your agent with:

  • Reinforcement learning: Learn from successes/failures
  • Multi-agent systems: Coordinate multiple specialized agents
  • Chain-of-thought prompting: Improve reasoning quality
  • External knowledge bases: RAG for domain-specific knowledge

Conclusion

You've built a functional agentic AI system! While this is a simplified version, it demonstrates the core concepts:

  • Goal decomposition
  • Tool use
  • Autonomous execution
  • Memory and state management

As you build more complex agents, focus on reliability, safety, and clear communication of agent actions.

Happy building!