Reducing Ambiguity in a SQL Agent using a domain glossary and RAG


Reducing “wrongness” using better prompts

In my previous post I described writing a SQL Agent to allow me to query a database using human-readable language. My first iterations were promising but had problems with “wrongness”: the agent would pick the wrong fields to try to answer the question, or it would try to query on fields that did not even exist. Without any further help, it could find the available fields by querying the schema, but it could not discern meaning. While this might be a way of finding and eventually fixing confusing table names or column names (I’m having recurring nightmares of a database with 3 description columns, all with misspelled column names), realistically you’re going to have to work with what you already have.

Hence in my agent I’ve added a domain glossary to give guidance to the SQL agent. Now the agent has a prompt with a description of what all of the fields actually mean, to bridge the gap between human language questions and SQL queries. The glossary includes domain-specific information that is not found in the schema, such as metadata about the dataset and how to perform joins.

That’s not quite enough in my use case, however, as tests revealed that the agent got similar values confused even when I provided a description in the glossary. Replacing the indicator list with a [retrieval augmented generation] tool fixed the problem for those tests.

What is a Domain Glossary and Why Do We Need It?

My SQL Agent already has all of the tools it needs to introspect the database schema, so any information held in the schema itself is already available to it. What about information that is not included in the schema? The schema does not tell us the name and source of the data set, the SQL dialect we are using, the indicator codes that match indicator values with meaning, or even things like a trailing space in the region label for sub-Saharan Africa (Sub-Saharan Africa having that extra space can make queries break when you need to select for that region). If the agent is going to know these things, they have to be in the context.

The way we include this information in the prompt is with a domain glossary, or as Collibra describes it a business glossary. In our case the domain glossary contains the following sections:

  • a prelude including the domain name (World Bank World Development Indicators) and the SQL dialect (SQLite)
  • the indicator codes of WDI data, such as infant and maternal mortality and GDP, which are the subject of the question
  • a tail of value-level facts: region and income-group names. This includes the quirks like the trailing space in Sub-Saharan Africa , country-name disambiguation (South Korea is “Korea, Rep.” for example), and JOIN shapes for the database’s normalized schema

Importantly, none of this stuff is derivable from the schema, which the agent can already access. It’s the domain knowledge that bridges the gap between what the LLM knows from its training data and what it can derive from the database.

Why do we need RAG?

In my first runs, I included all 28 indicators in a static domain glossary, so the LLM had that information in every prompt. The full list of indicators made the meaning of each ambiguous to the LLM, however. Particularly these indicators tripped up the agent:

  • “SH.DYN.MORT”, “Mortality rate, under-5 (per 1,000 live births)”, “Health”
  • “SH.STA.MMRT”, “Maternal mortality ratio (modeled estimate, per 100,000 live births)”, “Health”
  • “SP.DYN.IMRT.IN”, “Mortality rate, infant (per 1,000 live births)”, “Health”

For questions about under-5 mortality it would choose any of these three values as the relevant indicator, returning inaccurate results.

The solution to this problem is using RAG to find the most relevant indicators, removing the ambiguity we introduced by adding the entire list of indicators to the prompt. Particularly in this case we are creating embeddings of the indicator names and of the input question, then running cosine similarity to rank the top 8 indicators for the input question. Why cosine similarity? The higher the cosine, the smaller the angle between the vectors, so the embeddings with the highest cosine are most closely semantically related.

In the domain glossary, I replaced the static indicators section with a dynamic indicators section using the top 8 indicators by cosine similarity. It’s important to note that the RAG step isn’t solving for a problem with context size. The entire indicator list fits easily in the context, but does not sufficiently weight the importance of each indicator to the question. The RAG step removes some indicators that we don’t need and ranks the indicators that we do include.

Testing in my SQL agent showed the improvement. My 5-question test suite intermittently failed on question 5, “Which low-income countries had electricity access grow by more than 30 percentage points between 2010 and 2020, and how did their under-5 mortality change over the same period?” The RAG improvement fixed the failure and also might have sped up the query. Below is a table showing the results: each question runs 3 trials, stability is how often the trials agree with each other, correctness is how often they match a hand-graded reference step, and Q5 is stability/correctness for question 5 specifically.

ConfigSuccessLatencyStableCorrectQ5
Full 28-code glossary15/1515.6 s93%87%67/33
Top-k retrieval15/1514.6 s100%100%100/100

The takeaway

LLM agents are not able to understand domain information about your data sets with just schema tools. Using a domain glossary you can close the gap between the general knowledge the LLM brings in from its training data and the specific domain knowledge it needs to complete business-specific tasks. Even with a domain glossary, you can have trouble telling the LLM which data is most important to the question. Using RAG you can clear up ambiguity in the context and get the results you want.

Comments

View & reply on Bluesky →

No comments yet. Be the first to reply on Bluesky!