Most people think of AI as something that answers questions. You type something in, it types something back. That mental model made sense in 2022. It does not describe what AI can do today.
This first build is intentionally simple. The point is not to build a full analytics platform. The point is to show the foundation: an AI model can choose an action, and software can execute that action.
What This Agent Does
A Tool Invocation Agent is an AI system that has access to tools and knows when to use them.
Here is the simplest way to understand it. Imagine you hired an assistant and told them: here are the reports, here are the calculators, here is the company data, now handle this request. A regular chatbot can only talk about the task. A tool-using agent can decide which tool is needed and trigger real code to do the work.
In this build, the agent works with a small CSV file of advertising campaign data. The user can ask for something like:
Show me spend by campaign
The model reads that request and chooses the right tool:
spend_by_campaign
Then Python runs the tool, reads the CSV, totals the campaign spend, and prints the result.
The agent also supports another request:
Show me clicks by month
In that case, the model chooses:
clicks_by_month
Again, the model does not calculate the numbers. The model chooses the tool. Python does the math.
That separation matters. The AI is the decision-maker. The code is the operator.
Why I Built This
Tool invocation is the most fundamental capability that separates an AI agent from a chatbot, and it is the foundation every advanced build in this series stands on.
This matters because every serious AI use case eventually reaches the same question:
Can the AI actually connect to our data and use it?
Without tool invocation, an AI can explain what campaign performance means. With tool invocation, it can read campaign data, calculate real totals, and return an answer grounded in actual numbers.
The difference is the difference between a knowledgeable assistant and an assistant who can actually access your systems.
The Architecture – How It Works
Think about how an expert handles a request. They don’t immediately start talking. They pause, decide what information is needed, go get that information, and then form an answer using the real data they retrieved.
A Tool Invocation Agent works the same way.
The flow is simple:
- The user submits a request.
- The model chooses which tool fits the request.
- Python runs that tool.
- The tool result is returned to the user.
From the outside, the user sees a clean answer. On the inside, a small automated pipeline ran.
The Core Components
The Language Model
The model is the decision-maker. It reads the user request and the list of available tools, then chooses which tool should run.
In this build, the model is not using a complex framework or native function-calling schema. It returns a simple JSON object:
{"tool": "spend_by_campaign"}
If no tool fits, it returns:
{"tool": "none"}
That keeps the first lesson clean. The model’s job is to choose. Python’s job is to execute.
The Tools
Each tool is a normal Python function.
This build has two tools:
- spend_by_campaign
- clicks_by_month
Both tools read the same CSV file. One totals spend by campaign. The other totals clicks by month.
The model does not directly touch the CSV file. It only chooses which tool should run.
The Tool Registry
The tool registry is a simple Python dictionary:
TOOLS = {
"spend_by_campaign": spend_by_campaign,
"clicks_by_month": clicks_by_month,
}
This dictionary is the agent’s tool menu. If the model selects spend_by_campaign, Python looks up that function and runs it.
The Executor
The executor receives the model’s tool choice, checks that the tool exists, runs the function, and prints the result.
That is the heart of tool invocation:
The AI chooses the action.
The software performs the action.
The answer comes from real data.
The Model-Agnostic Layer
Every agent in this series routes through LiteLLM. LiteLLM is a proxy, a piece of software that sits between your code and any AI provider.
Your code talks to LiteLLM. LiteLLM talks to DeepSeek, Gemini, Claude, OpenAI, or whichever provider you configure.
The practical result is that swapping the AI model behind this agent requires changing the model name in the .env file. The code does not change.
That matters because no business system should be locked to one AI provider. Models improve. Prices change. Providers have outages. A model-agnostic architecture gives you room to move.
For this build, I tested the agent on DeepSeek V4 Pro and Gemini Flash. Both passed.
The Build: Step by Step
Everything below is what actually ran. Not theory.
Step 1: Load the environment
The agent starts by loading the values it needs from .env.
REQUIRED_VARS = [
"LITELLM_BASE_URL",
"MODEL_NAME",
"LITELLM_API_KEY",
"DATA_FILE_PATH",
]
These variables tell the agent where LiteLLM is running, which model to use, which API key to send, and where the CSV file lives.
This keeps sensitive values and machine-specific paths out of the code.
Step 2: Call the model through LiteLLM
The current build uses Python’s standard library to call LiteLLM’s OpenAI-compatible HTTP endpoint.
def call_llm(messages):
base_url = os.getenv("LITELLM_BASE_URL", "").rstrip("/")
payload = json.dumps({
"model": os.getenv("MODEL_NAME"),
"messages": messages,
"temperature": 0,
}).encode("utf-8")
What this code is actually doing:
The agent prepares a request for the model. It includes the model name, the conversation messages, and a low temperature so the model behaves consistently.
The important part is that the code does not talk directly to DeepSeek or Gemini. It talks to LiteLLM. LiteLLM handles the provider-specific details behind the scenes.
Step 3: Read the campaign data
The agent reads a CSV file using Python’s built-in CSV tools.
def read_campaign_rows():
data_path = os.getenv("DATA_FILE_PATH")
with open(data_path, newline="", encoding="utf-8") as file:
return list(csv.DictReader(file))
This is the data access layer.
In a real deployment, this function could read from a database, CRM, accounting system, warehouse system, or internal API. For the teaching version, a CSV keeps the pattern visible.
Step 4: Define the tools
The first tool totals spend by campaign.
def spend_by_campaign():
totals = {}
for row in read_campaign_rows():
campaign = row["campaign_name"]
totals[campaign] = totals.get(campaign, 0.0) + float(row["spend"])
return totals
The second tool totals clicks by month.
def clicks_by_month():
totals = {}
for row in read_campaign_rows():
month = row["date"][:7]
totals[month] = totals.get(month, 0) + int(row["clicks"])
return totals
These functions are the actual workers. They do not “think.” They just execute reliably.
That is exactly what I want from business automation. Let the model decide which tool is needed. Let deterministic code do the calculation.
Step 5: Register the tools
The tools are registered in one plain dictionary.
TOOLS = {
"spend_by_campaign": spend_by_campaign,
"clicks_by_month": clicks_by_month,
}
This is intentionally small. No framework. No complex schema. No abstraction layer pretending to be more important than the lesson.
Adding another tool later means adding another function and putting it in this dictionary.
Step 6: Ask the model which tool to use
The model receives the user request and the available tool names.
def choose_tool(user_request):
tool_list = "\n".join(f"- {name}" for name in TOOLS)
The prompt tells the model to return only JSON:
Return only JSON in this format: {"tool": "tool_name"}.
If none of the tools fit, return {"tool": "none"}.
This is the routing decision.
The model is not asked to calculate campaign spend. It is not asked to summarize the CSV. It is only asked to choose the correct tool.
That narrow responsibility is what makes the system easier to understand and easier to trust.
Step 7: Execute the selected tool
Once the model chooses a tool, Python runs it.
def run_agent(user_request):
print(f"[USER] {user_request}")
tool_name = choose_tool(user_request)
if tool_name == "none":
print("[AGENT] I do not have a tool for that request.")
return
print(f"[AGENT] Selected tool: {tool_name}")
result = TOOLS[tool_name]()
print(json.dumps(result, indent=2))
What this code is actually doing:
The user asks a question. The model chooses a tool. If the tool exists, Python runs it. If the model says no available tool fits, the agent says so clearly and stops.
No pretending. No hallucinated answer. No fake data.
Example Output
When I asked:
Show me spend by campaign
The agent selected:
spend_by_campaign
And returned:
{ "Brand_Awareness": 11000.0, "Product_Launch": 22600.0, "Retargeting": 9450.0, "Holiday_Special": 19100.0 }
When I asked:
Show me clicks by month
The agent selected:
clicks_by_month
And returned monthly click totals from the CSV.
When I asked something outside the available tools, the agent did not invent an answer. It said it did not have a tool for that request.
That behavior is important. A useful agent should know what it can do and what it cannot do.
What I Would Do Differently
This build is intentionally small. That is the point.
The first version of this post tried to show too much at once: tool schemas, calculator tools, lookup tools, multi-step orchestration, and richer examples. All of that is useful later, but it makes the first lesson harder to see.
The current version is cleaner.
It demonstrates the smallest working unit of tool invocation:
The model chooses one tool.
Python runs that tool.
The result comes from real data.
What this build does not do yet:
It does not chain multiple tools.
It does not generate charts.
It does not remember past conversations.
It does not accept every possible natural-language phrasing.
It does not connect to a production database.
Those are not failures. Those are future layers.
A production version could add database access, retries, audit logs, authentication, and more sophisticated routing. But the foundation should stay the same.
What This Means
Before this build, the answer to “can AI connect to our data?” was usually “sort of, with a lot of manual copy-paste.”
Yes, if the system is designed correctly, AI can choose a tool, run code against real data, and return a grounded result.
For an architecture firm, this is the difference between an AI that explains building codes and an AI that checks project specifications against municipal requirements.
For a logistics company, this is the difference between an AI that explains supply chain concepts and an AI that checks live inventory levels and flags which SKUs need action today.
For an accounting firm, this is the difference between an AI that understands depreciation schedules and an AI that pulls the client’s actual asset register and flags discrepancies.
For a marketing team, this is the difference between an AI that talks about campaign performance and an AI that reads campaign data and calculates what actually happened.
The tool invocation pattern is what makes AI useful in a business context rather than just impressive in a demo.
Everything in this series builds on this foundation.
Tools Used
- LiteLLM: MIT :github.com/BerriAI/litellm
- OpenAI Python SDK : Apache 2.0 : github.com/openai/openai-python
- python-dotenv -:BSD-3-Clause : github.com/theskumar/python-dotenv
- NosisTech Agent Engineering : github.com/nosistech/agent-engineering
(c) 2026 NosisTech LLC. Licensed under CC BY 4.0. Use freely : just credit us.