Skip to content
SDB
Agentic AI

Chapter 02 · intermediate · 25 min

Agent Architectures & Reasoning

ReAct, chain-of-thought, tool calling, and memory patterns that power modern agents

Subhendu Datta BhowmikAI Tutorials

Core Agent Architecture Patterns

Building reliable agents requires choosing the right reasoning architecture. The wrong pattern leads to hallucinations, loops, or getting stuck. The right one makes the agent feel magical.

The ReAct Pattern

ReAct (Reason + Act) is the foundational pattern for most production agents. First described by Yao et al. (2022), it interleaves reasoning and action in a structured loop:

Thought: I need to find the current stock price for AAPL.
Action: search_web(query="AAPL stock price today")
Observation: Apple Inc (AAPL) is trading at $189.42, up 1.3%
Thought: I have the price. I should also check if there's recent news.
Action: search_web(query="Apple Inc news this week")
Observation: Apple announced new AI features for iPhone 17...
Thought: I have both pieces of information needed for the answer.
Final Answer: AAPL is at $189.42 (+1.3%) with positive sentiment following AI announcements.

The key insight: by forcing the model to write a Thought before each action, you get more deliberate, auditable decisions.

ReAct Agent Implementationpython
from anthropic import Anthropic
import json

client = Anthropic()

SYSTEM_PROMPT = """You are a helpful research agent. For each task, think step-by-step.

Use this format:
Thought: <your reasoning about what to do next>
Action: <tool_name>
Action Input: <json input for the tool>

After seeing an Observation, continue with another Thought/Action or give a Final Answer:
Final Answer: <your complete answer to the user>"""

tools = [
    {
        "name": "search_web",
        "description": "Search the internet for current information",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "calculate",
        "description": "Perform mathematical calculations",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "Math expression to evaluate"}
            },
            "required": ["expression"]
        }
    }
]

def execute_tool(name: str, inputs: dict) -> str:
    """Mock tool execution — replace with real implementations."""
    if name == "search_web":
        return f"[Search results for: {inputs['query']}] Found 10 relevant results..."
    elif name == "calculate":
        try:
            return str(eval(inputs["expression"]))
        except Exception as e:
            return f"Error: {e}"
    return "Tool not found"

def react_agent(user_query: str, max_steps: int = 6) -> str:
    messages = [{"role": "user", "content": user_query}]

    for step in range(max_steps):
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=2048,
            system=SYSTEM_PROMPT,
            tools=tools,
            messages=messages,
        )

        # If model gave a final text answer
        if response.stop_reason == "end_turn":
            for block in response.content:
                if hasattr(block, "text"):
                    return block.text

        # Handle tool calls
        messages.append({"role": "assistant", "content": response.content})
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                print(f"  Tool: {block.name}({block.input}) -> {result[:80]}...")
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })

        if tool_results:
            messages.append({"role": "user", "content": tool_results})

    return "Agent reached maximum steps without completing the task."

# Usage
result = react_agent("What is 15% of the GDP of France in 2023?")
print(result)

Structured Tool Calling

Modern LLMs support structured tool calling where tools are defined as JSON schemas. This is more reliable than text-based ReAct because:

  • The model outputs structured JSON (not freeform text) for tool calls
  • The framework can parse and route calls deterministically
  • Schemas enforce types and required fields

Defining Good Tools

Tool design is critical. A well-designed tool:

  • Has a clear, specific description (the LLM reads this to decide when to use it)
  • Has typed parameters with descriptions
  • Does one thing well (avoid Swiss Army knife tools)
  • Returns structured, parseable results
Well-Designed Tool Schemapython
# Good: specific, typed, with clear descriptions
tools = [
    {
        "name": "get_stock_price",
        "description": (
            "Retrieve the current stock price and 24h change for a given ticker symbol. "
            "Use this when the user asks about stock prices, market data, or investment info."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "ticker": {
                    "type": "string",
                    "description": "Stock ticker symbol (e.g. AAPL, TSLA, MSFT)"
                },
                "currency": {
                    "type": "string",
                    "enum": ["USD", "EUR", "GBP"],
                    "description": "Currency for the price. Defaults to USD.",
                    "default": "USD"
                }
            },
            "required": ["ticker"]
        }
    },
    {
        "name": "get_company_info",
        "description": (
            "Get fundamental information about a publicly traded company: "
            "sector, market cap, P/E ratio, and recent earnings."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "company_name_or_ticker": {
                    "type": "string",
                    "description": "Company name (e.g. 'Apple') or ticker (e.g. 'AAPL')"
                }
            },
            "required": ["company_name_or_ticker"]
        }
    }
]

