Chain-of-Table: рассуждения через трансформацию таблиц

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

Chain-of-Table (цепочка таблиц) — техника, при которой модель отвечает на вопросы по табличным данным через пошаговую трансформацию самой таблицы. Вместо того чтобы рассуждать текстом о строках и столбцах, модель на каждом шаге выполняет одну атомарную операцию: фильтрует строки, выбирает столбцы, сортирует, группирует или добавляет вычисляемый столбец.

В 2026 году Chain-of-Table полезнее понимать не как замену SQL или BI-инструментам, а как промежуточный reasoning-формат для небольших и средних таблиц прямо внутри LLM-пайплайна. Он хорош там, где структура данных уже табличная, но тащить полноценный query engine ещё рано или неудобно.

Представьте огромную таблицу в Excel. Вам задали вопрос: «Кто из сотрудников в отделе маркетинга получил премию больше 50 000?». Вы не пытаетесь ответить, просто глядя на всю таблицу. Вместо этого вы: 1) фильтруете по отделу «маркетинг», 2) фильтруете по премии > 50 000, 3) смотрите оставшиеся строки. Chain-of-Table работает точно так же — модель пошагово «сужает» таблицу, пока ответ не станет очевидным.

Суть в двух словах

LLM плохо работают с таблицами напрямую: теряют строки, путают столбцы, ошибаются в подсчётах. Обычный Chain of Thought не помогает — модель рассуждает текстом о структурированных данных, и ошибки накапливаются.

Chain-of-Table предлагает другой подход: вместо текстовых рассуждений — пошаговая трансформация таблицы. Модель планирует цепочку табличных операций и на каждом шаге создаёт новую, упрощённую таблицу. Финальная таблица — это ответ.

Доступные операции:

  • select_column — оставить только нужные столбцы
  • select_row — отфильтровать строки по условию
  • group_by — сгруппировать и агрегировать
  • sort_by — отсортировать по столбцу
  • add_column — добавить вычисляемый столбец
ПромптClaude
Используй подход Chain-of-Table.

Таблица:
| Город | Население (млн) | Страна | ВВП на душу ($) |
|-------|----------------|--------|----------------|
| Токио | 13.96 | Япония | 40 247 |
| Дели | 32.94 | Индия | 2 257 |
| Шанхай | 28.52 | Китай | 12 556 |
| Сан-Паулу | 22.43 | Бразилия | 8 918 |
| Мехико | 21.78 | Мексика | 10 046 |
| Каир | 22.18 | Египет | 3 699 |
| Мумбаи | 21.67 | Индия | 2 257 |
| Пекин | 21.54 | Китай | 12 556 |

Вопрос: В каких городах Китая население превышает 25 миллионов?
Ответ модели

Шаг 1: select_row(Страна == 'Китай')

Город Население (млн) Страна ВВП на душу ($)
Шанхай 28.52 Китай 12 556
Пекин 21.54 Китай 12 556

Шаг 2: select_row(Население > 25)

Город Население (млн) Страна ВВП на душу ($)
Шанхай 28.52 Китай 12 556

Шаг 3: select_column(Город)

Город
Шанхай

Ответ: Шанхай — единственный город Китая с населением более 25 миллионов.

Обычный промпт
Вопрос: В каких городах Китая население > 25 млн? Давай посмотрим... Китай — это Шанхай и Пекин. Шанхай — 28.52 млн, это больше 25. Пекин — 21.54 млн, это меньше 25. Хотя нет, подожди... 21.54 — больше или меньше 25? Кажется, меньше. Ответ: Шанхай. (Верно, но на больших таблицах модель начнёт путаться)
Chain-of-Table
Шаг 1: select_row(Страна == 'Китай') → 2 строки Шаг 2: select_row(Население > 25) → 1 строка Шаг 3: select_column(Город) → Шанхай Каждый шаг — атомарная операция. Ошибки менее вероятны.
Chain-of-Table особенно полезен для таблиц с 10+ строками и вопросов, требующих нескольких операций (фильтрация + сортировка + агрегация). Для простых таблиц с 3-5 строками обычного промпта достаточно.

Как работает Chain-of-Table

Доступные операции

Авторы Chain-of-Table выделяют пять базовых операций, которых достаточно для подавляющего большинства табличных вопросов:

