Що формат схеми не може розповісти вашому агенту: формат відкритих знань у BigQuery
Експеримент з BigQuery та Gemini ADK: пакети у форматі Open Knowledge Format у Git надали агенту те, чого не могла схема — стабілізували відповіді та усунули помилку GMV у розмірі 29%.
Джерела: **What Your Schema Cannot Tell Your Agent, Open Knowledge Format Can**
Файл інструкцій, який ніхто не володіє
Як тільки ШІ-агент під’єднується до сховища даних, рано чи пізно з’являється певний документ. Спочатку це три-чотири акуратні рядки в системних інструкціях: не враховувати повернення коштів у дохід, фінансовий рік починається у квітні, ігнорувати внутрішні тестові рахунки. З часом кількість рядків у файлі зростає до приблизно чотирьохсот. Лише один інженер має сміливість його редагувати, тоді як аналітик, який насправді відповідає за визначення показників, навіть ніколи його не бачив.
Величезний розмір — це ще не найгірше. Проблема в тому, що ніхто не може підтримувати цей документ у належному стані. У рядку інструкцій немає вказаного власника, процесу перегляду чи терміну дії. Якщо відділ фінансів у січні оприлюднить нову політику щодо доходів, інженер про це ніколи не дізнається, аналітик не зможе змінити код, і інструкції самі по собі не закінчують свою дію.
Під впливом такого тертя знання стають застарілими або так і не формуються повністю, тож агент працює на основі неповних визначень та невисловлених припущень. А прогалини не призводять до очевидних невдач агента – немає збоїв, помилок синтаксису SQL чи порожніх результатів. Агент аналізує первинну структуру даних, вгадує відсутні бізнес-правила та надає охайні, правдоподібні дані, які потім потрапляють на панель керування керівництва. Дратівлива проблема технічного обслуговування тихо перетворюється на проблему точності.
Формат відкритих знань (OKF) спрямований саме на усунення цієї прогалини. Бізнес-правила більше не зберігаються у рядках запитів чи непрозорих векторних базах даних, а стають звичайними файлами Markdown із YAML-метаданими, які зберігаються в Git. Власність на дані стає чітко визначеною, зміни вносяться через pull requests, а терміни закінчення дії є видимими як для аналітиків, так і для інженерів.
Скептики стверджують, що потужніші моделі та більші вікна контексту роблять це марним, адже агенту можна передати сирі схеми та документи, і він зможе самостійно вивести визначення. Компактний експеримент перевірив, як змінюється ціна неповних знань та як впливають зміни в основі OKF. Для цього використовувалися дві таблиці BigQuery, одне бізнес-запитання та агент, створений за допомогою Google’s Agent Development Kit (ADK), у різних налаштуваннях знань.
Сценарій тесту
Це онлайн-ринок із таблицями orders та payments, а запитання — це те, що менеджер з продуктів міг би поставити щоранку у понеділок.
Скільки обсягу GMV принесли успішно здійснені платежі QRIS у регіоні Jabodetabek у липні 2026 року?
Три терміни потребують пояснення. GMV означає загальну вартість товарів, тобто суму товарів, проданих протягом певного періоду. QRIS – це національний стандарт QR-оплат у Індонезії. Jabodetabek – це назва метрополійного регіону Джакарти: Джакарта, Богор, Депок, Тангеранг та Бекасі.
Усі необхідні для відповіді на запитання колонки існують у базі даних, проте схема не може на нього відповісти. У запитанні приховано чотири рішення, кожне з яких було прийняте людиною:
- Статус, який підтверджує надходження грошей. Оплати містять як
status_code, так іtransaction_status. Привабливий кодstatus_code = 200свідчить лише про те, що виклик шлюзу відбувся успішно, включаючи випадки скасування. Гроші надійшли на рахунок продавця лише тоді, колиtransaction_status = 'settlement'. Значення, використані для прикладу, скопійовані з публічного API реального платіжного шлюзу.
settlement доки не буде оброблено повернення коштів. Тому фільтр потребує умови orders.order_status = 'completed', проте схема ніде про це не згадує.orders.order_ts, отримаємо одну суму, а якщо за payments.settlement_ts — іншу, тому замовлення від 31 липня може потрапити до серпня.region не існує. Jabodetabek доводиться вказувати як тринадцять окремих значень destination_city, а Kota та Kab. позначають два різні місця, а не різні варіанти написання.Помилка в будь-якому з цих елементів все одно призводить до виконання запиту. Не виникає жодної помилки, жодних порожніх результатів та жодних слідів виконання коду — лише належним чином сформоване число, призначене для подальшої обробки.
Контекст проти знань
Очевидним рішенням є записати чотири рішення та передати їх моделі разом із схемою. Здатна модель часто може на основі цього дійти правильної відповіді. Але вона робить це знову під час наступної сесії, і ніщо не змушує дві сесії дійти однакового висновку. Визначення GMV не зберігається ніде; його знову обчислюють за потреби процесом, який не пам’ятає висновків попереднього дня.
Ця різниця визначає всю логіку аргументації. Контекст — це матеріал, над яким модель робить висновки. Знання — це висновок, до якого хтось вже дійшов та схвалив його. Процес міркувань оплачується щоразу під час кожної сесії, тоді як письмовий висновок оплачується лише один раз.
Але де це записується? Не у рядку запиту, з вищезазначених причин. База даних чи сховище векторів забезпечує місце для зберігання, але усуває можливість перегляду, оскільки аналітик не буде створювати запит на зміни до сховища ембеддингів.
Що таке OKF
Пропозиція Google — це OKF, і вона навмисно проста. Пакет — це просто папка з файлами Markdown із YAML-передметом, яка поширюється через Git, без використання бази даних, сховища векторів чи SDK. Один файл описує одну концепцію, type — єдиний обов’язковий ключ передмета, а звичайні посилання Markdown з’єднують файли, що перетворює пакет на граф замість простої купки файлів.
Більш корисним є запитання про те, що знаходиться всередині. Типи не реєструються централізовано, і користувачі змушені миритися з тими, яких вони не розпізнають, тому наведений нижче текст є початковим словниковим запасом, а не схемою. У двох опублікованих зразках пакетів від Google каталоги виглядають так:
tables/таdatasets/вказують, що це за дані: що означає кожен рядок, як поєднуються стовпці, які значення enum існують. Цю частину можна отримати з сховища даних, як показано на наступному кроці.metrics/пояснює, що означає певне число. Сюди входить GMV та його чотири умови.references/містить територіальні знання, які не мають жодних стовпців, наприклад, інформацію про тринадцять міст Jabodetabek, таксономію продуктів чи список внутрішніх тестових облікових записів.
policies/ зберігає документ, який є джерелом істини, на яке посилається визначення, наприклад, правило визнання доходів, яке відділ фінансів оновлює щороку у січні.computations/ та attesters/ зберігають схвалений SQL-код для певного показника разом із кодом, який перевіряє, чи результат справді був отриманий за допомогою цього коду.skills/ містить процедури: коли щось використовувати, його передумови та кроки виконання.Чесний критерій для визначення того, чи щось належить до пакету: чи можна було б пояснити це новому аналітику протягом його першого тижня? Якщо так, то це потрапляє до файлу.
Версія v0.2 специфікації додає три поля frontmatter, які безпосередньо вирішують проблему початкового стану. Поле sources вказує, з чого було отримано певне поняття. Поле verified збирає події підтвердження у форматі {by, at}, а поняття без таких подій означається як unverified. Поле stale_after визначає абсолютний момент, після якого поняття вважається застарілим. Разом вони забезпечують визначення власника, процедуру перевірки та термін дії, причому все це знаходиться в одному файлі, який аналітик може редагувати, а рецензент — читати у вигляді pull request.
Чи може команда просто створити власну структуру папок? Це можливо, але спільна термінологія дозволяє робити те, чого не може приватна структура. Описаний нижче агент-референт може підготувати половину набору даних, оскільки він вже розуміє, що таке концепція BigQuery Table. Рецензенти знають, до чого зобов’язують їх підписи verified. Формат також є нейтральним щодо постачальника, не прив’язаним до жодного агента, фреймворку чи постачальника моделей, тож знання, записані сьогодні, залишаються актуальними після зміни технологічного стеку.
Багато читачів вважатимуть, що це навичка агента з додатковими кроками, і це перетин є настільки значним, що потребує розгляду. OKF включає type: Skill як просто один тип концепції. У зразковому пакеті Google файл skills/run-on-bq.md структурований як «Коли використовувати», «Передумови» та «Кроки», що нагадує звичайну навичку агента. Навички є підмножиною OKF, а не синонімом. Навичка інструктує агента, як діяти, тоді як OKF пояснює, що означають терміни, хто останнім їх перевіряв та коли відповідь стає неактуальною. Її використання складається з трьох кроків: створити те, що вже відомо складу, написати те, що відомо лише людям, та дозволити агенту прочитати обидва варіанти.
Перший крок: створити те, що склад вже знає
Якщо таблиця BigQuery вже задокументована, немає потреби вручну щось писати. У репозиторії OKF є агент для створення контенту, і один командний запит перетворює набір даних BigQuery на пакет.
uv tool install git+https://github.com/GoogleCloudPlatform/open-knowledge-format
reference-agent enrich \
--source bq \
--dataset "$PROJECT_ID.marketplace" \
--out bundles/generated \
--no-web
Він генерує документ datasets/, документ tables/ для кожної таблиці та оновлені файли index.md у всіх місцях. Додавання параметра --no-web пропускає необов’язковий крок об’єднання існуючої документації, що робить перший запуск повторюваним.
Результат кращий, ніж очікувалося. У ньому описана структура кожної таблиці, знайдений відповідний ключ з’єднання та задокументований перелік статусів. Рідко створюються документи типу Metric чи Reference, оскільки джерело у форматі BigQuery надає лише концепції наборів даних та таблиць, і нічого більше. Межі визначаються тим, що сам склад даних вже записує про себе.
Перевірте це, перш ніж припускати, що інші склади будуть поводитися так само. Значення статусу з’являються у створених документах лише тому, що хтось додав їх у описи стовпців у DDL. Якщо спрямувати генератор на прості, недокументовані стовпці, у нього буде значно менше інформації для роботи.
Створені документи можуть містити помилки, і в експерименті є приклад цього. Під час однієї з обробок було створено шаблон запиту з sum(t2.total_price), але такого стовпця не існує. Фактична назва — gross_amount, яка вже вказана у власній таблиці схеми того ж файлу на два розділи раніше. Виконання цього фрагмента завершується невдачею.
Саме тут корисною є опція verified. Кожен створений документ записує generated: {by: reference_agent/...}, але не містить запису про подію verified, тому сам формат позначає його як неперевірений. Пакет документів фіксує власне походження, а розділення результатів роботи машини від людських рішень не вимагає жодних змін.
Другий етап: запишіть те, що знають лише люди
Два вручну написані файли в одному пакеті та у тому ж форматі достатні для фіксації чотирьох прихованих рішень. metrics/gmv.md є більш важливим з них, а його frontmatter показує, де знаходяться поля довіри.
---
type: Metric
title: Gross Merchandise Value (GMV)
description: Total settled transaction value for a period in IDR, excluding cancellations, reversals and returns.
tags: [metrics, finance, headline-metric]
generated: { by: human:analytics-lead@marketplace.example, at: 2026-09-01T09:00:00+07:00 }
verified:
- { by: human:analytics-lead@marketplace.example, at: 2026-09-01T11:00:00+07:00 }
stale_after: 2027-01-31T00:00:00+07:00
---
У тексті детально описано всі чотири умови та чітко зазначено, що схема не задовольняє жодної з них. Завершення операції означає transaction_status = 'settlement', а не status_code = 200. Замовлення мають мати статус order_status = 'completed' без можливості скасування. Для цього періоду критерієм є settlement_ts; перевірка здійснюється у діапазоні з напіввідкритими кінцями з чітким зсувом +07: від 2026-07-01 00:00:00+07 включно до 2026-08-01 00:00:00+07 виключно. Використання оператора BETWEEN зі звичайними рядками дат призводить до того, що останній день не береться до уваги, а кожен часовий маркер розглядається як UTC, тому кожна межа зсувається на сім годин.
Значення stale_after справді виконує важливу функцію. Воно вирішує проблему, яка виникає у січні, за один рядок: щороку у січні відділ фінансів оновлює правила визнання доходів, тож визначення стає застарілим 31 січня 2027 року, і агенту наказують повідомляти про це замість того, щоб використовувати актуальні цифри.
У другому файлі references/jabodetabek.md перераховано тринадцять точних значень параметра destination_city та попереджається про використання оператора LIKE '%Jakarta%', який може проігнорувати вісім з них.
Саме тут співпраця стає конкретною. Переопределення показника GMV тепер здійснюється через pull request до файлу у форматі Markdown, який може переглянути відповідальний аналітик, без необхідності негайної редагування чи перезавантаження сервера. У зразковому наборі Google навіть зберігає застарілий файл gross-margin-legacy.md, щоб історичні результати залишалися відтворюваними — що неможливо зробити за допомогою простих та швидких форматів тексту.
Третя частина: агент, який їх читає
Для отримання доступу до даних агент використовує керований сервер MCP BigQuery від Google Cloud за адресою https://bigquery.googleapis.com/mcp, який активується після увімкнення API BigQuery для проекту. Нічого не потрібно розгортати.
Єдиний користувацький код у проекті — це інструмент, який читає файл.
def read_okf_concept(path: str) -> dict:
"""Read one OKF concept document from the local bundle.
Args:
path: Path relative to the bundle root, for example "metrics/gmv.md".
Call this with "index.md" first to find which concepts exist.
Returns:
'status', and on success 'content' with the raw markdown and frontmatter.
"""
doc = (OKF_BUNDLE / path.lstrip("/")).resolve()
# `path` is model-controlled. Block anything that escapes the bundle.
if not doc.is_relative_to(OKF_BUNDLE) or not doc.is_file():
return {"status": "error", "error": f"no concept at {path}"}
return {"status": "success", "content": doc.read_text(encoding="utf-8")}
А потім сам агент визначається.
bigquery_mcp = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://bigquery.googleapis.com/mcp",
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
},
sse_read_timeout=300.0,
),
header_provider=_bigquery_auth_header,
tool_filter=[
"list_dataset_ids",
"list_table_ids",
"get_dataset_info",
"get_table_info",
"execute_sql_readonly",
],
)
root_agent = Agent(
name="marketplace_analytics_agent",
model="gemini-3.7-flash",
instruction=(
"Before writing any SQL: call read_okf_concept('index.md'), then read every "
"concept the question touches: the metric, the payment status, the geography.\n"
"Use only the filters, joins and period cuts those concepts define. "
"Never invent a status value or a metric formula.\n"
f"Run queries with execute_sql_readonly, passing projectId='{PROJECT_ID}'.\n"
"Show the SQL you ran, and name the concepts you relied on."
),
tools=[read_okf_concept, bigquery_mcp],
)
Потім експеримент запустив цю розмову.
Що насправді сталося
Агент, запит та початкові дані залишалися незмінними у всіх конфігураціях. Змінювався лише каталог, на який вказував OKF_BUNDLE, причому кожна конфігурація працювала чотири рази.
Без шару знань, коли інструмент читання вказував на порожню папку та єдиним джерелом інформації була інтроспекція схеми сервера MCP, чотири запуски одного й того ж запиту дали три різні числа: IDR 371,251,000, 333,020,000 та 371,886,000. Найвище значення перевищувало істинне на 29%.
З використанням створеного пакету та одного запуску функції reference-agent enrich числа більше не змінювалися. Щоразу отримувалося значення IDR 329,590,000 — стабільне та відтворюване, але на 15% завищене.
Додавши два вручну створені концепти, щоразу отримувалося значення IDR 286,740,000. Це правильний результат, і у відповіді вказуються концепти, на які спиралися.
Прогрес є очевидним: точність відповідей залежить від того, наскільки точними є надані знання. Без додаткових шарів агент припускається відсутню логіку на кожному кроці, що призводить до похибок у розмірі до 29%. Контекст схеми, отриманий за допомогою машин, усуває цю нестабільність та знижує похибки до 15%, але він не може виконати завдання самостійно, оскільки залишкова проблема полягає у бізнес-політиці, а не у технічних аспектах. Лише чіткі, перевірені людиною визначення неписаних правил, такі як точні межі часових міток та статуси замовлень, дозволяють агенту надійно досягати точних результатів.
Найбільшим фактором, що впливає на точність, виявилося не довше запитання та не більша модель. Це структуровані, контрольовані за версіями знання про домен, розміщені там, де закінчується схема бази даних.
Що цей підхід не вирішує
Функціонування залежить від інструкцій, тому модель може їх проігнорувати, і ніщо не заважає писати SQL до того, як буде прочитано певний концепт. Той, хто хоче отримати жорстку гарантію, може додати ADK before_tool_callback, який відхилятиме запит, якщо протягом цього кроку жоден концепт не був прочитаний.
Markdown також не забезпечує перевірки схеми. Аналітик може додати неправильно сформований концепт, а OKF навмисно залишається поблажливим, вимагаючи від користувачів толерувати невідомі типи та недійсні посилання. Справжній захист забезпечується перевіркою коду у запиті на інтеграцію, а також параметрами verified та stale_after, які розкривають агенту неперевірені чи прострочені визначення під час їх читання. У специфікації рівні довіри позначаються як рекомендаційні сигнали, тож їх використання залежить від того, хто створює агента. Цей агент їх читає, і інші також повинні це робити.
Ці показники також не є критеріями оцінки: одне запитання, одна модель та чотири запуски для кожної налаштовки на спеціально підібраних даних, створених для створення „пасток“. Вони демонструють механізм, але нічого не говорять про середній вплив OKF на точність.
Урок, який залишається актуальним
Цей механізм все ще має цінність. Коли агент з обробки даних робить помилку, відповідь не виглядає неправильною. Жодні винятки не виникають, жодних результатів не повертається порожніх, і ніщо не стає червоним. SQL-запит є коректним, а графік виглядає обґрунтовано, тому він потрапляє на слайд задовго до того, як хтось почне сумніватися в ньому.
Найбільшою небезпекою є не хаотичні помилки. Без будь-яких орієнтирів агент повертав кожного разу нове число, що легко можна було помітити протягом одного дня. Маючи лише половину необхідних знань, він повертав одне й те саме число кожного разу, водночас невимушено згадуючи альтернативу, яку не міг обґрунтувати як кращу. Стабільний результат здається доказом правильності, але насправді нічого не доводить.
Краща модель не обов’язково є тим, що заповнює прогалину. Одна команда зафіксувала все, що склад може сказати про себе, і зупинилась саме там, де починаються людські рішення: часова мітка, яка визначає період, статуси замовлень, які їх підраховують, статус, який означає надходження грошей. Це вибір, а не факт, і його слід зафіксувати один раз тим, хто його зробив, у місці, де згодом можна буде звернутися до цієї особи.
Хорошим способом початку є проєкт меншого масштабу. Виберіть той показник, через який команда постійно сперечається, той, щодо якого різняться два дашборди, і який аналітик може дати відповідь. Запустіть генератор на відповідному наборі даних, а потім запишіть визначення разом із ім’ям власника в один файл та один запит на інтеграцію. Саме ця дискусія є справжньою роботою. Формат лише надає їй форму та фіксує, хто погодився та коли вона закінчується. Інструменти — це найнудніша частина. Цікаво те, що відбувається, коли аналітик та інженер мають дійти згоди щодо одного речення та зафіксувати його, а також які приховані рішення виявляються їхніми.
Пов’язана література
- Практичні нотатки: GUI для бази даних, який також контролює те, що дозволено вашому AI-агенту — Покрокове керівництво з використання Практичних нотаток: GUI для бази даних, який також контролює те, що дозволено вашому AI-агенту: контракти, перевірки та шаблони коду для команд, які впроваджують цю модель.
- Запуск Deep Agents у локальному OpenSandbox Backend — Як під’єднати OpenSandbox до Deep Agents через BaseSandbox, синхронізувати та асинхронно виконувати код, а також безпечно запускати агента для аналізу даних у локальному Docker-сандбоксі.