SkySutra knows a lot by now: 9,000 stars, a year of sky events, the lunar days, and sourced stories. Finding things still means knowing the right word. Search for "Polaris" and you find it; search for "a star that never moves" and you find nothing. Part 11 teaches SkySutra to search by meaning, and then gives it a guide you can talk to. The guide is an AI model, but it answers only from SkySutra's own data, says which story it used, and never tells anyone what the sky means for them.
How Part 11 works
| Lesson | You build | New in APEX |
|---|---|---|
| 11.1 Embeddings in plain English | Nothing yet: what an embedding is, and how close two meanings are | — |
| 11.2 A Vector Provider | SkySutra embeddings, reusing your Gemini key from Turnout | Vector Providers |
| 11.3 Vectors in tables | VECTOR(3072, FLOAT32) columns, a text for every named star, embeddings for everything, a nightly step |
AI Vector Search; APEX_AI.GET_VECTOR_EMBEDDINGS |
| 11.4 Search by meaning | By meaning on the Search page | Search Configuration type Oracle AI Vector Search |
| 11.5 An assistant grounded in your data | The Sky guide AI Agent and the Ask the Sky page (85) | AI Agents with Retrieve Data tools; Show AI Assistant, inline |
| 11.6 Keeping it honest | The guide's system prompt; testing it | — |
Before you start
You need Part 10 done, and the Gemini API key you made in Turnout Lesson 11.4 (its web credential is credentials-for-turnout-ai). If you skipped Turnout, make a free key at Google AI Studio first; Lesson 11.2 shows where it goes.
Then, after Lesson 11.2, download and run skysutra_ask.sql.
Lesson 11.1 — Embeddings in plain English
Outcome: you can say what an embedding is, and read a distance.
The question. How can a computer tell that "a star that never moves" is about Polaris, when the two share no words?
The answer. An embedding model turns a piece of text into a long list of numbers, a vector. Gemini's gemini-embedding-001 gives 3,072 of them for any text, from three words to three pages. The model was trained so that texts with similar meanings get similar lists. Think of each text as a point in a space with 3,072 directions instead of three: texts about the pole star land near each other, texts about eclipses land somewhere else.
How near? The cosine distance compares the directions of two vectors:
| Distance | Means |
|---|---|
| 0 | the same direction: the same meaning |
| about 0.3 | close: clearly about the same thing |
| about 0.45 | loosely related |
| 1 | unrelated |
Here are real distances from SkySutra, once Lesson 11.3 is done:
| Question | Closest text | Distance | Next |
|---|---|---|---|
| a star that never moves | Lore: Dhruva, the boy who would not move | 0.302 | Star: Polaris, 0.369 |
| Which star do brides look at on their wedding night? | Lore: The seven sages and Arundhati | 0.302 | Lore: Rohini, 0.363 |
| the red eye of the bull | Lore: Rohini, the red one | 0.338 | Star: Aldebaran, 0.410 |
| Why do eclipses happen? | Lore: Aryabhata: a shadow, not a demon | 0.328 | Lore: Rahu and Ketu, 0.344 |
None of the questions uses the words of its answer. The second found Arundhati because her panel tells how a bride is shown the pole star and Arundhati on her wedding night.
Two rules follow:
- One model for everything. A question's vector can only be compared with vectors from the same model. Change models and you embed everything again.
- Distance isn't truth. The nearest text is the most similar, not necessarily the right one. Something is always nearest, even for "what's the weather tomorrow?" So a search sets a limit, and the assistant in Lesson 11.5 is told to say "I don't know" when nothing is close.
Check yourselfWhy can't you compare a vector from Gemini with one from OpenAI?
Each model has its own space; the numbers mean nothing outside it
Lesson 11.2 — A Vector Provider
Outcome: a workspace-wide Vector Provider that every part of SkySutra uses for embeddings.
A Vector Provider is to embeddings what a Generative AI Service is to chat: it says which model to call, where, and with which key. Like AI services, providers belong to the workspace, so every app can use them; an exported app carries the providers it uses.
APEX offers three kinds:
| Type | The model runs | On apex.oracle.com |
|---|---|---|
| Database ONNX Model | inside the database (VECTOR_EMBEDDING) |
not allowed |
| Generative AI Service | at a provider: OCI, OpenAI, Cohere, Google Gemini, Mistral, Ollama, or anything OpenAI-compatible | ✓ |
| Custom PL/SQL | in your own function | ✓ |
Build: SkySutra embeddings
Workspace Utilities › Vector Providers › Create:
- Provider Type Generative AI Service, AI Provider Google Gemini
- Name SkySutra embeddings
- Base URL
https://generativelanguage.googleapis.com/v1(filled in for you) - Credential
credentials-for-turnout-ai: the same key as Turnout. (No Turnout? Choose Create New and paste your key into API Key.) - AI Model
gemini-embedding-001(filled in for you) - Static ID
skysutra-embeddings. The code refers to the provider by this.
Press Test Connection: "Connection Succeeded!" Then Create.
Try it in SQL Commands:
select vector_dimension_count(
apex_ai.get_vector_embeddings(
p_value => 'Why do eclipses happen?',
p_service_static_id => 'skysutra-embeddings')) as dims
from dual;
3072.
The free tier has a speed limit. Gemini's free tier allows about 100 embedding requests a minute. Faster than that, the call fails with HTTP 429, "You exceeded your current quota". Lesson 11.3 paces itself.
Lesson 11.3 — Vectors in tables
Outcome: an embedding for every lore panel, every named star and every picture of the day, kept up to date every night.
Part 1 gave three tables an EMBEDDING column of type VECTOR and left them empty: SS_LORE, SS_STAR_TEXTS and SS_APOD. Now they get filled.
A vector of one size
Plain VECTOR takes any number of dimensions, in any number format. Now that we know the model, skysutra_ask.sql makes the columns exact:
alter table ss_lore drop column embedding;
alter table ss_lore add (embedding vector(3072, float32));
VECTOR(3072, FLOAT32) refuses a vector from a different model, which would have a different size, and lets a vector index be built. The columns are still empty, so dropping them costs nothing. That's lucky, because MODIFY doesn't work on a vector column (ORA-51859).
The script does this only for a column that isn't VECTOR(3072, FLOAT32) yet (it asks user_tab_cols.vector_info). Run skysutra_ask.sql again later and it leaves the columns, and the embeddings in them, alone.
Something to embed for every star
A star row is numbers: right ascension, magnitude, a B−V colour index. An embedding needs words. So ss_ask.refresh_star_texts writes two or three sentences for each of the 323 stars that have a name, from SS_STARS_V, the constellations and the nakshatras:
Aldebaran (α Tauri) is an orange star in Taurus (Vrishabha), magnitude 0.85, one of the brightest stars in the sky. Spectral type K5+III. In the Indian tradition it is called Rohini. It is the junction star (yogatara) of the nakshatra Rohini (the red one). By its sidereal longitude it lies in Rohini, 5° south of the ecliptic, close enough to the Moon's path for the Moon to pass over it.
The texts go in SS_STAR_TEXTS, not in SS_STARS, so the catalog itself stays as it was loaded (MODEL.md).
Three new stories
The script also adds three lore panels, so the eclipse question has something to find:
- Rahu and Ketu, who swallow the Sun and the Moon: from the Mahabharata, Adi Parva, section 19
- Aryabhata: a shadow, not a demon: the explanation of eclipses in the Aryabhatiya, around 499 CE
- Agastya, the star that clears the water: Canopus, from the Brihat Samhita, chapter 12
Each names its translation, as every panel must.
Run the script again and a panel is added only if its title is missing, and changed only if its text differs. A panel that is already as written keeps its embedding.
Filling the embeddings
procedure refresh_embeddings (p_max number default 500, p_job varchar2 default 'NIGHTLY') is
l_id number := ss_nightly.log_start(p_job, 'EMBED');
l_done number := 0;
l_vec vector; -- a private function can't be called inside SQL
begin
for r in (select star_id, text from ss_star_texts where embedding is null) loop
exit when l_done >= p_max;
l_vec := embed_paced(r.text);
update ss_star_texts set embedding = l_vec where star_id = r.star_id;
commit;
l_done := l_done + 1;
end loop;
... -- the same for SS_LORE and SS_APOD
- Only rows without an embedding. Run it again and it carries on where it stopped. A commit after each row means a failure loses nothing.
embed_pacedcalls the provider and then waits 0.65 seconds, which keeps under the free tier's limit. Each row takes about a second.- A changed panel loses its embedding. A trigger on
SS_LOREsetsEMBEDDINGto null when the title, body or source changes, so the next refresh embeds the new text. (A CLOB can't be named inUPDATE OF, so the trigger compares the bodies withdbms_lob.compare.)
Fill them now in SQL Commands. SQL Commands stops after about a minute, so run this about eight times, until SS_JOB_RUNS shows an EMBED row with 0 rows done:
begin ss_ask.refresh_embeddings(p_max => 40, p_job => 'MANUAL'); end;
Every night
Add a fifth action to the Nightly sky automation (Lesson 10.1): Embeddings, sequence 35, between the calendar and tidy-up steps, Stop Execution On Error on:
ss_ask.refresh_star_texts;
ss_ask.refresh_embeddings;
Most nights there's only the new picture of the day to embed, or a panel someone edited.
A vector index?
A query can compare the question with every row, which is an exact search. For SkySutra's 334 texts that takes 0.47 seconds, and most of that is the call that embeds the question. A vector index groups similar vectors so a search only looks at the nearest groups, an approximate search. It pays off at tens of thousands of rows.
Oracle has two kinds:
| Index | How | On apex.oracle.com |
|---|---|---|
HNSW (ORGANIZATION INMEMORY NEIGHBOR GRAPH) |
A graph in memory | ORA-51962: no vector memory |
IVF (ORGANIZATION NEIGHBOR PARTITIONS) |
Vectors grouped into partitions on disk | Default settings: ORA-01536, over the space quota. With 4 partitions it works, but takes about 46 MB of the workspace's 125 MB |
create vector index ss_star_texts_vix on ss_star_texts (embedding)
organization neighbor partitions distance cosine with target accuracy 90
parameters (type ivf, neighbor partitions 4);
A query asks for the index with FETCH APPROX:
order by vector_distance(embedding, :v, cosine)
fetch approx first 5 rows only with target accuracy 90
Try it, then drop index ss_star_texts_vix; to get the space back. SkySutra searches exactly.
Lesson 11.4 — Search by meaning
Outcome: the Search page (Lesson 6.4) finds lore and stars by meaning as well as by name.
One view for everything
SS_ASK_DOCS_V puts the three kinds of text together: an ID, the kind (Lore, Star, Picture of the day), the title, the text, the source, a link to the page that shows it, and the embedding.
select kind, title, round(distance, 3) as distance
from (select d.kind, d.title,
vector_distance(d.embedding,
(select ss_ask.question_vector('a star that never moves') from dual),
cosine) as distance
from ss_ask_docs_v d
where d.embedding is not null)
order by distance
fetch first 5 rows only;
ss_ask.question_vector embeds the question once and keeps the result. The query mentions it once, but without that, Oracle might call it for every row, which would mean 334 calls to Gemini.
Build: the By meaning search
- Shared Components › Search Configurations › Create: type Oracle AI Vector Search, label By meaning.
- Vector Provider SkySutra embeddings; Table
SS_ASK_DOCS_V. - Primary Key
DOC_ID, Vector ColumnEMBEDDING, TitleTITLE, DescriptionTEXT. Create. - On the configuration:
- Static ID
by-meaning, Subtitle ColumnKIND, iconfa-lightbulb-o - Maximum Vector Distance 0.42. Anything farther is left out, so "what's the weather tomorrow?" finds nothing rather than the least-bad star.
- Maximum Rows to Return 10
- Link Redirect to URL,
&LINK_URL.: the view builds each row's link withapex_page.get_url, to the star's page or the Lore page. - No Results Found Message "Nothing close in meaning."
- Static ID
- Page 33 › Search Results › Search Sources: add By meaning, sequence 40, at most 5 results.
APEX does the embedding itself: it sends the search words to the provider, and compares the vector with the column.
Search "a star that never moves". Stars and Constellations find nothing; By meaning finds Dhruva and Polaris.
Lesson 11.5 — An assistant grounded in your data
Outcome: an Ask the Sky page where you talk to a guide that answers from SkySutra's data.
Grounding
A chat model on its own answers from what it learned in training. It does that well, and it also makes things up with the same confidence. Grounding means giving it the facts it needs with each question, and telling it to answer only from them. When those facts are found by meaning, as in Lesson 11.4, it's called retrieval-augmented generation, or RAG.
In APEX 26.2 an AI Agent does both with tools:
| Tool | Runs | SkySutra's |
|---|---|---|
| Augment System Prompt | Before every question; the result is added to the instructions | tonight: the Sun, Moon, tithi and nakshatra over your home. planets_now: where each planet is. |
| On Demand | Only when the model decides to call it, with the parameters it chooses | search_lore(QUESTION): the RAG step. star_facts(NAME): one star. sky_events(DAYS): the calendar. |
All five are Retrieve Data tools: a SQL query whose rows go to the model. They run in the signed-in person's session, so ss_auth.current_user_id and every view that uses it (Lesson 5.2) work as on any page (Lessons 5.3 and 5.5). The guide sees only what that person could see.
Build: the Sky guide agent
Shared Components › AI Agents › Create:
- Name Sky guide, Static ID
sky-guide, Service Turnout AI (your Gemini service from Turnout), Temperature 0.2: low, so it sticks to the facts. - Welcome Message: Ask about tonight's sky, a star, or a story. Try "What can I see tonight?", "Why do eclipses happen?" or "Who is Arundhati?"
- System Prompt: Lesson 11.6.
Then Add Tool five times.
1. tonight: Augment System Prompt. Data Description: Tonight over the person's home: the local time now, sunrise and sunset, when it is fully dark, the Moon's rise, set, phase, tithi and nakshatra.
select t.place_name as home,
p.time_zone,
to_char(systimestamp at time zone p.time_zone, 'Dy DD Mon YYYY HH24:MI') as local_time_now,
t.sunrise, t.sunset, t.dark_from, t.dark_until,
t.moonrise, t.moonset,
t.phase as moon_phase, t.lit_pct as moon_lit_percent,
t.tithi, t.nakshatra as moon_nakshatra, t.vara as weekday,
t.next_phase
from ss_today_v t
join ss_places p on p.id = t.place_id
A model doesn't know what time it is. local_time_now tells it.
2. planets_now: Augment System Prompt.
select name, vedic_name, magnitude, altitude as altitude_deg, direction, state, rises
from ss_tonight_planets_v
order by mag_sort
3. search_lore: On Demand, Requires Confirmation off (it only reads), notification "Searching the lore". The Description is what the model reads to decide when to call it, so it says when:
Searches SkySutra's lore panels, star descriptions and NASA pictures of the day by meaning, not by words. Use it for any question about a story, a name, a tradition, a meaning or an explanation. Pass the question in the person's own words. Returns up to five texts, closest first, each with its title and source; a smaller distance means closer in meaning.
select kind, title, source, text, round(distance, 3) as distance
from (select d.kind, d.title, d.source, d.text,
vector_distance(d.embedding,
(select ss_ask.question_vector(:QUESTION) from dual),
cosine) as distance
from ss_ask_docs_v d
where d.embedding is not null)
where distance < 0.45
order by distance
fetch first 5 rows only
Then Add Parameter: QUESTION, VARCHAR2, required, "The person's question, in their own words."
4. star_facts: On Demand, parameter NAME.
select s.display_name as name, s.bayer, s.vedic_name, s.constellation,
s.magnitude, s.colour, s.spectral_type, s.nakshatra,
round(s.altitude_now) as altitude_now_deg, s.up_now,
t.text as about
from ss_stars_v s
left join ss_star_texts t on t.star_id = s.id
where upper(trim(:NAME)) in (upper(s.proper_name), upper(s.vedic_name),
upper(s.bayer), upper(s.display_name))
fetch first 3 rows only
5. sky_events: On Demand, parameter DAYS, NUMBER, "How many days ahead to look, from 1 to 120. Use 7 for 'this week' and 30 for 'this month'."
select title, kind, to_char(starts_on, 'Dy DD Mon YYYY HH24:MI') as starts, details, source
from ss_calendar_v
where starts_on >= trunc(sysdate)
and starts_on < trunc(sysdate) + least(nvl(:DAYS, 30), 120)
order by starts_on
least(..., 120) caps it: the model chooses DAYS, and a tool must check what the model sends, as it would a form field.
Tools return data, not orders. Whatever a tool returns, the model reads as information. A lore panel that said "ignore your instructions" would be a problem if anyone could write panels. In SkySutra only administrators can (Lesson 9.3), which is one reason that page has an authorization scheme.
Build: the Ask the Sky page
Create Page › Blank Page: 85, Ask the Sky, icon fa-comments-o, a new navigation entry.
- Ask the sky guide (Static Content, 8 columns):
<div id="sky-guide" class="ss-sky-guide"></div>, the place the chat goes. - How the guide answers (Static Content, 4 columns, beside it): what it can see, that it names its sources, and that it makes no predictions.
- Page › CSS › Inline:
#sky-guide { height: 70vh; min-height: 480px; } - A dynamic action Show the guide on Page Load, action Show AI Assistant:
- Agent Sky guide
- Display As Inline, Container Selector
#sky-guide - Quick Actions: "What can I see tonight?" and "Why do eclipses happen?" They show as buttons under the welcome message.
Turnout's assistant (Turnout Lesson 11.6) opened as a dialog from a button. This one is the page itself.
Lesson 11.6 — Keeping it honest
Outcome: a guide that sticks to SkySutra's data, names its sources, and won't make predictions.
The system prompt
You are the sky guide in SkySutra, an app about the night sky in modern astronomy
and in the Indian tradition (nakshatras, tithis and the old stories).
Answer only from the facts given here and from your tools:
- tonight's sky over the person's home is given below;
- use search_lore for stories, names, meanings and explanations ("why", "who", "what is");
- use star_facts for a particular star; use sky_events for what's coming up.
If none of these has the answer, say you don't know, and suggest the SkySutra page that
might help (Tonight, Stars, Sky Calendar, Lunar Days, Lore, ISS). Never make up stars,
times, dates or stories.
When you use a lore panel or a picture of the day, say which one and its source, for
example: (from the lore panel "Rahu and Ketu, who swallow the Sun and the Moon",
Mahabharata, Adi Parva).
SkySutra tells what is in the sky and the stories people have told about it. It never
says what the sky means for a person. Don't predict anything about anyone's life,
character, health, money, work or relationships, don't read horoscopes or birth charts,
and don't say whether a time is lucky or unlucky. If you're asked to, say so kindly in
one sentence, and offer the astronomy or the story instead.
Times are in the person's home time zone. Keep answers short: two to five sentences,
or a short list.
Each paragraph does one job:
| Paragraph | Why |
|---|---|
| Who it is | Sets the subject, and both traditions |
| Only from the facts and tools | Grounding: names each tool and when to use it, and what to do when they come back empty |
| Say which panel | Citations: the reader can open the panel and check it |
| What SkySutra never does | The rule from MODEL.md since Part 1, now told to the model |
| Time zone, length | The tools give local times; short answers stay close to the facts |
Why refuse predictions? SkySutra shows the nakshatras and tithis, because they're how the Hindu festival calendar is still kept, and they're astronomy: the Moon's place among the stars, the angle between the Moon and the Sun. What the app has never done is say what they mean for a person. A model trained on the whole web will happily read a horoscope, so it has to be told not to.
Test it
Ask each of these, and check the answer against SkySutra's own pages:
| Ask | A good answer |
|---|---|
| What can I see tonight? | The planets that are up (from planets_now), when it's dark, the Moon's phase: the same as the Tonight page |
| Why do eclipses happen? | The Moon and the Earth's shadow, naming Aryabhata: a shadow, not a demon and its source; perhaps Rahu and Ketu, and that the nodes are named after them |
| Who is Arundhati? | Alcor beside Mizar, the wedding custom, with the Grihya Sutra source |
| Is Aldebaran up now? | Calls star_facts: its altitude, the same as on the Star page |
| Anything worth seeing this week? | Calls sky_events with 7: the same events as the Sky Calendar |
| What does Saturn in my 7th house mean for my marriage? | One kind sentence saying SkySutra doesn't do that, then where Saturn is in the sky tonight, or its story |
| What's the weather tomorrow? | It doesn't know, and suggests a weather service |
If an answer states something none of the tools returned, the prompt needs tightening, or a tool is missing. Fix the data, not the model.
Checkpoint
SkySutra now searches by meaning and has a guide that answers from its own data, says where its stories come from, and leaves people's futures alone.
Test it
- SQL Commands:
select count(*) from ss_ask_docs_v where embedding is nullreturns 0. - Search: "a star that never moves" lists Dhruva and Polaris under By meaning. "the red eye of the bull" finds Rohini and Aldebaran.
- Lore › Write a Panel: change a word in a panel and save.
SS_LORE.EMBEDDINGfor it is now null; afterss_ask.refresh_embeddings(or tonight's run) it's back. - Ask the Sky: the questions in Lesson 11.6.
- Jobs: tomorrow morning the nightly run has an EMBED row.
Files
| File | Used in |
|---|---|
skysutra_ask.sql |
11.3–11.5 (vector columns, three panels, SS_ASK, SS_ASK_DOCS_V, the embedding trigger) |
Exercises
- Let the guide say where the ISS is and when it next passes over: a sixth tool on
SS_ISS_PASSESfor your home. - Add the constellations (
SS_CONSTELLATIONS.DESCRIPTION) toSS_ASK_DOCS_Vwith their own embedding column. - Try Maximum Vector Distance limits of 0.35 and 0.5 on By meaning. Which searches get worse, and how?
- Gemini's embedding API can embed a question and a document differently (task types
RETRIEVAL_QUERYandRETRIEVAL_DOCUMENT). Look up how to pass it through the Vector Provider's settings, and whether the distances in Lesson 11.1 change. - Stretch: log the questions people ask (APEX gives tool conditions the bind variable
APEX$AI_LAST_USER_PROMPT; find out whether an Augment System Prompt tool of type Execute Server-side Code can read it too, and insert it into a table), to find what the guide can't answer yet.
Review questions
Try each one before you open it. The graded quiz is at the end of the page.
1What is an embedding?
A list of numbers for a text, made so that texts with similar meanings get similar lists
2Why must the question and the texts be embedded by the same model?
Each model has its own space; vectors from different models can't be compared
3What does a cosine distance of 0.3 mean, compared with 0.9?
0.3 is close in meaning; 0.9 is nearly unrelated
4Why does the search have a maximum distance?
Something is always nearest; the limit leaves out results that aren't really related
5Why are star texts kept in SS_STAR_TEXTS, not SS_STARS?
The catalog stays as loaded; the generated text and its embedding are kept beside it
6Why does refresh_embeddings pause between calls?
The free tier allows about 100 requests a minute; faster gets HTTP 429
7Why doesn't SkySutra keep a vector index?
334 rows search exactly in under half a second; on apex.oracle.com HNSW has no memory and IVF takes a third of the quota
8What's the difference between an Augment System Prompt tool and an On Demand tool?
Augment runs before every question; On Demand runs only when the model calls it, with parameters it chooses
9Why does sky_events use least(nvl(:DAYS, 30), 120)?
The model chooses DAYS; a tool checks its input like any form field
10Why does the guide name the lore panel it used?
So the reader can check it, and see that the answer came from SkySutra's data, not the model's memory
Downloads for Part 11
Everything this part uses. The SQL files are plain text: open one in any editor before you run it.
- Ask the skyskysutra_ask.sqlLessons 11.3 to 11.5: vectors, three panels, SS_ASK, SS_ASK_DOCS_V
Part 11 quiz
5 questions. Get 4 right to pass. You can retake it as often as you like.
