Type annotations are the contract
How parameter and return types reach the model. The planner never sees your method bodies, only the signatures your annotations produce.
Before you start
The planner never sees your method bodies. It sees their signatures, built straight from your type annotations. The types you write on each parameter and return are the contract the model plans against. This track makes that contract visible by printing the plan the model generates.
1. Two typed primitives#
# textkit.py
from opensymbolicai.blueprints import PlanExecute
from opensymbolicai.core import primitive
class TextKit(PlanExecute):
@primitive(read_only=True)
def repeat(self, text: str, times: int) -> str:
"""Repeat text the given number of times."""
return text * times
@primitive(read_only=True)
def shout(self, text: str) -> str:
"""Return text uppercased with an exclamation mark."""
return text.upper() + "!"The agent turns those annotations into the signatures it shows the model:
repeat(text: str, times: int) -> str
"""Repeat text the given number of times."""
shout(text: str) -> str
"""Return text uppercased with an exclamation mark."""That is all the planner knows about repeat: its name, parameter types, return
type, and docstring. The body text * times stays on your side.
2. Run it and read the plan#
# main.py
from textkit import TextKit
from opensymbolicai.llm import LLMConfig
llm = LLMConfig(provider="ollama", model="qwen2.5-coder:7b")
agent = TextKit(llm=llm)
result = agent.run("repeat the word go 3 times, then shout the result")
print(result.plan)
print(result.result)uv run main.pyOutput:
go_repeated = repeat(text="go", times=3)
result = shout(text=go_repeated)
GOGOGO!Read the plan. The model passed text="go" as a quoted string and times=3 as
a bare integer, because that is what the signature said each one was. The string
return of repeat flows into shout, whose parameter is also a string. The
types lined up, so the plan runs.
3. Change one type, change the plan#
Annotate times as str instead of int:
- def repeat(self, text: str, times: int) -> str:
+ def repeat(self, text: str, times: str) -> str:Now the signature tells the model times is a string, so it plans accordingly
and passes times="3" (quoted). The body still does text * times, which
multiplies a string by a string and raises a TypeError at runtime.
The types are the contract. Write them accurately and the planner follows them. Write them wrong and the plan will reflect the wrong types, not the body.