Lesson 08 / 22
Tool Definition
Measuring what a tool's name, description, and schema add to the loop: on 24 fictional tasks the definition is given at four levels, and when only the name is given, 113 of 157 calls come back as tool errors, the valid call rate stays at 0.2803, and task success is 0.3333. Adding the description brings the errors down to 56 and raises task success to 0.6667. Adding the schema drops the call count to 74 and the errors to 8, and the valid rate becomes 0.8919, but task success stalls at 0.7917: the schema gives the field names, not which field carries which value, and two swapped indexes return 99948 instead of 52 in a call that passes the schema. The example call zeroes this share, raises task success to 0.9583, and changes the steps, the calls, and the tool errors not at all; its cost is the catalog growing from 97 to 164 tokens, and tokens per correct answer rising from 1126 to 1212.
Published Updated
In this lesson
- Builds a tool's name, description, schema, and example call as separate definition levels.
- Compares the valid call rate, the number of tool errors, and task success across the four definition levels.
- Tells a call that passes the schema but returns a wrong result apart from a tool error.
- Evaluates the token cost of the catalog with the tokens per correct answer measure.
Contents
The previous seven lessons built the agent loop: perception, reasoning, the sequence of action and observation, the separation of the planner and the executor, the step budget, the stopping criterion, the self-critique round, the multi-agent arrangement, and the approval point on a tool with a side effect. One thing was silently assumed throughout: the tool was ready and correct.
This lesson removes that assumption: what does a tool’s definition add to the loop? The Building Applications with Model APIs course measured the form of a single call: with no catalog given the valid call rate was 0.0500, and with names and parameters given together it was 0.6917. That measurement was about parsing a text and is not repeated; the numbers are taken as input. The call here crosses a function boundary, and what is measured is what the definition adds across the loop.
- TM1. The environment, the tool set, the task set, and the agent loop are fictional; no real model endpoint, tool server, or runtime is called. 60 fictional subscribers, 5 fictional documents, 5 tools, and 24 tasks each solved by a known tool sequence. Seed 20260218, second run 20260219.
- TM2. The tool definition is given at four levels: name only; name and description; name, description, and schema; schema and example call. Each level includes the one before it.
- TM3. The definition determines two things: the multiplier on the share of picking the right tool and the fill share of each declared field. The name is a weak hint; the description says what the tool does, and the schema says which fields it asks for.
- TM4. The schema gives the field names; it does not say which value a field carries. Two
fields of the same type (
onceki(previous) andsimdiki(current)) swap with a share of 0.30; the example call zeroes this share. - TM5. A tool error is an error the tool itself returns: a missing field or a nonexistent tool. Fields outside the schema are filtered at the call boundary; a valid call is one that returns no error.
- TM6. The catalog stays in the prompt at every step; its cost is the token count of the level.
- TM7. The baseline is the name only level; this is the loop without scaffolding. The step budget is eight at all four levels.
- TM8. Task success is the result the loop leaves being equal to the correct answer; it does not depend on whether the agent stopped on its own.
The Four Levels of Definition
The code below builds the environment, the tool set, the task set, and the agent loop, then adds the
definition layer on top of the loop. The call boundary still filters fields outside the schema, but
whether a declared field is filled now depends on the level of the definition. The tool names in the code are the read_meter, consumption, tariff, search_docs, and write_record tools of the course’s earlier lessons; they are counted in tokens here, so they stay Turkish.
SEED, M32 = 20260218, 0xFFFFFFFF def rng(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def next_value(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return next_value def pick(u, w): t = 0.0 for i, x in enumerate(w): t += x if u < t: return i return len(w) - 1 def tokenize(m): b = [] for s in str(m).lower().replace(",", " , ").split(): while len(s) > 5: b.append(s[:5]) s = s[5:] b.append(s) return b def trace_id(m): h = 2166136261 for c in str(m): h = ((h ^ ord(c)) * 16777619) & M32 return h # ---- FICTIONAL environment: records of the measurement network # wire words stay Turkish: they are counted in tokens (same rule as the MCP course) # onceki/simdiki/tip/bolge = previous/current/type/region; mesken/isyeri = residential/commercial REGION = ["dogu", "bati", "kuzey", "guney", "merkez"] TYPE = ["mesken", "isyeri"] SUBSCRIBERS = {} for i in range(60): r = rng(SEED + 31 * i) previous = 10000 + int(r() * 80000) SUBSCRIBERS[f"A{i + 1:03d}"] = {"bolge": REGION[int(r() * 5)], "tip": TYPE[int(r() * 2)], "onceki": previous, "simdiki": previous + int(r() * 70)} TIERS, ABOVE = ((10, 4), (25, 7), (40, 11)), 16 COMMERCIAL_FACTOR = 2 DOCUMENTS = {"TR01": "tarife basamaklari 10 25 40 metrekup ve ustu", "TR02": "isyeri abonesinde birim ucret iki katidir", "SY01": "endeks farki tuketimi verir sayac donusu 100000 eklenir", "AR01": "ariza kaydi acildiginda saha ekibi iki gun icinde gider", "KS01": "planli kesinti duyurusu bir hafta once yayimlanir"} CALLS = [0] # tool call counter def _charge(m3, type_): remaining, lower, total = m3, 0, 0 for upper, unit in TIERS: band = min(remaining, upper - lower) if band <= 0: break total += band * unit remaining -= band lower = upper total += remaining * ABOVE return total * (COMMERCIAL_FACTOR if type_ == "isyeri" else 1) def t_read_meter(abone=None, **_): a = SUBSCRIBERS.get(abone) if a is None: return {"hata": "abone yok"} return {"onceki": a["onceki"], "simdiki": a["simdiki"], "tip": a["tip"], "bolge": a["bolge"]} def t_consumption(onceki=None, simdiki=None, **_): if onceki is None or simdiki is None: return {"hata": "eksik alan"} f = simdiki - onceki return {"m3": f + 100000 if f < 0 else f} def t_tariff(m3=None, tip="mesken", **_): if m3 is None: return {"hata": "eksik alan"} return {"tutar": _charge(m3, tip)} def t_document(anahtar=None, **_): if not anahtar: return {"hata": "eksik alan"} q = set(tokenize(anahtar)) best, code = -1, None for k, m in DOCUMENTS.items(): o = len(q & set(tokenize(m))) if o > best: best, code = o, k return {"kod": code, "metin": DOCUMENTS[code]} if best > 0 else {"hata": "bulunamadi"} def t_record(abone=None, sutun=None, deger=None, **_): if not (abone and sutun and deger is not None): return {"hata": "eksik alan"} return {"yazildi": True, "abone": abone, "sutun": sutun, "deger": deger} TOOLS = { "sayac_oku": {"function": t_read_meter, "fields": ("abone",), "side_effect": False}, "tuketim": {"function": t_consumption, "fields": ("onceki", "simdiki"), "side_effect": False}, "tarife": {"function": t_tariff, "fields": ("m3", "tip"), "side_effect": False}, "belge_ara": {"function": t_document, "fields": ("anahtar",), "side_effect": False}, "kayit_yaz": {"function": t_record, "fields": ("abone", "sutun", "deger"), "side_effect": True}, } TOOL_NAMES = list(TOOLS) # ---- FICTIONAL tasks: each one is solved by a known tool sequence TASKS = [] for i in range(24): r = rng(SEED + 9973 * i) sub = f"A{1 + int(r() * 60):03d}" t = pick(r(), [0.30, 0.22, 0.20, 0.28]) if t == 0: TASKS.append({"name": f"G{i + 1:02d}", "kind": "charge", "subscriber": sub, "request": f"{sub} abonesinin bu donem bedeli nedir", "sequence": ("sayac_oku", "tuketim", "tarife")}) elif t == 1: TASKS.append({"name": f"G{i + 1:02d}", "kind": "consumption", "subscriber": sub, "request": f"{sub} abonesinin bu donem tuketimi kac metrekup", "sequence": ("sayac_oku", "tuketim")}) elif t == 2: TASKS.append({"name": f"G{i + 1:02d}", "kind": "document", "subscriber": sub, "request": "sayac donusunde endeks farki nasil hesaplanir", "sequence": ("belge_ara",)}) else: TASKS.append({"name": f"G{i + 1:02d}", "kind": "record", "subscriber": sub, "request": f"{sub} abonesinin bedelini hesapla ve kayda yaz", "sequence": ("sayac_oku", "tuketim", "tarife", "kayit_yaz")}) def correct_answer(g): a = SUBSCRIBERS[g["subscriber"]] f = a["simdiki"] - a["onceki"] m3 = f + 100000 if f < 0 else f if g["kind"] == "consumption": return m3 if g["kind"] == "document": return "SY01" return _charge(m3, a["tip"]) # ---- FICTIONAL agent: at each step it picks a tool or finishes def selection(d): return max(0.34, 0.80 - 0.09 * (d - 1)) # share of picking the right tool, falls with depth DESCRIPTION = { "sayac_oku": "abonenin onceki ve simdiki sayac endeksini dondurur", "tuketim": "iki endeks arasindaki farki metrekup olarak verir", "tarife": "metrekup ve abone tipinden donem bedelini hesaplar", "belge_ara": "anahtar metne en yakin kurgu belgeyi getirir", "kayit_yaz": "bir abonenin bir sutununa deger yazar ; yan etkilidir"} EXAMPLE = {"sayac_oku": "sayac_oku ( abone = A007 )", "tuketim": "tuketim ( onceki = 41200 , simdiki = 41255 )", "tarife": "tarife ( m3 = 55 , tip = mesken )", "belge_ara": "belge_ara ( anahtar = endeks farki )", "kayit_yaz": "kayit_yaz ( abone = A007 , sutun = bedel , deger = 470 )"} # level : name , selection factor , field fill share , swap share DEFINITION = {1: ("name only", 0.62, 0.45, 0.30), 2: ("name + description", 0.86, 0.72, 0.30), 3: ("name + description + schema", 1.00, 1.00, 0.30), 4: ("schema + example call", 1.00, 1.00, 0.00)} def catalog(level, name): s = name if level >= 2: s += " : " + DESCRIPTION[name] if level >= 3: s += " ( " + " , ".join(TOOLS[name]["fields"]) + " )" if level >= 4: s += " ornek : " + EXAMPLE[name] return s CATALOG_TOKENS = {d: sum(len(tokenize(catalog(d, a))) for a in TOOLS) for d in DEFINITION} def call_defined(name, state, level, rd): """The defined variant of the shared call boundary: whether a declared field is filled depends on the level of the definition.""" CALLS[0] += 1 if name not in TOOLS: return {"hata": "arac yok"} _, _, share, swap = DEFINITION[level] kw = {a: state[a] for a in TOOLS[name]["fields"] if a in state and rd() < share} if {"onceki", "simdiki"} <= set(kw) and rd() < swap: kw["onceki"], kw["simdiki"] = kw["simdiki"], kw["onceki"] return TOOLS[name]["function"](**kw) def agent_defined(g, level=4, max_steps=8, seed=SEED): """The shared agent loop; the only differences are that the call passes through the definition layer and the catalog stays in the prompt at every step.""" r = rng(seed ^ trace_id(g["name"])) rd = rng(seed ^ trace_id(g["name"] + "tanim")) factor = DEFINITION[level][1] state = {"abone": g["subscriber"], "anahtar": g["request"], "sutun": "bedel"} trace, step, tok, errors, foreign = [], 0, 0, 0, 0 seq, k = g["sequence"], 0 while step < max_steps: step += 1 tok += 60 + 22 * len(trace) + CATALOG_TOKENS[level] target = seq[k] if k < len(seq) else None if target is None: if r() < 0.88: break choice = TOOL_NAMES[int(r() * len(TOOL_NAMES))] else: choice = target if r() < selection(k + 1) * factor else TOOL_NAMES[int(r() * len(TOOL_NAMES))] if choice == "kayit_yaz": state["deger"] = state.get("sonuc") s = call_defined(choice, state, level, rd) trace.append((choice, "hata" in s)) tok += 30 if "hata" in s: errors += 1 continue foreign += choice not in seq state.update({kk: vv for kk, vv in s.items() if kk != "hata"}) if choice == "tuketim": state["sonuc"] = s["m3"] if choice == "tarife": state["sonuc"] = s["tutar"] if choice == "belge_ara": state["sonuc"] = s["kod"] if choice == target: k += 1 return {"steps": step, "tokens": tok, "errors": errors, "foreign": foreign, "answer": state.get("sonuc")} for d in DEFINITION: print(f"level {d} {DEFINITION[d][0]:<27} catalog {CATALOG_TOKENS[d]:>3} tokens") print(" level 3 line :", catalog(3, "tuketim")) print(" level 4 suffix : ornek :", EXAMPLE["tuketim"]) g = TASKS[2] for d in (3, 4): s = agent_defined(g, d) print(f" {g['name']} {g['kind']:<11} level {d} answer {s['answer']:>6} correct" f" {correct_answer(g)} tool errors {s['errors']}")
level 1 name only catalog 10 tokens level 2 name + description catalog 71 tokens level 3 name + description + schema catalog 97 tokens level 4 schema + example call catalog 164 tokens level 3 line : tuketim : iki endeks arasindaki farki metrekup olarak verir ( onceki , simdiki ) level 4 suffix : ornek : tuketim ( onceki = 41200 , simdiki = 41255 ) G03 consumption level 3 answer 99948 correct 52 tool errors 1 G03 consumption level 4 answer 52 correct 52 tool errors 1
The last two lines are the core of this lesson. The same task, the same seed, the same tool sequence,
and the same number of tool errors. At the third level the agent called the tuketim (consumption) tool with
the two fields the schema declares, the call passed the schema, the tool returned no error, and the
result came out 99948 instead of 52. The schema declares the names onceki and simdiki; it does
not declare which one is smaller. When the fields swap, the difference goes negative, the meter
rollover rule kicks in, and the tool returns a consumption close to a hundred thousand. This call is
valid and wrong.
The Numbers of the Four Levels
The same 24 fictional tasks run at four definition levels; task success is printed separately for the two seeds.
def measure(level, seed=SEED): CALLS[0] = 0 ok = steps = tok = errors = foreign = 0 for g in TASKS: s = agent_defined(g, level, seed=seed) ok += s["answer"] == correct_answer(g) steps += s["steps"] tok += s["tokens"] errors += s["errors"] foreign += s["foreign"] return ok / len(TASKS), CALLS[0], errors, foreign, steps, tok print("definition level calls errors valid task success A task success B" " steps tokens tokens per answer") for d in DEFINITION: a, b = measure(d), measure(d, SEED + 1) print(f"{DEFINITION[d][0]:<27} {a[1]:>5} {a[2]:>6} {(a[1] - a[2]) / a[1]:>6.4f}" f" {a[0]:>14.4f} {b[0]:>14.4f} {a[4]:>5} {a[5]:>6} {a[5] / (a[0] * 24):>17.0f}") a = measure(4) print(f"highest level : {a[3]} of {a[1] - a[2]} valid calls call a tool" f" outside the task's sequence")
definition level calls errors valid task success A task success B steps tokens tokens per answer name only 157 113 0.2803 0.3333 0.4167 167 27950 3494 name + description 111 56 0.4955 0.6667 0.5833 129 27489 1718 name + description + schema 74 8 0.8919 0.7917 0.7917 97 21387 1126 schema + example call 74 8 0.8919 0.9583 0.9583 97 27886 1212 highest level : 8 of 66 valid calls call a tool outside the task's sequence
The baseline is the first row: when only the name is known, 113 of 157 calls come back as tool errors, the valid call rate is 0.2803, and task success is 0.3333. Most of the errors are missing fields. The name does not say what the tool wants; the agent cannot know which key to pass from the state it holds. The cost shows in the steps column: although the budget is eight, the total step count is 167, more than seven per task. A failed call spends a step and does not advance the sequence.
When the description is added, the errors fall to 56, the calls to 111, and task success becomes 0.6667. The description does two jobs: it raises the share of picking the right tool and it hints at what information the fields ask for. When the schema is added, the calls fall to 74, the errors to 8, the valid rate becomes 0.8919, and the steps fall to 97. The remaining eight errors are unrelated to the schema: a wrongly chosen tool fails its own precondition.
What the Schema Does Not Say
The third and fourth rows carry the same calls, the same errors, and the same steps; their task success separates as 0.7917 and 0.9583. The 0.1666 between them is what the example call adds, and it arrives without adding a single step to the loop. The run-to-run difference is 0.0834 at the first two levels and zero at the last two; 0.1666 is above twice this band and counts as measured. The example call supplies the one piece of information the schema cannot: which value the field carries.
The cost is in the tokens column. The catalog grows from 97 tokens to 164 tokens, and because it stays in the prompt at every step, the total tokens rise from 21387 to 27886. Tokens per correct answer rise from 1126 to 1212: while the example call raises task success, at this scale it raises the unit cost as well. A scaffolding whose cost is not written down counts as unmeasured; the cost here is sixty-seven tokens.
The last line is the outermost limit of the schema. Even at the highest level, eight of the 66 valid calls call a tool that is not in the task’s sequence. In the Building Applications with Model APIs course the same residue was fourteen wrong tool names; there the call was a text, here it crossed a function boundary and the tool ran correctly. A definition says what can be called; it does not say what should be called.
Summary
- The tool definition splits into four levels: name only, name and description, schema, and schema and example call.
- When only the name is given, 113 of 157 calls come back as tool errors, the valid rate is 0.2803, and task success is 0.3333; a failed call spends a step and raises the total steps to 167.
- The description brings the errors down to 56 and the schema to 8; with the schema the calls fall to 74, the steps to 97, and the valid rate becomes 0.8919.
- The schema gives the field names and not which value a field carries: two swapped indexes return 99948 instead of 52 in a call that passes the schema.
- The example call raises task success from 0.7917 to 0.9583 and changes the steps, the calls, and the errors not at all; its cost is the catalog growing to 164 tokens and the tokens per correct answer rising from 1126 to 1212.
- Even at the highest level, eight of the 66 valid calls call a tool outside the task’s sequence; being valid is not being correct.
Next Step
In this lesson the tool was called and the dictionary it returned was written to the state as it was; every field was carried to the next step and stayed in the prompt again at every step. This is one reason the tokens column rises above a thousand tokens per task. Carrying the result as it is is not the only option: it can be truncated, it can be summarized, or only the needed field can be kept. Each lowers the tokens, and each risks dropping something. The next lesson measures in which form the result is given back to the model and prints task success side by side with the tokens that accumulate across the loop.
To keep your progress and take notes, Log in
My notes
Log in to take notes.