Use this file to discover all available pages before exploring further.
Tool calling lets language models do more than just generate text — they can request the execution of Python functions, receive the results, and incorporate those results into their response. LLM supports tool usage across both the command-line interface and the Python API, enabling everything from simple calculations to multi-step agentic workflows.
Tools can be dangerous. Applications built on top of LLMs are vulnerable to prompt injection attacks, where malicious third-party content tricks the model into taking harmful actions.Be especially wary of the lethal trifecta — if your tool-enabled LLM has access to private data, exposure to malicious instructions (web pages, emails, GitHub issues), and the ability to exfiltrate information, an attacker could steal your private data by leaving malicious instructions in content your LLM processes.
By default LLM will run up to 5 consecutive tool-call/response loops before stopping. Use --cl (--chain-limit) to change this:
# Allow up to 10 tool loopsllm -T llm_version 'What version is this?' --cl 10# No limit — run until the model stops requesting toolsllm -T llm_version 'What version is this?' --cl 0
Some plugins bundle multiple related tools into a toolbox — a single --tool argument that loads several tools at once. Toolbox names always start with a capital letter:
Every tool in LLM is an instance of llm.Tool. You can create one from any Python function using Tool.function():
import llmdef multiply(x: int, y: int) -> int: """Multiply two numbers together.""" return x * ytool = llm.Tool.function(multiply)# tool.name → "multiply"# tool.description → "Multiply two numbers together."# tool.input_schema → Pydantic model built from the function signature
LLM extracts the tool name from the function name, the description from the docstring, and builds a JSON input schema by inspecting the function signature and type hints.
For more advanced needs — bundling related tools, storing state between calls, or making tools configurable — subclass llm.Toolbox:
import llmclass Memory(llm.Toolbox): _memory = None def _get_memory(self): if self._memory is None: self._memory = {} return self._memory def set(self, key: str, value: str): "Set something as a key" self._get_memory()[key] = value def get(self, key: str): "Get something from a key" return self._get_memory().get(key) or "" def append(self, key: str, value: str): "Append something as a key" memory = self._get_memory() memory[key] = (memory.get(key) or "") + "\n" + value def keys(self): "Return a list of keys" return list(self._get_memory().keys())
Any method that does not start with an underscore is exposed as a tool. The toolbox instance persists state across multiple tool invocations in the same conversation.Use a toolbox from Python:
model = llm.get_model("gpt-4.1-mini")memory = Memory()conversation = model.conversation(tools=[memory])print(conversation.chain("Set name to Simon", after_call=print).text())print(memory._memory)# {'name': 'Simon'}print(conversation.chain("Set name to Penguin", after_call=print).text())print(conversation.chain("Print current name", after_call=print).text())
Always include a docstring in your tool functions. The docstring becomes the tool description that the model uses to decide when and how to call the tool.
Use type hints for all parameters
LLM builds the input schema by inspecting the function signature. Without type hints, all parameters default to str. Be explicit:
def search(query: str, max_results: int = 10) -> list: """Search for items matching the query.""" ...
Return strings or JSON-serializable objects
Tool functions can return a string or any object that can be converted to a string. Returning a dict or list works — LLM will serialize it for the model.To also return attachments (images, files) alongside text output, return an llm.ToolOutput:
import llmdef generate_image(prompt: str) -> llm.ToolOutput: """Generate an image based on the prompt.""" image_content = generate_image_from_prompt(prompt) return llm.ToolOutput( output="Image generated successfully", attachments=[llm.Attachment( content=image_content, mimetype="image/png" )], )
Store API secrets with llm keys set
If your plugin tool needs API credentials, store them with llm keys set api-name and retrieve them with the llm.get_key() utility. This prevents secrets from being logged to the database as part of tool call arguments.
Access the ToolCall object from inside a tool
If your tool needs its own tool_call_id — for example to key external state against a specific invocation — add a parameter named llm_tool_call to your function. It receives the llm.ToolCall object and is hidden from the schema the model sees:
import llmdef lookup(name: str, llm_tool_call: llm.ToolCall) -> str: "Look up a name." return do_lookup(name, request_id=llm_tool_call.tool_call_id)
This works for both sync and async functions and for llm.Toolbox methods.
For multi-step loops that keep going until the model stops requesting tools, use model.chain():
chain_response = model.chain( "Convert panda to upper", tools=[upper],)print(chain_response.text())# The word "panda" converted to uppercase is "PANDA".
Stream the response as it generates:
for chunk in model.chain("Convert panda to upper", tools=[upper]): print(chunk, end="", flush=True)
Iterate over individual responses in the chain:
chain = model.chain("Convert panda to upper", tools=[upper])for response in chain.responses(): print(response.prompt) for chunk in response: print(chunk, end="", flush=True)
Use the before_call= parameter to inspect or block tool calls before they execute. Raise llm.CancelToolCall to prevent a specific call:
import llmfrom typing import Optionaldef upper(text: str) -> str: "Convert text to uppercase." return text.upper()def before_call(tool: Optional[llm.Tool], tool_call: llm.ToolCall): print(f"About to call {tool.name} with {tool_call.arguments}") if tool.name == "upper" and "bad" in repr(tool_call.arguments): raise llm.CancelToolCall("Not allowed to call upper on text containing 'bad'")model = llm.get_model("gpt-4.1-mini")response = model.chain( "Convert panda to upper and badger to upper", tools=[upper], before_call=before_call,)print(response.text())
Use after_call= to log results after each execution:
def after_call(tool: llm.Tool, tool_call: llm.ToolCall, tool_result: llm.ToolResult): print(f"{tool.name}({tool_call.arguments}) → {tool_result.output}")response = model.chain( "Convert panda to upper", tools=[upper], after_call=after_call,)
Raise llm.CancelToolCall inside a before_call hook to stop a specific tool call. The model will be informed that the call was cancelled and can adjust its response:
def before_call(tool, tool_call): if tool.name == "delete_files": raise llm.CancelToolCall("Deletion not permitted in this session")
Raise llm.PauseChain inside a tool implementation when the tool cannot complete without outside input — for example, when human approval is required:
import llmdef delete_files(path: str) -> str: if not approval_already_recorded(path): record_approval_request(path) raise llm.PauseChain("waiting for approval to delete " + path) do_delete(path) return "deleted"
Unlike other exceptions (which become "Error: ..." tool results sent back to the model), PauseChain propagates cleanly out of the chain. Before re-raising, LLM populates two attributes:
pause.tool_call — the llm.ToolCall whose implementation paused
pause.tool_results — results of sibling calls that completed
try: chain_response.text()except llm.PauseChain as pause: print("Paused on", pause.tool_call.name, pause.tool_call.tool_call_id)
To resume after a pause (or a crash, or a server restart), re-run the chain with the persisted message history:
chain = model.chain( messages=persisted_messages, # ends in assistant tool calls with no results tools=[delete_files], system=system_prompt,)chain.text()
Calls that already have matching results in the history are skipped — only unresolved calls are re-executed.
Pass pass_self=True if the function needs access to the toolbox instance as self:
def my_function(self, arg1: str) -> str: return f"Called on {self} with {arg1}"toolbox.add_tool(my_function, pass_self=True)
Override prepare() (or prepare_async() for async contexts) to run setup logic — such as consulting an MCP server for available tools — before the toolbox is first used:
class MyToolbox(llm.Toolbox): def prepare(self): tools = fetch_tools_from_server() for t in tools: self.add_tool(t)