Tools#
A Tool is a Python callable the reasoning state can invoke during its loop. BAF auto-introspects each function’s signature, type hints and docstring to build a JSON schema in the OpenAI / Anthropic function-calling format — you don’t write any schema by hand.
At runtime the LLM picks a tool, BAF validates the arguments against the auto-generated schema, the function runs, and the stringified result goes back to the LLM as the next observation.
How to use#
The simplest path is new_tool() — pass any
callable, BAF builds the wrapper and registers it on the agent:
from baf.core.agent import Agent
agent = Agent('example_agent')
def get_weather(city: str) -> str:
"""Get current weather for a city."""
return f"sunny in {city}"
agent.new_tool(get_weather)
Type hints and the docstring drive the schema:
str/int/float/bool/list[X]/dictmap to the equivalent JSON schema types.Optional[X]/X | Nonemakes the parameter optional.The first non-empty line of the docstring becomes the tool description (overridable via the
description=argument).A Google-style
Args:block populates per-parameter descriptions.
Note
Each tool is wrapped so that any exception it raises becomes an
ERROR: <type>: <message> string fed back to the LLM. This way the
model can read the failure and recover (retry with corrected
arguments, skip the task, etc.) instead of breaking the loop.
Decorator form#
For agents whose tools live in the same file, the @agent.tool
decorator is syntactic sugar over new_tool():
@agent.tool
def echo(text: str) -> str:
"""Echo the user-supplied text back, unchanged."""
return text
You can also pass name / description overrides:
@agent.tool(name='lookup_user', description='Find a user by id.')
def _lookup(user_id: int) -> dict:
...
Bulk-load tools from a module or folder#
When tools are organised in their own Python file (or folder of files),
load_tools() walks every public top-level
callable and registers each as a tool:
agent.load_tools('./tools.py') # single module
agent.load_tools('./tools') # every .py file in the folder
Names starting with _ and callables imported from other modules are
skipped, so importing helpers in your tools module does not pollute the
registry.
Pre-built Tool instances#
If you already hold a Tool instance, register
it with add_tool() (mirroring how
add_intent() works for intents):
from baf.reasoning import Tool
tool = Tool(get_weather, description='Get weather for a city.')
agent.add_tool(tool)
Argument validation#
Before a tool is executed, BAF validates the LLM-supplied arguments against the auto-generated schema:
Unknown / missing required arguments fail with a clear
ToolErrormessage the LLM can read.Light coercion is applied:
intaccepts strings of digits,floatacceptsintand parseable strings,boolaccepts the case-insensitive strings"true"/"false".
Built-in predefined tools#
BAF ships with two families of predefined tools that the reasoning state
uses out of the box. They live under baf.library.tool and are
registered automatically when you set up the relevant primitives:
Workspace tools (Workspaces page) —
list_directory,read_file, and (when at least one workspace is writable)write_file,create_file,delete_file. Registered the moment the first workspace is added to the agent.Task-planning tools (Reasoning state page) —
add_tasks,complete_task,skip_task. Per-invocation tools created fresh for every reasoning loop so each user request has its own task list.
API References#
Agent:
baf.core.agent.AgentAgent.new_tool():
baf.core.agent.Agent.new_tool()Agent.add_tool():
baf.core.agent.Agent.add_tool()Agent.load_tools():
baf.core.agent.Agent.load_tools()Agent.tool() decorator:
baf.core.agent.Agent.tool()Tool:
baf.reasoning.tool.ToolToolError:
baf.reasoning.tool.ToolError