ОперацияОписаниеSQL-аналог
f_select_column(columns)Оставить только указанные столбцыSELECT col1, col2
f_select_row(condition)Отфильтровать строки по условиюWHERE condition
f_group_by(column, agg)Сгруппировать и агрегироватьGROUP BY col
f_sort_by(column, order)Отсортировать по столбцуORDER BY col
f_add_col(name, expression)Добавить вычисляемый столбецSELECT *, expr AS col

Результаты на бенчмарках

Точность на WikiTableQuestions (% accuracy)
Chain-of-Table67%
Dater (Text-to-SQL)65%
Chain of Thought62%
Direct Prompting55%
Точность на TabFact (% accuracy)
Chain-of-Table85%
Chain of Thought80%
Dater78%
Direct Prompting72%

Развёрнутый пример: сложный аналитический вопрос

ПромптChatGPT
Используй метод Chain-of-Table для ответа на вопрос.

Таблица продаж:
| Менеджер | Регион | Q1 (тыс. ₽) | Q2 (тыс. ₽) | Q3 (тыс. ₽) | Q4 (тыс. ₽) |
|----------|--------|-------------|-------------|-------------|-------------|
| Иванов | Москва | 450 | 520 | 480 | 610 |
| Петрова | СПб | 380 | 410 | 390 | 445 |
| Сидоров | Москва | 520 | 490 | 550 | 580 |
| Козлова | Казань | 290 | 310 | 340 | 370 |
| Морозов | СПб | 410 | 440 | 460 | 500 |
| Волкова | Москва | 390 | 430 | 470 | 510 |

Вопрос: Какой менеджер из Москвы показал наибольший рост продаж от Q1 к Q4?
Ответ модели

Шаг 1: f_select_row(Регион == 'Москва')

Менеджер Регион Q1 (тыс. ₽) Q2 (тыс. ₽) Q3 (тыс. ₽) Q4 (тыс. ₽)
Иванов Москва 450 520 480 610
Сидоров Москва 520 490 550 580
Волкова Москва 390 430 470 510

Шаг 2: f_add_col('Рост Q1→Q4', Q4 - Q1)

Менеджер Q1 (тыс. ₽) Q4 (тыс. ₽) Рост Q1→Q4
Иванов 450 610 160
Сидоров 520 580 60
Волкова 390 510 120

Шаг 3: f_sort_by('Рост Q1→Q4', desc)

Менеджер Рост Q1→Q4
Иванов 160
Волкова 120
Сидоров 60

Ответ: Иванов показал наибольший рост продаж от Q1 к Q4 — на 160 тыс. ₽ (с 450 до 610).

Когда использовать Chain-of-Table

Идеальные сценарии:

  • Аналитика данных: вопросы по CSV, Excel, отчётам с числами
  • Сравнения: «кто лучше», «что больше», «в каком году»
  • Агрегации: суммы, средние, подсчёты по группам
  • Фильтрация и ранжирование: топ-N, выбросы, пороговые значения

Не подходит:

  • Таблицы с 1-3 строками — overhead не оправдан
  • Вопросы, не требующие работы с данными
  • Очень большие таблицы (100+ строк) — не помещаются в контекст LLM

Плюсы

  • Точность: каждый шаг — атомарная операция, сложно ошибиться
  • Прозрачность: промежуточные таблицы показывают ход рассуждения
  • Масштабируемость: работает с таблицами 10-50 строк, где текстовый CoT ломается
  • +5-10% на табличных бенчмарках vs Chain of Thought
  • Естественно для аналитиков: напоминает работу в Excel или SQL

Минусы

  • Больше токенов: промежуточные таблицы занимают место в контексте
  • Ограничение контекста: большие таблицы (100+ строк) не помещаются
  • Не для всех задач: текстовые вопросы без табличной структуры лучше решать CoT
  • Требует хорошей модели: слабые модели плохо планируют цепочки операций
Chain-of-Table работает лучше всего с таблицами от 5 до 50 строк. Для очень больших таблиц (100+ строк) лучше использовать Text2SQL и выполнять запросы в реальной базе данных. Для маленьких таблиц (1-3 строки) достаточно обычного промпта — Chain-of-Table добавит лишнюю сложность.

Как понимать Chain-of-Table в 2026

