Building an AI Financial Analyst with Oracle Select AI Agent — Part 1
What It Is, Why I Went This Route, and How the Pieces Fit
Important
Disclaimer: The writing and musing of the author do not necessarily reflect the views of his employer.
If you have been asked to put a chat interface in front of your financial data and stalled at the point of deciding what actually runs the thing, this is what I found. There are a lot of moving parts in that decision and not much written about the trade-offs.
Everything in this series is backed by a repository of scripts that build the whole stack and have been run end to end against a live database. Part 2 walks the build and the things that catch you, Part 3 the custom tools, Part 4 how this stack fails and how to trace it back, and Part 5 a tool I built to generate all of it.
github.com/BASoapbox/ACME-Corp-Select-AI-Agent-Project
I should say up front that I am not an expert on any of this. I built one thing, it works, and these are the notes.
The problem
The finance team I had in mind sits between two kinds of knowledge, and both are awkward to reach.
The first is structured — general ledger transactions, department budgets, period close status, chart of accounts. It changes daily, it lives in Autonomous Data Warehouse, and answering questions about it takes SQL that most users don't have.
The second is unstructured — reconciliation policies, SOX compliance guides, close checklists. These are PDFs, and answering questions about them means knowing where to look or asking someone who does.
So what happens today is a ticket. Someone raises one, waits for an analyst, and gets an answer hours later. For "what's the status of the March close?" or "which accounts need Level 2 approval?", that is a lot of friction for very little.
What I wanted was a chat interface where anyone could ask in plain English and get an answer immediately — and, critically, an answer that came from real data rather than the model's imagination. That constraint drove most of what follows.
What Select AI Agent is
Select AI is Oracle's framework for connecting a large language model to your database through SQL. It has been around a while for natural-language-to-SQL: ask a question, it writes the SQL, runs it, narrates the result.
Select AI Agent is the larger idea. It puts an agent orchestration layer inside the database, through the DBMS_CLOUD_AI_AGENT package, and that buys you several things the basic version doesn't:
- Multiple tools the agent routes between depending on the question
- A reasoning loop where the model picks a tool, calls it, looks at what came back, and either answers or calls another
- Custom tools — register your own PL/SQL function and the agent calls it with the right parameters
- Multi-turn conversation through a
conversation_id - Everything in-database — orchestration, vector search, SQL generation and Python all run inside the database
The four objects
The framework has exactly four, and they stack.

