Token太贵?我用这个数据格式把上下文窗口扩大2倍

JSON在LLM场景下的Token成本问题

1.1 JSON的Token开销

JSON设计于2001年,初衷是解决服务间的数据交换。二十年后,当LLM成为技术栈的核心组件时,JSON的局限性开始显现。

在LLM场景中,Token是计价单位。JSON的大量引号、逗号、嵌套结构直接推高了API成本。同样的数据量,JSON格式要比实际内容多消耗60-70%的Token。

举个例子。一个包含10条用户记录的查询结果:

JSON格式(87 Tokens):

json 复制代码
{
  "users": [
    {"id": 1, "name": "Alice", "email": "alice@example.com", "active": true},
    {"id": 2, "name": "Bob", "email": "bob@example.com", "active": false}
  ]
}

那些引号、逗号、大括号,每一个都在烧钱。当你的RAG系统需要一次性塞进20个文档片段时,上下文窗口有一半被JSON的格式符号占用了。

1.2 ISON:表结构式的数据格式

要降低Token消耗,需要一种更紧凑、同时让LLM容易理解的格式。

ISON(Interchange Simple Object Notation)正是基于这个需求设计的。

它的核心思路是:采用LLM训练数据中常见的表结构和关系模式。SQL查询结果、CSV文件、数据库表------这些格式在预训练语料中出现了数十亿次,LLM对它们的理解成本极低。

同样的数据,ISON格式(34 Tokens):

ison 复制代码
table.users
id:int name:string email active:bool
1 Alice alice@example.com true
2 Bob bob@example.com false

列名只定义一次、没有引号逗号、空格分隔值。同样是10条记录,Token消耗从87降到34,省了60%以上。


ISON实战模板

2.1 场景一:RAG检索结果

RAG检索出来的文档片段,用ISON传给LLM,上下文窗口能多塞一倍内容。

模板代码:

python 复制代码
from ison_py import parse, to_json
import json

def rag_results_to_ison(chunks):
    """将RAG检索结果转换为ISON格式"""
    ison_template = """table.chunks
chunk_id source content relevance_score
"""
    for i, chunk in enumerate(chunks, 1):
        # 清理内容中的换行和引号,避免破坏格式
        content = chunk['content'].replace('\n', ' ').replace('"', '')[:500]
        ison_template += f"{i} {chunk['source']} \"{content}\" {chunk['score']}\n"

    return ison_template

# 使用示例
chunks = [
    {"source": "doc1.pdf", "content": "用户登录流程包括...", "score": 0.95},
    {"source": "doc2.pdf", "content": "认证模块使用JWT...", "score": 0.87},
]

ison_data = rag_results_to_ison(chunks)
# 直接塞进prompt
prompt = f"基于以下检索结果回答问题:\n\n{ison_data}\n\n问题:..."

效果对比:

格式 Token数 可塞文档数
JSON ~800 5个
ISON ~320 12个

上下文窗口利用率直接翻倍。

2.2 场景二:多Agent数据传递

多个Agent协作时,中间数据用ISON传递,能显著降低Agent间的通信成本。

模板代码:

python 复制代码
def agent_context_to_ison(agent_id, context_data, references=None):
    """多Agent上下文传递模板"""
    ison = f"""object.agent_context
agent_id task status
{agent_id} {context_data['task']} {context_data['status']}

"""
    if context_data.get('results'):
        ison += """table.results
item_id value confidence
"""
        for r in context_data['results']:
            ison += f"{r['id']} {r['value']} {r['confidence']}\n"

    if references:
        ison += """table.references
ref_id ref_type target_id
"""
        for ref in references:
            ison += f"{ref['id']} {ref['type']} :{ref['target']}\n"

    return ison

# 使用示例
context = {
    "task": "data_analysis",
    "status": "completed",
    "results": [
        {"id": 1, "value": 150, "confidence": 0.92},
        {"id": 2, "value": 230, "confidence": 0.88},
    ]
}
refs = [
    {"id": "r1", "type": "DEPENDS_ON", "target": "agent_001"},
    {"id": "r2", "type": "PRODUCES", "target": "agent_003"},
]