Сегодня эту технику разумно ставить между тремя слоями:

  • обычным CoT, где модель просто рассуждает о таблице текстом
  • structured outputs, где важен уже только финальный формат ответа
  • SQL / dataframe / spreadsheet tools, где операции выполняются внешним движком

Chain-of-Table полезен именно как промежуточный reasoning layer. Он помогает удерживать структуру на стороне модели, когда задача ещё достаточно компактна для контекста, но уже слишком неудобна для “чтения глазами”.

Где техника реально окупается

На практике CoTable лучше всего работает в сценариях вроде:

  • ad hoc analytics по маленьким CSV, выгрузкам и markdown-таблицам
  • обучающие и explainable workflows, где нужно показать ход обработки таблицы
  • lightweight product features: QA по отчёту, summary по таблице, сравнение и ранжирование
  • агентные пайплайны, где промежуточная таблица удобнее, чем длинное текстовое описание фильтров

Если задача сводится к 2-4 понятным шагам над таблицей и сами данные помещаются в контекст без боли, техника обычно даёт хороший баланс между прозрачностью и простотой.

Где лучше выбрать другой маршрут

У Chain-of-Table есть и чёткие границы:

  1. Для больших таблиц и реальных production dashboards почти всегда лучше SQL или dataframe tools.
  2. Для полностью детерминированных вычислений над таблицей внешний tool надёжнее, чем LLM-симуляция операций.
  3. Для смешанных задач “таблица + документы + внешние источники” часто полезнее agent workflow с retrieval и tool calls, чем чистый CoTable.

Хорошее правило: если человек инстинктивно открыл бы Excel-фильтр и сделал пару шагов руками, Chain-of-Table подходит. Если человек уже пишет SQL, то и модели, скорее всего, стоит идти в SQL path, а не оставаться в prompt-layer.

Реализация Chain-of-Table

Промпт-шаблон

Ключ к работе Chain-of-Table — структурированный промпт, который объясняет модели доступные операции и формат вывода:

COT_TABLE_SYSTEM = """Ты аналитик данных. Отвечай на вопросы по таблицам,
используя метод Chain-of-Table.

Доступные операции:
1. f_select_column(columns: list) — оставить только указанные столбцы
2. f_select_row(condition: str) — отфильтровать строки по условию
3. f_group_by(column: str, agg: str) — сгруппировать (agg: count/sum/avg/min/max)
4. f_sort_by(column: str, order: str) — отсортировать (asc/desc)
5. f_add_col(name: str, expression: str) — добавить вычисляемый столбец

Правила:
- На каждом шаге выполняй РОВНО ОДНУ операцию
- После каждой операции покажи результат в виде markdown-таблицы
- Таблица каждого шага — вход для следующего шага
- В конце сформулируй ответ на основе финальной таблицы
- Не пропускай шаги, не объединяй операции"""

Формат промежуточных таблиц

Таблицы в промпте передаются в формате Markdown. Этот формат хорошо поддерживается всеми крупными моделями:

def table_to_markdown(headers: list[str], rows: list[list]) -> str:
    """Конвертирует данные в Markdown-таблицу."""
    header_line = "| " + " | ".join(headers) + " |"
    separator = "| " + " | ".join("---" for _ in headers) + " |"
    row_lines = [
        "| " + " | ".join(str(cell) for cell in row) + " |"
        for row in rows
    ]
    return "\n".join([header_line, separator] + row_lines)


def build_cot_table_prompt(table_md: str, question: str) -> str:
    """Собирает промпт для Chain-of-Table."""
    return f"""Таблица:
{table_md}

Вопрос: {question}

Используй метод Chain-of-Table. Выполняй операции по одной,
показывая промежуточную таблицу после каждого шага."""

Интеграция с pandas: Chain-of-Table как планировщик

Мощный паттерн — использовать Chain-of-Table для планирования операций, а выполнять их в pandas. Модель генерирует план операций, а код выполняет их точно:

import pandas as pd
from openai import OpenAI

client = OpenAI()

PLANNER_PROMPT = """Ты планируешь цепочку операций над таблицей.
Верни JSON-массив операций. Каждая операция — объект:

{"op": "select_row", "condition": "column == 'value'"}
{"op": "select_column", "columns": ["col1", "col2"]}
{"op": "sort_by", "column": "col1", "order": "desc"}
{"op": "group_by", "column": "col1", "agg": {"col2": "sum"}}
{"op": "add_col", "name": "new_col", "expression": "col1 - col2"}

Верни ТОЛЬКО JSON-массив, без пояснений."""


