Read the planning prompt
Call build_plan_prompt(task) on any agent to see the exact string the model receives. Three sections appear: DEFINITIONS (primitives and examples), CONTEXT (the task), and INSTRUCTIONS (fixed output rules).
Before you start
Track 15 added expanded_intent to a decomposition to make the planner's
reasoning explicit. This tutorial shows where that reasoning actually lands:
inside the full prompt string the model receives. Call build_plan_prompt(task)
and the string comes back without sending anything to the model.
1. The agent#
The same Calculator from Track 1, plus one decomposition:
# calculator.py
from opensymbolicai.blueprints import PlanExecute
from opensymbolicai.core import decomposition, primitive
class Calculator(PlanExecute):
"""A tiny calculator agent."""
@primitive(read_only=True)
def add(self, a: float, b: float) -> float:
"""Add two numbers."""
return a + b
@primitive(read_only=True)
def multiply(self, a: float, b: float) -> float:
"""Multiply two numbers."""
return a * b
@primitive(read_only=True)
def subtract(self, a: float, b: float) -> float:
"""Subtract b from a."""
return a - b
@decomposition(intent="what is 2 plus 3?")
def _example_add(self) -> float:
result = self.add(2, 3)
return result2. Print the prompt#
The section markers in the raw string are machine-readable tokens. Import them and replace them with readable labels before printing:
# main.py
from calculator import Calculator
from opensymbolicai.llm import LLMConfig
from opensymbolicai.models import (
PROMPT_CONTEXT_BEGIN, PROMPT_CONTEXT_END,
PROMPT_DEFINITIONS_BEGIN, PROMPT_DEFINITIONS_END,
PROMPT_INSTRUCTIONS_BEGIN, PROMPT_INSTRUCTIONS_END,
)
LABELS = {
PROMPT_DEFINITIONS_BEGIN: "# --- DEFINITIONS START ---",
PROMPT_DEFINITIONS_END: "# --- DEFINITIONS END ---",
PROMPT_CONTEXT_BEGIN: "# --- CONTEXT START ---",
PROMPT_CONTEXT_END: "# --- CONTEXT END ---",
PROMPT_INSTRUCTIONS_BEGIN:"# --- INSTRUCTIONS START ---",
PROMPT_INSTRUCTIONS_END: "# --- INSTRUCTIONS END ---",
}
def annotate(prompt: str) -> str:
for marker, label in LABELS.items():
prompt = prompt.replace(marker, label)
return prompt
llm = LLMConfig(provider="ollama", model="qwen2.5-coder:7b")
agent = Calculator(llm=llm)
prompt = agent.build_plan_prompt("what is 7 times 8 minus 3?")
print(annotate(prompt))build_plan_prompt never calls the model, so Ollama does not need to be
running.
uv run main.py3. What you see#
# --- DEFINITIONS START ---
## Available Primitive Methods
add(a: float, b: float) -> float
"""Add two numbers."""
multiply(a: float, b: float) -> float
"""Multiply two numbers."""
subtract(a: float, b: float) -> float
"""Subtract b from a."""
## Example Decompositions
### Example 1
Intent: what is 2 plus 3?
Python:
result = add(2, 3)
return result
# --- DEFINITIONS END ---
# --- CONTEXT START ---
Generate Python code to accomplish this task: what is 7 times 8 minus 3?
# --- CONTEXT END ---
# --- INSTRUCTIONS START ---
1. Output Python assignment statements, then a final `return` statement
2. You can ONLY call the primitive methods listed above
3. Do NOT use imports, loops, conditionals, or function definitions
...
# --- INSTRUCTIONS END ---4. What lands where#
- DEFINITIONS: everything the model is allowed to call.
@primitivemethods appear under "Available Primitive Methods" with their signatures and docstrings.@decompositionmethods appear under "Example Decompositions" withself.stripped from the body. This section changes when you add or remove primitives or decompositions. - CONTEXT: the task string you passed to
build_plan_prompt. This is the only part that varies per call. - INSTRUCTIONS: fixed output rules. The model must produce assignment statements
ending with
return. This section never changes.
5. Try it: add a primitive#
Add a divide primitive to Calculator, run again, and watch DEFINITIONS grow
by one entry. CONTEXT and INSTRUCTIONS stay identical.