LlamaIndexllama-index-core 0.14 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
30 small wins to finish your pathNext lesson →

Agent tools: giving an agent more than search

A tool is a function an agent can call, and an agent with more than one tool picks the right tool for each question.

Last updated: 28 Sep, 2026 · LlamaIndex 0.14

The last lesson gave the agent one tool, the help centre. Real assistants also do lookups that are not in documents, such as an order status. Add a second tool and watch the agent choose between them.

Wrapping a Python function as a tool

A FunctionTool turns any function into a tool. The docstring and type hints tell the agent what it does and what argument it needs.

python
from llama_index.core.tools import FunctionTool

ORDERS = {"A1001": "shipped, arriving Tuesday", "A1002": "waiting for payment"}

def order_status(order_id: str) -> str:
    """Look up the delivery status of an order by its id."""
    return ORDERS.get(order_id, "no order with that id")

order_tool = FunctionTool.from_defaults(order_status)

The two tools side by side

The order tool answers from a dictionary. The help tool, from the last lesson, answers from the documents. The agent now holds both.

python
help_tool = QueryEngineTool.from_defaults(
    index.as_query_engine(llm=ExtractiveLLM(), similarity_top_k=2),
    name="help_centre", description="Policy questions about refunds, delivery and lamps.")
# tools=[order_tool, help_tool] when the agent is built

Choosing a tool by the question

The stand-in generator does the choosing a real model would do: if the question has an order id, call the lookup; otherwise search the documents. Once a tool has run, its result is the answer.

python
import re

def route(messages, **kwargs):
    done = [m for m in messages if m.role == MessageRole.TOOL]
    if done:
        return ChatMessage(role=MessageRole.ASSISTANT, content=done[-1].content)
    question = messages[-1].content
    order_id = re.search(r"[A-Z]\d{4}", question)
    name, args = ("order_status", {"order_id": order_id.group()}) if order_id \
        else ("help_centre", {"input": question})
    return ChatMessage(role=MessageRole.ASSISTANT, blocks=[ToolCallBlock(
        tool_call_id="c1", tool_name=name, tool_kwargs=args)])

One agent picking between two tools

The whole program. Three questions, and the agent sends each to the tool that can answer it.

Example
import asyncio
import re
from uuid import uuid4

from llama_index.core import Settings, SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.base.llms.types import ChatMessage, MessageRole, ToolCallBlock
from llama_index.core.llms import MockFunctionCallingLLM
from llama_index.core.tools import FunctionTool, QueryEngineTool
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

from extractive_llm import ExtractiveLLM

Settings.embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")

ORDERS = {"A1001": "shipped, arriving Tuesday", "A1002": "waiting for payment"}


def order_status(order_id: str) -> str:
    """Look up the delivery status of an order by its id."""
    return ORDERS.get(order_id, "no order with that id")


order_tool = FunctionTool.from_defaults(order_status)
index = VectorStoreIndex.from_documents(SimpleDirectoryReader("help").load_data())
help_tool = QueryEngineTool.from_defaults(
    index.as_query_engine(llm=ExtractiveLLM(), similarity_top_k=2),
    name="help_centre", description="Policy questions about refunds, delivery and lamps.")


def route(messages, **kwargs):
    done = [m for m in messages if m.role == MessageRole.TOOL]
    if done:
        return ChatMessage(role=MessageRole.ASSISTANT, content=done[-1].content)
    question = messages[-1].content
    order_id = re.search(r"[A-Z]\d{4}", question)
    if order_id:                                # an order id: use the lookup tool
        return ChatMessage(role=MessageRole.ASSISTANT, blocks=[ToolCallBlock(
            tool_call_id="c" + uuid4().hex[:6], tool_name="order_status",
            tool_kwargs={"order_id": order_id.group()})])
    return ChatMessage(role=MessageRole.ASSISTANT, blocks=[ToolCallBlock(  # else: search the docs
        tool_call_id="c" + uuid4().hex[:6], tool_name="help_centre",
        tool_kwargs={"input": question})])


agent = FunctionAgent(tools=[order_tool, help_tool],
                      llm=MockFunctionCallingLLM(response_generator=route),
                      system_prompt="Use order_status for order ids and help_centre for policy.")


async def main():
    for question in ["Where is order A1001?", "Can I refund a sale item?", "Where is order A9999?"]:
        print(question)
        print("  ", await agent.run(question))


asyncio.run(main())

Which tool answered each question

  • The order questions carry an id like A1001, so the agent calls order_status and returns the dictionary value.
  • The refund question has no id, so the agent searches the documents through help_centre.
  • The unknown order reaches the lookup, which reports no order with that id, a real result from the function.

A function tool vs a query engine tool

ToolAnswers fromGood for
FunctionToolYour Python codeLookups, calculations, actions
QueryEngineToolYour indexed documentsQuestions the docs can answer

When an agent needs more than one tool

  • Order or account lookups that live in a database, not in the help documents.
  • An action such as starting a return alongside answering policy questions.
  • Routing to the right knowledge base when there is more than one.
Watch out. The agent picks a tool from its name and description, so a vague description sends questions to the wrong tool. Name each tool for what it answers and say so in one line.
Try it yourself
  • Add a third order id to ORDERS and ask about it.
  • Ask "Where is order A1001 and can I refund it?" and see which single tool the router picks.
  • Change the id pattern so it also matches lowercase, then ask about "order a1001".

Little by little, you're building something great.