vegman.dev
All posts

When the Input Is a Table, You Don't Need RAG

2026-02-05case

You export a table from your maintenance system: vessel, unit, status, criticality. You hand it to an AI assistant and ask it to work out what to fix first. It replies with a paragraph: «It is recommended to pay attention to high-criticality units and prioritize their maintenance». Formally, there's nothing to argue with. You can't send it to a mechanic.

From there almost everyone goes down the same road: «so we need RAG». RAG (retrieval-augmented generation) is a setup where, before answering, the model searches your database for relevant chunks of text and answers with them in hand. On regulations, manuals and correspondence it works beautifully. On a table it doesn't work, and the settings aren't the problem.

This article is about how to tell, in a minute, a «find by meaning» task from a «count and group» task, and what to build in the second case. By the end you'll stop bolting vector search onto problems that need a filter and a group-by.

A quick test: is the agent working, or just talking nicely

I have a personal test for agent maturity. Agent here means a model that has tools: it can read a file, run code, write a new file. Not just a chat window.

The test is this: give it a table, not a knowledge base. And ask it not to talk about the data, but to produce a document you can send into work.

The domain I use is a teaching one, but true to life: a fleet, equipment, maintenance. An ordinary export, four columns: vessel, unit, status, criticality. Nothing exotic - every service department has this table, it's just called something different.

The scenario fits into 15 minutes:

  • take the source file read-only
  • find everything with the statuses «failure» and «needs maintenance»
  • group it by vessel
  • assemble the table into a separate report file
  • at the end, give a short conclusion: what's burning first by criticality.

Then just look at what came out. If the output is the text «it is recommended to pay attention» - what you have in front of you is a conversationalist. If the output is a file you can open and hand out to people - a doer. The difference isn't the model's eloquence, it's what it does with the data at all.

Задание агенту (15 минут):

Файл «выгрузка.csv» - открывать ТОЛЬКО на чтение.

1. Отобрать строки, где статус = «отказ» или «требует обслуживания».
2. Сгруппировать результат по колонке «судно».
3. Внутри каждой группы отсортировать по критичности.
4. Записать таблицу в НОВЫЙ файл «отчет.csv». Исходник не изменять.
5. В конце - короткий вывод: что горит первым по критичности
   и по какому правилу это решено.

На выходе жду файл, а не ответ.
Тест на зрелость: задание сформулировано как операция над файлами, а не как вопрос про данные

What happens when the input is a table

To see why RAG misses here, look at its mechanics. At the core is vector search: each chunk of text is turned in advance into a set of numbers (a «vector»), and chunks close in meaning end up close to each other. You ask a question - the system finds the most similar fragments and hands them to the model.

The key word is similar. Vector search answers exactly one question: «what resembles my query». That's an excellent question to ask of documents.

But a table gets asked completely different questions:

  • how many
  • who has more
  • show me everything for the period
  • group by this column.

Similar rows don't add up to the right total. «All units in failure» is not «the ten rows most similar to the word failure», it's an exact match on a column, every last one of them. Semantic search will hand you a plausible selection, and you won't even notice what isn't in it: a missing row leaves no trace. In documents, incompleteness is an annoyance. In a work plan, incompleteness is a unit nobody drove out to fix.

Hence the main conclusion, the one this was all for: when the input is a table, the agent isn't working as a text search engine, it's working as an executor of small operations on data. Filter. Grouping. Prioritization. This isn't magic and it isn't «understanding» - it's selection and sorting that someone has to carry out honestly, down to the last row.

flowchart LR
  A["Исходная выгрузка: судно, узел, статус, критичность"] -->|только чтение| B["Фильтр: отказ и требует обслуживания"]
  B --> C["Группировка по судну"]
  C --> D["Сортировка по критичности"]
  D --> E["Новый файл-отчет"]
  E --> F["Короткий вывод: что горит первым"]
Путь данных в таком отчете: чтение, фильтр, группировка, сортировка, новый файл. Векторному поиску тут негде встать

What a correct solution looks like

When the test passes, I see three things - and they matter more than any words in the answer.

First: the agent doesn't reply, it makes a selection. It doesn't retell the table in words - it processes it and packs the result into a report. There's text at the end, but it's secondary: it's a caption under the table, not a substitute for it.

Second: the source data stays untouched. The source is opened read-only, the result is written to a new file. This isn't a formality and it isn't paranoia. A tool that can silently overwrite your working export will never earn trust - and rightly so. One ruined source file closes the subject of automation in a department for a long time.

Third: the output is a work plan, not a list of advice. The difference is checked in one move: can you forward this file to the person who'll be doing the repair, and will they understand what to do. «It is recommended to strengthen monitoring» can't be forwarded. A filtered and sorted «vessel - unit - status - criticality» table can.

Where this scales from here

The good news: there's no chasm between a prototype like this and something serious. The whole extension is the same operations on data, there are just more of them.

  • Priority rules. Critical plus failure equals red zone. The rule is stated in one line and from then on applied to every row the same way, without «in my opinion».
  • History and trends. Once several reports have piled up, a new question appears: what breaks most often. That's grouping again, only now by time as well.
  • Scheduled runs and notifications. The report is assembled on a schedule, and the person responsible gets not a general digest but their own slice: here's what's burning on your side.

Notice what isn't on this list. Not one item requires vector search. Not one.

Put it to work

What to check in practice

Take an hour and do this on your own data - your own, that's where all the truth is.

  • Export a real table from your system and keep a few columns: object, item, status, importance. Your names, the same meaning.
  • State the task as work with files, not as a question. What to read, what to select, how to group, where to write it.
  • Write out two conditions explicitly: the source file is read-only, the result goes into a new file.
  • Ask for a short conclusion at the end, and be sure to ask by what rule it was arrived at.
  • Time it. If there's no file after 15 minutes, the task was phrased as a conversation, not as an operation on data.
  • Check one or two groups by hand: open the source, filter it yourself, compare with the rows in the report. It's boring and it's the only way to find out the truth.

How to know whether it worked

There's one criterion and it's binary: the resulting file can be sent into work without rewriting. Not «contains useful considerations», but - open it, read it, hand it out to people.

It's checked with three questions:

  • Is the source file unchanged?
  • Did your manual selection match the agent's - not one extra row, not one missing?
  • Does the conclusion at the end rest on a rule you can say out loud?

If all three are «yes», what you have isn't a chatbot, it's a small working tool. And notice what you didn't assemble to get there: no vector database, no index, no RAG. Simply because a table isn't about search by meaning. It's about data.