Skip to main content

Generate a plan

Learn how to create structured, multi-agent plans using your LLM of choice and familiarise yourself with the structure of plans created using Portia.

TL;DR
  • A plan is the set of steps an LLM thinks it should take in order to respond to a user prompt.
  • A plan is represented by the Plan class and can be generated from a user prompt using the plan method of the Portia class (SDK reference ↗).
    • Portia uses optimised system prompts and structured outputs to ensure adherence to a plan.
    • You can create your own plans manually or reload existing plans, which is especially useful for repeatable plan runs.

Overview of plans in Portia

A plan is the set of steps an LLM thinks it should take in order to respond to a user prompt. Plans are:

  • Immutable: Once a plan is generated, it cannot be altered. This is important for auditability.
  • Structured: We use optimised system prompts to guide the LLM along a simple design language when generating a plan. This makes the plan format predictable and easy to process for the purposes of automation.
  • Human-readable: Our planning language is in a simple, serialisable format. It is easy to render and present to users in a human readable front-end experience. This helps your users easily stay on top of your LLM's reasoning.

While Portia generates a plan in response to a user prompt and then runs it, you also have the option to create plans yourself manually. This is especially suitable for your users' more repeatable routines.

Introducing a Plan

Let's bring this one to life by looking at an example plan below, created in response to the query Search for the latest SpaceX news from the past 48 hours and if there are at least 3 articles, email Avrana (avrana@kern.ai) a summary of the top 3 developments with subject 'Latest SpaceX Updates'.

plan.json
{
"steps": [
{
"task": "Search for the latest SpaceX news from the past 48 hours using the search tool.",
"inputs": [
{
"name": "$search_query",
"value": "latest SpaceX news from past 48 hours",
"description": "Search query to find SpaceX news from the past 48 hours."
}
],
"tool_id": "search_tool",
"output": "$spacex_news_results",
},
{
"task": "Summarize the top 3 developments from the SpaceX news articles.",
"inputs": [
{
"name": "$spacex_news_results",
"description": "The list of SpaceX news articles returned by the search tool."
}
],
"tool_id": "llm_tool",
"output": "$spacex_summary",
"condition": "if $spacex_news_results contains at least 3 articles"
},
{
"task": "Email Avrana a summary of the top 3 SpaceX developments with the subject 'Latest SpaceX Updates'.",
"inputs": [
{
"name": "$spacex_summary",
"description": "The summary of the top 3 SpaceX developments."
},
{
"name": "$email_recipients",
"value": "['avrana@kern.ai']",
"description": "List of email recipients."
},
{
"name": "$email_subject",
"value": "Latest SpaceX Updates",
"description": "The subject line for the email."
}
],
"tool_id": "portia:google:gmail:send_email",
"output": "$email_sent",
"condition": "if $spacex_news_results contains at least 3 articles"
}
]
}

A plan includes a series of steps defined by

  • "task" A task describing the objective of that particular step.
  • "input" The inputs required to achieve the step. Notice how the LLM is guided to weave the outputs of previous steps as inputs to the next ones where applicable e.g. $spacex_news_results coming out of the first step acts as an input to the second one.
  • "tool_id" Any relevant tool needed for the completion of the step. Portia is able to filter for the relevant tools during the multi-shot plan generation process. As we will see later on in this tutorial you can specify the tool registries (directories) you want when handling a user prompt, including local / custom tools and ones provided by third parties. In this example we are referencing tools from Portia's cloud-hosted library, prefixed with portia:.
  • "output" The step's final output. As mentioned above, every step output can be referenced in future steps. As we will see shortly, these outputs are serialised and saved in plan run state as it is being executed.
  • "condition" An optional condition that's used to control the execution of the step. If the condition is not met, the step will be skipped. This condition will be evaluated by our introspection agent, with the context of the plan and plan run state.

Create a plan from a user prompt

When responding to a user's prompt with Portia, you can either chain the plan generation process to the subsequent instantiation of a plan run from it, or you can choose to decouple them. The latter option allows you for example to display the plan to the user and tweak it before running a plan.

Let's look at how we generate a plan from a user prompt. Paste the code below into your project and run it:

main.py
from dotenv import load_dotenv
from portia import (
Portia,
default_config,
example_tool_registry,
)

load_dotenv()

# Instantiate a Portia instance. Load it with the default config and with the example tools.
portia = Portia(tools=example_tool_registry)

# Generate the plan from the user query
plan = portia.plan('Which stock price grew faster in 2024, Amazon or Google?')

# Serialise into JSON and print the output
print(plan.model_dump_json(indent=2))

As mentioned earlier in the documentation, the Portia instance class is your main entrypoint to interact with Portia's libraries (SDK reference ↗). The plan method is available from the Portia instance class and allows you to generate a plan from the query. Running the plan method per the code above returns a Plan object (SDK reference ↗) which looks as follows:

plan.json
{
"id": "plan-1dcd74a4-0af5-490a-a7d0-0df4fd983977",
"plan_context": {
"query": "Which stock price grew faster, Amazon or Google?",
"tool_ids": [
"calculator_tool",
"weather_tool",
"search_tool"
]
},
"steps": [
{
"task": "Search for the latest stock price growth data for Amazon.",
"inputs": [],
"tool_name": "Search Tool",
"output": "$amazon_stock_growth"
},
{
"task": "Search for the latest stock price growth data for Google.",
"inputs": [],
"tool_name": "Search Tool",
"output": "$google_stock_growth"
},
{
"task": "Compare the stock price growth of Amazon and Google.",
"inputs": [
{
"name": "$amazon_stock_growth",
"value": null,
"description": "The stock price growth data for Amazon."
},
{
"name": "$google_stock_growth",
"value": null,
"description": "The stock price growth data for Google."
}
],
"tool_name": null,
"output": "$stock_growth_comparison"
}
]
}

The plan method can take the following additional parameters:

  • tools in order to confine the plan generation to a narrower set of tools if required (for simplicity or for user-access considerations). In our example above we provided the example_tool_registry, which is a collection of three open source tools in our SDK.
  • example_plans expects a list of Plan objects. This allows you to use existing plans as inspiration or templates, which improves repeatability for more routine plan runs.

User Led Learning

Example plans can be used to bias the planner towards actions, tool use and behaviours, while also improving the planners ability to generate more complex plans. Broadly, the process for doing this with portia is 3 steps below

  • "Like" plans saved to Portia Cloud from the dashboard to signal that they are patterns you want to reinforce.
  • Pull "Liked" plans based on semantic similarity to the user intent in a query by using our freshly minted portia.storage.get_similar_plans method (SDK reference ↗).
  • Finally, ingest those similar plans as example plans in the Planning agent using the portia.plan method's example_plans property (SDK reference ↗).

For a deep dive into this feature and a practical example, check out our ULL blog post on example plans ↗.

Now that you know how to generate plans in response to a user query, let's take a look at how to run a plan in the next section.