Write, register, and control custom tools in Logicore — from a simple type-annotated function to class-based tools with Pydantic validation.
Logicore gives you four ways to define a custom tool. All four end up in the same internal structures (internal_tools for schemas, custom_tool_executors for callables) and are equally callable by the LLM.
When you need full control over the schema — exact parameter names, descriptions, or compatibility with a strict external API — supply the schema yourself:
from logicore import Agentdef get_exchange_rate(base: str, quote: str) -> dict: if base == quote: return {"success": True, "rate": 1.0} return {"success": True, "rate": 83.12, "base": base, "quote": quote}schema = { "type": "function", "function": { "name": "get_exchange_rate", "description": "Get FX conversion rate for a currency pair.", "parameters": { "type": "object", "properties": { "base": {"type": "string", "description": "Base currency code, e.g. USD"}, "quote": {"type": "string", "description": "Quote currency code, e.g. INR"} }, "required": ["base", "quote"] } }}agent = Agent(llm="ollama")agent.add_custom_tool(schema, get_exchange_rate)
This also accepts a run_sql-style function with guard logic:
def run_sql(query: str, limit: int = 100): if not query.strip().lower().startswith("select"): return {"success": False, "error": "Only SELECT statements are allowed"} return {"success": True, "rows": [{"id": 1, "name": "Ada"}], "limit": limit}agent.add_custom_tool(schema, run_sql)
For reusable, testable tool modules, subclass BaseTool from logicore.tools.base. The schema is derived automatically from the args_schema Pydantic model.
from pydantic import BaseModel, Fieldfrom logicore import Agentfrom logicore.tools.base import BaseTool, ToolResultclass StockPriceParams(BaseModel): symbol: str = Field(..., description="Ticker symbol like MSFT")class StockPriceTool(BaseTool): name = "get_stock_price" description = "Get latest stock price for a ticker symbol" args_schema = StockPriceParams def run(self, symbol: str) -> ToolResult: return ToolResult(success=True, content={"symbol": symbol, "price": 432.15})tool = StockPriceTool()agent = Agent(llm="ollama")agent.add_custom_tool(tool.schema, lambda **kwargs: tool.run(**kwargs))
ToolResult is a dict subclass with success, content, and error keys:
class ToolResult(dict): def __init__(self, success: bool, content: Any = None, error: str = None): ...
When a tool function raises an uncaught exception, Logicore wraps the error and sends it back to the LLM as the tool result. The model sees the error and adjusts its approach.For recoverable failures, return an error field instead of raising:
def lookup_order(order_id: str, **kwargs) -> dict: """Look up an order by ID. Args: order_id (str): The order identifier. Returns: dict: Order details or an error message. """ order = db.find(order_id) if not order: # Return a structured error — do not raise return {"success": False, "error": f"Order {order_id} not found"} return {"success": True, "data": order}
Returning a structured error lets the LLM explain the failure to the user in natural language. Raising an exception has the same effect but produces a less readable error string.
Annotate every parameter. Prefer dict, str, list, and int over ambiguous return types. For BaseTool subclasses, use a Pydantic args_schema.
Stable response shape
Keep the shape of successful responses consistent across calls. The LLM learns to parse your output; changing the structure between invocations degrades reliability.
Descriptions that guide routing
The tool description answers: when should this tool be used? Parameter descriptions should include accepted formats and examples. Include Args and Returns in docstrings.
Hallucination tolerance
Include **kwargs to absorb unexpected parameters. Validate critical arguments in code and return friendly errors rather than crashing.
Single-purpose, deterministic behavior
Keep tools focused on one action. Add timeouts and retries for external calls. Avoid hidden side effects unless the tool is explicitly designed to mutate state.