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 особенно полезен для таблиц с 10+ строками и вопросов, требующих нескольких операций (фильтрация + сортировка + агрегация). Для простых таблиц с 3-5 строками обычного промпта достаточно.
Аналитика данных: вопросы по 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 добавит лишнюю сложность.
Сегодня эту технику разумно ставить между тремя слоями:
обычным 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 понятным шагам над таблицей и сами данные помещаются в контекст без боли, техника обычно даёт хороший баланс между прозрачностью и простотой.
Для больших таблиц и реальных production dashboards почти всегда лучше SQL или dataframe tools.
Для полностью детерминированных вычислений над таблицей внешний tool надёжнее, чем LLM-симуляция операций.
Для смешанных задач “таблица + документы + внешние источники” часто полезнее agent workflow с retrieval и tool calls, чем чистый CoTable.
Хорошее правило: если человек инстинктивно открыл бы Excel-фильтр и сделал пару шагов руками, Chain-of-Table подходит. Если человек уже пишет SQL, то и модели, скорее всего, стоит идти в SQL path, а не оставаться в prompt-layer.
Ключ к работе 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-таблицы
- Таблица каждого шага — вход для следующего шага
- В конце сформулируй ответ на основе финальной таблицы
- Не пропускай шаги, не объединяй операции"""
Мощный паттерн — использовать 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))
Альтернативный подход — генерировать 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;
Размер контекста. Таблица с 50 строками и 10 столбцами занимает ~2000 токенов. Промежуточные таблицы удваивают расход. Таблица в 100+ строк может не поместиться.
Типы данных. LLM может неправильно интерпретировать числа с разделителями (1 000 vs 1000), даты в разных форматах, валюты.
Сложные джойны. Chain-of-Table работает с одной таблицей. Для объединения нескольких таблиц лучше использовать Text2SQL.