Skip to main content

Overview

Tools let your DialogMachine take actions during a conversation — look up a customer record, book a slot, query a database, call an API. When the LLM decides a tool is needed, SuperDialog executes it automatically and feeds the result back into the conversation. Three tool types, one interface:
TypeExecutionBest for
PythonToolIn-processLocal functions, direct DB calls, any Python code
HttpToolHTTP requestExternal REST APIs, microservices
MCPToolMCP protocolModel Context Protocol servers

Registering Python tools

There are three ways to register a Python function as a tool.

1. @tool decorator

The fastest way. Schema is inferred from type hints and description from the docstring.
from superdialog.tools import tool

@tool
async def lookup_customer(customer_id: str) -> dict:
    """Look up customer record by ID."""
    return await crm.get(customer_id)

dialog_machine = DialogMachine(flow=flow, llm="...", tools=[lookup_customer])
Override the name or description when the function name or docstring isn’t suitable:
@tool(name="check_stock", description="Check inventory availability by SKU")
async def check_inventory(sku: str, qty: int) -> dict:
    return await inventory_api.check(sku, qty)

2. Plain functions in tools=[]

No decorator needed — pass any function directly. SuperDialog wraps it automatically using the function name as the tool id and the docstring as the description.
async def lookup_customer(customer_id: str) -> dict:
    """Look up customer record by ID."""
    return await crm.get(customer_id)

async def check_inventory(sku: str) -> dict:
    """Check inventory levels."""
    return await inventory_api.check(sku)

dialog_machine = DialogMachine(
    flow=flow,
    llm="...",
    tools=[lookup_customer, check_inventory],   # plain functions, no wrapping
)

3. Function references on the flow model

Attach functions directly to ConversationFlow.tools or FlowNode.tools when building flows in Python. This gives you scoping — flow-level tools are available on every node, node-level tools only on that node.
from superdialog.flow.models import ConversationFlow, FlowNode, Edge

async def search_kb(query: str) -> dict:
    """Search the knowledge base."""
    return await kb.search(query)

async def check_availability(date: str) -> dict:
    """Check appointment availability."""
    return await calendar.slots(date)

flow = ConversationFlow(
    system_prompt="You are an appointment assistant.",
    initial_node="greeting",
    tools=[search_kb],                        # flow-level: available on every node
    nodes=[
        FlowNode(
            id="collect",
            name="Collect Info",
            instruction="Collect patient name and preferred date.",
            tools=[check_availability],       # node-level: only on this node
            edges=[
                Edge(id="e_confirm", condition="All info collected", target_node_id="confirm")
            ],
        ),
        ...
    ],
)

# No tool_handlers dict required — functions are bound directly
machine = await DialogStateMachine.from_flow(flow, adapter)
@tool-decorated functions work here too:
@tool
async def lookup_customer(customer_id: str) -> dict:
    """Look up a customer."""
    ...

flow = ConversationFlow(
    tools=[lookup_customer],
    ...
)

PythonTool

For explicit control over the tool’s id, name, description, or schema:
from superdialog import PythonTool

# Convenience constructor — infers id, name, description, schema from the function
tool = PythonTool.of(lookup_customer)

# Explicit constructor
tool = PythonTool(
    id="crm-lookup",
    name="lookup_customer",
    description="Fetch full customer record from CRM by customer ID",
    fn=lookup_customer,
)

HttpTool

For external REST APIs. SuperDialog POSTs to the URL with the tool’s input as a JSON body.
from superdialog import HttpTool
import os

tool = HttpTool(
    id="lookup",
    name="lookup",
    description="Look up a customer by partial Aadhaar number",
    url="https://api.company.io/customer/lookup",
    method="POST",    # default
    auth={"type": "bearer", "token": os.environ["COMPANY_KEY"]},
)
Auth shapes:
# Bearer token
auth={"type": "bearer", "token": "your-token"}

MCPTool

For servers that implement the Model Context Protocol. Requires pip install superdialog[mcp].
from superdialog import MCPTool

tool = MCPTool(
    id="search",
    name="search",
    description="Search the internal knowledge base",
    server="https://mcp.company.io",
)
MCPTool connects lazily on first use via SSE. Auto-discovery of all tools published by an MCP server is planned for a follow-up release.

Tool.from_dict

Deserialize a tool from a plain dict — useful when loading tool specs from JSON config files:
from superdialog import Tool

# HTTP tool from config
tool = Tool.from_dict({
    "type": "http",
    "id": "lookup",
    "name": "lookup",
    "description": "Look up customer",
    "url": "https://api.company.io/lookup",
})

# Python tool from config (pass handler_registry to resolve function by name)
tool = Tool.from_dict(
    {"type": "python", "id": "verify", "name": "verify", "description": "...", "handler_id": "verify_fn"},
    handler_registry={"verify_fn": verify_customer},
)

Tool results and flow transitions

Tool results are automatically merged into node slot values and made available in the conversation context. A tool can also trigger a flow transition by returning a ToolResult with a transition_edge_id:
from superdialog.machine.models import ToolResult

async def book_appointment(slot_id: str) -> ToolResult:
    """Book the appointment for the given slot."""
    success = await calendar.book(slot_id)
    if success:
        return ToolResult(
            data={"booked": True, "slot": slot_id},
            transition_edge_id="booking_confirmed",   # triggers edge transition
        )
    return ToolResult(data={"booked": False})

Example: full flow with scoped tools

import asyncio
from superdialog.flow.models import ConversationFlow, FlowNode, Edge
from superdialog.machine.machine import DialogStateMachine
from superdialog.tools import tool

# Flow-level tool — available on every node
@tool
async def search_kb(query: str) -> dict:
    """Search the knowledge base for answers."""
    return {"results": await kb.search(query)}

# Node-level tool — only available on the collect_info node
async def check_availability(date: str) -> dict:
    """Check available appointment slots for a date."""
    return {"slots": await calendar.get_slots(date)}

async def verify_insurance(policy_number: str) -> dict:
    """Verify patient insurance coverage."""
    return {"verified": await insurance_api.verify(policy_number)}

flow = ConversationFlow(
    system_prompt="You are an appointment booking assistant.",
    initial_node="greeting",
    tools=[search_kb],                        # available everywhere
    nodes=[
        FlowNode(
            id="greeting",
            name="Greeting",
            static_text="Hello! I can help you book an appointment.",
            edges=[Edge(id="e_start", condition="User ready", target_node_id="collect_info")],
        ),
        FlowNode(
            id="collect_info",
            name="Collect Info",
            instruction="Collect patient name, date, and insurance policy number.",
            tools=[check_availability, verify_insurance],   # node-scoped
            edges=[Edge(id="e_confirm", condition="All info collected", target_node_id="confirm")],
        ),
        FlowNode(
            id="confirm",
            name="Confirm",
            static_text="Your appointment is confirmed!",
            is_final=True,
        ),
    ],
)

machine = await DialogStateMachine.from_flow(flow, adapter)