Skip to content

Generate Text

The uc_ai.generate_text() function has four overloaded signatures. Two of them read the configuration from the package globals. The other two take a p_config JSON object instead. See Config-driven variants.

FUNCTION generate_text (
p_user_prompt IN CLOB,
p_system_prompt IN CLOB DEFAULT NULL,
p_provider IN provider_type,
p_model IN model_type,
p_max_tool_calls IN PLS_INTEGER DEFAULT NULL,
p_response_json_schema IN JSON_OBJECT_T DEFAULT NULL,
p_run_context IN JSON_OBJECT_T DEFAULT NULL
) RETURN JSON_OBJECT_T;
FUNCTION generate_text (
p_messages IN JSON_ARRAY_T,
p_provider IN provider_type,
p_model IN model_type,
p_max_tool_calls IN PLS_INTEGER DEFAULT NULL,
p_response_json_schema IN JSON_OBJECT_T DEFAULT NULL,
p_run_context IN JSON_OBJECT_T DEFAULT NULL
) RETURN JSON_OBJECT_T;

The same two shapes, each with an extra p_config parameter placed right after p_model:

FUNCTION generate_text (
p_user_prompt IN CLOB,
p_system_prompt IN CLOB DEFAULT NULL,
p_provider IN provider_type,
p_model IN model_type,
p_config IN JSON_OBJECT_T,
p_max_tool_calls IN PLS_INTEGER DEFAULT NULL,
p_response_json_schema IN JSON_OBJECT_T DEFAULT NULL,
p_run_context IN JSON_OBJECT_T DEFAULT NULL
) RETURN JSON_OBJECT_T;
FUNCTION generate_text (
p_messages IN JSON_ARRAY_T,
p_provider IN provider_type,
p_model IN model_type,
p_config IN JSON_OBJECT_T,
p_max_tool_calls IN PLS_INTEGER DEFAULT NULL,
p_response_json_schema IN JSON_OBJECT_T DEFAULT NULL,
p_run_context IN JSON_OBJECT_T DEFAULT NULL
) RETURN JSON_OBJECT_T;
ParameterTypeRequiredDescription
p_user_promptCLOBYesThe main prompt from the user. This is the primary input for the AI model.
p_system_promptCLOBNoSystem-level instructions that guide the behavior and response style of the AI.
p_providerprovider_typeYesAI provider to use. See uc_ai spec for constants.
p_modelmodel_typeYesSpecific AI model to use (varies by provider). See uc_ai spec for constants.
p_max_tool_callsPLS_INTEGERNoMaximum number of tool calls allowed in the conversation (default: 10).
p_response_json_schemaJSON_OBJECT_TNoJSON schema for structured output. The response then conforms to this schema.
p_run_contextJSON_OBJECT_TNoName/value pairs bound to this call. Every tool receives them under _ctx. See the run context.
ParameterTypeRequiredDescription
p_messagesJSON_ARRAY_TYesArray of conversation messages in the standard format (see Message Structure below).
p_providerprovider_typeYesAI provider to use. See uc_ai spec for constants.
p_modelmodel_typeYesSpecific AI model to use (varies by provider). See uc_ai spec for constants.
p_max_tool_callsPLS_INTEGERNoMaximum number of tool calls allowed in the conversation (default: 10).
p_response_json_schemaJSON_OBJECT_TNoJSON schema for structured output. The response then conforms to this schema.
p_run_contextJSON_OBJECT_TNoName/value pairs bound to this call. Every tool receives them under _ctx. See the run context.

The main user input that you want the AI to respond to. This can be a question, instruction, or any text you want the AI to process.

l_result := uc_ai.generate_text(
p_user_prompt => 'What is Oracle APEX?',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna
);

System-level instructions that define the role, behavior, and response style of the AI. They shape how the AI reads a user prompt and how it answers.

-- Professional assistant
l_result := uc_ai.generate_text(
p_user_prompt => 'Explain database normalization',
p_system_prompt => 'You are a professional database consultant. Provide clear, technical explanations with practical examples.',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_terra
);
-- Casual tone
l_result := uc_ai.generate_text(
p_user_prompt => 'How do I optimize this SQL query?',
p_system_prompt => 'You are a friendly database expert. Use simple language and provide step-by-step guidance.',
p_provider => uc_ai.c_provider_anthropic,
p_model => uc_ai_anthropic.c_model_claude_4_5_haiku
);