The build order is bottom-up, and it is worth internalising because it is also the debugging order. Tools first, because they are what the agent can actually do. Then the task, which is the routing instruction telling the model which tool suits which question. Then the agent, which owns a role and an explicit list of tools. Then the team, which is the thing you actually call.
Each layer only knows the one below it. When something breaks, that hierarchy tells you where to look — a point I'll return to in Part 4, because each layer's errors impersonate a different layer's problems with some conviction.
Why in-database
This is not a case for one Oracle product over another; both are reasonable depending on what you're doing. But for this problem the comparison came out fairly one-sided.
| OCI GenAI Agent | Select AI Agent | |
|---|---|---|
| Where it runs | Managed OCI service | In-database |
| Setup | OCI Console | PL/SQL packages |
| NL2SQL | Via SQL tool + DB Tools connection | Native |
| Retrieval | Managed Knowledge Base | Vector index in the database |
| Custom tools | Client-side function calling | Any PL/SQL function, run in-database |
| Cost | Separate service billing | Included with the database |
The deciding factor was custom tools. I needed more than queries and document search — moving averages, anomaly detection, regression forecasting. The GenAI Agent service can call custom functions, but your application executes them and hands back the result; nothing runs inside the database. Select AI Agent registers a PL/SQL function as a tool and calls it in place, including Python through OML4Py Embedded Python Execution (EPE), with pandas and numpy running inside the database.
The security picture helped too. Everything authenticates as the database's own Resource Principal, which means the front end holds no credentials at all — a property I'll come back to in Part 3, because it turns out to solve a problem I initially thought was unsolvable.
What I built
A team called ACME_ANALYST_TEAM, whose single agent ACME_ANALYST has six tools:
Built-in — ACME_SQL_TOOL for natural language to SQL against live GL tables, and ACME_RAG_TOOL for semantic search over policy PDFs.
Custom PL/SQL — ACME_TREND_TOOL for period-over-period trends using LAG(), ACME_FORECAST_TOOL for linear regression, ACME_ANOMALY_TOOL for STDDEV()-based outlier detection.
Custom Python — ACME_PYTHON_TOOL, pandas and numpy statistics running inside the database through OML4Py.
The agent decides which fits. Ask about August expenses and it calls the SQL tool. Ask what the reconciliation policy says and it calls RAG. Ask which department has the most volatile spending and it calls the Python tool, which runs that Python in the database and hands back computed statistics.
I wrote no routing logic. The model reads the task instruction, which maps question types to tools, and decides — and it will call more than one tool in a single response when the question calls for it.
Running it yourself
The whole stack builds from four scripts. If you want to read Parts 2 and 3 with something actually running in front of you, this is the shortest path there.
What you need first. An Autonomous Database on 26ai, a bucket holding the sample policy PDFs, and one IAM policy granting the database's own Resource Principal access to Generative AI and Object Storage. That last one catches nearly everyone — being a tenancy administrator does not cover it, because the database authenticates as itself. You will also want SQLcl, which ships with the Oracle extension for VS Code.
Configure once. A single file holds every environment value; nothing else needs editing.
git clone https://github.com/BASoapbox/ACME-Corp-Select-AI-Agent-Project
cd ACME-Corp-Select-AI-Agent-Project
cp SA_ENV.sql.template SA_ENV.sql
Fill in region, compartment OCID, model and bucket. That file is gitignored and holds no password — the scripts that need one prompt at run time.
Then run four scripts, in order.
| Script | Run as | What it does |
|---|---|---|
SA_00_create_schema.sql |
ADMIN | Creates the schema |
SA_01_admin_setup.sql |
ADMIN | Resource Principal, grants, OML roles, the EPE network ACL |
SA_02_ddl_dml.sql |
schema | Seven tables and the GL data, with three anomalies planted in it |
SA_03_agent_setup.sql |
schema | Comments, profiles, vector index, six tools, agent, task, team |
sql -name <admin-connection> @SA_00_create_schema.sql
sql -name <admin-connection> @SA_01_admin_setup.sql
sql -name <schema-connection> @SA_02_ddl_dml.sql
sql -name <schema-connection> @SA_03_agent_setup.sql
Each script verifies its own work before the next one builds on it, so read the output rather than watching it scroll. When something breaks later, you want to already know which layers underneath it are solid.
Then ask it something.
DECLARE
l_conv VARCHAR2(36);
l_resp CLOB;
BEGIN
l_conv := DBMS_CLOUD_AI.CREATE_CONVERSATION();
l_resp := DBMS_CLOUD_AI_AGENT.RUN_TEAM(
team_name => 'ACME_ANALYST_TEAM',
user_prompt => 'What were Engineering expenses in August 2025?',
params => '{"conversation_id":"' || l_conv || '"}');
DBMS_OUTPUT.PUT_LINE(l_resp);
END;
/
You should get roughly $176,000 — a cloud migration project and some architecture consulting, and the largest single spike in the data. A smooth, average-looking number instead, or a confident answer with no figures in it at all, means something upstream didn't take. Telling those two apart is most of Part 4.
That is the entire build. Parts 2 and 3 are about why each piece is shaped the way it is; Part 4 is about what to do when it isn't.
Two things worth noticing
The model never guesses at figures. Every factual answer comes from a tool returning real data, and the task instruction says so explicitly: only report data returned by tools, never invent figures. That, to me, is the difference between something useful and an expensive hallucination engine.
And everything is auditable, which I did not expect going in — USER_AI_AGENT_TOOL_HISTORY logs every tool invocation with its inputs and output. When a user gets a wrong answer you can go and see exactly what was called and what came back. That view does more work than any error message in this stack.
In Part 2 I walk the build — the data, the comments that make natural-language SQL work, the profiles, the vector index, and the four objects that stack on top — along with the things that catch you even with working scripts in front of you.
Built on Oracle Autonomous Database 26ai · OCI Generative AI · OML4Py