CSV, JSON или Markdown: какой формат лучше для промптов

Формат данных влияет на то, насколько легко языковой модели прочитать таблицу, понять вложенные поля и не перепутать разделители. В этой статье разберём, когда использовать CSV, JSON или Markdown, а затем соберём воспроизводимый тест и готовый Python-скрипт без внешних зависимостей.
Короткий ответ
Для хранения исходных данных выбирайте JSON, если структура вложенная, и CSV, если данные плоские и табличные. Для объяснения данных в промпте часто удобен Markdown: таблица визуально читаема и хорошо отделяет строки и столбцы.
Однако Markdown-таблица не должна становиться главным хранилищем. Это формат представления. Надёжная схема выглядит так:
- хранить оригинал в CSV или JSON;
- преобразовывать его в Markdown только перед отправкой в промпт;
- экранировать вертикальные черты, обратные слэши и переносы строк;
- проверять количество строк, колонок и размер результата.
Что выбрать: CSV, JSON или Markdown
| Формат | Сильные стороны | Ограничения | Когда использовать |
|---|---|---|---|
| CSV | Компактный, удобен для плоских таблиц, поддерживается стандартной библиотекой Python. | Вложенные поля и типы данных требуют дополнительных соглашений. | Экспорт из таблиц, списки записей, простые датасеты. |
| JSON | Сохраняет вложенность, массивы, числа, логические значения и null. | Может занимать больше места и быть менее удобным для беглого чтения. | API-ответы, конфигурации, сложные объекты. |
| Markdown | Хорошо виден человеку и модели, строки и колонки легко различать. | Разделитель | имеет специальное значение; переносы строк ломают разметку. |
Финальное представление табличных данных в промпте. |
Размер в байтах нельзя считать универсальным показателем качества. Он зависит от кодировки, отступов, имён полей и способа экранирования. Поэтому сравнение ниже не утверждает, что один формат всегда лучше другого: оно показывает поведение конкретного набора данных.
Почему простая замена запятых на вертикальные черты опасна
Такой код выглядит убедительно, но некорректен:
row.replace(",", " | ")
Запятая может находиться внутри значения, например в адресе или описании. Кроме того, CSV допускает поля с переносами строк внутри кавычек. В JSON проблема проявляется иначе: объект может содержать вложенный объект или массив, которые нельзя бездумно превращать в несколько колонок.
Безопасный преобразователь должен сначала разобрать формат специализированным
парсером, а уже затем сериализовать каждую ячейку. Для CSV это модуль
csv, для JSON — модуль json. Оба входят в стандартную
библиотеку Python.
Правила безопасной Markdown-таблицы
В скрипте ниже используются четыре правила:
- обратный слэш экранируется первым, чтобы не возникали двойные трактовки;
- символ
|превращается в\|и не создаёт новый столбец; - перенос строки заменяется на видимую последовательность
\n; - вложенные JSON-значения записываются компактным JSON внутри одной ячейки.
Последнее правило сохраняет структуру вложенного поля в пределах одной ячейки.
Если модели нужно активно рассуждать по вложенным полям, можно дополнительно
сделать плоскую таблицу с колонками вроде user.name и
user.contacts.email. Это уже другая схема представления, поэтому
её следует выбирать явно.
Готовый Python-скрипт
Сохраните код в файл convert_table.py. Скрипт читает CSV или JSON,
создаёт Markdown-файл и печатает размеры всех трёх представлений в UTF-8.
#!/usr/bin/env python3
import argparse
import csv
import json
from pathlib import Path
from typing import Any, Dict, Iterable, List
def escape_markdown_cell(value: str) -> str:
"""Сохраняет содержимое ячейки в одной строке Markdown-таблицы."""
value = value.replace("\\", "\\\\")
value = value.replace("|", "\\|")
value = value.replace("\r\n", "\\n")
value = value.replace("\n", "\\n")
value = value.replace("\r", "\\n")
return value
def json_cell(value: Any) -> str:
"""Представляет JSON-значение компактно и без потери Unicode."""
if isinstance(value, (dict, list)):
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
if value is None:
return "null"
if isinstance(value, bool):
return "true" if value else "false"
return str(value)
def read_csv(path: Path) -> List[Dict[str, str]]:
with path.open("r", encoding="utf-8-sig", newline="") as handle:
reader = csv.DictReader(handle)
if reader.fieldnames is None:
raise ValueError("CSV не содержит строку заголовков.")
rows = []
for row_number, row in enumerate(reader, start=2):
if None in row:
raise ValueError(
f"Строка {row_number} содержит лишние значения: {row[None]!r}"
)
rows.append({key: value if value is not None else "" for key, value in row.items()})
return rows
def read_json(path: Path) -> List[Dict[str, Any]]:
with path.open("r", encoding="utf-8") as handle:
data = json.load(handle)
if isinstance(data, dict):
data = [data]
if not isinstance(data, list) or not all(isinstance(item, dict) for item in data):
raise ValueError("JSON должен быть объектом или массивом объектов.")
return data
def columns_for(rows: Iterable[Dict[str, Any]]) -> List[str]:
columns = []
seen = set()
for row in rows:
for key in row:
key = str(key)
if key not in seen:
seen.add(key)
columns.append(key)
return columns
def to_markdown(rows: List[Dict[str, Any]], columns: List[str]) -> str:
if not columns:
raise ValueError("Не найдено ни одного поля.")
header = "| " + " | ".join(escape_markdown_cell(c) for c in columns) + " |"
separator = "| " + " | ".join("---" for _ in columns) + " |"
body = []
for row in rows:
cells = []
for column in columns:
value = row.get(column, "")
cells.append(escape_markdown_cell(json_cell(value)))
body.append("| " + " | ".join(cells) + " |")
return "\n".join([header, separator, *body]) + "\n"
def byte_size(path: Path) -> int:
return len(path.read_bytes())
def main() -> None:
parser = argparse.ArgumentParser(
description="Преобразование CSV или JSON в безопасную Markdown-таблицу."
)
parser.add_argument("input", type=Path)
parser.add_argument("-o", "--output", type=Path, default=Path("table.md"))
parser.add_argument(
"--format",
choices=("csv", "json"),
required=True,
help="Формат входного файла."
)
args = parser.parse_args()
if args.format == "csv":
rows = read_csv(args.input)
else:
rows = read_json(args.input)
columns = columns_for(rows)
markdown = to_markdown(rows, columns)
args.output.write_text(markdown, encoding="utf-8", newline="\n")
print(f"Строк данных: {len(rows)}")
print(f"Колонок: {len(columns)}")
print(f"Markdown: {args.output} ({byte_size(args.output)} байт UTF-8)")
print(f"Источник: {args.input} ({byte_size(args.input)} байт UTF-8)")
if __name__ == "__main__":
main()
Воспроизводимое сравнение размеров
Создайте тестовые файлы с одинаковыми данными. В примере есть вертикальная черта, запятая, перенос строки, массив и вложенный объект. Это пример для проверки, а не измерение внешнего набора данных.
mkdir -p prompt-format-test
cd prompt-format-test
cat > data.csv <<'EOF'
id,name,note,profile
1,"Анна, Петрова","Первая строка
Вторая строка","{""role"":""editor"",""active"":true}"
2,"Илья","Значение с | внутри","{""role"":""analyst"",""tags"":[""python"",""llm""]}"
EOF
cat > data.json <<'EOF'
[
{
"id": 1,
"name": "Анна, Петрова",
"note": "Первая строка\nВторая строка",
"profile": {
"role": "editor",
"active": true
}
},
{
"id": 2,
"name": "Илья",
"note": "Значение с | внутри",
"profile": {
"role": "analyst",
"tags": ["python", "llm"]
}
}
]
EOF
python3 ../convert_table.py data.csv --format csv -o from-csv.md
python3 ../convert_table.py data.json --format json -o from-json.md
wc -c data.csv data.json from-csv.md from-json.md
Команда wc -c показывает размер файлов в байтах. Результаты
зависят от версии исходных файлов и не должны заранее подставляться в статью
или отчёт. Для честного сравнения запускайте обе конвертации на одной машине
и не меняйте входные данные между запусками.
Если нужно сравнивать именно представления, а не оформление исходников, используйте JSON без отступов и CSV с одинаковым порядком колонок. Для практического промпта важен также размер токенизированного текста, но точное число токенов зависит от конкретной модели и её токенизатора. Размер в UTF-8 — воспроизводимый приближённый показатель, а не замена токенизации.
Проверка результата
Откройте созданный файл from-csv.md и проверьте:
- строка с
Анна, Петроваосталась одной ячейкой; - перенос строки отображается как последовательность
\n, а не ломает таблицу; - символ
|отображается внутри ячейки; - вложенный объект не исчез и остался JSON-значением;
- число строк данных совпадает с исходным файлом.
Быстрая структурная проверка для CSV:
python3 -c 'import csv; from pathlib import Path; print(sum(1 for _ in csv.DictReader(Path("data.csv").open(encoding="utf-8-sig", newline=""))))'
Быстрая проверка JSON:
python3 -c 'import json; from pathlib import Path; data=json.loads(Path("data.json").read_text(encoding="utf-8")); print(len(data) if isinstance(data, list) else 1)'
Эти команды только читают локальные файлы и печатают количество записей. Перед обработкой реальных данных убедитесь, что путь к файлу указан явно и не содержит неожиданных подстановок оболочки.
Типовые ошибки
Разбор CSV через split
line.split(",") не учитывает кавычки и переносы строк внутри
полей. Используйте csv.DictReader и открывайте файл с
newline="".
Незащищённый символ |
В Markdown вертикальная черта разделяет колонки. Если не заменить её на
\|, число столбцов изменится.
Прямое преобразование вложенного JSON
Массивы и объекты нельзя надёжно свести к одной строке с помощью
str(). Используйте json.dumps с
ensure_ascii=False и компактными разделителями.
Смешивание исходника и представления
Markdown удобен для чтения, но его не следует использовать как единственную копию данных. Храните исходный CSV или JSON отдельно, чтобы можно было повторить преобразование и проверить результат.
Неверная интерпретация размера
Меньший файл не всегда означает лучший промпт. Слишком компактное представление может быть менее понятным модели или человеку. Сравнивайте размер вместе с корректностью и качеством ответа на ваших задачах.
Ограничения подхода
Скрипт рассчитан на CSV с первой строкой заголовков и JSON, который является
объектом или массивом объектов. Он не угадывает разделитель CSV: для файлов
с точкой с запятой, табуляцией или нестандартными кавычками потребуется
явно расширить параметры csv.DictReader.
Markdown-экранирование в скрипте предназначено для сохранения таблицы в одной строке на запись. Это не универсальный обратимый формат: если нужно восстановить исходный текст побайтно, сохраняйте оригинал и используйте Markdown только как копию для промпта.
Для больших файлов таблица может оказаться слишком длинной для контекстного окна модели. В таком случае лучше отфильтровать нужные записи, разделить данные на части или передать JSON через инструмент, который поддерживает структурированные аргументы.
Итог
Если данные плоские и их нужно быстро передать в таблице, начните с CSV. Если важны вложенные поля и типы, храните JSON. Если задача — дать модели компактное визуально понятное представление, преобразуйте копию данных в Markdown и экранируйте содержимое ячеек.
Практическое правило простое: источник должен оставаться структурированным, а Markdown — проверяемым слоем представления. Так вы сможете повторить преобразование, сравнить размеры и обнаружить потерю данных до отправки промпта.
Дополнительные инструкции собраны в разделе гайдов, а термины — в глоссарии.