Przeczytałeś dokumentację, wiesz że TOON redukuje liczbę tokenów o połowę na danych tabelarycznych. Teraz chcesz faktycznie to podpiąć. Ten artykuł dotyczy instalacji: odczytu i zapisu plików .toon, walidacji TOON na granicach systemu, budowania middleware Express parsującego ciała żądań TOON i składania pipeline'u baza danych → prompt, który podaje TOON bezpośrednio do LLM. Prawdziwy kod, prawdziwe wzorce — bez przykładów zabawkowych.
Konfiguracja
Zainstaluj pakiet z npm. Jest tylko ESM, więc będziesz potrzebował "type": "module" w swoim package.json lub użyj rozszerzenia .mjs. Node.js 18+ to wszystko czego potrzebujesz — bez plików konfiguracyjnych, bez wtyczek.
npm install @toon-format/toonimport { encode, decode } from '@toon-format/toon';
// That's it. encode() → TOON string, decode() → JS value.Odczyt i zapis plików TOON
Moduł fs Node.js obsługuje I/O. Przekaż zawartość pliku bezpośrednio do decode(), lub przekaż swoje dane do encode() i zapisz wynik na dysk. Poniżej są oba wzorce — synchroniczny dla skryptów i narzędzi CLI, asynchroniczny dla tras serwera.
// --- Sync (scripts, CLI tools) ---
import { readFileSync, writeFileSync } from 'fs';
import { encode, decode } from '@toon-format/toon';
// Read a .toon file and decode it to a JS value
const raw = readFileSync('./data/products.toon', 'utf8');
const products = decode(raw);
console.log(products); // → JS array or object
// Encode a JS value and write it to a .toon file
const inventory = [
{ sku: 'WDG-001', name: 'Widget A', qty: 142, price: 9.99 },
{ sku: 'WDG-002', name: 'Widget B', qty: 87, price: 14.49 },
{ sku: 'GDG-001', name: 'Gadget X', qty: 31, price: 49.99 },
];
writeFileSync('./data/inventory.toon', encode(inventory, { indent: 2 }), 'utf8');
// --- Async (server routes, pipelines) ---
import { promises as fs } from 'fs';
import { encode, decode } from '@toon-format/toon';
// Read
async function loadReportData(filePath) {
const raw = await fs.readFile(filePath, 'utf8');
return decode(raw); // throws if malformed — handle upstream
}
// Write
async function saveSnapshot(data, filePath) {
const toon = encode(data, { indent: 2 });
await fs.writeFile(filePath, toon, 'utf8');
}'utf8'. Pliki TOON są zwykłym tekstem. Pominięcie argumentu kodowania zwraca Buffer — decode() oczekuje ciągu i zgłosi błąd typu, jeśli zostanie przekazany Buffer.Walidacja TOON na granicach systemu
decode() zgłasza błąd przy nieprawidłowym wejściu, co jest właściwym zachowaniem dla parsera, ale niewygodne na granicy API lub konsumera kolejki, gdzie potrzebujesz ustrukturyzowanego wyniku, a nie nieprzechwyconego wyjątku. Rozwiązaniem jest cienki wrapper, który zamienia zgłoszenie na wartość zwracaną. To wzorzec, po który sięgniesz w Express procesorach tras, procesorach kolejek i wszędzie tam, gdzie zewnętrzne dane wchodzą do Twojego systemu.
import { decode } from '@toon-format/toon';
/**
* Safely parse a TOON string.
* Returns { valid: true, data } on success,
* or { valid: false, error } on failure — never throws.
*/
export function validateToon(input) {
if (typeof input !== 'string') {
return { valid: false, error: 'Input must be a string' };
}
try {
const data = decode(input);
return { valid: true, data };
} catch (err) {
return { valid: false, error: err.message };
}
}
Użycie w trasie Express lub konsumerze kolejki wygląda identycznie — wywołaj validateToon(), rozgałęź na valid i albo kontynuuj z data, albo zwróć 400 / przenieś wiadomość do dead-letter z ciągiem error. Wzorzec try/catch utrzymuje kod wywołujący czysty i przewidywalny.
// Example: queue consumer
queue.process('ingest-toon', async (job) => {
const result = validateToon(job.data.payload);
if (!result.valid) {
console.error('Rejecting malformed TOON:', result.error);
return; // dead-letter, skip, or throw depending on your queue
}
await db.insert(result.data);
});
Budowanie middleware TOON dla Express
express.json() parsuje ciała application/json i umieszcza wynik na req.body. Oto to samo dla application/toon. Wstaw go przed swoimi procedurami obsługi tras, a reszta stosu nigdy nie zauważy różnicy.
import { decode } from '@toon-format/toon';
/**
* Express middleware: parses application/toon request bodies
* and attaches the decoded value to req.body.
*/
export function toonBodyParser(req, res, next) {
const contentType = req.headers['content-type'] ?? '';
if (!contentType.includes('application/toon')) {
return next(); // not our content type, pass through
}
let body = '';
req.setEncoding('utf8');
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
try {
req.body = decode(body);
next();
} catch (err) {
res.status(400).json({ error: 'Invalid TOON body', detail: err.message });
}
});
req.on('error', (err) => {
res.status(500).json({ error: 'Request stream error', detail: err.message });
});
}
// Wire it up:
// app.use(toonBodyParser);
// app.post('/api/import', (req, res) => {
// // req.body is already the decoded JS value
// res.json({ received: Array.isArray(req.body) ? req.body.length : 1 });
// });Konwersja wyników bazy danych do TOON przed wysłaniem do LLM
To jest wzorzec, do którego TOON został zbudowany. Odpytujesz bazę danych, otrzymujesz tablicę wierszy, kodujesz do TOON i wstawiasz bezpośrednio do swojego promptu. LLM otrzymuje całą strukturę bez narzutu powtarzania kluczy JSON. Oto realistyczny pipeline używający node-postgres (pg):
import pg from 'pg';
import { encode } from '@toon-format/toon';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
async function buildOrderPrompt(customerId) {
// Step 1: query the database
const { rows } = await pool.query(
`SELECT order_id, created_at, status, total_cents, item_count
FROM orders
WHERE customer_id = $1
ORDER BY created_at DESC
LIMIT 50`,
[customerId]
);
if (rows.length === 0) {
return null;
}
// Step 2: encode rows to TOON
// encode() handles all quoting automatically — no pre-processing needed
const toonData = encode(rows, { indent: 2 });
// Step 3: build the prompt
return [
'Analyse the following order history for a customer support case.',
'Data is in TOON tabular format: name[count]{col1,col2,...}: followed by one row per line.',
'',
toonData,
'',
'Summarise any patterns that suggest the customer has a recurring issue.'
].join('\n');
}
// Calling code:
const prompt = await buildOrderPrompt('cust_8821');
if (prompt) {
const reply = await callLlm(prompt); // your LLM client here
console.log(reply);
}Ten sam wzorzec działa dla dowolnego klienta SQL lub ORM — Prisma, Drizzle, Knex, Sequelize — o ile Twoje zapytanie zwraca zwykłe obiekty JS. encode() pobiera nazwy kluczy z pierwszego wiersza i używa ich jako nagłówków kolumn; kolejne wiersze są zapisywane jako wartości oddzielone przecinkami. Wynik zapytania 50 wierszy, który kosztowałby ~1 500 tokenów jako tablica JSON, typowo kosztuje ~600–700 tokenów jako TOON.
Obsługa błędów i przypadków brzegowych
Kilka rzeczy wartych wiedzenia przed wdrożeniem:
- LLM zwraca zniekształcony TOON. Modele nie zawsze doskonale odtwarzają format, szczególnie przy pierwszej próbie. Opakuj
decode()w try/catch (lub użyjvalidateToon()powyżej). Jeśli się nie powiedzie, zaloguj surową odpowiedź, zwróć błąd do wywołującego i — jeśli potrzebujesz niezawodnie ustrukturyzowanego wyjścia — dodaj powtórzenie z wyraźnym promptem korekcyjnym: "Twoja ostatnia odpowiedź nie była prawidłowym TOON. Proszę sformatuj ją ponownie." - Wartości zawierające przecinki lub dwukropki. TOON używa przecinków do separacji wartości i dwukropków w składni obiektowej — oba są znaczącymi znakami.
encode()wykrywa je automatycznie i opakowuje wartość w podwójne cudzysłowy. Nigdy nie musisz wstępnie przetwarzać swoich danych; po prostu przekazuj surowe ciągi. - Null i undefined.
encode()serializujenulljakonull(bez cudzysłowów) i całkowicie pomija właściwościundefined— to samo zachowanie coJSON.stringify(). Podczas dekodowania, gołynulljest zwracany jako JSnull. - Puste tablice.
encode([])zwraca prawidłową pustą tablicę TOON.decode()przerabia ją z powrotem czysto. Zabezpiecz upstream, jeśli Twój prompt LLM nie powinien zawierać pustego zestawu danych. - Bardzo duże wyniki zapytań. Nie ma twardego limitu w bibliotece, ale LLM mają limity okna kontekstowego. Stronicuj lub
LIMITswoje zapytania przed kodowaniem — 100–200 wierszy to rozsądny pułap dla większości promptów.
validateToon(). Dopuszczenie zniekształconego payloadu do warstwy DB sprawia, że debugowanie jest znacznie trudniejsze niż wychwycenie go na granicy.Podsumowanie
Wzorce w tym artykule obejmują większość tego, czego potrzebujesz do integracji TOON w prawdziwym kodzie Node.js: fs dla I/O plików, wrapper validateToon() dla bezpiecznego parsowania granic, drop-in middleware Express dla ciał application/toon, i pipeline DB-do-promptu, który zamienia wiersze SQL w efektywne tokenowo wejście LLM. Sama biblioteka — @toon-format/toon — nie wchodzi Ci w drogę: dwie funkcje, brak konfiguracji, zgłasza błąd przy nieprawidłowym wejściu. Użyj Walidatora TOON do sprawdzania wyjść podczas tworzenia, Formattera TOON do inspekcji zakodowanych danych, JSON do TOON do konwersji istniejących zestawów danych przed wklejeniem ich do promptu i TOON do JSON jeśli potrzebujesz przekazać zdekodowaną odpowiedź do downstream systemu oczekującego JSON.