# Bad: vague, overloaded tool (avoid this)
bad_tool = {
    "name": "get_data",
    "description": "Get some data",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string"}
        }
    }
}

Memory Architecture

Agents without memory repeat themselves, forget context, and can't learn from past interactions. There are four memory types every agent builder should know:

1. In-Context Memory (Short-Term)

The conversation history passed directly in the LLM's context window.

  • Capacity: limited by context length (128K–1M tokens in modern models)
  • Retrieval: instant — the model sees everything
  • Use for: current conversation, recent tool results, task state

2. Episodic Memory (Long-Term Logs)

A persistent log of past agent runs, decisions, and outcomes.

  • Storage: database or vector store
  • Retrieval: semantic search for relevant past episodes
  • Use for: "last time I did X, it failed because Y"

3. Semantic Memory (Knowledge Base)

Stored facts, documents, and domain knowledge the agent can query.

  • Storage: vector database (ChromaDB, Pinecone, Qdrant)
  • Retrieval: RAG — embed query, find similar chunks
  • Use for: company docs, product knowledge, FAQs

4. Procedural Memory (Learned Skills)

Stored instructions, prompts, or fine-tuned behaviors.

  • Storage: system prompts, prompt templates, fine-tuned weights
  • Retrieval: selected based on task type
  • Use for: "how to handle billing questions", "coding style guide"
Agent with External Memory (RAG)python
from anthropic import Anthropic
import chromadb

client = Anthropic()
chroma = chromadb.Client()
collection = chroma.get_or_create_collection("agent_memory")

def remember(key: str, content: str) -> None:
    """Store a memory in the vector database."""
    collection.add(
        documents=[content],
        ids=[key],
        metadatas=[{"key": key}]
    )

def recall(query: str, n_results: int = 3) -> list[str]:
    """Retrieve relevant memories by semantic similarity."""
    results = collection.query(query_texts=[query], n_results=n_results)
    return results["documents"][0] if results["documents"] else []

def agent_with_memory(user_query: str) -> str:
    # Retrieve relevant memories
    memories = recall(user_query)
    memory_context = ""
    if memories:
        memory_context = "\n\nRelevant past context:\n" + "\n".join(
            f"- {m}" for m in memories
        )

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=f"You are a helpful assistant with memory.{memory_context}",
        messages=[{"role": "user", "content": user_query}],
    )

    answer = response.content[0].text

    # Store this interaction as a memory
    remember(
        key=f"interaction_{hash(user_query)}",
        content=f"Q: {user_query}\nA: {answer[:200]}"
    )

    return answer

Parallel Tool Calling

Modern agents can call multiple tools simultaneously when the calls are independent. This dramatically reduces latency for multi-tool tasks:

# Sequential (slow): 3 API calls in series = 3x latency
price = get_stock_price("AAPL")
info = get_company_info("AAPL")
news = search_news("Apple Inc")

# Parallel (fast): all 3 at once — Claude handles this automatically
# when it determines the calls are independent

Claude 3.5 Sonnet and later models automatically emit parallel tool calls when it detects independence between the calls.

Summary

PatternBest ForTradeoff
ReActResearch, multi-source queriesVerbose, more tokens
Structured tool callingDeterministic actionsRequires schema design
In-context memoryShort tasksLimited by context window
RAG memoryLong-running agentsRetrieval quality matters
Parallel tool callsMulti-data-source tasksRequires independent tools

In the next chapter, we'll look at CrewAI — a framework for building teams of specialized agents that collaborate to solve complex tasks.

Knowledge check

In the ReAct pattern, what purpose does the "Thought" step serve?

Agentic AI