Skip to content

Profile Agents

A profile agent runs a prompt profile on each call.

A profile agent gives the same AI answer as a direct call to uc_ai_prompt_profiles_api.execute_profile. The difference is the work around the call:

  • Execution tracking: UC AI logs every call in the uc_ai_agent_executions table, with the status, the token counts, and the timing. This log is your audit trail, and it makes a fault easier to find.
  • Session grouping: use a session ID to group related agent calls. You can then trace a whole multi-step process as one unit.
  • Composability: a workflow, an orchestrator, and a conversation can all reference a profile agent. Profile agents are the building blocks of every other pattern.

First, create a prompt profile (or use an existing one):

DECLARE
l_profile_id NUMBER;
BEGIN
l_profile_id := uc_ai_prompt_profiles_api.create_prompt_profile(
p_code => 'GEO_ASSISTANT',
p_description => 'Answers geography questions',
p_system_prompt_template => 'You are a geography assistant. Answer in one short sentence.',
p_user_prompt_template => '{question}',
p_provider => uc_ai.c_provider_openai,
p_model => uc_ai_openai.c_model_gpt_5_6_luna,
p_status => uc_ai_prompt_profiles_api.c_status_active
);
COMMIT;
END;
/

Then wrap it in an agent:

DECLARE
l_agent_id NUMBER;
BEGIN
l_agent_id := uc_ai_agents_api.create_agent(
p_code => 'geo_agent',
p_description => 'Answers geography questions in one sentence',
p_agent_type => uc_ai_agents_api.c_type_profile,
p_prompt_profile_code => 'GEO_ASSISTANT',
p_status => uc_ai_agents_api.c_status_active
);
COMMIT; -- agents must be committed before they can be executed
END;
/
DECLARE
l_result json_object_t;
l_params json_object_t := json_object_t();
l_session_id VARCHAR2(100);
BEGIN
l_session_id := uc_ai_agents_api.generate_session_id;
l_params.put('question', 'What is the capital of France?');
l_result := uc_ai_agents_api.execute_agent(
p_agent_code => 'geo_agent',
p_input_parameters => l_params,
p_session_id => l_session_id
);
DBMS_OUTPUT.PUT_LINE('Answer: ' || l_result.get_clob('final_message'));
END;
/

UC AI passes p_input_parameters as the prompt profile parameters. Each key maps to a {placeholder} in the profile templates.

A profile agent and an orchestrator agent can also take files next to the text input, through the p_files parameter. Files include a PDF and an image. UC AI attaches each file to the user message, so the model can analyze it in the same call. Build the collection with uc_ai_message_api.t_files. The model must be multimodal (see file analysis).

DECLARE
l_result json_object_t;
l_files uc_ai_message_api.t_files := uc_ai_message_api.t_files();
BEGIN
l_files.extend;
l_files(1).media_type := 'application/pdf';
l_files(1).data_blob := (SELECT blob_content FROM my_docs WHERE id = 1);
l_files(1).filename := 'report.pdf';
l_result := uc_ai_agents_api.execute_agent(
p_agent_code => 'doc_agent',
p_input_parameters => json_object_t('{"question": "Summarize the attached report."}'),
p_files => l_files
);
DBMS_OUTPUT.PUT_LINE(l_result.get_clob('final_message'));
END;
/

p_files also works with p_follow_up_message, to attach a file to a follow-up turn. Only a profile agent and an orchestrator agent support it. A file for a workflow agent or a handoff agent raises an error.

Every call to execute_agent creates a row in uc_ai_agent_executions. Query this table to see what happened:

SELECT ae.id,
a.code AS agent_code,
ae.status,
ae.created_by,
ae.total_input_tokens,
ae.total_output_tokens,
ae.started_at,
ae.completed_at
FROM uc_ai_agent_executions ae
JOIN uc_ai_agents a ON a.id = ae.agent_id
WHERE ae.session_id = :session_id
ORDER BY ae.started_at;

Each row also holds the environment of the caller: created_by, the database user, the APEX user, session, app, and page, the module, and a full env_context JSON snapshot. Session tracking describes these columns.

A workflow with three profile agent steps writes three execution rows under one session. You can then see which step used the most tokens, or which step failed.