ison_context = agent_context_to_ison("agent_002", context, refs)

2.3 场景三:数据库查询结果直接导出

查询完数据库直接输出ISON,比JSON省一半Token。

python 复制代码
import sqlite3

def query_to_ison(db_path, query, table_name="results"):
    """SQL查询结果直接转ISON"""
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    cursor.execute(query)

    # 获取列名
    columns = [desc[0] for desc in cursor.description]
    rows = cursor.fetchall()

    # 生成ISON
    ison = f"table.{table_name}\n"
    ison += " ".join(columns) + "\n"

    for row in rows:
        # 处理字符串中的空格,需要引号包裹
        values = []
        for v in row:
            if isinstance(v, str) and (" " in v or v in ["true", "false", "null"]):
                values.append(f'"{v}"')
            else:
                values.append(str(v))
        ison += " ".join(values) + "\n"

    conn.close()
    return ison

# 使用示例
ison_output = query_to_ison("data.db", "SELECT id, name, status FROM users LIMIT 10")

几条使用技巧

3.1 字符串引号的省略规则

不是所有字符串都需要引号,但以下几种情况必须加:

情况 示例 处理
包含空格 New York "New York"
保留字 true, false, null "true", "null"
看起来像数字 12345(实际是ID) "12345"
以冒号开头 :tag(不是引用) ":tag"

省Token技巧 :字段名用下划线代替空格,比如first_name"first name"省2个Token(咱就说能省一点是一点)。

3.2 关系引用的三种写法

ISON支持跨表引用,但写法不同用途不同:

ison 复制代码
table.teams
id name
10 Engineering
20 Marketing

table.employees
id name team
101 Mahesh :10           # 简单引用
102 John :team:10        # 带命名空间
103 Alice :MEMBER_OF:10  # 带关系类型
  • :10 - 简单引用,最省Token
  • :team:10 - 命名空间引用,防止ID冲突
  • :MEMBER_OF:10 - 关系类型引用,适合知识图谱场景

建议:普通场景用简单引用,复杂关系用关系类型。

3.3 用引用代替深度嵌套

ISON推荐用引用代替深度嵌套。比如订单和用户信息:

不推荐(嵌套):

ison 复制代码
object.order
id customer.name customer.address.city total
1001 Alice "New York" 125.50

推荐(引用):

ison 复制代码
table.customers
id name city
1 Alice "New York"

table.orders
id customer_id total
1001 :1 125.50

嵌套字段虽然支持,但可读性和可维护性不如引用方式。

3.4 ISON与ISONL的选择

ISONL(ISON Lines)是ISON的流式格式,每行包含一条完整记录,用管道符分隔字段定义和值。它相当于JSONL之于JSON的关系,适合日志、事件流等需要逐行追加数据的场景。

格式 适用场景 特点
ISON 配置文件、静态数据 多行结构清晰
ISONL 日志、事件流、微调数据 每行自包含,适合追加

ISONL示例(一行一条记录):

isonl 复制代码
table.events|ts type user|10:30:00 click :1
table.events|ts type user|10:31:00 view :2

数据对比与适用场景

4.1 实际Token成本对比

基于官方20个数据集的基准测试(GPT-4o tokenizer):

格式 Token总数 相比JSON 解析准确率
JSON 12,668 baseline 84.7%
ISON 3,550 -72% 88.3%

不仅Token省70%+,LLM解析准确率还更高。

4.2 什么时候用ISON,什么时候用JSON

场景 推荐格式 理由
LLM prompt上下文 ISON 省Token,LLM理解更好
Agent间通信 ISON 降低传输成本
RAG检索结果 ISON 上下文窗口利用率高
服务间API JSON 通用性更好
前端数据交互 JSON 浏览器原生支持
长期存储 JSON 生态更成熟

核心原则:ISON用在LLM边界,JSON用在系统内部。

4.3 快速上手

如果你用Python,一行命令安装:

bash 复制代码
pip install ison-py

然后就可以用上面的模板代码直接开始了。

ISON目前支持6种语言:Python、JavaScript、TypeScript、Rust、Go、C++。

