Tools / Functions
Tools give the AI access to your data
Register a tool to give the AI access to your database or to an external API. During the conversation, the AI decides if it needs a tool and requests a call. UC AI then calls the tool and returns the result to the AI. The AI uses this result in its answer.
You can divide tools into two categories:
- Additional Context: too much information in a conversation makes the answers slower, more expensive, and less accurate. A tool gives this context only when the user asks for it.
- Actions: a tool lets the AI create, update, or delete data in your database. A tool can also start an action in another system with a REST call.
Tools call a PL/SQL function
Section titled âTools call a PL/SQL functionâIn UC AI, a tool is a PL/SQL function that the AI can call. The function must have these attributes:
- The AI passes one parameter of type
CLOB. This parameter holds JSON, so it can carry more than one argument. It can also hold an empty object. - The function returns a
CLOB.
Like Structured Output, a tool needs a description of its parameters in a JSON Schema. A tool GET_WEATHER has the two parameters city and unit, and unit defaults to celsius. Its schema looks like this:
{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "get weather parameters", "properties": { "city": { "type": "string", "description": "Name of the city to get weather information for" }, "unit": { "type": "string", "description": "Temperature unit. Defaults to celsius", "enum": ["celsius", "fahrenheit", "kelvin"] } }, "required": ["city"]}A precise schema definition makes the tool call more reliable.
Best practices
Section titled âBest practicesâ- Always return feedback: every piece of information helps the AI. If an error occurs, return a text that describes the error. The AI can then call the tool again with corrected input. After a successful action, return a success message also. The AI then knows the result.
- Validate inputs: make sure that all required parameters are present. Make sure also that each value obeys its constraints for enum, minimum, and maximum. If a value breaks a constraint, return an error text. The AI can then call the tool again with corrected parameters.
- Describe the tool completely: more context about a tool helps the AI decide when to call it. Write careful descriptions and constraints in your JSON schema. For some providers, examples in the system prompt help also. Give an example call and example parameters for each tool.
- The providers differ greatly: the intelligence of the model controls how well it picks and calls tools. Reasoning helps the AI plan its steps and call tools more precisely. Some providers are still weaker at tool calls than others. We recommend Anthropic for tool calls today.
- Restrict the available tools with tags: many registered tools add much context. This context makes the decision harder for the AI. Filter the tools by tag to give the AI fewer options.
Registering a tool
Section titled âRegistering a toolâUse uc_ai_tools_api.create_tool_from_schema to register a tool:
declare l_schema json_object_t; l_tool_id uc_ai_tools.id%type;begin l_schema := json_object_t.parse('{ "$schema": "http://json-schema.org/draft-07/schema#", ...}');
-- Create the tool l_tool_id := uc_ai_tools_api.create_tool_from_schema( p_tool_code => 'GET_WEATHER', p_description => 'Get the current weather for a given city', p_function_call => 'return my_pkg.get_weather(:parameters);', p_json_schema => l_schema, p_tags => apex_t_varchar2('weather','weather-api') ); commit;end;Parameters:
p_tool_code: a unique identifierp_description: a description of the task of your tool. A good description improves the results.p_function_call: the PL/SQL function that UC AI calls. This function must return aCLOB. If the tool has parameters, use:parametersto pass them into aCLOBparameter.p_json_schema: the JSON schema of the parametersp_tags: the tags of the tool. Tags enable or disable a tool for one call.
create_tool_from_schema is create-only. If a tool with the same p_tool_code exists, the function raises an error.
Upserting a tool with merge_tool_from_schema
Section titled âUpserting a tool with merge_tool_from_schemaâuc_ai_tools_api.merge_tool_from_schema creates the tool, or updates its description, parameters, and tags. It takes the same parameters as create_tool_from_schema. Use it in idempotent deployment scripts:
l_tool_id := uc_ai_tools_api.merge_tool_from_schema( p_tool_code => 'GET_WEATHER', p_description => 'Get the current weather for a given city', p_function_call => 'return my_pkg.get_weather(:parameters);', p_json_schema => l_schema, p_tags => apex_t_varchar2('weather','weather-api'));An update replaces the parameters and the tags of the tool completely. The function deletes the rows of that tool in uc_ai_tool_parameters and uc_ai_tool_tags. It then writes new rows from the new schema and the new tags.
The run context
Section titled âThe run contextâA tool gets its arguments from the model. For some values that is not safe. In a âtalk to this documentâ agent, the tool must read document 7 because the run is bound to document 7. It must not read document 42 because the model asked for it.
The run context carries such values. The caller supplies them when the run
starts. UC AI adds them to the arguments of every tool in that run, under the
reserved key _ctx. No model sees this key, and no model can set it.
l_result := uc_ai_agents_api.execute_agent( p_agent_code => 'DOC_AGENT', p_run_context => json_object_t('{"document_id": "7", "tenant_id": "ACME"}'));The handler of the tool then receives:
{ "query": "invoice terms", "_ctx": { "document_id": "7", "tenant_id": "ACME" }}Leave the scoped value out of the schema
Section titled âLeave the scoped value out of the schemaâDo not declare the scoped value as a tool parameter. The model then cannot name a document, and the value has one source:
l_schema := json_object_t.parse('{ "type": "object", "properties": { "query": { "type": "string", "description": "What to look for in the document" } }, "required": ["query"]}');
l_tool_id := uc_ai_tools_api.create_tool_from_schema( p_tool_code => 'DOC_SEARCH', p_description => 'Search the current document', p_function_call => 'return doc_pkg.search(:parameters);', p_json_schema => l_schema, p_tags => apex_t_varchar2('doc'));The handler reads the document from _ctx:
function search(p_params in clob) return clobas l_args json_object_t := json_object_t(p_params); l_doc number;begin l_doc := to_number(l_args.get_object('_ctx').get_string('document_id')); -- l_doc is 7, whatever the model sent return doc_pkg.search_chunks(l_doc, l_args.get_string('query'));end search;What you must know
Section titled âWhat you must knowâ_ctx is always present. It holds an empty object when the run carries no
context. A tool that needs a value must decide what to do when the value is
missing. A clear text result is better than an error, because the model can read
it:
if l_doc is null then return 'Error: this run is not bound to a document.';end if;_ctx is a reserved name. A tool that declares a parameter with this name is
rejected at registration.
Read the arguments by name. A handler that counts the keys, or that walks
all of them, sees _ctx as one more key. The run context adds this key to the
arguments of every tool, and such a handler still compiles. The built-in
MEMORY tool had this defect: it treated âone keyâ as a wrapper object of a
provider, and it stopped finding the wrapper. Read l_args.get_string('city'),
not the shape of the object.
The run context reaches a tool through the arguments. It does not reach a PL/SQL workflow step, because a step is not a tool.
Where else the run context applies
Section titled âWhere else the run context appliesâThe same values also fill prompt placeholders and can scope the memory of an agent. Read Prompt profiles and Agent memory.
Outside an agent, pass the context to generate_text:
l_result := uc_ai.generate_text( p_user_prompt => 'Summarize this document', p_provider => uc_ai.c_provider_openai, p_model => uc_ai_openai.c_model_gpt_5_6_sol, p_run_context => json_object_t('{"document_id": "7"}'));Using tools
Section titled âUsing toolsâYou must enable tools before the AI can use them. By default, the AI gets every enabled tool. Set uc_ai.g_tool_tags to give the AI a subset of the tools.
declare l_result json_object_t;begin uc_ai.g_enable_tools := true; -- optionally restrict tools to certain tags uc_ai.g_tool_tags := apex_t_varchar2('weather');
l_result := uc_ai.generate_text( p_user_prompt => 'How is the weather in Paris?', p_provider => UC_AI.c_provider_openai, p_model => UC_AI_OPENAI.c_model_gpt_5_6_sol, p_max_tool_calls => 6 );end;p_max_tool_calls limits how many tools the AI can call in one interaction.
Examples
Section titled âExamplesâLook up data
Section titled âLook up dataâData structure
Section titled âData structureâThis table holds data about employees:
SQL> select first_name, last_name, email from tt_users;
FIRST_NAME LAST_NAME EMAIL_____________ ____________ ___________________________________Michael Scott michael.scott@dundermifflin.comPam Beesly pam.beesly@dundermifflin.comDwight Schrute dwight.schrute@dundermifflin.comJim Halpert jim.halpert@dundermifflin.comAngela Martin angela.martin@dundermifflin.comKevin Malone kevin.malone@dundermifflin.com
6 rows selected.Function to return data
Section titled âFunction to return dataâNow create a function that returns this data as one CLOB in JSON format:
FUNCTION get_all_users_jsonRETURN CLOB IS l_json CLOB;BEGIN SELECT JSON_ARRAYAGG( JSON_OBJECT( 'user_id' VALUE user_id, 'first_name' VALUE first_name, 'last_name' VALUE last_name, 'email' VALUE email, 'hire_date' VALUE TO_CHAR(hire_date, 'YYYY-MM-DD'), 'is_active' VALUE is_active ) ORDER BY last_name, first_name RETURNING CLOB ) INTO l_json FROM tt_users ;
RETURN COALESCE(l_json, '[]');END get_all_users_json;Tool registration
Section titled âTool registrationâNow register a tool. It tells the AI how to call this function:
declare l_tool_id uc_ai_tools.id%type;begin -- Create the tool l_tool_id := uc_ai_tools_api.create_tool_from_schema( p_tool_code => 'TT_GET_USERS', p_description => 'Get information on all the users in the system', p_function_call => 'return tt_timetracking_api.get_all_users_json();', p_json_schema => null, p_tags => apex_t_varchar2('users','testing-tools') ); commit;end;/Testing
Section titled âTestingâdeclare l_result JSON_OBJECT_T; l_final_message CLOB;begin uc_ai.g_enable_tools := true; -- enable tools usage
l_result := uc_ai.generate_text( p_user_prompt => 'What is the email address of Jim?', p_system_prompt => 'You are an assistant to a time tracking system. Your tools give you access to user, project and timetracking information. Answer concise and short.', p_provider => uc_ai.c_provider_google, p_model => uc_ai_google.c_model_gemini_3_7_flash );
l_final_message := l_result.get_clob('final_message'); sys.dbms_output.put_line('AI Response: ' || l_final_message); -- Jim's email address is jim.halpert@dundermifflin.com.end;/This is the message array from the return object:
{ // ... "messages": [ { "role": "system", "content": "You are an assistant to a time tracking system. Your tools give you access to user, project and timetracking information. Answer concise and short." }, { "role": "user", "content": [ { "type": "text", "text": "What is the email address of Jim?" } ] }, { "role": "assistant", "content": [ { "type": "tool_call", "toolCallId": "call_Xk2MJTRWFbIZWSGk2jAnQR9n", "toolName": "TT_GET_USERS", "args": "{}" } ] }, { "role": "tool", "content": [ { "type": "tool_result", "toolCallId": "call_Xk2MJTRWFbIZWSGk2jAnQR9n", "toolName": "TT_GET_USERS", "result": "[{\"user_id\":2,\"first_name\":\"Pam\",\"last_name\":\"Beesly\",\"email\":\"pam.beesly@dundermifflin.com\",\"hire_date\":\"2024-11-01\",\"is_active\":\"Y\"},{\"user_id\":4,\"first_name\":\"Jim\",\"last_name\":\"Halpert\",\"email\":\"jim.halpert@dundermifflin.com\",\"hire_date\":\"2024-02-01\",\"is_active\":\"Y\"},{\"user_id\":6,\"first_name\":\"Kevin\",\"last_name\":\"Malone\",\"email\":\"kevin.malone@dundermifflin.com\",\"hire_date\":\"2024-10-01\",\"is_active\":\"N\"},{\"user_id\":5,\"first_name\":\"Angela\",\"last_name\":\"Martin\",\"email\":\"angela.martin@dundermifflin.com\",\"hire_date\":\"2024-05-01\",\"is_active\":\"Y\"},{\"user_id\":3,\"first_name\":\"Dwight\",\"last_name\":\"Schrute\",\"email\":\"dwight.schrute@dundermifflin.com\",\"hire_date\":\"2024-02-01\",\"is_active\":\"Y\"},{\"user_id\":1,\"first_name\":\"Michael\",\"last_name\":\"Scott\",\"email\":\"michael.scott@dundermifflin.com\",\"hire_date\":\"2024-05-01\",\"is_active\":\"Y\"}]" } ] }, { "role": "assistant", "content": [ { "type": "text", "text": "Jim's email address is jim.halpert@dundermifflin.com.", "providerOptions": { "refusal": null, "annotations": [] } } ] } ]}Write data and parameters
Section titled âWrite data and parametersâThe system in this example is a time tracking system. Each employee in the users table can clock in on any project in the projects table, with a note.
This needs three tools:
TT_GET_USERS: to get the usersTT_GET_PROJECTS: to get the projects (the same pattern as the users example)TT_CLOCK_IN: to clock in on a project
Function definition
Section titled âFunction definitionâThe clock-in tool needs parameters. Examine the TT_CLOCK_IN function:
FUNCTION clock_in_json( p_parameters in clob) return clobas l_json json_object_t; l_user_email VARCHAR2(255 char); l_project_name VARCHAR2(255 char); l_notes VARCHAR2(4000 char);begin logger.log_info('clock_in_json called with parameters: ' || p_parameters);
l_json := json_object_t(p_parameters); l_user_email := l_json.get_string('user_email'); l_project_name := l_json.get_string('project_name'); l_notes := l_json.get_string('notes');
if l_user_email is null then return 'Error: user_email is required'; elsif l_project_name is null then return 'Error: project_name is required'; end if;
begin return clock_in( p_email => l_user_email, p_project_name => l_project_name, p_notes => l_notes ); exception when others then return 'Error: ' || sqlerrm || ' - Backtrace: ' || sys.dbms_utility.format_error_backtrace; end;end clock_in_json;The function has one parameter of type CLOB. The AI communicates in JSON. The function must therefore extract each attribute from this CLOB.
The function returns a CLOB again, because an LLM expects text. After a successful clock-in, it returns a text such as âUser (X) clocked in on project (Y) with note (Z)â.
The function raises no error. It returns the error message as text instead. The LLM reads this message and can recover. For a project name with a typo, the function returns âError: Project âProjecXâ not foundâ. The AI then asks the user for the correct name, or calls the project lookup tool again.
Create tool definition
Section titled âCreate tool definitionâNow create the tool definition for the clock-in function. A detailed description with example parameters helps the AI. The AI then knows what it can do with the tool.
declare l_tool_id uc_ai_tools.id%type; l_schema json_object_t;begin l_schema := json_object_t('{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "user_email": { "type": "string", "description": "The email address of the user to clock in" }, "project_name": { "type": "string", "description": "The name of the project to clock in to" }, "notes": { "type": "string", "description": "Optional description of what the user is working on" } }, "required": [ "user_email", "project_name" ]}');
-- Create the tool l_tool_id := uc_ai_tools_api.create_tool_from_schema( p_tool_code => 'TT_CLOCK_IN', p_description => 'Clock in a user to the time tracking system. This needs a user_email and project_name as parameters. You can get these from other tools. Optionally pass notes if given by the user.Example parameters: {"user_email": "user@example.com","project_name": "TV Marketing", "notes": "Look for actors"} or {"user_email": "john.doe@gmail.com","project_name": "Inventing Teleportation"}', p_function_call => 'return tt_timetracking_api.clock_in_json(:parameters);', p_json_schema => l_schema, p_tags => apex_t_varchar2('time-tracking','users','projects') ); commit;end;/Provider (server-side) tools
Section titled âProvider (server-side) toolsâThe tools above run in your database. Most providers also have their own
server-side tools that run on the infrastructure of the provider. Examples
are Anthropic web search, the OpenAI Responses API web search, and Google
search. UC AI can pass these tools through. Set uc_ai.g_provider_tools to a
JSON array of provider-native tool definitions. UC AI appends them to the
tools array of the request without a change. The provider runs them, never
your database:
BEGIN uc_ai.g_provider_tools := json_array_t( '[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}]' );
-- next generate_text call lets the model search the web provider-sideEND;/Provider tools work together with your local tools. UC AI sends them also when
g_enable_tools is false. See
Provider (server-side) tools
for the full reference and per-provider formats.
Tools vs. MCP
Section titled âTools vs. MCPâMCP and tools are similar. In both, you tell the AI what it can do. The AI then decides from the conversation if it needs a call.
The architecture is the main difference. MCP targets distributed systems. In such a system, the AI chat runs on your system but needs context from another system. An email provider is a good example. It gives you an MCP server for your emails. Your system then works with the emails on the server of the email provider.
UC AI calls the AI from inside the database, next to your data. It therefore needs no MCP server. You read your data directly with SQL and PL/SQL, and you give it to the AI in a tool definition.
UC AI does not call external MCP servers today. A future version can add this feature. Your users then reach their emails through the MCP of their email provider.