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.
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.
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 builtChoosing 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.
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.
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 callsorder_statusand 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
| Tool | Answers from | Good for |
|---|---|---|
| FunctionTool | Your Python code | Lookups, calculations, actions |
| QueryEngineTool | Your indexed documents | Questions 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.
Related
- Previous: FunctionAgent: an agent that queries your index
- Next: Long-term memory: recalling past sessions
- Reference: Understanding: tools
- Add a third order id to
ORDERSand 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.