Building AI Agents: Tools, Memory and Multi-Agent Systems
In this tutorial, you'll learn about Building AI Agents: Tools, Memory and Multi. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
AI agents are autonomous LLM-powered programs that use tools, maintain memory across interactions, and make decisions to accomplish complex tasks with minimal human intervention.
What You'll Learn
In this tutorial, you'll learn to build AI agents using LangChain and CrewAI, including tool creation and usage, memory systems for conversation history, multi-agent Orchestration, and building autonomous agents for real-world tasks.
Why It Matters
LLMs alone are passive — they generate text but cannot act. AI agents extend LLMs with tools (web search, calculators, APIs), memory (conversation history, knowledge graphs), and planning (task decomposition, multi-step reasoning). Agents can research topics, write and execute code, manage workflows, and coordinate with other agents. They represent the next evolution of AI from chatbots to autonomous digital workers.
Real-World Use
Doda Browser integrates an AI agent that helps users manage bookmarks and tabs. The agent can search the web, organize bookmarks into folders, summarize open tabs, set reminders based on page content, and coordinate with other agents for complex multi-step workflows like travel planning.
What is an AI Agent?
An AI agent combines an LLM with tools, memory, and planning. The LLM acts as the reasoning engine. Tools extend the agent's capabilities beyond text generation — calculators for math, search APIs for information retrieval, code interpreters for execution. Memory stores past interactions and learned information. Planning breaks complex goals into smaller steps. The agent loop repeats: perceive (read input), think (plan action), act (use tool), observe (get tool result).
import json
from typing import Callable
class SimpleAgent:
def __init__(self, tools: dict[str, Callable]):
self.tools = tools
self.memory = []
def add_tool(self, name: str, func: Callable, description: str):
self.tools[name] = {"func": func, "description": description}
def run(self, task: str) -> str:
self.memory.append({"role": "user", "content": task})
result = f"Processed task using tools: {list(self.tools.keys())}"
self.memory.append({"role": "assistant", "content": result})
return result
def get_memory(self, last_n: int = 3):
return self.memory[-last_n:]
def search_web(query: str) -> str:
return f"Search results for '{query}': [result 1, result 2, result 3]"
def calculator(expression: str) -> str:
return str(eval(expression))
agent = SimpleAgent({})
agent.add_tool("search", search_web, "Search the web for information")
agent.add_tool("calculate", calculator, "Perform mathematical calculations")
result1 = agent.run("What is the population of France?")
result2 = agent.run("Calculate 15 * 27 + 100")
print(f"Task 1 result: {result1}")
print(f"Task 2 result: {result2}")
print(f"\nAgent memory ({len(agent.memory)} entries):")
for entry in agent.get_memory(2):
print(f" {entry['role']}: {entry['content'][:50]}...")
Expected output:
Task 1 result: Processed task using tools: ['search', 'calculate']
Task 2 result: Processed task using tools: ['search', 'calculate']
Agent memory (4 entries):
user: Calculate 15 * 27 + 100...
assistant: Processed task using tools: ['search', 'calculate']...
LangChain Agents
LangChain provides a complete framework for building agents with tool integration, memory, and prompt management. The AgentExecutor runs the agent loop: it takes the user input and available tools, the LLM decides which tool to use and with what arguments, executes the tool, and continues until it determines the task is complete. LangChain supports multiple agent types including OpenAI Functions, ReAct (Reasoning + Acting), and Plan-and-Execute.
from langchain.agents import Tool, AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain.prompts import PromptTemplate
from langchain.llms.fake import FakeListLLM
import datetime
@tool
def get_current_time(query: str) -> str:
"""Returns the current date and time."""
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression."""
try:
return str(eval(expression))
except:
return "Error in expression"
tools = [get_current_time, calculate]
responses = [
"I need to use the calculate tool.",
"Action: calculate\nAction Input: 2+2",
"Observation: 4",
"The answer is 4.",
"I need to use the get_current_time tool.",
"Action: get_current_time\nAction Input: time",
f"Observation: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"The current time is " + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ".",
]
llm = FakeListLLM(responses=responses)
prompt = PromptTemplate.from_template(
"Answer the following question: {input}\n\nTools: {tools}\n\n{agent_scratchpad}"
)
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False)
result1 = agent_executor.invoke({"input": "What is 2+2?"})
result2 = agent_executor.invoke({"input": "What time is it?"})
print(f"Math result: {result1['output']}")
print(f"Time result: {result2['output']}")
print(f"Tools available: {[t.name for t in tools]}")
Expected output:
Math result: The answer is 4.
Time result: The current time is 2026-06-22 12:00:00.
Tools available: ['get_current_time', 'calculate']
Memory Systems
Memory enables agents to recall past interactions and maintain context across conversations. ConversationBufferMemory stores all past messages. ConversationSummaryMemory compresses long conversations into summaries. VectorStoreMemory retrieves relevant past conversations using semantic search. Memory is essential for multi-turn tasks where the agent needs to remember user preferences, previous results, or ongoing context.
from langchain.memory import ConversationBufferMemory
from langchain.schema import HumanMessage, AIMessage
memory = ConversationBufferMemory(return_messages=True)
memory.chat_memory.add_message(HumanMessage(content="My name is Alice"))
memory.chat_memory.add_message(AIMessage(content="Hello Alice! How can I help you today?"))
memory.chat_memory.add_message(HumanMessage(content="What is my name?"))
memory.chat_memory.add_message(AIMessage(content="Your name is Alice."))
history = memory.load_memory_variables({})
messages = history['history']
print(f"Memory entries: {len(messages)}")
for msg in messages:
print(f" {msg.type}: {msg.content}")
memory.clear()
memory.chat_memory.add_message(HumanMessage(content="New conversation"))
loaded = memory.load_memory_variables({})
print(f"\nAfter clear, entries: {len(loaded['history'])}")
Expected output:
Memory entries: 4
human: My name is Alice
ai: Hello Alice! How can I help you today?
human: What is my name?
ai: Your name is Alice.
After clear, entries: 1
Multi-Agent Systems with CrewAI
CrewAI orchestrates multiple agents that collaborate on tasks. Each agent has a role, goal, and set of tools. Agents can delegate tasks, share information, and work in sequence or parallel. A Crew defines the agents and their workflow. Multi-agent systems excel at complex tasks requiring different expertise — a researcher agent gathers information, a writer agent creates content, and a reviewer agent checks quality.
from typing import List, Dict
class Agent:
def __init__(self, role: str, goal: str, tools: List[str]):
self.role = role
self.goal = goal
self.tools = tools
self.output = ""
def execute(self, task: str) -> str:
self.output = f"[{self.role}] Processed: {task}"
return self.output
class Crew:
def __init__(self, agents: List[Agent], tasks: List[Dict]):
self.agents = agents
self.tasks = tasks
def run(self) -> Dict[str, str]:
results = {}
for task in self.tasks:
agent = next(a for a in self.agents if a.role == task['agent'])
result = agent.execute(task['description'])
results[task['name']] = result
return results
researcher = Agent(
role="Researcher",
goal="Gather accurate information",
tools=["web_search", "document_reader"]
)
writer = Agent(
role="Writer",
goal="Create clear, engaging content",
tools=["text_editor", "grammar_check"]
)
reviewer = Agent(
role="Reviewer",
goal="Ensure quality and accuracy",
tools=["fact_checker", "style_guide"]
)
crew = Crew(
agents=[researcher, writer, reviewer],
tasks=[
{"name": "research", "description": "Research AI agents topic", "agent": "Researcher"},
{"name": "draft", "description": "Write tutorial outline", "agent": "Writer"},
{"name": "review", "description": "Review for accuracy", "agent": "Reviewer"}
]
)
results = crew.run()
print(f"Agents: {[a.role for a in crew.agents]}")
print(f"Tasks: {[t['name'] for t in crew.tasks]}")
for task_name, output in results.items():
print(f" {task_name}: {output}")
Expected output:
Agents: ['Researcher', 'Writer', 'Reviewer']
Tasks: ['research', 'draft', 'review']
research: [Researcher] Processed: Research AI agents topic
draft: [Writer] Processed: Write tutorial outline
review: [Reviewer] Processed: Review for accuracy
Agent Architecture
flowchart TD
A[User Input] --> B[Agent Orchestrator]
B --> C{Agent Type}
C --> D[Single Agent]
C --> E[Multi-Agent Crew]
D --> F[LLM Reasoning]
F --> G{Tool Needed?}
G -->|Yes| H[Execute Tool]
H --> I[Process Result]
I --> F
G -->|No| J[Generate Response]
E --> K[Researcher Agent]
E --> L[Writer Agent]
E --> M[Reviewer Agent]
K --> N[Share Context]
L --> N
M --> N
N --> J
J --> O[Memory Update]
O --> P[Final Output]
Common Errors and Mistakes
| Mistake | Why It Happens | How to Fix |
|---|---|---|
| Agent stuck in loop | Tool returns unexpected result | Add iteration limit, error handling in tools |
| Tool hallucination | Agent invokes non-existent tool | Use structured tool definitions with validation |
| Memory overflow | Context exceeds LLM window | Use summary memory or Sliding Window |
| Poor tool descriptions | Agent cannot choose correctly | Write clear tool names and descriptions |
| No delegation strategy | Agents conflict or duplicate work | Define clear role boundaries and handoff protocol |
Practice Questions
- What distinguishes an AI agent from a basic LLM chat?
Answer: An agent has access to tools (search, calculator, APIs), memory (conversation history), and planning (multi-step reasoning). A basic LLM generates text from context. An agent can act — search the web, run code, call APIs — based on LLM reasoning.
- How does the agent loop work in LangChain?
Answer: The agent receives input, the LLM decides the next action (Final Answer or tool call), executes the tool, observes the result, and repeats until it produces a Final Answer. The AgentExecutor manages this loop with iteration limits.
- Why is memory important for multi-turn agent interactions?
Answer: Memory preserves context across turns. Without memory, the agent forgets user preferences, earlier results, and conversation history. This makes multi-step tasks impossible because the agent treats each interaction as independent.
- How do multi-agent systems improve over single agents?
Answer: Different agents specialize in different roles (research, writing, verification). They work in parallel, share context, and provide checks and balances. Complex tasks benefit from specialized expertise rather than a generalist agent.
- What is the ReAct pattern in agent design?
Answer: ReAct combines Reasoning (thinking about what to do) with Acting (executing a tool). The agent outputs thoughts, actions, and observations in sequence, making its reasoning transparent. This improves both accuracy and interpretability.
Challenge
Build a multi-agent research system with CrewAI. Create three agents: a Researcher (searches web and reads documents), an Analyst (interprets data and identifies patterns), and a Reporter (writes a structured summary). The system should take a research question, have the agents collaborate on finding and synthesizing information, and produce a final report with citations. Add memory so the system can refine results based on follow-up questions.
Real-World Task
Design an AI agent for automated customer support triage. The agent receives customer tickets, uses an LLM to classify the issue type and urgency, searches the knowledge base for relevant solutions, and either answers directly or routes to the appropriate human agent. If no solution is found, it escalates to a senior agent with a summary of what was attempted. Include memory for follow-up on the same ticket.
Next Steps
Deploy agents with LangChain and Docker. Monitor agent performance with MLflow. Explore OpenAI function calling for structured tool use.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro