Tell the program what your tools return
By the end of this lesson, you can describe tool result fields and measure how those descriptions affect a run.
Describe tool result fields
Section titled “Describe tool result fields”Lesson 3 showed a program that read reading.temperature_celsius from a tool that
returns temp_c. Lesson 2 showed Mistral reading temp_limit_c from a tool that
returns max_temp_c, and reporting a confident zero.
Two different models, the same mistake, so it is not carelessness. They guessed because nothing in front of them named the field.
A direct tool call and a callTool are told different things. This lesson prints
both.
What the program is told about your tools
Section titled “What the program is told about your tools”UC AI writes a list of your tools into the description of uc_ai__run_code. That
list is the catalog, and it is everything a program is told. Print it:
l_tools := uc_ai_tools_api.get_tools_array( p_provider => uc_ai.c_provider_anthropic, p_tool_tags => apex_t_varchar2('coldchain'), p_enable_tools => true, p_programmatic_tools => true);The catalog part of that description reads:
Callable tools (await callTool code -> arguments): await callTool("CC_LIST_SHIPMENTS", { product_class }) // List every cold-chain shipment with its numeric id, its shipment number, its carrier, its lane and its product class. await callTool("CC_GET_READINGS", { shipment_id }) // Return the temperature readings of ONE shipment, in time order. await callTool("CC_GET_LIMITS", { }) // Return the temperature limit and the claim rule of each product class.Each catalog line contains the tool code, argument names, and description.
What is not there:
- no types, and no required flags
- no JSON schema, and no parameter descriptions from it
- nothing at all about the shape of the result
A direct tool call gets the JSON schema, which holds a description for each
parameter. A callTool gets a bare name. Neither of them documents what the
tool returns. For a program, the description is the only place that fact can
live.
Correct the descriptions
Section titled “Correct the descriptions”Every description now names the fields the tool returns:
l_tool_id := uc_ai_tools_api.merge_tool_from_schema( p_tool_code => 'CC_GET_READINGS', p_description => 'Return the temperature readings of ONE shipment, in time order. ' || 'Argument shipment_id is the numeric shipment_id from CC_LIST_SHIPMENTS, ' || 'not the shipment number. Returns { shipment_id, readings } where readings ' || 'is an array of { ts, temp_c }, about 49 rows. temp_c is degrees Celsius ' || 'and ts is the time of the reading.', p_function_call => 'return cc_analyst_pkg.get_readings(:ARGUMENTS);', p_json_schema => json_object_t('{ ... }'), p_tags => apex_t_varchar2('coldchain'), p_code_mode_access => 'code');Two rules produce a description a program can use:
- Name the fields of the result.
Returns { shipment_id, readings } where readings is an array of { ts, temp_c }. Nothing else can tell it. - Say what an argument is, not what it is called.
shipment_id is the numeric shipment_id from CC_LIST_SHIPMENTS, not the shipment numberis the difference between a program that works and one that passes"SHP-2047"to a number.
Hide the two big tools from the model
Section titled “Hide the two big tools from the model”The same call changed something else. p_code_mode_access is now 'code':
| Value | Meaning |
|---|---|
direct | A normal tool only. A program cannot call it. |
code | Not in the tool list of the model. Only a program can call it. |
both | Available either way. This is the default for a new tool. |
CC_LIST_SHIPMENTS and CC_GET_READINGS are now code. CC_GET_LIMITS stays
both, because it returns two small rows and a person can reasonably ask about
it.
Tools the model can call directly: CC_GET_LIMITS uc_ai__run_codeThe 48 KB of readings can no longer reach the context window at all, because no path exists that puts them there.
Ask the same question again
Section titled “Ask the same question again”run program calls in out trips secs finish_reason------------------------------------------------------------------------------------classic, 40 calls no 26 34413 3723 3 46.6 stopcode mode yes 5 20280 3380 5 48 stopcode, bulk hidden yes 1 3981 991 2 15.4 stopOne tool call. 3981 input tokens. 15.4 seconds.
Against the classic run in the first row: 26 times fewer tool calls, 8.6 times fewer input tokens, and three times faster. The answer is the same:
| Shipment | Class | Carrier | Breach Duration | Claim (EUR) |
|---|---|---|---|---|
| SHP-2047 | FROZEN | Meridian Cargo | 150 min | €720 |
| SHP-2052 | CHILLED | Nordfracht | 90 min | €240 |
| SHP-2061 | FROZEN | Baltic Reefer | 210 min | €960 |
RESULT: SHP-2047, SHP-2052, SHP-2061 | 1920
Your wording will differ. What must match is the data, and the checks below.
And the program now reads the right fields:
const readings = readingResults[i].readings;const { max_temp_c, sample_minutes, max_minutes_above, claim_per_hour_eur } = limit;
for (const reading of readings) { if (reading.temp_c > max_temp_c) { runLength++; } else {Not one word of the prompt changed between lesson 2 and this run. The descriptions changed.
The catalog has a size limit
Section titled “The catalog has a size limit”The whole catalog has to fit in the description of one tool, so UC AI gives it
about 31 KB. One description cannot fill that alone, because
uc_ai_tools.description holds at most 4000 characters. About eight descriptions of
that size do fill it.
The section The catalog has a size limit in 04_catalog.sql registers nine filler
tools with 3900 characters of padding each, and then counts what survived:
preamble + catalog: 29082 characterscode-callable tools: 12in the catalog: 10LEFT OUT: 2 (CC_FILLER_2, CC_FILLER_3)
Nothing failed, and no tool in that last line is callablefrom a program. The log holds the only warning.The NAMES on that line are not predictable. Another databasewith the same tools leaves out a different pair.Twelve tools are code-callable and ten reached the program. Two did not, and a program can no longer call them.
The first number counts the fixed preamble of about 900 characters as well as the catalog. So it stays under the budget even though UC AI dropped two tools. UC AI skips a tool that does not fit and carries on with the next one. The space left at the end is therefore smaller than one more description.
The run did not fail, the model saw no error, and the only record is one line in the log:
Code-mode catalog size limit (31855 bytes) reached: 2 tool(s) left out and NOTcallable from a program. Shorten the descriptions of code-mode tools to fit moreof them in.Three ways to stay inside the limit:
- Write short descriptions. Name the fields, and stop.
- Set a tool that a program never calls to
direct. It then costs the catalog nothing. - Use tags. A run that needs four tools must not carry forty.
Verification
Section titled “Verification”select code, code_mode_access, active from uc_ai_tools where code like 'CC\_%' escape '\' order by code;CODE CODE_MODE_ACCESS ACTIVECC_GET_LIMITS both 1CC_GET_READINGS code 1CC_LIST_SHIPMENTS code 1Then print the tool names the model can call directly. CC_GET_READINGS must not
be among them, and uc_ai__run_code must be.
Key takeaways
Section titled “Key takeaways”- The catalog holds tool codes, argument names and descriptions. It holds no types, no schema, and nothing about the result.
- For a
codetool, the description is the whole API reference. Name the fields it returns and say what each argument means. code_mode_accessofcoderemoves a bulk tool from the context window completely. That is where most of the saving is.merge_tool_from_schemakeeps the access value it is not given. State it explicitly in a deployment script.- The catalog is capped near 31 KB. UC AI drops the tools that do not fit and writes one warning to the log. Which tools it drops is not defined.
Full reference: Choosing which tools are available where.