What @primitive actually does
The gate that makes a method callable by the planner. A plain method is invisible; @primitive registers it.
Before you start
@primitive is a gate. It registers a method so the planner is allowed to call
it. A plain method with no decorator is invisible to the planner. This track
shows that gate by taking one method through before and after.
The task is the kind of thing a language model gets wrong on its own: counting the letters in a word. With a primitive doing the counting, the answer is exact.
1. Start without the decorator#
Here is a one-method agent. The method is ordinary Python, no @primitive.
# counter.py
from opensymbolicai.blueprints import PlanExecute
class LetterCounter(PlanExecute):
def count_letter(self, word: str, letter: str) -> int:
"""Count how many times `letter` appears in `word`."""
return word.count(letter)Run it and it fails:
result = agent.run("how many times does the letter r appear in strawberry")
print(result.success) # False
print(result.error) # Function '...' is not allowed. Only primitive
# methods and allowed builtins can be called.The planner had no registered primitive to call. It tried to reach for
count_letter and execution refused it.
2. Add the decorator#
Mark the same method with @primitive. Nothing else changes.
# counter.py
from opensymbolicai.blueprints import PlanExecute
from opensymbolicai.core import primitive
class LetterCounter(PlanExecute):
@primitive(read_only=True)
def count_letter(self, word: str, letter: str) -> int:
"""Count how many times `letter` appears in `word`."""
return word.count(letter)3. Run it#
# main.py
from counter import LetterCounter
from opensymbolicai.llm import LLMConfig
llm = LLMConfig(provider="ollama", model="qwen2.5-coder:7b")
agent = LetterCounter(llm=llm)
result = agent.run("how many times does the letter r appear in strawberry")
print(result.result) # 3uv run main.pyOutput:
3Takeaway#
The planner may only call registered primitives. @primitive is what moves a
method from "invisible" to "callable."