This is the third article of the series, Agentic AI Design Patterns; here, we will talk about the Agentic AI Planning Pattern. Let’s refresh what we have learned in the two articles – We have studied how agents can reflect and use tools to access information. In the Reflection pattern, we have seen the AI agents using the iterative process of generation and self-assessment to improve the output quality. Next, the Tool use pattern is a crucial mechanism that enables AI to interact with external systems, APIs, or resources beyond its internal capabilities.
You can find both the articles here:
Also, here are the 4 Agentic AI Design Patterns: Top 4 Agentic AI Design Patterns for Architecting AI Systems.
Now, talking about the Planning Pattern. Let’s take an example of a smart assistant who doesn’t only reflect and pull in outside information when needed but also decides the sequence of steps to solve a bigger problem. Pretty cool, right? But here’s where it gets really interesting: how does this assistant decide on the best sequence of steps to accomplish big, multi-layered goals? Effective planning is determining a structured sequence of actions to complete complex, multi-step objectives.
What does a planning pattern provide?
Planning Patterns provide strategies for language models to divide large tasks into manageable subgoals, enabling them to tackle intricate challenges step-by-step while keeping the overarching goal in focus. This article will discuss the Planning pattern in detail with the ReAct and ReWOO techniques.
The Agentic AI Planning Pattern is a framework that focuses on breaking down a larger problem into smaller tasks, managing those tasks effectively, and ensuring continuous improvement or adaptation based on task outcomes. The process is iterative and relies on a structured flow to ensure that the AI system can adjust its plan as needed, moving closer to the desired goal with each iteration.
The Planning Pattern has the following main components:
The Agentic AI Planning Pattern leverages a structured loop of planning, task generation, execution, and replanning to ensure that AI systems can autonomously work towards complex goals. This pattern supports adaptability by allowing the AI to modify its approach in response to task outcomes, making it robust and responsive to dynamic environments or changing objectives.
The above-given illustration depicts a sequential image understanding process, with steps that align with the agentic AI planning pattern. In agentic AI, an “agent” takes actions based on observations and planned responses to achieve a specific goal. Here’s how each step in the image fits into the agentic AI framework:
There are two different approaches to task decomposition for agentic AI planning, specifically designed for handling complex tasks in dynamic and variable real-world environments. Given the limitations of attempting a single-step plan for complex objectives, decomposition into manageable parts becomes essential. This process, akin to the “divide and conquer” strategy, involves breaking down a complex goal into smaller, more achievable sub-goals.
Here’s an explanation of each approach:
In essence, the decomposition-first method separates the stages of decomposition and execution: it completes all planning for the sub-goals before any execution begins. This approach can be effective in stable environments where changes are minimal during the planning process.
The interleaved approach, decomposition and execution occur in a more intertwined manner:
In a nutshell,
In complex AI planning, choosing between these approaches depends on the environment and the task’s variability. The decomposition-first approach emphasises structure and pre-planning, while the interleaved method prioritises adaptability and real-time responsiveness.
Both approaches have their own strengths, but they also bring unique challenges when faced with highly dynamic and unpredictable scenarios. To navigate such complexity, an emerging framework known as ReAct (Reasoning and Acting) has become increasingly popular in AI research. ReAct synthesizes reasoning and acting in a way that enables agents to think critically about their actions, adjusting their strategies based on immediate feedback. This framework, which blends structured planning with real-time adjustments, allows agents to make more sophisticated decisions and handle variability in diverse environments.
As we already know, LLMs showcase impressive capabilities in providing language understanding and decision-making. However, their ability to reason and act has been studied as separate topics. This section will discuss how LLMs can use reasoning and action planning to handle complex tasks with greater synergy with the ReAct approach. Here’s the evolution and significance of the ReAct (Reason + Act) framework in language model (LM) systems. It contrasts traditional approaches (reasoning-only and action-only models) with ReAct, which combines reasoning and acting capabilities. Let’s break down each part of the ReAct architecture to understand what it conveys.
In essence, ReAct provides a more holistic approach to task completion by combining internal reasoning with external action-taking, making it more flexible and effective in real-world applications where purely reasoning or acting models fall short.
Also, here is the comparison of 4 prompting methods: (a) Standard, (b) Chain-of-thought (CoT, Reason Only), (c) Act-only, and (d) ReAct (Reason+Act), solving a HotpotQA (Yang et al., 2018) question. (2) Comparison of (a) Act-only and (b) ReAct prompting to solve an AlfWorld (Shridhar et al., 2020b) game.
The ReACT (Reason + Act) approach outperforms the others by leveraging reasoning and actions in tandem. This allows the AI to adapt to dynamic environments and complex questions. This framework leads to more sophisticated and accurate outcomes, making it highly suitable for tasks that require both thought and interaction.
Also read: Implementation of ReAct Agent using LlamaIndex and Gemini
This section aims to outline the process of building an AI agent that leverages the OpenAI API and the httpx library. It introduces the basic structure of creating a chatbot class capable of handling user inputs and executing responses through OpenAI’s language model. The section explains implementing the ReAct pattern to enable a loop of thought, action, pause, and observation. It describes registering custom actions (e.g., Wikipedia search, calculation, blog search) for enhanced functionality. This facilitates dynamic interaction where the agent can use external actions to refine and complete its answers. Let’s get straight to the Basic Structure of building AI Agent:
This code defines a ChatBot class for interacting with OpenAI’s GPT model. It initialises with an optional system prompt, stores conversation history, processes user input, and retrieves responses from the model using OpenAI’s API, simulating conversational capabilities for various applications or chatbot functionalities.
import openai
import re
import httpx
class ChatBot:
def __init__(self, system=""):
self.system = system
self.messages = []
if self.system:
self.messages.append({"role": "system", "content": system})
def __call__(self, message):
self.messages.append({"role": "user", "content": message})
result = self.execute()
self.messages.append({"role": "assistant", "content": result})
return result
def execute(self):
completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=self.messages)
return completion.choices[0].message.content
The code outlines a structured process for answering questions using a loop of Thought, Action, PAUSE, and Observation. It defines how an AI agent should think through a question, take appropriate actions (calculations or information searches), pause for results, observe outcomes, and ultimately provide an answer.
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer.
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.
Your available actions are:
calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point
syntax if necessary
wikipedia:
e.g. wikipedia: Django
Returns a summary from searching Wikipedia
simon_blog_search:
e.g. simon_blog_search: Django
Search Simon's blog for that term
Example session:
Question: What is the capital of France?
Thought: I should look up France on Wikipedia
Action: wikipedia: France
PAUSE
You will be called again with this:
Observation: France is a country. The capital is Paris.
You then output:
Answer: The capital of France is Paris
""".strip()
After implementation of the ReAct Pattern, we will implement the actions:
Next, we need to register these actions in a dictionary so the AI agent can use them:
known_actions = {
"wikipedia": wikipedia,
"calculate": calculate,
"simon_blog_search": simon_blog_search
}
This code defines a function or query that simulates a chatbot interaction with a user-specified question. It iteratively processes responses up to a maximum number of turns, extracting and executing specific actions using known handlers and updating prompts based on observations until a final result is returned or printed.
def query(question, max_turns=5):
i = 0
bot = ChatBot(prompt)
next_prompt = question
while i < max_turns:
i += 1
result = bot(next_prompt)
print(result)
actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]
if actions:
action, action_input = actions[0].groups()
if action not in known_actions:
raise Exception(f"Unknown action: {action}: {action_input}")
print(" -- running {} {}".format(action, action_input))
observation = known_actions[action](action_input)
print("Observation:", observation)
next_prompt = f"Observation: {observation}"
else:
return result
print(query("What does England share borders with?"))
For full code implementation, refer to this article: Comprehensive Guide to Build AI Agents from Scratch.
Let’s see the implementation of the Planning Pattern using ReAct with LangChain:
The objective is to implement a tool-augmented AI agent using LangChain and OpenAI’s GPT models that can autonomously conduct research and answer complex questions by integrating custom tools like web search through the Tavily API. This agent is designed to simulate human-like problem-solving by executing a planning pattern called ReAct (Reasoning and Action). It builds a loop of reasoning and action steps, evaluates responses, and makes decisions to gather and analyze information effectively. The setup supports real-time data queries and structured decision-making, enabling enhanced responses to questions like “What are the names of Ballon d’Or winners since its inception?”
!pip install langchain==0.2.0
!pip install langchain-openai==0.1.7
!pip install langchain-community==0.2.0
from getpass import getpass
OPENAI_KEY = getpass('Enter Open AI API Key: ')
Struggling with finding the OpenAI API key? Check out this article – How to Generate Your Own OpenAI API Key and Add Credits?
Get a free API key from here
TAVILY_API_KEY = getpass('Enter Tavily Search API Key: ')
import os
os.environ['OPENAI_API_KEY'] = OPENAI_KEY
os.environ['TAVILY_API_KEY'] = TAVILY_API_KEY
Here, we create custom tools which are wrappers on top of the Tavily API.
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import tool
import requests
import json
tv_search = TavilySearchResults(max_results=3, search_depth='advanced',
max_tokens=10000)
@tool
def search_web(query: str) -> list:
"""Search the web for a query."""
tavily_tool = TavilySearchResults(max_results=2)
results = tavily_tool.invoke(query)
return results
from langchain_openai import ChatOpenAI
chatgpt = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [search_web]
chatgpt_with_tools = chatgpt.bind_tools(tools)
prompt = "What are the names of Ballon d'Or winners since its inception?"
response = chatgpt_with_tools.invoke(prompt)
response.tool_calls
Output
[{'name': 'search_web',
'args': {'query': "list of Ballon d'Or winners"},
'id': 'call_FW0h6OpObqVQAIJnOtGLJAXe',
'type': 'tool_call'}]
Now that we have defined the tools and the LLM, we can create the agent. We will use a tool-calling agent to bind the tools to the agent with a prompt. We will also add the capability to store historical conversations as memory.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
SYS_PROMPT = """You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop, you output an Answer.
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.
wikipedia:
e.g. wikipedia: Ballon d'Or
Returns a summary from searching Wikipedia.
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [Wikipedia, duckduckgo_search, Calculator]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
"""
prompt_template = ChatPromptTemplate.from_messages(
[
("system", SYS_PROMPT),
MessagesPlaceholder(variable_name="history", optional=True),
("human", "{query}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
prompt_template.messages
Output
Now, we can initiate the agent with the LLM, the prompt, and the tools. The agent is responsible for taking in input and deciding what actions to take. REMEMBER the Agent does not execute those actions – that the AgentExecutor does
Note that we are passing in the model chatgpt, not chatgpt_with_tools.
That is because create_tool_calling_agent will call .bind_tools for us under the hood. This should ideally be used with an LLM which supports tool \ function calling.
from langchain.agents import create_tool_calling_agent
agent = create_tool_calling_agent(chatgpt, tools, prompt_template)
agent
Finally, we combine the agent (the brains) with the tools inside the AgentExecutor (which will repeatedly call the agent and execute tools).
from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose = True)
agent_executor
query = """Tell me the Ballon d'Or winners since it started?
"""
response = agent_executor.invoke({"query": query})
from IPython.display import display, Markdown
display(Markdown(response['output']))
Also read: Comprehensive Guide to Build AI Agents from Scratch
If you want to dig deep into Generative AI then explore: GenAI Pinnacle Program!
ReWOO (Reasoning without Observation) is a new agent architecture proposed by Xu et al. that emphasises an efficient approach to multi-step planning and variable substitution in large language model (LLM) systems. It addresses some of the limitations in ReAct-style agent architectures, particularly around execution efficiency and model fine-tuning. Here’s a breakdown of how ReWOO improves over traditional approaches:
Here’s the workflow of the ReWOO (Reasoning Without Observation) agent model. This model is designed to improve efficiency in multi-step reasoning and tool usage by minimizing redundant observations and focusing on planned sequences of actions. Here’s a step-by-step explanation of each component and the flow of information:
Here are the key enhancements of ReWOO:
The process flows through the following steps:
In essence, ReWOO enhances the agent’s efficiency by separating the reasoning (Planner) and execution (Worker) phases, thereby creating a faster and more resource-efficient framework for complex tasks.
Two distinct methods for task reasoning in a system involving large language models (LLMs) are (a) Reasoning with Observation and (b) ReWOO (Reasoning with Observations and Organized Evidence). Here’s a comparison based on the given diagram:
For the Hands-on ReWoo, I am referring to the ReWOO recipe from Vadym Barda using LangGraph. For now, I am not mentioning the libraries and other requirements, but I will dig into defining the graph state, planner, executor, and solver.
In LangGraph, each node updates a shared graph state, which serves as input whenever a node is activated. Below, the state dictionary is defined to contain essential task details, such as task, plan, steps, and other necessary variables.
from typing import List
from typing_extensions import TypedDict
class ReWOO(TypedDict):
task: str
plan_string: str
steps: List
results: dict
result: str
The planner module uses a language model to generate a structured plan in the form of a task list. Each task in the plan is represented by strings that can include special variables (like #E{0-9}+) for substituting values from previous results. In this example, the agent has access to two tools:
The prompt instructs the model on how to create a plan, specifying which tools to use and how to reference prior results using variables.
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o")
prompt = """For the following task, make plans that can solve the problem step by step. For each plan, indicate \
which external tool together with tool input to retrieve evidence. You can store the evidence into a \
variable #E that can be called by later tools. (Plan, #E1, Plan, #E2, Plan, ...)
# Task Example
task = "what is the exact hometown of the 2024 mens australian open winner"
result = model.invoke(prompt.format(task=task))
print(result.content)
Output
Plan: Use Google to search for the 2024 Australian Open winner.
#E1 = Google[2024 Australian Open winner]
Plan: Retrieve the name of the 2024 Australian Open winner from the search results.
#E2 = LLM[What is the name of the 2024 Australian Open winner, given #E1]
...
The planner node connects to the graph, creating a get_plan node that receives the ReWOO state and updates it with new steps and plan_string.
import re
from langchain_core.prompts import ChatPromptTemplate
regex_pattern = r"Plan:\s*(.+)\s*(#E\d+)\s*=\s*(\w+)\s*\[([^\]]+)\]"
prompt_template = ChatPromptTemplate.from_messages([("user", prompt)])
planner = prompt_template | model
def get_plan(state: ReWOO):
task = state["task"]
result = planner.invoke({"task": task})
matches = re.findall(regex_pattern, result.content)
return {"steps": matches, "plan_string": result.content}
The executor iterates through each planned task, executing specified tools sequentially. It uses helper functions to determine the current task and performs variable substitution before each tool call.
from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults()
def _get_current_task(state: ReWOO):
if "results" not in state or state["results"] is None:
return 1
if len(state["results"]) == len(state["steps"]):
return None
else:
return len(state["results"]) + 1
def tool_execution(state: ReWOO):
_step = _get_current_task(state)
_, step_name, tool, tool_input = state["steps"][_step - 1]
_results = (state["results"] or {}) if "results" in state else {}
for k, v in _results.items():
tool_input = tool_input.replace(k, v)
if tool == "Google":
result = search.invoke(tool_input)
elif tool == "LLM":
result = model.invoke(tool_input)
else:
raise ValueError
_results[step_name] = str(result)
return {"results": _results}
The solver aggregates results from each executed tool and generates a conclusive answer based on the evidence collected.
solve_prompt = """Solve the following task or problem. To solve the problem, we have made step-by-step Plan and \
retrieved corresponding Evidence to each Plan. Use them with caution since long evidence might \
contain irrelevant information.
{plan}
Now solve the question or task according to provided Evidence above. Respond with the answer
directly with no extra words.
Task: {task}
Response:"""
def solve(state: ReWOO):
plan = ""
for _plan, step_name, tool, tool_input in state["steps"]:
_results = (state["results"] or {}) if "results" in state else {}
for k, v in _results.items():
tool_input = tool_input.replace(k, v)
step_name = step_name.replace(k, v)
plan += f"Plan: {_plan}\n{step_name} = {tool}[{tool_input}]"
prompt = solve_prompt.format(plan=plan, task=state["task"])
result = model.invoke(prompt)
return {"result": result.content}
The graph is a directed workflow that coordinates interactions between the planner, tool executor, and solver nodes. Conditional edges ensure the process loops until all tasks are completed.
def _route(state):
_step = _get_current_task(state)
if _step is None:
return "solve"
else:
return "tool"
from langgraph.graph import END, StateGraph, START
graph = StateGraph(ReWOO)
graph.add_node("plan", get_plan)
graph.add_node("tool", tool_execution)
graph.add_node("solve", solve)
graph.add_edge("plan", "tool")
graph.add_edge("solve", END)
graph.add_conditional_edges("tool", _route)
graph.add_edge(START, "plan")
app = graph.compile()
# Stream output to visualize final results
for s in app.stream({"task": task}):
print(s)
print("---")
#Input: task = "what is the exact hometown of the 2024 mens australian open winner"
from IPython.display import Image, display
from langchain_core.runnables.graph import MermaidDrawMethod
display(
Image(
app.get_graph().draw_mermaid_png(
draw_method=MermaidDrawMethod.API,
)
)
)
print(s["solve"]["result"])
Output
San Candido, Italy
The agentic AI planning pattern offers significant advantages, especially when a task’s complexity prevents predetermined step-by-step decomposition. Planning enables agents to dynamically decide their course of action, allowing for adaptive and context-aware problem-solving. It enhances flexibility and capability in handling unpredictable tasks, making it a powerful tool in situations demanding strategic foresight and decision-making.
However, this capability comes with notable limitations. The dynamic nature of planning introduces unpredictability, making it harder to foresee how an agent might behave in any given scenario. Unlike more deterministic agentic workflows, such as Reflection or Tool Use—which are reliable and effective—planning remains less mature and can yield inconsistent results. While current planning capabilities present challenges, the rapid advancements in AI research suggest that these limitations will likely diminish over time, leading to more robust and predictable planning functionalities.
Know more about it here.
Also, to understand the Agent AI better, explore: The Agentic AI Pioneer Program
We explored the Agentic AI Planning Pattern, which is fundamental for structuring and executing complex, multi-step tasks in AI systems. This pattern enables AI to decompose large goals into smaller, manageable sub-goals, ensuring that the overall objective is approached methodically while remaining adaptable to real-time feedback and changes. We discussed two primary decomposition approaches: Decomposition-First, which emphasizes pre-planning for stable environments, and Interleaved, which allows for flexible execution and adaptive planning in dynamic settings. Additionally, we touched on the ReAct framework, showcasing how combining reasoning and acting can create a more interactive and iterative AI problem-solving approach. Lastly, we introduced ReWOO, an advanced architecture that enhances efficiency by minimizing redundant observations and focusing on planned sequences, thus optimizing task completion in complex environments.
These frameworks collectively highlight the power of integrating structured planning, iterative execution, and adaptive strategies for robust agentic AI systems capable of handling complex real-world challenges.
In our next article, we will be talking about the Multi-Agent Pattern!
If you’re interested in learning more about Agentic AI Planning Patterns, I recommend:
Ans. An Agentic AI Planning Pattern refers to a structured approach or framework that AI systems use to make decisions and execute plans autonomously, aiming to achieve specific objectives while interacting with the environment.
Ans. These patterns are crucial for developing AI systems that can operate independently, adapt to new information, and efficiently solve complex problems without direct human input.
Ans. Unlike basic AI algorithms that may operate based on pre-programmed instructions, Agentic AI Planning Patterns allow for dynamic decision-making and long-term strategic planning, giving AI systems the ability to act with a degree of autonomy.
Ans. Key components typically include goal-setting mechanisms, decision-making algorithms, resource allocation strategies, and adaptive learning capabilities to update plans based on real-time data.
Ans. They are commonly applied in areas such as robotics, autonomous vehicles, strategic game-playing AIs, and complex simulation systems where independent problem-solving is required.