Tool Integration¶
lionagi converts Python functions into LLM-callable tools automatically. The Tool class wraps any callable with schema generation, preprocessing, postprocessing, and argument validation.
How It Works¶
When you pass a function to Branch(tools=[...]), lionagi:
- Inspects the function signature and docstring
- Generates an OpenAI-compatible JSON schema via
function_to_schema() - Wraps it in a
Toolobject registered with theActionManager - Sends the schema to the LLM during
operate()orReAct()calls - Automatically invokes the function when the LLM requests it
Basic Usage¶
from lionagi import Branch
def calculate_sum(a: float, b: float) -> float:
"""Add two numbers together.
Args:
a: First number.
b: Second number.
"""
return a + b
branch = Branch(
tools=[calculate_sum],
system="You have access to a calculator tool."
)
# operate() with actions=True enables tool invocation
result = await branch.operate(
instruction="What is 15 + 27?",
actions=True,
)
The Tool Class¶
Tool extends Element and wraps a callable with metadata:
from lionagi.protocols.action.tool import Tool
tool = Tool(
func_callable=calculate_sum,
# Optional: pre/post processing
preprocessor=lambda args: {k: float(v) for k, v in args.items()},
postprocessor=lambda result: f"The answer is {result}",
# Optional: strict argument validation
strict_func_call=False,
)
# Auto-generated schema
print(tool.tool_schema)
# {'type': 'function', 'function': {'name': 'calculate_sum', ...}}
print(tool.function) # 'calculate_sum'
print(tool.required_fields) # {'a', 'b'}
Key Tool Parameters¶
| Parameter | Type | Description |
|---|---|---|
func_callable | Callable | The function to wrap (required) |
tool_schema | dict | Custom schema (auto-generated if omitted) |
request_options | type[BaseModel] | Pydantic model for input validation |
preprocessor | Callable | Transform arguments before execution |
postprocessor | Callable | Transform results after execution |
strict_func_call | bool | Enforce exact parameter matching |
mcp_config | dict | MCP server tool config (see MCP Servers) |
Async Tools¶
Both sync and async functions are supported. Async functions are awaited automatically:
import httpx
async def fetch_url(url: str) -> str:
"""Fetch content from a URL.
Args:
url: The URL to fetch.
"""
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.text[:500]
branch = Branch(tools=[fetch_url])
Registering Tools¶
Tools can be registered at construction or later:
# At construction
branch = Branch(tools=[func_a, func_b])
# After construction
branch.register_tools([func_c, func_d])
# With update=True to replace existing tools
branch.register_tools([func_a_v2], update=True)
# Check registered tools
print(branch.tools) # dict of {name: Tool}
You can pass raw functions, Tool objects, LionTool subclasses, or MCP config dicts.
Pydantic Request Validation¶
Use request_options to validate tool arguments with a Pydantic model:
from pydantic import BaseModel, Field
class SearchRequest(BaseModel):
query: str = Field(..., min_length=1)
max_results: int = Field(default=5, ge=1, le=20)
def search(query: str, max_results: int = 5) -> str:
"""Search for information."""
return f"Results for '{query}' (max {max_results})"
tool = Tool(
func_callable=search,
request_options=SearchRequest,
)
function_to_schema()¶
The schema generator extracts function metadata from type hints and docstrings:
from lionagi.libs.schema.function_to_schema import function_to_schema
def example(name: str, count: int) -> bool:
"""Do something with name and count.
Args:
name: The name to process.
count: How many times to process.
"""
return True
schema = function_to_schema(example)
# {
# 'type': 'function',
# 'function': {
# 'name': 'example',
# 'description': 'Do something with name and count.',
# 'parameters': {
# 'type': 'object',
# 'properties': {
# 'name': {'type': 'string', 'description': 'The name to process.'},
# 'count': {'type': 'number', 'description': 'How many times to process.'}
# },
# 'required': ['name', 'count']
# }
# }
# }
Supported type mappings: str -> string, int/float -> number, list/tuple -> array, bool -> boolean, dict -> object.
Using Tools with operate() and ReAct()¶
operate()¶
result = await branch.operate(
instruction="Search for Python tutorials",
actions=True, # Enable tool calling
invoke_actions=True, # Auto-invoke tools (default)
action_strategy="concurrent", # or "sequential"
)
ReAct()¶
ReAct runs a think-act-observe loop, automatically using registered tools:
result = await branch.ReAct(
instruct={
"instruction": "Research Python best practices and summarize findings",
"context": {"focus": "async programming"},
},
tools=True, # Use all registered tools (default)
max_extensions=3, # Max reasoning iterations
verbose=True,
)
Built-in Tools¶
ReaderTool¶
Reads files and URLs using the docling document converter. Requires pip install lionagi[reader].
from lionagi.tools import ReaderTool
branch = Branch(
tools=[ReaderTool],
system="You can read documents and URLs."
)
result = await branch.ReAct(
instruct={"instruction": "Read and summarize this document: report.pdf"},
max_extensions=4,
)
ReaderTool supports three actions:
- open: Convert a file or URL to text, returns a
doc_idand length - read: Read a slice of a previously opened document by
doc_idand offset - list_dir: List files in a directory
Custom LionTool¶
For reusable tools with internal state, subclass LionTool:
from lionagi.tools.base import LionTool
from lionagi.protocols.action.tool import Tool
class MyStatefulTool(LionTool):
is_lion_system_tool = True
system_tool_name = "my_tool"
def __init__(self):
super().__init__()
self.state = {}
def to_tool(self) -> Tool:
def my_tool(action: str, key: str, value: str = None) -> str:
"""Stateful key-value store."""
if action == "set":
self.state[key] = value
return f"Set {key}"
return self.state.get(key, "Not found")
return Tool(func_callable=my_tool)
# Register like any other tool
branch = Branch(tools=[MyStatefulTool])
Best Practices¶
- Type hints: Always add type hints -- they drive schema generation
- Docstrings: Use Google-style docstrings with
Args:sections for parameter descriptions - Return strings: LLMs work best when tools return string results
- Error handling: Wrap tool logic in try/except and return error messages as strings
- Async preferred: Use async functions for I/O-bound tools to avoid blocking