A profile agent calls the model directly. Its turn therefore writes the most detailed uc_ai_agent_messages transcript: the user prompt, the reasoning, tool_call, and tool_result rows, and the assistant answer. On every agent row, agent_code names the profile agent.

seqroleagent_codetool_namecontent
1user(null)What's the weather in NYC?
2tool_callweather_agentget_weather
3tool_resultweather_agentget_weather{"tempC":18,"sky":"clear"}
4assistantweather_agentIt's 18 C and clear in NYC.

On a follow-up turn, UC AI appends only the new messages of that turn, and continues the seq of the session. An earlier turn keeps its own rows.

Every workflow step, every orchestrator delegate, and every conversation participant is a profile agent.

For example, a sequential workflow references profile agents by their agent code:

{
"workflow_type": "sequential",
"steps": [
{
"agent_code": "geo_agent",
"input_mapping": { "question": "{$.input.question}" },
"output_key": "geo_result"
},
{
"agent_code": "summarizer_agent",
"input_mapping": { "text": "{$.steps.geo_result}" },
"output_key": "summary"
}
]
}

An orchestrator lists them as delegates that the AI can call as tools:

{
"delegate_agents": ["geo_agent", "math_agent", "history_agent"]
}

And conversations list them as participants:

{
"agents": [
{ "agent_code": "brainstormer_agent", "input_mapping": { ... } },
{ "agent_code": "critic_agent", "input_mapping": { ... } }
]
}

In every pattern, UC AI tracks each profile agent run separately. You therefore see every AI call in the chain.

A profile agent supports a multi-turn conversation across several execute_agent calls. A follow-up message builds on the earlier conversation, so the calls are not independent.

  1. On the first call, the agent runs its prompt profile as usual, with the system prompt and the user prompt from the templates. UC AI stores the full conversation in the execution result, with every tool call exchange.
  2. On a follow-up call, pass p_follow_up_message with the same p_session_id. UC AI loads the earlier conversation messages, appends your new message, and sends the full history to the LLM.
DECLARE
l_result json_object_t;
l_session_id VARCHAR2(100);
BEGIN
l_session_id := uc_ai_agents_api.generate_session_id;
-- First call: uses prompt profile template substitution as usual
l_result := uc_ai_agents_api.execute_agent(
p_agent_code => 'geo_agent',
p_input_parameters => json_object_t('{"question": "What are the 5 largest cities in Europe?"}'),
p_session_id => l_session_id
);
DBMS_OUTPUT.PUT_LINE(l_result.get_clob('final_message'));
-- Follow-up: the LLM sees the full previous conversation
l_result := uc_ai_agents_api.execute_agent(
p_agent_code => 'geo_agent',
p_follow_up_message => 'Which of those have the best public transport?',
p_session_id => l_session_id
);
DBMS_OUTPUT.PUT_LINE(l_result.get_clob('final_message'));
-- Another follow-up
l_result := uc_ai_agents_api.execute_agent(
p_agent_code => 'geo_agent',
p_follow_up_message => 'Compare the top 2 in more detail.',
p_session_id => l_session_id
);
DBMS_OUTPUT.PUT_LINE(l_result.get_clob('final_message'));
END;
/

Each follow-up call creates its own execution row in uc_ai_agent_executions. The same session_id links all of these rows.

A long conversation sends much history to the LLM. To control the token usage, set p_max_history_messages at the creation of the agent. The agent then uses a sliding window:

DECLARE
l_agent_id NUMBER;
BEGIN
l_agent_id := uc_ai_agents_api.create_agent(
p_code => 'chat_agent',
p_description => 'A conversational assistant',
p_agent_type => uc_ai_agents_api.c_type_profile,
p_prompt_profile_code => 'CHAT_PROFILE',
p_max_history_messages => 20,
p_status => uc_ai_agents_api.c_status_active
);
END;
/

When the history passes the limit of p_max_history_messages, UC AI keeps the system message, which is the first message, and the most recent N messages.

A workflow or a conversation is not always necessary. Use a profile agent alone when:

  • You have a single AI task, and you want execution tracking for the token usage, the timing, and the success or failure
  • You want to adopt the agent framework step by step. Start with a wrapper around an existing prompt profile, and compose the agents later
  • You need a consistent API in your application. execute_agent works the same for every agent type, so a later change from a profile agent to a workflow needs no change in the calling code