def plan_operations(table_md: str, question: str) -> list[dict]:
    """LLM планирует цепочку операций."""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": PLANNER_PROMPT},
            {"role": "user", "content": f"Таблица:\n{table_md}\n\nВопрос: {question}"}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    import json
    result = json.loads(response.choices[0].message.content)
    return result if isinstance(result, list) else result.get("operations", [])


def execute_operations(df: pd.DataFrame, operations: list[dict]) -> pd.DataFrame:
    """Выполняет спланированные операции в pandas."""
    for op in operations:
        match op["op"]:
            case "select_row":
                df = df.query(op["condition"])
            case "select_column":
                df = df[op["columns"]]
            case "sort_by":
                ascending = op.get("order", "asc") == "asc"
                df = df.sort_values(op["column"], ascending=ascending)
            case "group_by":
                df = df.groupby(op["column"]).agg(op["agg"]).reset_index()
            case "add_col":
                df[op["name"]] = df.eval(op["expression"])
    return df


# Использование
df = pd.read_csv("sales.csv")
table_md = df.to_markdown(index=False)
question = "Какой регион принёс максимальную выручку в Q4?"

operations = plan_operations(table_md, question)
# [{"op": "select_column", "columns": ["Регион", "Q4"]},
#  {"op": "group_by", "column": "Регион", "agg": {"Q4": "sum"}},
#  {"op": "sort_by", "column": "Q4", "order": "desc"}]

result = execute_operations(df, operations)
print(result.head(1))

Chain-of-Table как SQL-планировщик

Альтернативный подход — генерировать SQL вместо pandas-операций:

SQL_PLANNER_PROMPT = """Ты аналитик. На основе вопроса и таблицы
составь SQL-запрос. Таблица называется `data`.

Используй подход Chain-of-Table: разбей запрос на шаги
с помощью CTE (WITH ... AS).

Верни ТОЛЬКО SQL, без пояснений."""


def generate_sql(table_md: str, question: str) -> str:
    """LLM генерирует SQL через Chain-of-Table."""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": SQL_PLANNER_PROMPT},
            {"role": "user", "content": f"Таблица:\n{table_md}\n\nВопрос: {question}"}
        ],
        temperature=0
    )
    sql = response.choices[0].message.content
    # Очистка от markdown-блока
    if "```sql" in sql:
        sql = sql.split("```sql")[1].split("```")[0]
    return sql.strip()


# Результат — пошаговый SQL через CTE:
# WITH step1 AS (
#     SELECT * FROM data WHERE region = 'Москва'
# ),
# step2 AS (
#     SELECT manager, q4 - q1 AS growth FROM step1
# )
# SELECT manager, growth FROM step2 ORDER BY growth DESC LIMIT 1;

Ограничения и альтернативы

  1. Размер контекста. Таблица с 50 строками и 10 столбцами занимает ~2000 токенов. Промежуточные таблицы удваивают расход. Таблица в 100+ строк может не поместиться.
  2. Типы данных. LLM может неправильно интерпретировать числа с разделителями (1 000 vs 1000), даты в разных форматах, валюты.
  3. Сложные джойны. Chain-of-Table работает с одной таблицей. Для объединения нескольких таблиц лучше использовать Text2SQL.

Альтернативные подходы к табличным данным:

ПодходИдеяКогда использовать
Chain-of-TableПошаговая трансформация таблицы5-50 строк, 2-5 операций
Text2SQL (DATER)Генерация SQL-запросаРеальная БД, сложные джойны
TableGPTСпециализированная модель для таблицМассовая обработка таблиц
Program of ThoughtsГенерация pandas-кодаВычисления, визуализации
Chain of ThoughtТекстовое рассуждениеМаленькие таблицы (1-5 строк)

Проверьте себя

Проверьте себя

1. В чём главное отличие Chain-of-Table от текстового Chain of Thought при работе с таблицами?

2. Какая операция Chain-of-Table аналогична SQL-конструкции WHERE?

3. Для каких таблиц Chain-of-Table наименее эффективен?