Agents can pick between several data sources and tools per question, which a fixed query engine can't.
An agent is an LLM in a loop: it reads the request, decides which tool to call, looks at the result and repeats until it can answer. In LlamaIndex the standard agent is `FunctionAgent`, which uses the model's tool (function) calling:
agent = FunctionAgent(tools=[multiply, search_docs], llm=OpenAI(model="gpt-4o-mini"),
system_prompt="You are a helpful assistant.")
response = await agent.run("What is 7 * 8, and what does the handbook say about leave?")Tools. A plain Python function becomes a tool automatically: its name, type hints and docstring tell the model what it does, so write the docstring for the model. FunctionTool.from_defaults(fn, name=..., description=...) gives more control. `QueryEngineTool.from_defaults(query_engine, name=..., description=...)` turns a whole RAG pipeline into one tool. That is how an agent can choose between, say, an HR index and a product index.
Async. agent.run(...) is asynchronous. async/await is Python's way of running code that waits for slow things (like model calls) without blocking everything else. In practice: put your calls inside an async def main():, write await before agent.run(...), and start it with asyncio.run(main()). In Jupyter notebooks you can use await directly.
Other agent types. ReActAgent works with models that lack native tool calling, by reasoning in text. CodeActAgent writes and runs code as its actions, which needs a sandbox.
Older APIs such as OpenAIAgent, AgentRunner and ReActAgent.from_tools are legacy; use the workflow-based agents.
Example
import asyncio
from llama_index.core import Document, VectorStoreIndex
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import QueryEngineTool
from llama_index.llms.openai import OpenAI
def multiply(a: float, b: float) -> float:
"""Multiply two numbers."""
return a * b
hr_index = VectorStoreIndex.from_documents([Document(text="Staff get 25 leave days a year.")])
hr_tool = QueryEngineTool.from_defaults(
hr_index.as_query_engine(), name="hr_handbook",
description="Answers questions about HR policies such as leave and benefits.")
agent = FunctionAgent(tools=[multiply, hr_tool], llm=OpenAI(model="gpt-4o-mini"),
system_prompt="Use tools for facts and arithmetic.")
async def main():
response = await agent.run("How many leave days do 3 employees get in total?")
print(str(response)) # 3 employees get 75 leave days in total.
asyncio.run(main())Remember
FunctionAgent(tools, llm, system_prompt)runs a tool-calling loop- Functions become tools via name, type hints and docstring; QueryEngineTool wraps RAG
await agent.run(...)insideasync def, started withasyncio.run
Watch out for
- Calling agent.run(...) without await in normal code and printing a handler object instead of the answer.