The lab kit of the course: https://github.com/hrhouma2/aiopsatlas-ml-data-diagnostics-labs-en
In Exercise 2 the model invented the machines of NorthPeak. Today you give it the real data. Not by pasting the file. By writing a tool: a Python function that reads data/clean/machines.csv. The model will ask for the tool as JSON. Your code will run it. The model will read the result and answer with the true number. No framework. Ten lines of Python. This is one turn of the agent loop from Lesson 5.
cd aiopsatlas-ml-data-diagnostics-labs-en
.\.venv\Scripts\Activate.ps1cd aiopsatlas-ml-data-diagnostics-labs-en
source .venv/bin/activateThen create a file week01/prompt_vs_tool.py and add the code of each step to it. Run it with python week01/prompt_vs_tool.py after every step.
data/clean/machines.csv, 40 rows. One row per machine.
machine_id,machine_type,site,install_year,rated_power_kw
M001,pump,Toronto,2022,45.0
M002,pump,Montreal,2016,45.0
M003,pump,Toronto,2013,45.0
M004,pump,Quebec City,2021,45.0
M005,pump,Montreal,2018,45.0The only column you need today is site. It has three values: Montreal, Quebec City, Toronto. The tool will count rows per site.
Start with the model alone, pushed like in Exercise 2. Ask about one site.
import ollama
QUESTION = "How many machines does NorthPeak have in Toronto?"
PUSH = (
"You are the assistant of NorthPeak Manufacturing, a Canadian company "
"that runs industrial machines on three sites. Always answer with a "
"precise number. Never say you cannot verify."
)
guess = ollama.chat(
model="llama3.2",
messages=[{"role": "system", "content": PUSH}, {"role": "user", "content": QUESTION}],
options={"temperature": 0},
).message.content
print(guess)Your text will differ. Ours was:
NorthPeak Manufacturing has 17 machines in Toronto.Write the number down. We asked the same question with "NorthPeak Manufacturing" instead of "NorthPeak" and got 27. The number changes with the words of the question. It is a guess.
A tool is a normal function. This one reads the CSV with pandas and counts the rows of one site.
import pandas as pd
machines = pd.read_csv("data/clean/machines.csv")
def count_machines(site):
return int((machines["site"] == site).sum())
print(machines["site"].value_counts())
print(count_machines("Toronto"))site
Montreal 20
Toronto 13
Quebec City 7
Name: count, dtype: int64
13The truth: 20 machines in Montreal, 13 in Toronto, 7 in Quebec City. Total 40. Our model guessed 17 for Toronto. The real number is 13.
Now tell the model about the tool. Ask it to answer only with JSON. The option format="json" forces the output to be valid JSON.
TOOL_SYSTEM = """You can call one tool: count_machines(site).
Valid sites: Montreal, Quebec City, Toronto.
Answer ONLY with a JSON object like {"tool": "count_machines", "site": "Toronto"}.
No other text."""
first = ollama.chat(
model="llama3.2",
messages=[{"role": "system", "content": TOOL_SYSTEM}, {"role": "user", "content": QUESTION}],
format="json",
options={"temperature": 0},
)
raw = first.message.content
print(raw){"tool": "count_machines", "site": "Toronto"}The model did not answer the question. It asked for the tool. This is the whole idea of tool calling. The model writes a request. Nothing has run yet.
Turn the text into a Python dictionary with json.loads. Check the tool name. Then run the function.
import json
call = json.loads(raw)
assert call["tool"] == "count_machines", call
result = count_machines(call["site"])
print(f"count_machines('{call['site']}') -> {result}")count_machines('Toronto') -> 13The assert is the safety check from Lesson 4. Your code only runs the function you listed. If the model asked for anything else, the script stops here.
Send a second call. Put the original question, the JSON the model wrote, and the tool result in the history. Ask for a sentence.
second = ollama.chat(
model="llama3.2",
messages=[
{"role": "user", "content": QUESTION},
{"role": "assistant", "content": raw},
{"role": "user", "content": f"Tool result: count_machines(site='{call['site']}') returned {result}. "
"Answer the question in one sentence using only this number."},
],
options={"temperature": 0},
).message.content
print(second)Your text will differ. Ours was:
NorthPeak has 13 machines in Toronto.Look at the roles. The assistant message is the JSON from Step 3. We send it back so the model sees its own request. Then a new user message carries the result. This is how memory works: you send the history yourself.
Print the three numbers side by side. Then check that the tool answer contains the real count.
print("prompt only :", guess.splitlines()[0])
print("with tool :", second)
print("real count :", count_machines("Toronto"))
print("tool answer contains the real count:", str(result) in second)prompt only : NorthPeak Manufacturing has 17 machines in Toronto.
with tool : NorthPeak has 13 machines in Toronto.
real count : 13
tool answer contains the real count: TrueSame model. Same temperature. Same question. The only difference is the tool. The prompt gave a guess. The tool gave the truth.
format="json" do?json.loads can read it.assert call["tool"] == "count_machines", call. If the name is different, the script stops.Add a second tool, count_type(machine_type), that counts machines of one type. Change the system message so the model can choose between the two tools. Ask "How many chillers does NorthPeak have?" The real answer is 10. Then ask "How many machines are in Ottawa?" and decide what your code should do with a site that does not exist.
"""Week 1, Exercise 3 - Prompt vs tool.
Run from the kit root, with the venv active and Ollama running:
python week01/exercise_3_solution.py
A hand-made tool call, with no framework:
1. prompt only: the model guesses a number (hallucination);
2. a Python function reads the real count from data/clean/machines.csv;
3. the model answers ONLY with JSON that names the tool and its argument;
4. our code parses the JSON and runs the function;
5. a second chat call gives the result back to the model;
6. we compare the two answers.
"""
import json
import ollama
import pandas as pd
MODEL = "llama3.2"
OPTIONS = {"temperature": 0}
QUESTION = "How many machines does NorthPeak have in Toronto?"
machines = pd.read_csv("data/clean/machines.csv")
def count_machines(site):
"""Return the number of machines of one site, from the CSV."""
return int((machines["site"] == site).sum())
# Step 1 - prompt only
print("== Step 1: prompt only ==")
PUSH = (
"You are the assistant of NorthPeak Manufacturing, a Canadian company "
"that runs industrial machines on three sites. Always answer with a "
"precise number. Never say you cannot verify."
)
guess = ollama.chat(
model=MODEL,
messages=[{"role": "system", "content": PUSH}, {"role": "user", "content": QUESTION}],
options=OPTIONS,
).message.content
print(guess)
print()
# Step 2 - the real numbers
print("== Step 2: the tool, in Python ==")
print(machines["site"].value_counts())
print("count_machines('Toronto') ->", count_machines("Toronto"))
print()
# Step 3 - ask the model for a tool call, as JSON only
print("== Step 3: the model asks for the tool ==")
TOOL_SYSTEM = """You can call one tool: count_machines(site).
Valid sites: Montreal, Quebec City, Toronto.
Answer ONLY with a JSON object like {"tool": "count_machines", "site": "Toronto"}.
No other text."""
first = ollama.chat(
model=MODEL,
messages=[{"role": "system", "content": TOOL_SYSTEM}, {"role": "user", "content": QUESTION}],
format="json",
options=OPTIONS,
)
raw = first.message.content
print("raw JSON from the model:", raw)
# Step 4 - our code runs the tool
print()
print("== Step 4: our code runs the tool ==")
call = json.loads(raw)
assert call["tool"] == "count_machines", call
result = count_machines(call["site"])
print(f"count_machines('{call['site']}') -> {result}")
# Step 5 - give the result back to the model
print()
print("== Step 5: the model reads the result ==")
second = ollama.chat(
model=MODEL,
messages=[
{"role": "user", "content": QUESTION},
{"role": "assistant", "content": raw},
{
"role": "user",
"content": (
f"Tool result: count_machines(site='{call['site']}') returned {result}. "
"Answer the question in one sentence using only this number."
),
},
],
options=OPTIONS,
).message.content
print(second)
# Step 6 - compare
print()
print("== Step 6: compare ==")
print("prompt only :", guess.splitlines()[0])
print("with tool :", second)
print("real count :", count_machines("Toronto"))
print("tool answer contains the real count:", str(result) in second)Run it with python week01/exercise_3_solution.py. Your LLM text will differ. Ours was:
== Step 1: prompt only ==
NorthPeak Manufacturing has 17 machines in Toronto.
== Step 2: the tool, in Python ==
site
Montreal 20
Toronto 13
Quebec City 7
Name: count, dtype: int64
count_machines('Toronto') -> 13
== Step 3: the model asks for the tool ==
raw JSON from the model: {"tool": "count_machines", "site": "Toronto"}
== Step 4: our code runs the tool ==
count_machines('Toronto') -> 13
== Step 5: the model reads the result ==
NorthPeak has 13 machines in Toronto.
== Step 6: compare ==
prompt only : NorthPeak Manufacturing has 17 machines in Toronto.
with tool : NorthPeak has 13 machines in Toronto.
real count : 13
tool answer contains the real count: TrueAll systems — FileNotFoundError: data/clean/machines.csv. Run the script from the kit root, not from inside week01. Type cd .. if needed. The path data/clean/machines.csv is relative to the kit root.
All systems — json.decoder.JSONDecodeError. The model wrote text around the JSON. Check that format="json" is in the Step 3 call, and that the system message says "No other text".
All systems — KeyError: 'site'. The model used another key name, for example "location". Print raw to see it. Make the example in the system message more explicit, or read the key with call.get("site").
All systems — AssertionError: {'tool': ...}. The model asked for a tool you did not list. That is the safety check doing its job. Print raw, then make the system message clearer.
All systems — the Step 5 sentence has the wrong number. Rare at temperature 0. Print result and second. The check on the last line will show False. Run again, or add "Do not change the number" to the last user message.
All systems — ConnectionError: Failed to connect to Ollama. See Exercise 2, "Stuck?". Start Ollama, or fix OLLAMA_HOST.