Java暂无官方库,以下是我实现的一个简化版,满足基础的解析和生成需求:

ruby 复制代码
https://github.com/yuboon/ai-examples/tree/main/004-jison/IsonUtils.java
java 复制代码
public class IsonUtils {

    /**
     * 解析ISON表格格式为List<Map>
     */
    public static List<Map<String, Object>> parseTable(String isonText) {
        List<Map<String, Object>> results = new ArrayList<>();
        String[] lines = isonText.trim().split("\n");

        // 第一行是表名,跳过
        // 第二行是字段定义
        String[] headers = lines[1].split(" ");

        // 后续行是数据
        for (int i = 2; i < lines.length; i++) {
            String[] values = lines[i].split(" ");
            Map<String, Object> row = new LinkedHashMap<>();
            for (int j = 0; j < headers.length; j++) {
                String header = headers[j].split(":")[0]; // 去掉类型注解
                row.put(header, parseValue(values[j]));
            }
            results.add(row);
        }
        return results;
    }

    /**
     * 生成ISON表格格式字符串
     */
    public static String generateTable(String tableName,
                                       List<String> headers,
                                       List<List<Object>> rows) {
        StringBuilder sb = new StringBuilder();
        sb.append("table.").append(tableName).append("\n");
        sb.append(String.join(" ", headers)).append("\n");

        for (List<Object> row : rows) {
            List<String> formatted = new ArrayList<>();
            for (Object val : row) {
                formatted.add(formatValue(val));
            }
            sb.append(String.join(" ", formatted)).append("\n");
        }
        return sb.toString();
    }

    private static Object parseValue(String val) {
        if (val.equals("true")) return true;
        if (val.equals("false")) return false;
        if (val.equals("null")) return null;
        if (val.startsWith(":")) return val; // 引用
        try {
            return Integer.parseInt(val);
        } catch (NumberFormatException e) {
            try {
                return Double.parseDouble(val);
            } catch (NumberFormatException e2) {
                return val.replace("\"", ""); // 去掉引号
            }
        }
    }

    private static String formatValue(Object val) {
        if (val == null) return "null";
        if (val instanceof Boolean) return val.toString();
        if (val instanceof Number) return val.toString();
        String str = val.toString();
        // 包含空格或是保留字时加引号
        if (str.contains(" ") || str.contains("\t") ||
            str.equals("true") || str.equals("false") || str.equals("null")) {
            return "\"" + str + "\"";
        }
        return str;
    }
}

// 使用示例:解析
String ison = """
    table.users
    id:int name:string email
    1 Alice alice@example.com
    2 Bob bob@example.com
    """;
List<Map<String, Object>> users = IsonUtils.parseTable(ison);

// 使用示例:生成
List<String> headers = Arrays.asList("id:int", "name:string", "email");
List<List<Object>> rows = Arrays.asList(
    Arrays.asList(1, "Alice", "alice@example.com"),
    Arrays.asList(2, "Bob", "bob@example.com")
);
String isonOutput = IsonUtils.generateTable("users", headers, rows);

Token成本在LLM应用里是刚性支出,同样的效果,用ISON能省60-70%的传输Token,相当于把上下文窗口扩大2倍。如果你的应用有频繁的数据传输给LLM,值得一试。

相关推荐
Victor3562 小时前
MongoDB(17)如何在MongoDB中创建集合?
后端
摸鱼的春哥2 小时前
春哥的Agent通关秘籍13:实现RAG查询
前端·javascript·后端
Victor3562 小时前
MongoDB(16)如何在MongoDB中创建数据库?
后端
NAGNIP11 小时前
轻松搞懂全连接神经网络结构!
人工智能·算法·面试
勇哥java实战分享11 小时前
程序员的明天:AI 时代下的行业观察与个人思考
后端
moshuying12 小时前
别让AI焦虑,偷走你本该有的底气
前端·人工智能
掘金码甲哥12 小时前
超性感的轻量级openclaw平替,我来给你打call
后端
董董灿是个攻城狮13 小时前
零基础带你用 AI 搞定命令行
人工智能