A conversation that you build yourself, as a JSON_ARRAY_T of message objects. With it, you can give a complete conversation history, or continue an earlier conversation. Each message must follow the standard message format, with the role and content properties.

DECLARE
l_messages JSON_ARRAY_T;
l_result JSON_OBJECT_T;
BEGIN
-- Build conversation messages
l_messages := JSON_ARRAY_T();
-- Add system message
l_messages.append(JSON_OBJECT_T('{
"role": "system",
"content": "You are a helpful SQL expert."
}'));
-- Add user messages
l_messages.append(JSON_OBJECT_T('{
"role": "user",
"content": "How do I create a table in Oracle?"
}'));
l_messages.append(JSON_OBJECT_T('{
"role": "assistant",
"content": "You can create a table using the CREATE TABLE statement..."
}'));
l_messages.append(JSON_OBJECT_T('{
"role": "user",
"content": "Now show me how to add a foreign key constraint"
}'));
-- Continue the conversation
l_result := uc_ai.generate_text(
p_messages => l_messages,
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna
);
END;
/

Message Structure: Each message in the array must have this structure:

  • role: One of "system", "user", "assistant", or "tool"
  • content: The message content (can be null for assistant messages with tool calls)
  • tool_calls: (Optional) Array of tool calls for assistant messages
  • tool_call_id: (Required for tool messages) the ID of the tool call that this message answers

Use Cases for Message Array:

  • Continue previous conversations
  • Implement conversation memory and history
  • Fine-tune conversation context
  • Handle complex multi-turn interactions
  • Process conversation logs or transcripts

Use Signature 1 (Individual Prompts) when:

  • Starting a new conversation
  • Making simple, single-turn requests
  • You want the function to build the message array

Use Signature 2 (Message Array) when:

  • Continuing an existing conversation
  • You need fine control over the conversation structure
  • Implementing conversation memory across multiple calls
  • Processing pre-existing conversation logs

Using Signature 1:

DECLARE
l_result JSON_OBJECT_T;
BEGIN
l_result := uc_ai.generate_text(
p_user_prompt => 'What are the benefits of using Oracle APEX?',
p_system_prompt => 'You are an Oracle APEX expert consultant.',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna
);
END;
/

This example shows how to continue a conversation using the message array signature.

UC AI converts in both directions between the standard message format and the format of each provider. You can therefore change the provider and the model in the middle of a conversation.

declare
l_messages json_array_t;
l_result json_object_t;
l_response_messages json_array_t;
begin
-- Initial conversation
l_result := uc_ai.generate_text(
p_user_prompt => 'What is the rarest chemical element?',
p_system_prompt => 'You are an assistant for chemical students in school.',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna
);
dbms_output.put_line('Response: ' || l_result.get_string('final_message'));
-- Get the complete message history from the first call
l_messages := l_result.get_array('messages');
-- Add a follow-up question
l_messages.append(
uc_ai_message_api.create_simple_user_message(
'How is it called in german, japanese and portuguese?'
)
);
-- Continue the conversation with full context
l_result := uc_ai.generate_text(
p_messages => l_messages,
p_provider => uc_ai.c_provider_google,
p_model => uc_ai_google.c_model_gemini_3_7_flash
);
dbms_output.put_line('Follow-up response: ' || l_result.get_string('final_message'));
END;

Specifies which AI provider to use. Available providers:

Provider ConstantValueDescription
uc_ai.c_provider_openai'openai'OpenAI GPT models
uc_ai.c_provider_anthropic'anthropic'Anthropic Claude models
uc_ai.c_provider_google'google'Google Gemini models
-- Using different providers
l_result := uc_ai.generate_text(
p_user_prompt => 'Explain machine learning',
p_provider => uc_ai.c_provider_openai, -- or c_provider_anthropic, c_provider_google
p_model => uc_ai_openai.c_model_gpt_5_6_luna
);

The specific AI model to use. See the package headers for available models under each provider:

  • uc_ai_openai
  • uc_ai_anthropic
  • uc_ai_google
-- Model selection examples
DECLARE
l_result JSON_OBJECT_T;
BEGIN
-- For simple tasks - use fast, cost-effective models
l_result := uc_ai.generate_text(
p_user_prompt => 'Summarize this paragraph',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna
);
-- For complex analysis - use more capable models (GPT-5.6 Sol, Claude Opus 5, Gemini 3.1 Pro)
l_result := uc_ai.generate_text(
p_user_prompt => 'Analyze this complex financial dataset and provide strategic recommendations',
p_provider => uc_ai.c_provider_anthropic,
p_model => uc_ai_anthropic.c_model_claude_5_sonnet
);
END;
/

Limits the maximum number of tool calls the AI can make during the conversation. This prevents infinite loops and controls costs.

  • Default: NULL (10 tool calls)
-- Limiting tool calls
l_result := uc_ai.generate_text(
p_user_prompt => 'Calculate monthly sales totals and create a summary report',
p_system_prompt => 'You have access to database tools to query sales data',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_terra,
p_max_tool_calls => 5 -- Allow up to 5 tool calls
);
-- Check if limit was exceeded
IF l_result.get_string('finish_reason') = 'max_tool_calls_exceeded' THEN
DBMS_OUTPUT.PUT_LINE('Consider increasing max_tool_calls for this task');
END IF;

You can give a custom JSON schema and force the AI to answer in this format. The output structure is then consistent and predictable. Use a schema when an automated system reads the output, or when you store the result.

See Structured Output for more information and usage examples.

By default, generate_text reads its configuration from the uc_ai globals and the provider package globals. This configuration holds the base URL, the credential, the reasoning level, the tool tags, and the provider-specific options. Set these globals first, then call generate_text.

The p_config overloads take all of this configuration as one JSON object, without a change to the globals. They exist for another library or package that calls UC AI directly and self-contained:

  • The call neither reads nor changes the globals. It takes its configuration only from p_config.
  • The call is safe for concurrent and re-entrant use. Each call owns its configuration for its whole duration. A nested call, for example an agent that runs as a tool, cannot disturb it.
  • A key that you omit falls back to the framework default, that is the value that uc_ai.reset_globals restores. It does not fall back to the current global value.

p_config takes the same JSON structure as uc_ai_prompt_profiles_api.apply_model_config. It holds root-level keys such as g_base_url, g_enable_tools, g_tool_tags, g_reasoning_level, g_max_tool_calls, g_apex_web_credential, and g_extra_headers. It also takes an optional nested object with the name of the provider, for the provider-specific settings. An unknown key raises ORA-20503. An unknown provider raises ORA-20306.

DECLARE
l_config JSON_OBJECT_T;
l_result JSON_OBJECT_T;
BEGIN
l_config := JSON_OBJECT_T('{
"g_enable_tools": true,
"g_tool_tags": ["timetracking"],
"g_apex_web_credential": "MY_OPENAI_CRED",
"openai": {
"g_use_responses_api": false,
"g_reasoning_effort": "low"
}
}');
l_result := uc_ai.generate_text(
p_user_prompt => 'Clock me in to the marketing project.',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna,
p_config => l_config
);
DBMS_OUTPUT.PUT_LINE(l_result.get_clob('final_message'));
END;
/

For structured output in a config-driven call, pass the schema in p_response_json_schema. UC AI ignores a response_schema key inside p_config.

p_run_context binds name/value pairs to one call. UC AI adds them to the arguments of every tool under the reserved key _ctx, so a tool handler can read a value that the model cannot choose:

l_result := uc_ai.generate_text(
p_user_prompt => 'What are the payment terms?',
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"}')
);

Inside an agent run the run context of that run wins. A p_run_context passed to a nested generate_text call is ignored, so it cannot widen the scope of the run.

The run context describes the whole feature.

Set uc_ai.g_extra_headers before the call to send extra HTTP headers with every provider REST request. Examples are a tenant ID, a tracing header, or a header that an API gateway needs:

BEGIN
uc_ai.g_extra_headers('X-Tenant-Id') := 'acme';
uc_ai.g_extra_headers('X-Trace-Id') := 'abc123';
-- next generate_text / generate_embeddings call sends both headers
END;
/

With the config-driven overloads, pass them as a JSON object under the g_extra_headers key instead:

{ "g_extra_headers": { "X-Tenant-Id": "acme", "X-Trace-Id": "abc123" } }

UC AI appends your headers after its own headers for Content-Type, authentication, and the provider version. Do not use a name that the framework already sets. UC AI sends such a header in addition to its own header, and not instead of it. uc_ai.reset_globals clears g_extra_headers, the same as every other global.

Set uc_ai.g_extra_body to a JSON object to send a provider parameter that the SDK does not wrap. Examples are top_p, stop_sequences, service_tier, metadata, and the Anthropic cache_control. UC AI merges the top-level keys of this object into the outgoing request body:

BEGIN
uc_ai.g_extra_body := json_object_t('{"top_p": 0.9, "stop_sequences": ["END"]}');
-- next generate_text call adds top_p and stop_sequences to the request body
END;
/

With the config-driven overloads, pass the same object under the g_extra_body key:

{ "g_extra_body": { "top_p": 0.9, "stop_sequences": ["END"] } }

Notes:

  • The shape is provider-specific. Write each key exactly as the API of the target provider expects it. A key here overwrites the value of the framework. You can therefore also change a parameter that the SDK sets, for example the Anthropic temperature.
  • A nested object replaces the whole object. There is no deep merge.
  • The “core” keys that define the conversation are protected: model, messages, input, instructions, system, and tools. UC AI ignores a value for these keys in g_extra_body and writes it to the log. Use p_model, p_messages, and g_provider_tools for these values.
  • For OCI, g_extra_body merges into the chatRequest object and not into the outer request body. OCI keeps its generation parameters in chatRequest.
  • uc_ai.reset_globals clears g_extra_body.

You can also enable the server-side tools of a provider, next to the local PL/SQL tools that you register. The provider runs these tools, not your database. Examples are Anthropic web search and the OpenAI Responses API web search. 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:

BEGIN
-- Anthropic server-side web search
uc_ai.g_provider_tools := json_array_t(
'[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}]'
);
DECLARE
l_result json_object_t;
BEGIN
l_result := uc_ai.generate_text(
p_user_prompt => 'What is the latest stable version of Oracle APEX?'
, p_provider => uc_ai.c_provider_anthropic
, p_model => uc_ai_anthropic.c_model_claude_4_5_haiku
);
END;
END;
/

For the OpenAI Responses API (the default OpenAI path):

{ "g_provider_tools": [ { "type": "web_search_preview" } ] }

Notes:

  • UC AI passes each definition without a change. You must match the exact schema of the provider, and you must enable the tool in your provider account. The provider documentation gives the details: Anthropic tool use, OpenAI tools.
  • UC AI sends provider tools also when local tools are disabled (g_enable_tools = false). When you set both, UC AI sends both.
  • The provider runs these tools and returns the results inline. UC AI never calls them locally. The result blocks in the response, such as server_tool_use and web_search_tool_result, pass through the conversation. They do not count as local tool calls.
  • UC AI appends a Google server tool, for example {"googleSearch": {}}, next to the function-declarations entry. The Gemini API expects this position.
  • uc_ai.reset_globals clears g_provider_tools.

The uc_ai.generate_text() function returns a JSON_OBJECT_T with information about the AI interaction. This object holds the conversation history, the usage statistics, and metadata about the model and the provider.

The return object contains the following properties:

{
"final_message": "The AI's final response text",
"messages": [...], -- Complete conversation history (JSON_ARRAY_T)
"finish_reason": "stop", -- How the conversation ended (VARCHAR2)
"usage": { -- Token usage statistics (JSON_OBJECT_T)
"prompt_tokens": 45, -- input tokens used (NUMBER)
"completion_tokens": 23, -- output tokens used (NUMBER)
"reasoning_tokens": 15 -- amount of output tokens used for reasoning (NUMBER)
"total_tokens": 68, -- total tokens used (NUMBER)
},
"tool_calls_count": 2, -- Number of tools executed (NUMBER)
"model": "gpt-5.6-luna", -- AI model used (VARCHAR2)
"provider": "openai" -- AI provider (VARCHAR2)
}

Three more properties are present only in the conditions that produce them: block_reason, provider_finish_reason and error_message. The table below gives the condition for each one.

PropertyTypeDescription
final_messageCLOBThe final assistant message from the conversation. With p_response_json_schema it holds the JSON as text
messagesJSON_ARRAY_TComplete conversation history including user, system, assistant, and tool messages
finish_reasonVARCHAR2Completion reason: stop, tool_calls, length, content_filter, max_tool_calls_exceeded
usageJSON_OBJECT_TToken usage statistics with prompt_tokens, completion_tokens, and total_tokens
tool_calls_countNUMBERTotal number of tool calls executed during the conversation
modelVARCHAR2The AI model used (for example gpt-5.6-luna or claude-sonnet-5)
providerVARCHAR2AI provider identifier (openai, anthropic, google)
block_reasonVARCHAR2Google only, and only when the provider blocked the prompt. Holds the reason, for example PROHIBITED_CONTENT. finish_reason is content_filter and final_message is null
provider_finish_reasonVARCHAR2Present when the provider ended the run for a reason that maps to no value in the table below. finish_reason is then unknown, and this property holds the provider’s own word
error_messageVARCHAR2OCI Cohere only, and only when the provider reported an error instead of an answer. Holds the message that came with it
DECLARE
l_result JSON_OBJECT_T;
l_final_response CLOB;
l_finish_reason VARCHAR2(255);
BEGIN
-- Make AI request
l_result := uc_ai.generate_text(
p_user_prompt => 'What is the weather like in London?',
p_system_prompt => 'You are a helpful weather assistant.'
);
-- Get the final AI response
l_final_response := l_result.get_string('final_message');
DBMS_OUTPUT.PUT_LINE('AI Response: ' || l_final_response);
-- Check how the conversation ended
l_finish_reason := l_result.get_string('finish_reason');
DBMS_OUTPUT.PUT_LINE('Finish reason: ' || l_finish_reason);
END;
/
DECLARE
l_result JSON_OBJECT_T;
l_usage JSON_OBJECT_T;
l_total_tokens NUMBER;
l_tool_calls_count NUMBER;
BEGIN
l_result := uc_ai.generate_text(
p_user_prompt => 'Calculate the area of a circle with radius 5',
p_max_tool_calls => 3
);
-- Get usage statistics
l_usage := l_result.get_object('usage');
l_total_tokens := l_usage.get_number('total_tokens');
l_tool_calls_count := l_result.get_number('tool_calls_count');
DBMS_OUTPUT.PUT_LINE('Tokens used: ' || l_total_tokens);
DBMS_OUTPUT.PUT_LINE('Tool calls made: ' || l_tool_calls_count);
DBMS_OUTPUT.PUT_LINE('Model: ' || l_result.get_string('model'));
DBMS_OUTPUT.PUT_LINE('Provider: ' || l_result.get_string('provider'));
END;
/

The messages array contains the complete conversation history with different message types:

[
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "What is the weather in London?"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"London\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_123",
"content": "Temperature: 18°C, Condition: Partly cloudy"
},
{
"role": "assistant",
"content": "The current weather in London is 18°C and partly cloudy."
}
]
DECLARE
l_result JSON_OBJECT_T;
l_messages JSON_ARRAY_T;
l_message JSON_OBJECT_T;
l_role VARCHAR2(100);
l_content CLOB;
BEGIN
l_result := uc_ai.generate_text(
p_user_prompt => 'Tell me a joke and then explain why it is funny'
);
-- Get the complete message history
l_messages := l_result.get_array('messages');
-- Loop through all messages
FOR i IN 0 .. l_messages.get_size - 1 LOOP
l_message := JSON_OBJECT_T(l_messages.get(i));
l_role := l_message.get_string('role');
l_content := l_message.get_string('content');
DBMS_OUTPUT.PUT_LINE('Role: ' || l_role);
DBMS_OUTPUT.PUT_LINE('Content: ' || SUBSTR(l_content, 1, 200));
DBMS_OUTPUT.PUT_LINE('---');
END LOOP;
END;
/

The finish_reason indicates how the AI conversation ended:

Finish ReasonDescription
stopThe model completed its response naturally
tool_callsThe response ended with tool calls
lengthThe maximum token limit truncated the response
content_filterThe content policy filtered the response
max_tool_calls_exceededThe conversation reached the maximum number of tool calls
unknownThe provider used a reason UC AI does not map. provider_finish_reason holds the original

A provider can add a reason at any time. A branch on finish_reason therefore needs an ELSE case. The unknown value keeps a new provider reason from being read as stop.

DECLARE
l_result JSON_OBJECT_T;
l_finish_reason VARCHAR2(255);
BEGIN
l_result := uc_ai.generate_text(
p_user_prompt => 'Analyze this complex data...',
p_max_tool_calls => 5
);
l_finish_reason := l_result.get_string('finish_reason');
CASE l_finish_reason
WHEN 'stop' THEN
DBMS_OUTPUT.PUT_LINE('Conversation completed successfully');
WHEN 'length' THEN
DBMS_OUTPUT.PUT_LINE('Response was truncated due to length limits');
WHEN 'max_tool_calls_exceeded' THEN
DBMS_OUTPUT.PUT_LINE('Maximum tool calls reached - consider increasing limit');
WHEN 'content_filter' THEN
DBMS_OUTPUT.PUT_LINE('Response was filtered due to content policy');
ELSE
DBMS_OUTPUT.PUT_LINE('Unexpected finish reason: ' || l_finish_reason);
END CASE;
END;
/

If the AI provider returns an error, or if the request fails, uc_ai.generate_text raises an exception.